OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 180 Rev 182
Line 1... Line 1...
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
Line 16... Line 16...
16
import static org.openconcerto.utils.DesktopEnvironment.cmdSubstitution;
16
import static org.openconcerto.utils.DesktopEnvironment.cmdSubstitution;
17
 
17
 
18
import org.openconcerto.utils.cc.ITransformer;
18
import org.openconcerto.utils.cc.ITransformer;
19
 
19
 
20
import java.io.BufferedReader;
20
import java.io.BufferedReader;
-
 
21
import java.io.ByteArrayOutputStream;
21
import java.io.File;
22
import java.io.File;
22
import java.io.IOException;
23
import java.io.IOException;
23
import java.io.InputStreamReader;
24
import java.io.InputStreamReader;
24
import java.math.BigDecimal;
25
import java.math.BigDecimal;
25
import java.net.InetAddress;
26
import java.net.InetAddress;
26
import java.net.SocketException;
27
import java.net.SocketException;
27
import java.nio.file.Files;
28
import java.nio.file.Files;
-
 
29
import java.nio.file.Path;
28
import java.nio.file.Paths;
30
import java.nio.file.Paths;
29
import java.util.ArrayList;
31
import java.util.ArrayList;
30
import java.util.List;
32
import java.util.List;
31
import java.util.logging.Level;
33
import java.util.logging.Level;
32
import java.util.regex.Matcher;
34
import java.util.regex.Matcher;
Line 38... Line 40...
38
 * @author Sylvain
40
 * @author Sylvain
39
 * @see DesktopEnvironment
41
 * @see DesktopEnvironment
40
 */
42
 */
