17 |
ilm |
1 |
/*
|
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
|
3 |
*
|
|
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
|
|
5 |
*
|
|
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
|
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
|
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
|
|
9 |
* language governing permissions and limitations under the License.
|
|
|
10 |
*
|
|
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
|
|
12 |
*/
|
|
|
13 |
|
|
|
14 |
package org.openconcerto.utils;
|
|
|
15 |
|
|
|
16 |
import org.openconcerto.utils.DesktopEnvironment.Gnome;
|
|
|
17 |
import org.openconcerto.utils.DesktopEnvironment.KDE;
|
|
|
18 |
import org.openconcerto.utils.DesktopEnvironment.Mac;
|
|
|
19 |
import org.openconcerto.utils.DesktopEnvironment.Windows;
|
180 |
ilm |
20 |
import org.openconcerto.utils.DesktopEnvironment.XFCE;
|
|
|
21 |
import org.openconcerto.utils.OSFamily.Unix;
|
17 |
ilm |
22 |
import org.openconcerto.utils.io.PercentEncoder;
|
|
|
23 |
|
|
|
24 |
import java.io.BufferedOutputStream;
|
|
|
25 |
import java.io.BufferedWriter;
|
|
|
26 |
import java.io.File;
|
|
|
27 |
import java.io.IOException;
|
|
|
28 |
import java.io.OutputStreamWriter;
|
|
|
29 |
import java.io.PrintStream;
|
|
|
30 |
import java.io.Writer;
|
|
|
31 |
import java.net.URI;
|
|
|
32 |
import java.net.URISyntaxException;
|
|
|
33 |
import java.util.ArrayList;
|
|
|
34 |
import java.util.Arrays;
|
|
|
35 |
import java.util.List;
|
|
|
36 |
import java.util.regex.Matcher;
|
|
|
37 |
import java.util.regex.Pattern;
|
|
|
38 |
|
|
|
39 |
public abstract class EmailClient {
|
|
|
40 |
|
|
|
41 |
public static enum EmailClientType {
|
149 |
ilm |
42 |
Thunderbird, AppleMail, Outlook, XDG
|
17 |
ilm |
43 |
}
|
|
|
44 |
|
|
|
45 |
private static EmailClient PREFERRED = null;
|
|
|
46 |
|
|
|
47 |
/**
|
|
|
48 |
* Find the preferred email client.
|
|
|
49 |
*
|
|
|
50 |
* @return the preferred email client, never <code>null</code>.
|
|
|
51 |
* @throws IOException if an error occurs.
|
|
|
52 |
*/
|
|
|
53 |
public static final EmailClient getPreferred() throws IOException {
|
|
|
54 |
if (PREFERRED == null) {
|
|
|
55 |
PREFERRED = findPreferred();
|
|
|
56 |
// should at least return MailTo
|
|
|
57 |
assert PREFERRED != null;
|
|
|
58 |
}
|
|
|
59 |
return PREFERRED;
|
|
|
60 |
}
|
|
|
61 |
|
|
|
62 |
/**
|
|
|
63 |
* Clear the preferred client.
|
|
|
64 |
*/
|
|
|
65 |
public static final void resetPreferred() {
|
|
|
66 |
PREFERRED = null;
|
|
|
67 |
}
|
|
|
68 |
|
|
|
69 |
// XP used tabs, but not 7
|
|
|
70 |
// MULTILINE since there's several lines in addition to the wanted one
|
|
|
71 |
private static final Pattern registryPattern = Pattern.compile("\\s+REG_SZ\\s+(.*)$", Pattern.MULTILINE);
|
|
|
72 |
private static final Pattern cmdLinePattern = Pattern.compile("(\"(.*?)\")|([^\\s\"]+)\\b");
|
28 |
ilm |
73 |
// any whitespace except space and tab
|
|
|
74 |
private static final Pattern wsPattern = Pattern.compile("[\\s&&[^ \t]]");
|
17 |
ilm |
75 |
private static final Pattern dictPattern;
|
|
|
76 |
private static final String AppleMailBundleID = "com.apple.mail";
|
|
|
77 |
private static final String ThunderbirdBundleID = "org.mozilla.thunderbird";
|
|
|
78 |
static {
|
|
|
79 |
final String rolePattern = "(?:LSHandlerRoleAll\\s*=\\s*\"([\\w\\.]+)\";\\s*)?";
|
|
|
80 |
dictPattern = Pattern.compile("\\{\\s*" + rolePattern + "LSHandlerURLScheme = mailto;\\s*" + rolePattern + "\\}");
|
|
|
81 |
}
|
|
|
82 |
|
28 |
ilm |
83 |
private final static String createEncodedParam(final String name, final String value) {
|
|
|
84 |
return name + "=" + PercentEncoder.encode(value, StringUtils.UTF8);
|
17 |
ilm |
85 |
}
|
|
|
86 |
|
|
|
87 |
private final static String createASParam(final String name, final String value) {
|
28 |
ilm |
88 |
return name + ":" + StringUtils.doubleQuote(value);
|
17 |
ilm |
89 |
}
|
|
|
90 |
|
|
|
91 |
private final static String createVBParam(final String name, final String value) {
|
|
|
92 |
final String switchName = "/" + name + ":";
|
|
|
93 |
if (value == null || value.length() == 0)
|
|
|
94 |
return switchName;
|
|
|
95 |
// we need to encode the value since when invoking cscript.exe we cannot pass "
|
|
|
96 |
// since all arguments are re-parsed
|
|
|
97 |
final String encoded = PercentEncoder.encodeUTF16(value);
|
|
|
98 |
assert encoded.indexOf('"') < 0 : "Encoded contains a double quote, this will confuse cscript";
|
|
|
99 |
return switchName + '"' + encoded + '"';
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
/**
|
|
|
103 |
* Create a mailto URI.
|
|
|
104 |
*
|
|
|
105 |
* @param to the recipient, can be <code>null</code>.
|
|
|
106 |
* @param subject the subject, can be <code>null</code>.
|
|
|
107 |
* @param body the body of the email, can be <code>null</code>.
|
28 |
ilm |
108 |
* @param attachments files to attach, for security reason this parameter is ignored by at least
|
|
|
109 |
* Outlook 2007, Apple Mail and Thunderbird.
|
17 |
ilm |
110 |
* @return the mailto URI.
|
|
|
111 |
* @throws IOException if an encoding error happens.
|
|
|
112 |
* @see <a href="http://tools.ietf.org/html/rfc2368">RFC 2368</a>
|
61 |
ilm |
113 |
* @see <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=67254">Don't allow attachment
|
|
|
114 |
* of local file from non-local link</a>
|
17 |
ilm |
115 |
*/
|
28 |
ilm |
116 |
public final static URI getMailToURI(final String to, final String subject, final String body, final File... attachments) throws IOException {
|
17 |
ilm |
117 |
// mailto:p.dupond@example.com?subject=Sujet%20du%20courrier&cc=pierre@example.org&bcc=jacques@example.net&body=Bonjour
|
|
|
118 |
|
|
|
119 |
// Outlook doesn't support the to header as mandated by 2. of the RFC
|
28 |
ilm |
120 |
final String encodedTo = to == null ? "" : PercentEncoder.encode(to, StringUtils.UTF8);
|
17 |
ilm |
121 |
final List<String> l = new ArrayList<String>(4);
|
|
|
122 |
if (subject != null)
|
|
|
123 |
l.add(createEncodedParam("subject", subject));
|
|
|
124 |
if (body != null)
|
|
|
125 |
l.add(createEncodedParam("body", body));
|
28 |
ilm |
126 |
for (final File attachment : attachments)
|
17 |
ilm |
127 |
l.add(createEncodedParam("attachment", attachment.getAbsolutePath()));
|
|
|
128 |
final String query = CollectionUtils.join(l, "&");
|
|
|
129 |
try {
|
|
|
130 |
return new URI("mailto:" + encodedTo + "?" + query);
|
|
|
131 |
} catch (URISyntaxException e) {
|
|
|
132 |
throw new IOException("Couldn't create mailto URI", e);
|
|
|
133 |
}
|
|
|
134 |
}
|
|
|
135 |
|
28 |
ilm |
136 |
// see http://kb.mozillazine.org/Command_line_arguments_(Thunderbird)
|
|
|
137 |
// The escape mechanism isn't specified, it turns out we can pass percent encoded strings
|
|
|
138 |
private final static String getTBParam(final String to, final String subject, final String body, final File... attachments) {
|
149 |
ilm |
139 |
/**
|
|
|
140 |
* <pre>
|
|
|
141 |
"to='john@example.com,kathy@example.com',cc='britney@example.com',subject='dinner',body='How about dinner tonight?',attachment='file:///C:/cygwin/Cygwin.bat,file:///C:/cygwin/Cygwin.ico'";
|
|
|
142 |
* </pre>
|
|
|
143 |
*/
|
17 |
ilm |
144 |
|
|
|
145 |
final List<String> l = new ArrayList<String>(4);
|
|
|
146 |
if (to != null)
|
28 |
ilm |
147 |
l.add(createEncodedParam("to", to));
|
17 |
ilm |
148 |
if (subject != null)
|
28 |
ilm |
149 |
l.add(createEncodedParam("subject", subject));
|
17 |
ilm |
150 |
if (body != null)
|
28 |
ilm |
151 |
l.add(createEncodedParam("body", body));
|
|
|
152 |
final List<String> urls = new ArrayList<String>(attachments.length);
|
|
|
153 |
for (final File attachment : attachments) {
|
17 |
ilm |
154 |
// Thunderbird doesn't parse java URI file:/C:/
|
61 |
ilm |
155 |
final String rawPath = attachment.toURI().getRawPath();
|
|
|
156 |
// handle UNC paths
|
|
|
157 |
final String tbURL = (rawPath.startsWith("//") ? "file:///" : "file://") + rawPath;
|
28 |
ilm |
158 |
urls.add(tbURL);
|
17 |
ilm |
159 |
}
|
28 |
ilm |
160 |
l.add(createEncodedParam("attachment", CollectionUtils.join(urls, ",")));
|
17 |
ilm |
161 |
|
28 |
ilm |
162 |
return DesktopEnvironment.getDE().quoteParamForExec(CollectionUtils.join(l, ","));
|
17 |
ilm |
163 |
}
|
|
|
164 |
|
|
|
165 |
private final static String getAppleMailParam(final String subject, final String body) {
|
|
|
166 |
final List<String> l = new ArrayList<String>(3);
|
|
|
167 |
l.add("visible:true");
|
|
|
168 |
if (subject != null)
|
|
|
169 |
l.add(createASParam("subject", subject));
|
|
|
170 |
if (body != null)
|
|
|
171 |
l.add(createASParam("content", body));
|
|
|
172 |
|
|
|
173 |
return CollectionUtils.join(l, ", ");
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
// @param cmdLine "C:\Program Files\Mozilla Thunderbird\thunderbird.exe" -osint -compose "%1"
|
|
|
177 |
// @param toReplace "%1"
|
28 |
ilm |
178 |
private static String[] tbCommand(final String cmdLine, final String toReplace, final String to, final String subject, final String body, final File... attachments) {
|
|
|
179 |
final String composeArg = getTBParam(to, subject, body, attachments);
|
17 |
ilm |
180 |
|
|
|
181 |
final List<String> arguments = new ArrayList<String>();
|
|
|
182 |
final Matcher cmdMatcher = cmdLinePattern.matcher(cmdLine);
|
|
|
183 |
while (cmdMatcher.find()) {
|
|
|
184 |
final String quoted = cmdMatcher.group(2);
|
|
|
185 |
final String unquoted = cmdMatcher.group(3);
|
|
|
186 |
assert quoted == null ^ unquoted == null : "Both quoted and unquoted, or neither quoted nor quoted: " + quoted + " and " + unquoted;
|
|
|
187 |
final String arg = quoted != null ? quoted : unquoted;
|
|
|
188 |
|
|
|
189 |
final boolean replace = arg.equals(toReplace);
|
|
|
190 |
// e.g. on Linux
|
|
|
191 |
if (replace && !arguments.contains("-compose"))
|
|
|
192 |
arguments.add("-compose");
|
|
|
193 |
arguments.add(replace ? composeArg : arg);
|
|
|
194 |
}
|
|
|
195 |
|
|
|
196 |
return arguments.toArray(new String[arguments.size()]);
|
|
|
197 |
}
|
|
|
198 |
|
|
|
199 |
/**
|
|
|
200 |
* Open a composing window in the default email client.
|
|
|
201 |
*
|
|
|
202 |
* @param to the recipient, can be <code>null</code>.
|
|
|
203 |
* @param subject the subject, can be <code>null</code>.
|
|
|
204 |
* @param body the body of the email, can be <code>null</code>.
|
28 |
ilm |
205 |
* @param attachments files to attach, ATTN can be ignored if mailto: is used
|
|
|
206 |
* {@link #getMailToURI(String, String, String, File...)}.
|
17 |
ilm |
207 |
* @throws IOException if a program cannot be executed.
|
|
|
208 |
* @throws InterruptedException if the thread is interrupted while waiting for a native program.
|
|
|
209 |
*/
|
28 |
ilm |
210 |
public void compose(final String to, String subject, final String body, final File... attachments) throws IOException, InterruptedException {
|
17 |
ilm |
211 |
// check now as letting the native commands do is a lot less reliable
|
28 |
ilm |
212 |
for (File attachment : attachments) {
|
|
|
213 |
if (!attachment.exists())
|
|
|
214 |
throw new IOException("Attachment doesn't exist: '" + attachment.getAbsolutePath() + "'");
|
|
|
215 |
}
|
17 |
ilm |
216 |
|
28 |
ilm |
217 |
// a subject should only be one line (Thunderbird strips newlines anyway and Outlook sends a
|
|
|
218 |
// malformed email)
|
|
|
219 |
subject = wsPattern.matcher(subject).replaceAll(" ");
|
17 |
ilm |
220 |
final boolean handled;
|
|
|
221 |
// was only trying native if necessary, but mailto url has length limitations and can have
|
|
|
222 |
// encoding issues
|
28 |
ilm |
223 |
handled = composeNative(to, subject, body, attachments);
|
17 |
ilm |
224 |
|
|
|
225 |
if (!handled) {
|
28 |
ilm |
226 |
final URI mailto = getMailToURI(to, subject, body, attachments);
|
17 |
ilm |
227 |
java.awt.Desktop.getDesktop().mail(mailto);
|
|
|
228 |
}
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
static private String cmdSubstitution(String... args) throws IOException {
|
|
|
232 |
return DesktopEnvironment.cmdSubstitution(Runtime.getRuntime().exec(args));
|
|
|
233 |
}
|
|
|
234 |
|
|
|
235 |
private static EmailClient findPreferred() throws IOException {
|
|
|
236 |
final DesktopEnvironment de = DesktopEnvironment.getDE();
|
|
|
237 |
if (de instanceof Windows) {
|
|
|
238 |
// Tested on XP and 7
|
|
|
239 |
// <SANS NOM> REG_SZ "C:\Program Files\Mozilla
|
|
|
240 |
// Thunderbird\thunderbird.exe" -osint -compose "%1"
|
|
|
241 |
final String out = cmdSubstitution("reg", "query", "HKEY_CLASSES_ROOT\\mailto\\shell\\open\\command");
|
|
|
242 |
|
|
|
243 |
final Matcher registryMatcher = registryPattern.matcher(out);
|
|
|
244 |
if (registryMatcher.find()) {
|
|
|
245 |
final String cmdLine = registryMatcher.group(1);
|
|
|
246 |
if (cmdLine.contains("thunderbird")) {
|
|
|
247 |
return new ThunderbirdCommandLine(cmdLine, "%1");
|
|
|
248 |
} else if (cmdLine.toLowerCase().contains("outlook")) {
|
|
|
249 |
return Outlook;
|
|
|
250 |
}
|
|
|
251 |
}
|
|
|
252 |
} else if (de instanceof Mac) {
|
|
|
253 |
// (
|
|
|
254 |
// {
|
|
|
255 |
// LSHandlerRoleAll = "com.apple.mail";
|
|
|
256 |
// LSHandlerURLScheme = mailto;
|
|
|
257 |
// }
|
|
|
258 |
// )
|
|
|
259 |
final String bundleID;
|
|
|
260 |
final String dict = cmdSubstitution("defaults", "read", "com.apple.LaunchServices", "LSHandlers");
|
|
|
261 |
final Matcher dictMatcher = dictPattern.matcher(dict);
|
|
|
262 |
if (dictMatcher.find()) {
|
|
|
263 |
// LSHandlerRoleAll can be before or after LSHandlerURLScheme
|
|
|
264 |
final String before = dictMatcher.group(1);
|
|
|
265 |
final String after = dictMatcher.group(2);
|
|
|
266 |
assert before == null ^ after == null : "Both before and after, or neither before nor after: " + before + " and " + after;
|
|
|
267 |
bundleID = before != null ? before : after;
|
|
|
268 |
} else
|
|
|
269 |
// the default
|
|
|
270 |
bundleID = AppleMailBundleID;
|
|
|
271 |
|
|
|
272 |
if (bundleID.equals(AppleMailBundleID)) {
|
|
|
273 |
return AppleMail;
|
|
|
274 |
} else if (bundleID.equals(ThunderbirdBundleID)) {
|
|
|
275 |
// doesn't work if Thunderbird is already open:
|
|
|
276 |
// https://bugzilla.mozilla.org/show_bug.cgi?id=424155
|
|
|
277 |
// https://bugzilla.mozilla.org/show_bug.cgi?id=472891
|
|
|
278 |
// MAYBE find out if launched and let handled=false
|
28 |
ilm |
279 |
|
61 |
ilm |
280 |
final File appDir = ((Mac) de).getAppDir(bundleID);
|
17 |
ilm |
281 |
final File exe = new File(appDir, "Contents/MacOS/thunderbird-bin");
|
|
|
282 |
|
|
|
283 |
return new ThunderbirdPath(exe);
|
|
|
284 |
}
|
|
|
285 |
} else if (de instanceof Gnome) {
|
149 |
ilm |
286 |
if (de.getVersion().startsWith("2.")) {
|
|
|
287 |
// evolution %s
|
|
|
288 |
final String cmdLine = cmdSubstitution("gconftool", "-g", "/desktop/gnome/url-handlers/mailto/command");
|
|
|
289 |
if (cmdLine.contains("thunderbird")) {
|
|
|
290 |
return new ThunderbirdCommandLine(cmdLine, "%s");
|
|
|
291 |
}
|
17 |
ilm |
292 |
}
|
149 |
ilm |
293 |
return XDG;
|
17 |
ilm |
294 |
} else if (de instanceof KDE) {
|
|
|
295 |
// TODO look for EmailClient=/usr/bin/thunderbird in
|
|
|
296 |
// ~/.kde/share/config/emaildefaults or /etc/kde (ou /usr/share/config qui est un
|
|
|
297 |
// lien symbolique vers /etc/kde)
|
149 |
ilm |
298 |
return XDG;
|
180 |
ilm |
299 |
} else if (de instanceof XFCE) {
|
|
|
300 |
// .config/xfce4/helpers.rc contains "MailReader=desktopName"
|
|
|
301 |
// A custom one can be created in .local/share/xfce4/helpers/custom-MailReader.desktop
|
|
|
302 |
return XDG;
|
|
|
303 |
} else if (OSFamily.getInstance() instanceof Unix) {
|
|
|
304 |
return XDG;
|
17 |
ilm |
305 |
}
|
|
|
306 |
|
|
|
307 |
return MailTo;
|
|
|
308 |
}
|
|
|
309 |
|
|
|
310 |
public static final EmailClient MailTo = new EmailClient(null) {
|
|
|
311 |
@Override
|
28 |
ilm |
312 |
public boolean composeNative(String to, String subject, String body, File... attachments) {
|
17 |
ilm |
313 |
return false;
|
|
|
314 |
}
|
|
|
315 |
};
|
|
|
316 |
|
149 |
ilm |
317 |
public static final EmailClient XDG = new EmailClient(EmailClientType.XDG) {
|
|
|
318 |
@Override
|
|
|
319 |
public boolean composeNative(String to, String subject, String body, File... attachments) throws IOException, InterruptedException {
|
|
|
320 |
final ProcessBuilder pb = new ProcessBuilder("xdg-email");
|
|
|
321 |
if (subject != null) {
|
|
|
322 |
pb.command().add("--subject");
|
|
|
323 |
pb.command().add(subject);
|
|
|
324 |
}
|
|
|
325 |
if (body != null) {
|
|
|
326 |
pb.command().add("--body");
|
|
|
327 |
pb.command().add(body);
|
|
|
328 |
}
|
|
|
329 |
for (File attachment : attachments) {
|
|
|
330 |
pb.command().add("--attach");
|
|
|
331 |
pb.command().add(attachment.getAbsolutePath());
|
|
|
332 |
}
|
|
|
333 |
pb.command().add(to);
|
|
|
334 |
pb.inheritIO();
|
|
|
335 |
final Process process = pb.start();
|
|
|
336 |
process.getOutputStream().close();
|
|
|
337 |
final int returnCode = process.waitFor();
|
|
|
338 |
if (returnCode != 0)
|
|
|
339 |
throw new IllegalStateException("Non zero return code: " + returnCode);
|
|
|
340 |
return true;
|
|
|
341 |
}
|
|
|
342 |
};
|
|
|
343 |
|
17 |
ilm |
344 |
public static final EmailClient Outlook = new EmailClient(EmailClientType.Outlook) {
|
|
|
345 |
@Override
|
28 |
ilm |
346 |
protected boolean composeNative(String to, String subject, String body, File... attachments) throws IOException, InterruptedException {
|
|
|
347 |
final DesktopEnvironment de = DesktopEnvironment.getDE();
|
17 |
ilm |
348 |
final File vbs = FileUtils.getFile(EmailClient.class.getResource("OutlookEmail.vbs"));
|
|
|
349 |
final List<String> l = new ArrayList<String>(6);
|
|
|
350 |
l.add("cscript");
|
28 |
ilm |
351 |
l.add(de.quoteParamForExec(vbs.getAbsolutePath()));
|
17 |
ilm |
352 |
if (to != null)
|
|
|
353 |
l.add(createVBParam("to", to));
|
|
|
354 |
if (subject != null)
|
|
|
355 |
l.add(createVBParam("subject", subject));
|
|
|
356 |
// at least set a parameter otherwise the usage get displayed
|
|
|
357 |
l.add(createVBParam("unicodeStdIn", "1"));
|
28 |
ilm |
358 |
for (File attachment : attachments) {
|
|
|
359 |
l.add(de.quoteParamForExec(attachment.getAbsolutePath()));
|
|
|
360 |
}
|
17 |
ilm |
361 |
|
|
|
362 |
final Process process = new ProcessBuilder(l).start();
|
|
|
363 |
// VBScript only knows ASCII and UTF-16
|
28 |
ilm |
364 |
final Writer writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StringUtils.UTF16));
|
17 |
ilm |
365 |
writer.write(body);
|
|
|
366 |
writer.close();
|
|
|
367 |
final int returnCode = process.waitFor();
|
|
|
368 |
if (returnCode != 0)
|
|
|
369 |
throw new IllegalStateException("Non zero return code: " + returnCode);
|
|
|
370 |
return true;
|
|
|
371 |
}
|
|
|
372 |
};
|
|
|
373 |
|
|
|
374 |
public static final EmailClient AppleMail = new EmailClient(EmailClientType.AppleMail) {
|
|
|
375 |
@Override
|
28 |
ilm |
376 |
protected boolean composeNative(String to, String subject, String body, File... attachments) throws IOException, InterruptedException {
|
17 |
ilm |
377 |
final Process process = Runtime.getRuntime().exec(new String[] { "osascript" });
|
|
|
378 |
final PrintStream w = new PrintStream(new BufferedOutputStream(process.getOutputStream()));
|
|
|
379 |
// use ID to handle application renaming (always a slight delay after a rename for
|
|
|
380 |
// this to work, though)
|
|
|
381 |
w.println("tell application id \"" + AppleMailBundleID + "\"");
|
|
|
382 |
w.println(" set theMessage to make new outgoing message with properties {" + getAppleMailParam(subject, body) + "}");
|
|
|
383 |
if (to != null)
|
28 |
ilm |
384 |
w.println(" tell theMessage to make new to recipient with properties {address:" + StringUtils.doubleQuote(to) + "}");
|
|
|
385 |
for (File attachment : attachments) {
|
|
|
386 |
w.println(" tell content of theMessage to make new attachment with properties {file name:" + StringUtils.doubleQuote(attachment.getAbsolutePath()) + "} at after last paragraph");
|
17 |
ilm |
387 |
}
|
|
|
388 |
w.println("end tell");
|
|
|
389 |
w.close();
|
67 |
ilm |
390 |
if (w.checkError())
|
|
|
391 |
throw new IOException();
|
17 |
ilm |
392 |
|
|
|
393 |
final int returnCode = process.waitFor();
|
|
|
394 |
if (returnCode != 0)
|
|
|
395 |
throw new IllegalStateException("Non zero return code: " + returnCode);
|
|
|
396 |
return true;
|
|
|
397 |
}
|
|
|
398 |
};
|
|
|
399 |
|
|
|
400 |
public static abstract class Thunderbird extends EmailClient {
|
|
|
401 |
|
|
|
402 |
public static Thunderbird createFromExe(final File exe) {
|
83 |
ilm |
403 |
if (exe == null)
|
|
|
404 |
throw new NullPointerException();
|
|
|
405 |
if (!exe.isFile())
|
|
|
406 |
return null;
|
17 |
ilm |
407 |
return new ThunderbirdPath(exe);
|
|
|
408 |
}
|
|
|
409 |
|
|
|
410 |
public static Thunderbird createFromCommandLine(final String cmdLine, final String toReplace) {
|
|
|
411 |
return new ThunderbirdCommandLine(cmdLine, toReplace);
|
|
|
412 |
}
|
|
|
413 |
|
|
|
414 |
protected Thunderbird() {
|
|
|
415 |
super(EmailClientType.Thunderbird);
|
|
|
416 |
}
|
149 |
ilm |
417 |
|
|
|
418 |
@Override
|
|
|
419 |
public String toString() {
|
|
|
420 |
return this.getClass().getSimpleName();
|
|
|
421 |
}
|
17 |
ilm |
422 |
}
|
|
|
423 |
|
|
|
424 |
private static final class ThunderbirdCommandLine extends Thunderbird {
|
|
|
425 |
|
|
|
426 |
private final String cmdLine;
|
|
|
427 |
private final String toReplace;
|
|
|
428 |
|
|
|
429 |
private ThunderbirdCommandLine(final String cmdLine, final String toReplace) {
|
|
|
430 |
this.cmdLine = cmdLine;
|
|
|
431 |
this.toReplace = toReplace;
|
|
|
432 |
}
|
|
|
433 |
|
|
|
434 |
@Override
|
28 |
ilm |
435 |
protected boolean composeNative(String to, String subject, String body, File... attachments) throws IOException {
|
|
|
436 |
Runtime.getRuntime().exec(tbCommand(this.cmdLine, this.toReplace, to, subject, body, attachments));
|
17 |
ilm |
437 |
// don't wait for Thunderbird to quit if it wasn't launched
|
|
|
438 |
// (BTW return code of 1 means the program was already launched)
|
|
|
439 |
return true;
|
|
|
440 |
}
|
149 |
ilm |
441 |
|
|
|
442 |
@Override
|
|
|
443 |
public String toString() {
|
|
|
444 |
return super.toString() + " " + this.cmdLine;
|
|
|
445 |
}
|
17 |
ilm |
446 |
}
|
|
|
447 |
|
|
|
448 |
private static final class ThunderbirdPath extends Thunderbird {
|
|
|
449 |
|
|
|
450 |
private final File exe;
|
|
|
451 |
|
|
|
452 |
private ThunderbirdPath(File exe) {
|
|
|
453 |
this.exe = exe;
|
|
|
454 |
}
|
|
|
455 |
|
|
|
456 |
@Override
|
28 |
ilm |
457 |
protected boolean composeNative(String to, String subject, String body, File... attachments) throws IOException {
|
|
|
458 |
final String composeArg = getTBParam(to, subject, body, attachments);
|
17 |
ilm |
459 |
Runtime.getRuntime().exec(new String[] { this.exe.getPath(), "-compose", composeArg });
|
|
|
460 |
return true;
|
|
|
461 |
}
|
149 |
ilm |
462 |
|
|
|
463 |
@Override
|
|
|
464 |
public String toString() {
|
|
|
465 |
return super.toString() + " " + this.exe;
|
|
|
466 |
}
|
17 |
ilm |
467 |
}
|
|
|
468 |
|
|
|
469 |
private final EmailClientType type;
|
|
|
470 |
|
|
|
471 |
public EmailClient(EmailClientType type) {
|
|
|
472 |
this.type = type;
|
|
|
473 |
}
|
|
|
474 |
|
|
|
475 |
public final EmailClientType getType() {
|
|
|
476 |
return this.type;
|
|
|
477 |
}
|
|
|
478 |
|
28 |
ilm |
479 |
protected abstract boolean composeNative(final String to, final String subject, final String body, final File... attachments) throws IOException, InterruptedException;
|
17 |
ilm |
480 |
|
149 |
ilm |
481 |
@Override
|
|
|
482 |
public String toString() {
|
|
|
483 |
final EmailClientType t = this.getType();
|
|
|
484 |
return t == null ? "mailto" : t.toString();
|
|
|
485 |
}
|
|
|
486 |
|
17 |
ilm |
487 |
public final static void main(String[] args) throws Exception {
|
|
|
488 |
if (args.length == 1 && "--help".equals(args[0])) {
|
|
|
489 |
System.out.println("Usage: java [-Dparam=value] " + EmailClient.class.getName() + " [EmailClientType args]");
|
|
|
490 |
System.out.println("\tEmailClientType: mailto or " + Arrays.asList(EmailClientType.values()));
|
28 |
ilm |
491 |
System.out.println("\tparam: to, subject, body, files (seprated by ',' double it to escape)");
|
17 |
ilm |
492 |
return;
|
|
|
493 |
}
|
|
|
494 |
|
|
|
495 |
final EmailClient client = createFromString(args);
|
149 |
ilm |
496 |
System.out.println("Using " + (args.length == 0 ? "preferred" : "passed") + " client : " + client);
|
28 |
ilm |
497 |
final String to = System.getProperty("to", "Pierre Dupond <p.dupond@example.com>, p.dupont@server.com");
|
|
|
498 |
// ',to=' to test escaping of Thunderbird (passing subject='foo'bar' works)
|
|
|
499 |
final String subject = System.getProperty("subject", "Sujé € du courrier ',to='&;\\<> \"autre'\n2nd line");
|
|
|
500 |
final String body = System.getProperty("body", "Bonjour,\n\tsingle ' double \" backslash(arrière) \\ slash /");
|
|
|
501 |
final String filesPath = System.getProperty("files");
|
|
|
502 |
final String[] paths = filesPath == null || filesPath.length() == 0 ? new String[0] : filesPath.split("(?<!,),(?!,)");
|
|
|
503 |
final File[] f = new File[paths.length];
|
|
|
504 |
for (int i = 0; i < f.length; i++) {
|
|
|
505 |
f[i] = new File(paths[i].replace(",,", ","));
|
|
|
506 |
}
|
17 |
ilm |
507 |
client.compose(to, subject, body, f);
|
|
|
508 |
}
|
|
|
509 |
|
|
|
510 |
private static final EmailClient createFromString(final String... args) throws IOException {
|
|
|
511 |
// switch doesn't support null
|
|
|
512 |
if (args.length == 0)
|
|
|
513 |
return getPreferred();
|
|
|
514 |
else if ("mailto".equals(args[0]))
|
|
|
515 |
return MailTo;
|
|
|
516 |
|
|
|
517 |
final EmailClientType t = EmailClientType.valueOf(args[0]);
|
|
|
518 |
switch (t) {
|
149 |
ilm |
519 |
case XDG:
|
|
|
520 |
return XDG;
|
17 |
ilm |
521 |
case Outlook:
|
|
|
522 |
return Outlook;
|
|
|
523 |
case AppleMail:
|
|
|
524 |
return AppleMail;
|
|
|
525 |
case Thunderbird:
|
83 |
ilm |
526 |
EmailClient res = null;
|
|
|
527 |
if (args.length == 2) {
|
|
|
528 |
final File exe = new File(args[1]);
|
|
|
529 |
res = Thunderbird.createFromExe(exe);
|
|
|
530 |
if (res == null)
|
|
|
531 |
throw new IOException("Invalid exe : " + exe);
|
|
|
532 |
} else if (args.length == 3) {
|
|
|
533 |
res = Thunderbird.createFromCommandLine(args[1], args[2]);
|
|
|
534 |
} else {
|
17 |
ilm |
535 |
throw new IllegalArgumentException(t + " needs 1 or 2 arguments");
|
83 |
ilm |
536 |
}
|
|
|
537 |
return res;
|
17 |
ilm |
538 |
default:
|
|
|
539 |
throw new IllegalStateException("Unknown type " + t);
|
|
|
540 |
}
|
|
|
541 |
}
|
|
|
542 |
}
|