OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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