# -*- coding: utf-8 -*- import urllib from string import Template # Copyright Michael Anthony Puls II, http://shadow2531.com/ # Distributed under the Boost Software License, Version 1.0. # http://boost.org/LICENSE_1_0.txt . # Rules to follow http://shadow2531.com/opera/testcases/mailto/rfc2368-3.html # Example c++ versions of these functions are in # http://shadow2531.com/cpp/mailto_funcs1.zip # http://shadow2531.com/cpp/mailto_funcs2.zip # http://shadow2531.com/cpp/mailto_funcs3.zip # http://shadow2531.com/cpp/mailto_funcs4.zip # http://shadow2531.com/cpp/mailto_funcs5.zip # http://shadow2531.com/cpp/mailto_funcs6.zip # http://shadow2531.com/cpp/mailto_funcs7.zip # http://shadow2531.com/cpp/mailto_funcs8.zip # http://shadow2531.com/cpp/mailto_funcs9.zip # http://shadow2531.com/cpp/mailto_funcs10.zip # http://shadow2531.com/cpp/mailto_funcs11.zip # Example Javascript versions of these functions are in http://shadow2531.com/opera/testcases/mailto/mailto_funcs.js # Example Java versions of these functions are in http://shadow2531.com/java/MailtoFuncs.java # Example Ruby versions of these functions are in http://shadow2531.com/ruby/mailto_funcs.rb # Example Perl versions of these functions are in http://shadow2531.com/perl/mailto_funcs.pl # Example Tcl verions of these functions are in http://shadow2531.com/tcl/mailto_funcs.tcl # Newest versions: http://shadow2531.com/opera/testcases/mailto/MailtoURIParserPack.zip # Create a modified value that only contains a &-separated list of hname=hvalue pairs (so it can be tokenized) def prepURI(uri): check = uri.lower() if check.find("mailto:?") == 0: return uri[8:] if check.find("mailto:") == 0: prep = uri q = uri.find('?') if q != -1: prep = prep.replace('?', '&') return "to=" + prep[7:] return ""; # skip_method # 0 = concat (with delim) all hvalues for the hname (even if they're empty) # 1 = concat (with delim) all non-empty hvalues for the hname (to, cc and bcc rule) # 2 = concat (with delim) first non-empty hvalue for the hname and all (even if empty) hvalues after that. (body rule) # 3 = only use the last hvalue for the hname (even if the hvalue is empty) (subject rule) def searchMailtoURI(uri, hname, delim, skip_method): puri = prepURI(uri) lkey = hname.lower() hlist = puri.split('&') zskip = 1 # with skip_method 0, don't want to add a delim to an empty value during the first occurance of the hname value = "" for i in hlist: eq = i.find('=') if eq == -1 or i == "" or i[:eq].lower() != lkey: continue if skip_method != 3: if value != "": if skip_method == 1 and i[eq + 1:] == "": continue value += delim elif skip_method == 0: if zskip == 0: value += delim zskip = 0 value += i[eq + 1:] else: value = i[eq + 1:] return value def getEncodedTOValue(uri): return searchMailtoURI(uri, "to", "%2C%20", 1) def getDecodedTOValue(uri): return unicode(urllib.unquote(getEncodedTOValue(uri)), "utf-8") def getEncodedSubjectValue(uri): return searchMailtoURI(uri, "subject", "", 3) def getDecodedSubjectValue(uri): return unicode(urllib.unquote(getEncodedSubjectValue(uri)), "utf-8") def getEncodedBodyValue(uri): return searchMailtoURI(uri, "body", "%0D%0A", 2) def getDecodedBodyValue(uri): return unicode(urllib.unquote(getEncodedBodyValue(uri)), "utf-8") def getEncodedCCValue(uri): return searchMailtoURI(uri, "cc", "%2C%20", 1) def getDecodedCCValue(uri): return unicode(urllib.unquote(getEncodedCCValue(uri)), "utf-8") def getEncodedBCCValue(uri): return searchMailtoURI(uri, "bcc", "%2C%20", 1) def getDecodedBCCValue(uri): return unicode(urllib.unquote(getEncodedBCCValue(uri)), "utf-8") # Generates a basic, compatible mailto URI with no duplicate hnames #(for passing to clients with cheap mailto URI parsing) def getNormalizedMailtoURI(uri): nval = "mailto:" nval += getEncodedTOValue(uri) nval += "?subject=" nval += getEncodedSubjectValue(uri) nval += "&body=" nval += getEncodedBodyValue(uri) nval += "&cc=" nval += getEncodedCCValue(uri) nval += "&bcc=" nval += getEncodedBCCValue(uri) return nval # Example of resolving replacement keys in a "open with other application" mailto command field # ${w} = original mailto uri # ${n} = normalized mailto uri # ${t} = decoded to value # ${T} = encoded to value # ${s} = decoded subject value # ${S} = encoded subject value # ${m} = decoded body value # ${M} = encoded body value # ${c} = decoded cc value # ${C} = encoded cc value # ${b} = decoded bcc value # ${B} = encoded bcc value # $$ = $ def openWithOtherApplicationCommandAddInReplacements(uri, command): s = Template(command) return s.safe_substitute(w=uri, n=getNormalizedMailtoURI(uri), t=getDecodedTOValue(uri), T=getEncodedTOValue(uri), s=getDecodedSubjectValue(uri), S=getEncodedSubjectValue(uri), m=getDecodedBodyValue(uri), M=getEncodedBodyValue(uri), c=getDecodedCCValue(uri), C=getEncodedCCValue(uri), b=getDecodedBCCValue(uri), B=getEncodedBCCValue(uri)) def test(t, inc, out): if inc == out: print "test", t, "= pass" else: print "test", t, "= fail" print "before_" + inc + "_after" test(1, getEncodedTOValue("mailto:?to=email1%40site.com&to=email2%40site.com"), "email1%40site.com%2C%20email2%40site.com") test(2, getEncodedTOValue("mailto:email1%40site.com%2C%20email2%40site.com"), "email1%40site.com%2C%20email2%40site.com") test(3, getEncodedSubjectValue("mailto:?subject=one"), "one"); test(4, getEncodedSubjectValue("mailto:?subject=one&subject=two"), "two") test(5, getEncodedSubjectValue("mailto:?subject=one&subject="), "") test(6, getEncodedBodyValue("mailto:?body=line1%0D%0Aline2"), "line1%0D%0Aline2") test(7, getEncodedBodyValue("mailto:?body=line1%0D%0Aline2&body=line3%0D%0Aline4"), "line1%0D%0Aline2%0D%0Aline3%0D%0Aline4") test(8, getEncodedBodyValue("mailto:?body=&body=&body=&body=line1%0D%0Aline2&body=line3%0D%0Aline4&body=&body=&body=line8"), "line1%0D%0Aline2%0D%0Aline3%0D%0Aline4%0D%0A%0D%0A%0D%0Aline8") test(9, getEncodedBodyValue("mailto:?body=&body="), "") test(10, getEncodedCCValue("mailto:?cc=email1%40site.com"), "email1%40site.com") test(11, getEncodedCCValue("mailto:?cc=email1%40site.com%2C%20email2%40site.com&cc=email5%40site.com%2C%20email6%40site.com"), "email1%40site.com%2C%20email2%40site.com%2C%20email5%40site.com%2C%20email6%40site.com") test(12, getEncodedCCValue("mailto:"), "") test(13, getEncodedCCValue("mailto:?cc=&cc=&cc=&cc=email1%40site.com"), "email1%40site.com") test(14, getEncodedBCCValue("mailto:?bcc=email1%40site.com"), "email1%40site.com") test(15, getEncodedBCCValue("mailto:?bcc=email1%40site.com%2C%20email2%40site.com&bcc=email5%40site.com%2C%20email6%40site.com"), "email1%40site.com%2C%20email2%40site.com%2C%20email5%40site.com%2C%20email6%40site.com") test(16, getEncodedBCCValue("mailto:"), "") test(17, getEncodedBCCValue("mailto:?bcc=&bcc=&bcc=&bcc=email1%40site.com"), "email1%40site.com") test(18, getNormalizedMailtoURI("mailto:"), "mailto:?subject=&body=&cc=&bcc=") test(19, getNormalizedMailtoURI("mailto:email1%40site.com%2C%20email2%40site.com?subject=1&subject=2&subject=3&body=line1%0D%0Aline2&body=line3%0D%0Aline4&cc=test1%40site.com%2C%20test2%40site.com&cc=test3%40site.com%2C%20test4%40site.com&bcc=zap1%40site.com%2C%20zap2%40site.com&bcc=zap3%40site.com%2C%20zap4%40site.com&to=email3%40site.com&to=email4%40site.com"), "mailto:email1%40site.com%2C%20email2%40site.com%2C%20email3%40site.com%2C%20email4%40site.com?subject=3&body=line1%0D%0Aline2%0D%0Aline3%0D%0Aline4&cc=test1%40site.com%2C%20test2%40site.com%2C%20test3%40site.com%2C%20test4%40site.com&bcc=zap1%40site.com%2C%20zap2%40site.com%2C%20zap3%40site.com%2C%20zap4%40site.com") test(20, getNormalizedMailtoURI("mailto:email%40site.com?subject=1&body=2&cc=test%40site.com&bcc=zap%40site.com"), "mailto:email%40site.com?subject=1&body=2&cc=test%40site.com&bcc=zap%40site.com") test(21, getEncodedSubjectValue("mailto:?subject=1&subject="), "") test(22, searchMailtoURI("mailto:?foo=&foo=&foo=", "foo", "^", 0), "^^") test(23, searchMailtoURI("mailto:?foo=1&foo=&foo=", "foo", "^", 0), "1^^") big = "mailto:%22a%5C%5Cb%22%20%3Cemail1%40site.com%3E%2C%20email2%40site.com?to=&to=email3%40site.com%2C%20email4%40site.com&to=email5%40site.com%2C%20email6%40site.com&to=&to=&subject=You%20should%20not%20see%20me%2E&subject=last%20call%20for%20cats%20%26%20dogs%2E&body=&body=&body=&body=line1%0D%0Aline2&body=&body=&body=&body=line6&body=line7%0D%0A1+2+3+4+5%0D%0AColumn1A%09Column1B%09Column1C%09Column1D%0D%0A%E2%88%9A&cc=%22a%5C%5Cb%22%20%3Csomeone1%40site.com%3E%2C%20someone2%40site.com&cc=&cc=someone3%40site.com%2C%20someone4%40site.com%2C%20%22foo%20%5C%22bar%5C%22%22%20%3Cfoo%40bar%2Ecom%3E&cc=&cc=&bcc=%22a%5C%5Cb%22%20%3Csomeoneelse1%40site.com%3E%2C%20someoneelse2%40site.com&bcc=&bcc=someoneelse3%40site.com%2C%20someoneelse4%40site.com&bcc=&bcc=" test(24, getNormalizedMailtoURI(big), "mailto:%22a%5C%5Cb%22%20%3Cemail1%40site.com%3E%2C%20email2%40site.com%2C%20email3%40site.com%2C%20email4%40site.com%2C%20email5%40site.com%2C%20email6%40site.com?subject=last%20call%20for%20cats%20%26%20dogs%2E&body=line1%0D%0Aline2%0D%0A%0D%0A%0D%0A%0D%0Aline6%0D%0Aline7%0D%0A1+2+3+4+5%0D%0AColumn1A%09Column1B%09Column1C%09Column1D%0D%0A%E2%88%9A&cc=%22a%5C%5Cb%22%20%3Csomeone1%40site.com%3E%2C%20someone2%40site.com%2C%20someone3%40site.com%2C%20someone4%40site.com%2C%20%22foo%20%5C%22bar%5C%22%22%20%3Cfoo%40bar%2Ecom%3E&bcc=%22a%5C%5Cb%22%20%3Csomeoneelse1%40site.com%3E%2C%20someoneelse2%40site.com%2C%20someoneelse3%40site.com%2C%20someoneelse4%40site.com") test(25, openWithOtherApplicationCommandAddInReplacements("mailto:email%40site.com", "program.exe ${T}"), "program.exe email%40site.com") test(26, openWithOtherApplicationCommandAddInReplacements("mailto:email%40site.com", "program.exe ${w}"), "program.exe mailto:email%40site.com") test(27, openWithOtherApplicationCommandAddInReplacements("mailto:?to=email%40site.com", "program.exe \"${n}\""), "program.exe \"mailto:email%40site.com?subject=&body=&cc=&bcc=\"") test(28, openWithOtherApplicationCommandAddInReplacements(big, "\"c:\\program files\\program\\program.exe\" \"${T}\" \"${S}\" \"${M}\" \"${C}\" \"${B}\""), "\"c:\\program files\\program\\program.exe\" \"%22a%5C%5Cb%22%20%3Cemail1%40site.com%3E%2C%20email2%40site.com%2C%20email3%40site.com%2C%20email4%40site.com%2C%20email5%40site.com%2C%20email6%40site.com\" \"last%20call%20for%20cats%20%26%20dogs%2E\" \"line1%0D%0Aline2%0D%0A%0D%0A%0D%0A%0D%0Aline6%0D%0Aline7%0D%0A1+2+3+4+5%0D%0AColumn1A%09Column1B%09Column1C%09Column1D%0D%0A%E2%88%9A\" \"%22a%5C%5Cb%22%20%3Csomeone1%40site.com%3E%2C%20someone2%40site.com%2C%20someone3%40site.com%2C%20someone4%40site.com%2C%20%22foo%20%5C%22bar%5C%22%22%20%3Cfoo%40bar%2Ecom%3E\" \"%22a%5C%5Cb%22%20%3Csomeoneelse1%40site.com%3E%2C%20someoneelse2%40site.com%2C%20someoneelse3%40site.com%2C%20someoneelse4%40site.com\"") test(29, openWithOtherApplicationCommandAddInReplacements(big, "$$1$$"), "$1$") test(30, searchMailtoURI("mailto:?bla=test&foo=&foo=&foo=", "foo", "^", 0), "^^") test(31, searchMailtoURI("mailto:?bla=test&foo=&foo=1&foo=&bam=this", "foo", "^", 0), "^1^") test(32, searchMailtoURI("mailto:?bla=test&foo=1&foo=1&foo=&bam=this", "foo", "^", 0), "1^1^") test(33, searchMailtoURI("mailto:?bla=test&foo=1&foo=2&foo=3&bam=this", "foo", "^", 0), "1^2^3") test(34, getEncodedSubjectValue("mailto:?subject=1+2+3+4+5"), "1+2+3+4+5") test(35, searchMailtoURI("mailto:?foo=", "foo", "^", 0), "") test(36, getDecodedSubjectValue("mailto:?subject=%E2%88%9A"), u"\u221A") test(37, getEncodedBodyValue("mailto:?body=line1&body=line2"), "line1%0D%0Aline2") test(38, getEncodedTOValue("mailto:email1%40site.com?to=email2%40site.com"), "email1%40site.com%2C%20email2%40site.com") test(39, getEncodedSubjectValue("mailto:?subject=first&subject=last"), "last") test(40, getNormalizedMailtoURI("mailto:?to=email%40site.com&subject=first&subject=second&subject=last&body=line1&body=line2"), "mailto:email%40site.com?subject=last&body=line1%0D%0Aline2&cc=&bcc=") test(41, openWithOtherApplicationCommandAddInReplacements("mailto:email1%40site.com?to=email2%40site.com", "program.exe -to \"${T}\""), "program.exe -to \"email1%40site.com%2C%20email2%40site.com\"") test(42, openWithOtherApplicationCommandAddInReplacements("mailto:email1%40site.com?to=email2%40site.com", "program.exe ${n}"), "program.exe mailto:email1%40site.com%2C%20email2%40site.com?subject=&body=&cc=&bcc=") test(43, getEncodedBodyValue("mailto:?body=&body=&body=line1&body=&body=&body=line4&body=&body="), "line1%0D%0A%0D%0A%0D%0Aline4%0D%0A%0D%0A")