// 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 python versions of these functions are in Python example http://shadow2531.com/py/mailto_funcs.py // 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 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 versions of these functions are in http://shadow2531.com/tcl/mailto_funcs.tcl // Newest versions: http://shadow2531.com/opera/testcases/mailto/MailtoURIParserPack.zip public class MailtoFuncs { // Create a modified value that only contains a &-separated list of hname=hvalue pairs (so it can be tokenized) static String prepURI(String uri) { String check = uri.toLowerCase(); if (check.indexOf("mailto:?") == 0) { return uri.substring(8); } if (check.indexOf("mailto:") == 0) { String prep = uri; int q = uri.indexOf('?'); if (q != -1) { prep = prep.replace('?', '&'); } return "to=" + prep.substring(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) static String searchMailtoURI(String uri, String hname, String delim, int skip_method) { String puri = prepURI(uri); String lkey = hname.toLowerCase(); String hlist[] = puri.split("&"); String value = ""; boolean zskip = true; // with skip_method 0, don't want to add a delim to an empty value during the first occurance of the hname for (int i = 0; i < hlist.length; ++i) { int eq = hlist[i].indexOf('='); if (eq == -1 || hlist[i].equals("") || !hlist[i].substring(0, eq).toLowerCase().equals(lkey)) { continue; } if (skip_method != 3) { if (!value.equals("")) { if (skip_method == 1 && hlist[i].substring(eq + 1).equals("")) { continue; } value += delim; } else if (skip_method == 0) { if (!zskip) { value += delim; } zskip = false; } value += hlist[i].substring(eq + 1); } else { value = hlist[i].substring(eq + 1); } } return value; } static String getEncodedTOValue(String uri) { return searchMailtoURI(uri, "to", "%2C%20", 1); } static String getDecodedTOValue(String uri) { try { return java.net.URLDecoder.decode(getEncodedTOValue(uri), "utf-8"); } catch(Throwable x) { return ""; } } static String getEncodedSubjectValue(String uri) { return searchMailtoURI(uri, "subject", "", 3); } static String getDecodedSubjectValue(String uri) { try { return java.net.URLDecoder.decode(getEncodedSubjectValue(uri), "utf-8"); } catch (Throwable x) { return ""; } } static String getEncodedBodyValue(String uri) { return searchMailtoURI(uri, "body", "%0D%0A", 2); } static String getDecodedBodyValue(String uri) { try { return java.net.URLDecoder.decode(getEncodedBodyValue(uri), "utf-8"); } catch (Throwable x) { return ""; } } static String getEncodedCCValue(String uri) { return searchMailtoURI(uri, "cc", "%2C%20", 1); } static String getDecodedCCValue(String uri) { try { return java.net.URLDecoder.decode(getEncodedCCValue(uri), "utf-8"); } catch (Throwable x) { return ""; } } static String getEncodedBCCValue(String uri) { return searchMailtoURI(uri, "bcc", "%2C%20", 1); } static String getDecodedBCCValue(String uri) { try { return java.net.URLDecoder.decode(getEncodedBCCValue(uri), "utf-8"); } catch (Throwable x) { return ""; } } // Generates a basic, compatible mailto URI with no duplicate hnames //(for passing to clients with cheap mailto URI parsing) static String getNormalizedMailtoURI(String uri) { String 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 // %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 // %% = % static String openWithOtherApplicationCommandAddInReplacements(String uri, String command) { return String.format(command, uri, getNormalizedMailtoURI(uri), getDecodedTOValue(uri), getEncodedTOValue(uri), getDecodedSubjectValue(uri), getEncodedSubjectValue(uri), getDecodedBodyValue(uri), getEncodedBodyValue(uri), getDecodedCCValue(uri), getEncodedCCValue(uri), getDecodedBCCValue(uri), getEncodedBCCValue(uri)); } static void test(int t, String in, String out) { System.out.print("test "); System.out.print(t); System.out.print(" = "); if (in.equals(out)) { System.out.print("pass"); } else { System.out.print("fail\nbefore_"); System.out.print(in); System.out.print("_after"); } System.out.print("\n"); } public static void main(String args[]) { 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^^"); String 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"), "\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 \"%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"); } }