use URI::Escape; use strict; use warnings; # 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 Python versions of these functions are at http://shadow2531.com/py/mailto_funcs.py # Example TCL versions of these functions are at 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) sub prepareURIForParsing { my $uri = $_[0]; my $check = lc($uri); if (index($check, "mailto:?") != -1) { return substr($uri, 8); } elsif (index($check, "mailto:") != -1) { my $prep = $uri; $prep =~ s/\?/&/; return "to=" . substr($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) sub searchMailtoURI { my $uri = $_[0]; my $hname = $_[1]; my $delim = $_[2]; my $skip_method = $_[3]; my $puri = prepareURIForParsing($uri); my $lkey = lc($hname); my @hlist = split(/&/, $puri); my $zskip = 1; # with skip_method 0, don't want to add a delim to an empty value during the first occurance of the hname my $value = ""; foreach (@hlist) { my $eq = index($_, "="); if ($eq == -1 or $_ eq "" or lc(substr($_, 0, $eq)) ne $lkey) { next; } if ($skip_method != 3) { if ($value ne "") { if ($skip_method eq 1 and substr($_, $eq + 1) eq "") { next; } $value .= $delim; } elsif ($skip_method == 0) { if ($zskip == 0) { $value .= $delim; } $zskip = 0; } $value .= substr($_, $eq + 1); } else { $value = substr($_, $eq + 1); } } return $value; } sub getEncodedTOValue { return searchMailtoURI($_[0], "to", "%2C%20", 1); } sub getDecodedTOValue { return uri_unescape(getEncodedTOValue($_[0])); } sub getEncodedSubjectValue { return searchMailtoURI($_[0], "subject", "", 3); } sub getDecodedSubjectValue { return uri_unescape(getEncodedSubjectValue($_[0])); } sub getEncodedBodyValue { return searchMailtoURI($_[0], "body", "%0D%0A", 2); } sub getDecodedBodyValue { return uri_unescape(getEncodedBodyValue($_[0])); } sub getEncodedCCValue { return searchMailtoURI($_[0], "cc", "%2C%20", 1); } sub getDecodedCCValue { return uri_unescape(getEncodedCCValue($_[0])); } sub getEncodedBCCValue { return searchMailtoURI($_[0], "bcc", "%2C%20", 1); } sub getDecodedBCCValue { return uri_unescape(getEncodedBCCValue($_[0])); } # Generates a basic, compatible mailto URI with no duplicate hnames #(for passing to clients with cheap mailto URI parsing) sub getNormalizedMailtoURI { return "mailto:" . getEncodedTOValue($_[0]) . "?subject=" . getEncodedSubjectValue($_[0]) . "&body=" . getEncodedBodyValue($_[0]) . "&cc=" . getEncodedCCValue($_[0]) . "&bcc=" . getEncodedBCCValue($_[0]); } # Example of resolving replacement keys in a "open with other application" mailto command field # %1$2s = original mailto URI # %2$2s = Normalized mailto URI # %3$2s = decoded to value # %4$2s = encoded to value # %5$2s = decoded subject value # %6$2s = encoded subject value # %7$2s = decoded body value # %8$2s = encoded body value # %9$2s = decoded cc value # %10$2s = encoded cc value # %11$2s = decoded bcc value # %12$2s = encoded bcc value # %% = % sub openWithOtherApplicationCommandAddInReplacements { my $uri = $_[0]; my $command = $_[1]; return sprintf($command, $uri, getNormalizedMailtoURI($uri), getDecodedTOValue($uri), getEncodedTOValue($uri), getDecodedSubjectValue($uri), getEncodedSubjectValue($uri), getDecodedBodyValue($uri), getEncodedBodyValue($uri), getDecodedCCValue($uri), getEncodedCCValue($uri), getDecodedBCCValue($uri), getEncodedBCCValue($uri)); } sub test { my $t = $_[0]; my $in = $_[1]; my $out = $_[2]; if ($in eq $out) { print "test " . $t . " = pass"; } else { print "test" . $t . "= fail" . "\n"; print "before_" . $in . "_after"; } print "\n"; } # tests are the same as the java versions only some of the 2nd arguments to openWithOtherApplicationCommandAddInReplacements are encased with in # single quotes with the slashes remove from in front of the double quotes. (so, the sprintf stuff can work) 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^^"); my $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 %4$2s'), "program.exe email%40site.com"); test(26, openWithOtherApplicationCommandAddInReplacements("mailto:email%40site.com", 'program.exe %1$2s'), "program.exe mailto:email%40site.com"); test(27, openWithOtherApplicationCommandAddInReplacements("mailto:?to=email%40site.com", 'program.exe "%2$2s"'), "program.exe \"mailto:email%40site.com?subject=&body=&cc=&bcc=\""); test(28, openWithOtherApplicationCommandAddInReplacements($big, '"c:\\program files\\program\\program.exe" "%4$2s" "%6$2s" "%8$2s" "%10$2s" "%12$2s"'), "\"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 "%4$2s"'), "program.exe -to \"email1%40site.com%2C%20email2%40site.com\""); test(42, openWithOtherApplicationCommandAddInReplacements("mailto:email1%40site.com?to=email2%40site.com", 'program.exe %2$2s'), "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");