require 'cgi' # 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 Python versions of these functions are in http://shadow2531.com/py/mailto_funcs.py # Example Perl versions of these functions are in http://shadow2531.com/perl/mailto_funcs.pl # Example Tcl versions 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.downcase if check.index("mailto:?") == 0 return uri[8, uri.length] end if check.index("mailto:") == 0 prep = uri.sub(/\?/, '&') return "to=" + prep[7, prep.length] end return "" end # 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.downcase hlist = puri.split('&') zskip = 1 value = "" for i in hlist eq = i.index('=') if eq == -1 or i == "" or i[0, eq] != lkey next end if skip_method != 3 if value != "" if skip_method == 1 and i[eq + 1, i.length] == "" next end value += delim elsif skip_method == 0: if zskip == 0 value += delim end zskip = 0 end value += i[eq + 1, i.length] else value = i[eq + 1, i.length] end end return value end def getEncodedTOValue(uri) return searchMailtoURI(uri, "to", "%2C%20", 1) end def getDecodedTOValue(uri) return CGI::unescape(getEncodedTOValue(uri)) end def getEncodedSubjectValue(uri) return searchMailtoURI(uri, "subject", "", 3) end def getDecodedSubjectValue(uri) return CGI::unescape(getEncodedSubjectValue(uri)) end def getEncodedBodyValue(uri) return searchMailtoURI(uri, "body", "%0D%0A", 2) end def getDecodedBodyValue(uri) return CGI::unescape(getEncodedBodyValue(uri)) end def getEncodedCCValue(uri) return searchMailtoURI(uri, "cc", "%2C%20", 1) end def getDecodedCCValue(uri) return CGI::unescape(getEncodedCCValue(uri)) end def getEncodedBCCValue(uri) return searchMailtoURI(uri, "bcc", "%2C%20", 1) end def getDecodedBCCValue(uri) return CGI::unescape(getEncodedBCCValue(uri)) end # 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 end # 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 # %% = % # not sure how to do ruby format strings like python's template strings or even how to do ruby loops rights, so this looks ugly def openWithOtherApplicationCommandAddInReplacements(uri, command) hash = {'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)} nval = "" i = 0 while i < command.length if command[i, 1] == "%" if i + 1 < command.length c = command[i + 1, 1] if c == "%" nval += "%" elsif hash[c] != nil nval += hash[c] else nval += "%" nval += c end i += 2; else nval += "%" i += 1 end else nval += command[i, 1] i += 1 end end return nval end def test(t, inc, out) if inc == out puts "test #{t} = pass" else puts "test #{t} = fail" puts "before_" + inc + "_after" end end 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"), "\xE2\x88\x9A") 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");