41
public abstract class Platform {
43
public abstract class Platform {
42
 
44
 
-
 
45
    public static final String PROCESS_ALLOW_AMBIGUOUS_COMMANDS = "jdk.lang.Process.allowAmbiguousCommands";
-
 
46
 
43
    private static final int PING_TIMEOUT = 250;
47
    private static final int PING_TIMEOUT = 250;
44
 
48
 
45
    public static final Platform getInstance() {
49
    public static final Platform getInstance() {
46
        final OSFamily os = OSFamily.getInstance();
50
        final OSFamily os = OSFamily.getInstance();
47
        if (os == OSFamily.Windows) {
51
        if (os == OSFamily.Windows) {
Line 53... Line 57...
53
        } else {
57
        } else {
54
            return LINUX;
58
            return LINUX;
55
        }
59
        }
56
    }
60
    }
57
 
61
 
-
 
62
    public static class CannotPassArgumentException extends RuntimeException {
-
 
63
        private final String arg;
-
 
64
 
-
 
65
        private CannotPassArgumentException(String arg, String message) {
-
 
66
            super("Cannot pass " + arg + message);
-
 
67
            this.arg = arg;
-
 
68
        }
-
 
69
 
-
 
70
        public final String getArg() {
-
 
71
            return this.arg;
-
 
72
        }
-
 
73
    }
-
 
74
 
58
    public abstract boolean supportsPID();
75
    public abstract boolean supportsPID();
59
 
76
 
60
    public abstract boolean isRunning(final int pid) throws IOException;
77
    public abstract boolean isRunning(final int pid) throws IOException;
61
 
78
 
62
    public abstract String getPath(final File f);
79
    public abstract String getPath(final File f);
63
 
80
 
-
 
81
    public String getPath(final Path p) {
-
 
82
        return this.getPath(p.toFile());
-
 
83
    }
-
 
84
 
-
 
85
    public String getProcessArg(final String arg) {
-
 
86
        return arg;
-
 
87
    }
-
 
88
 
64
    public final String getPID() throws IOException {
89
    public final String getPID() throws IOException {
65
        // TODO remove reflection and getPreJava9PID() once on java 11
90
        // TODO remove reflection and getPreJava9PID() once on java 11
66
        try {
91
        try {
67
            final Class<?> phClass = Class.forName("java.lang.ProcessHandle");
92
            final Class<?> phClass = Class.forName("java.lang.ProcessHandle");
68
            final Object ph = phClass.getMethod("current").invoke(null);
93
            final Object ph = phClass.getMethod("current").invoke(null);
Line 151... Line 176...
151
        if (requiredCount <= 0)
176
        if (requiredCount <= 0)
152
            requiredCount = totalCount;
177
            requiredCount = totalCount;
153
        // Keep errors out of cmdSubstitution() (e.g. "ping: sendto: Message too long" when
178
        // Keep errors out of cmdSubstitution() (e.g. "ping: sendto: Message too long" when
154
        // setDontFragment(true))
179
        // setDontFragment(true))
155
        final Process proc = evalPB(command).redirectErrorStream(false).start();
180
        final Process proc = evalPB(command).redirectErrorStream(false).start();
-
 
181
 
-
 
182
        // some programs won't write anything until they read everything
-
 
183
        proc.getOutputStream().close();
-
 
184
        final ByteArrayOutputStream out = new ByteArrayOutputStream(5 * 1024);
-
 
185
        final ByteArrayOutputStream err = new ByteArrayOutputStream(2 * 1024);
-
 
186
        try (final ProcessStreams streams = new ProcessStreams(proc)) {
-
 
187
            streams.start(out, err);
-
 
188
            streams.awaitTermination();
-
 
189
        } catch (Exception e) {
-
 
190
            throw new IllegalStateException("Couldn't capture output of ping", e);
-
 
191
        }
-
 
192
 
156
        final String output = cmdSubstitution(proc);
193
        final String output = out.toString();
157
        try {
194
        try {
158
            this.waitForSuccess(proc, "ping");
195
            this.waitForSuccess(proc, "ping");
159
            final List<String> countAndLastLine = StringUtils.splitIntoLines(output);
196
            final List<String> countAndLastLine = StringUtils.splitIntoLines(output);
160
            if (countAndLastLine.size() != 2)
197
            if (countAndLastLine.size() != 2)
161
                throw new IllegalStateException("Not 2 lines in " + countAndLastLine);
198
                throw new IllegalStateException("Not 2 lines in " + countAndLastLine);
162
            final int replied = Integer.parseInt(countAndLastLine.get(0));
199
            final int replied = Integer.parseInt(countAndLastLine.get(0));
163
            assert replied <= totalCount;
200
            assert replied <= totalCount;
164
            final BigDecimal averageRTT = replied == 0 ? null : parsePingAverageRT(countAndLastLine.get(1).trim());
201
            final BigDecimal averageRTT = replied == 0 ? null : parsePingAverageRT(countAndLastLine.get(1).trim());
165
            return new PingResult(totalCount, replied, requiredCount, averageRTT);
202
            return new PingResult(totalCount, replied, requiredCount, averageRTT);
166
        } catch (Exception e) {
203
        } catch (Exception e) {
167
            throw new IllegalStateException("Couldn't use output :<<<\n" + output + "\n<<<", e);
204
            throw new IllegalStateException("Couldn't use output :<<<\n" + output + "\n<<<\nerr:<<<\n" + err.toString() + "\n<<<", e);
168
        }
205
        }
169
    }
206
    }
170
 
207
 
171
    /**
208
    /**
172
     * Eval the passed string with bash.
209
     * Eval the passed string with bash.
Line 228... Line 265...
228
            // --pid only works on Linux, -p also on Nexenta
265
            // --pid only works on Linux, -p also on Nexenta
229
            final Process p = Runtime.getRuntime().exec(new String[] { "ps", "-p", String.valueOf(pid) });
266
            final Process p = Runtime.getRuntime().exec(new String[] { "ps", "-p", String.valueOf(pid) });
230
            return this.exitStatus(p) == 0;
267
            return this.exitStatus(p) == 0;
231
        }
268
        }
232
 
269
 
-
 
270
        @Override
233
        public String getPath(final File f) {
271
        public String getPath(final File f) {
234
            return f.getPath();
272
            return f.getPath();
235
        }
273
        }
236
 
274
 
237
        @Override
275
        @Override
-
 
276
        public String getPath(final Path f) {
-
 
277
            return f.toString();
-
 
278
        }
-
 
279
 
-
 
280
        @Override
238
        protected String getBash() {
281
        protected String getBash() {
239
            return "bash";
282
            return "bash";
240
        }
283
        }
241
 
284
 
242
        @Override
285
        @Override
Line 379... Line 422...
379
        protected PingResult ping(InetAddress host, PingBuilder pingBuilder, int routingTableIndex) throws IOException {
422
        protected PingResult ping(InetAddress host, PingBuilder pingBuilder, int routingTableIndex) throws IOException {
380
            return FREEBSD.ping(host, pingBuilder, routingTableIndex);
423
            return FREEBSD.ping(host, pingBuilder, routingTableIndex);
381
        }
424
        }
382
    };
425
    };
383
 
426
 
-
 
427
    // return 171 for "1.8.0_171"
-
 
428
    // return 11 for "11.0.11"
-
 
429
    public static final int getUpdateVersion(final char sep) {
-
 
430
        final String vers = System.getProperty("java.version");
-
 
431
        final int lastIndexOf = vers.lastIndexOf(sep);
-
 
432
        // e.g. "13"
-
 
433
        if (lastIndexOf < 0)
-
 
434
            return 0;
-
 
435
        return Integer.parseInt(vers.substring(lastIndexOf + 1));
-
 
436
    }
-
 
437
 
-
 
438
    public static abstract class WindowsPlatform extends Platform {
-
 
439
        // on Windows program themselves are required to parse the command line, thus a lot of them
-
 
440
        // do it differently, see "How Command Line Parameters Are Parsed"
-
 
441
        // https://daviddeley.com/autohotkey/parameters/parameters.htm
-
 
442
 
-
 
443
        static private final Pattern quotePatrn = Pattern.compile("([\\\\]*)\"");
-
 
444
        static private final Pattern endSlashPatrn = Pattern.compile("([\\\\]+)\\z");
-
 
445
 
-
 
446
        static private boolean needsQuoting(String s) {
-
 
447
            final int len = s.length();
-
 
448
            if (len == 0) // empty string have to be quoted
-
 
449
                return true;
-
 
450
            for (int i = 0; i < len; i++) {
-
 
451
                switch (s.charAt(i)) {
-
 
452
                case ' ':
-
 
453
                case '\t':
-
 
454
                case '"':
-
 
455
                    return true;
-
 
456
                }
-
 
457
            }
-
 
458
            return false;
-
 
459
        }
-
 
460
 
-
 
461
        // see http://bugs.sun.com/view_bug.do?bug_id=6468220
-
 
462
        // e.g. find.exe, choice.exe
-
 
463
        public String quoteParamForMsftC(String s) {
-
 
464
            if (!needsQuoting(s))
-
 
465
                return s;
-
 
466
            if (s.length() > 0) {
-
 
467
                // replace '(\*)"' by '$1$1\"', e.g. '\quote " \"' by '\quote \" \\\"'
-
 
468
                // $1 needed so that the backslash we add isn't escaped itself by a preceding
-
 
469
                // backslash
-
 
470
                s = quotePatrn.matcher(s).replaceAll("$1$1\\\\\"");
-
 
471
                // replace '(\*)\z' by '$1$1', e.g. 'foo\' by 'foo\\'
-
 
472
                // needed to not escape closing quote
-
 
473
                s = endSlashPatrn.matcher(s).replaceAll("$1$1");
-
 
474
            }
-
 
475
            return '"' + s + '"';
-
 
476
        }
-
 
477
 
-
 
478
        @Override
-
 
479
        public String getProcessArg(String arg) {
-
 
480
            return this.getProcessArg(arg, false);
-
 
481
        }
-
 
482
 
-
 
483
        public final String getScriptProcessArg(String arg) {
-
 
484
            return this.getProcessArg(arg, true);
-
 
485
        }
-
 
486
 
-
 
487
        private String getProcessArg(String arg, final boolean script) {
-
 
488
            // Perhaps should have one method for .exe and one for .cmd/.bat (ProcessImpl checks
-
 
489
            // with isShellFile() and isExe()).
-
 
490
 
-
 
491
            if (script && arg.indexOf('"') >= 0)
-
 
492
                throw new CannotPassArgumentException(arg, ", it contains a double quote which is always removed by wscript!SplitCommandLine()");
-
 
493
 
-
 
494
            /*
-
 
495
             * If has VERIFICATION_WIN32_SAFE && !allowAmbiguousCommands, then ProcessImpl behaves
-
 
496
             * almost correctly : the argument we pass arrives as-is to the process (so the caller
-
 
497
             * shouldn't quote, ProcessImpl will fail with "Malformed argument has embedded quote"
-
 
498
             * if a character was escaped). Otherwise ProcessImpl does nothing if arg begins and
-
 
499
             * ends with double quotes (it's the caller responsibility to correctly quote).
-
 
500
             */
-
 
501
            final boolean doubleQuote;
-
 
502
            // 1.8 or 11
-
 
503
            final String specVers = System.getProperty("java.specification.version");
-
 
504
            // 8 or 11
-
 
505
            final int jreFamilyVersion = Integer.parseInt(specVers.startsWith("1.") ? specVers.substring(2) : specVers);
-
 
506
            final String javaVendor = System.getProperty("java.vendor");
-
 
507
            // VERIFICATION_WIN32_SAFE thanks to https://nvd.nist.gov/vuln/detail/CVE-2019-2958
-
 
508
            // For Oracle and OpenJDK :
-
 
509
            // https://www.oracle.com/java/technologies/javase/8u231-relnotes.html#JDK-8221858
-
 
510
            // https://github.com/openjdk/jdk/commit/5a98b8cfb0cd4c5ce84746e9fa5e42a86d1b2d24#diff-71e1dde85b2937bbf08d0bfd9e8a2e3553c578a3f58907ba6f4f30ec350db184
-
 
511
            // For AdoptOpenJDK :
-
 
512
            // https://github.com/AdoptOpenJDK/openjdk-jdk11u/commit/0074850a15405a676a492d09e4955d4053966bbc#diff-71e1dde85b2937bbf08d0bfd9e8a2e3553c578a3f58907ba6f4f30ec350db184
-
 
513
            if (jreFamilyVersion >= 14 || (javaVendor.equals("AdoptOpenJDK") && (jreFamilyVersion == 11 && getUpdateVersion('.') >= 5 || jreFamilyVersion == 13 && getUpdateVersion('.') >= 1))
-
 
514
                    || (jreFamilyVersion == 8 && getUpdateVersion('_') >= 231)) {
-
 
515
                // copied from ProcessImpl
-
 
516
                final SecurityManager security = System.getSecurityManager();
-
 
517
                final String value = System.getProperty(PROCESS_ALLOW_AMBIGUOUS_COMMANDS, (security == null ? "true" : "false"));
-
 
518
                final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value);
-
 
519
                if ((script || allowAmbiguousCommands) && arg.endsWith("\\")) {
-
 
520
                    // with script, we always do our own quoting since ProcessBuilder will double
-
 
521
                    // back slashes.
-
 
522
                    if (script || needsQuoting(arg)) {
-
 
523
                        throw new CannotPassArgumentException(arg,
-
 
524
                                ", if we'd double quote, then the string would end with 2 back slashes and 1 double quote, but ProcessImpl.unQuote() : 'not properly quoted, treat as unquoted'. So it would get quoted a second time. Try setting '"
-
 
525
                                        + PROCESS_ALLOW_AMBIGUOUS_COMMANDS + "' to 'false'.");
-
 
526
                    } else {
-
 
527
                        doubleQuote = false;
-
 
528
                    }
-
 
529
                } else if (!allowAmbiguousCommands && arg.length() >= 2 && arg.charAt(0) == '"' && arg.charAt(arg.length() - 1) == '"') {
-
 
530
                    throw new CannotPassArgumentException(arg,
-
 
531
                            "\nif we pass it as-is then ProcessImpl will consider the string already quoted and needsEscaping() will return false, thus the quotes will be removed by CommandLineToArgvW()"
-
 
532
                                    + "\nif we quote it then ProcessImpl.needsEscaping() will throw 'Malformed argument has embedded quote'");
-
 
533
                } else {
-
 
534
                    doubleQuote = allowAmbiguousCommands;
-
 
535
                }
-
 
536
            } else {
-
 
537
                doubleQuote = true;
-
 
538
            }
-
 
539
 
-
 
540
            if (script)
-
 
541
                return '"' + arg + '"';
-
 
542
            else
-
 
543
                return doubleQuote ? quoteParamForMsftC(arg) : arg;
-
 
544
        }
-
 
545
    }
-
 
546
 
384
    private static final class CygwinPlatform extends Platform {
547
    private static final class CygwinPlatform extends WindowsPlatform {
385
 
548
 
386
        @Override
549
        @Override
387
        public boolean supportsPID() {
550
        public boolean supportsPID() {
388
            return false;
551
            return false;
389
        }
552
        }