OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

Rev 174 Rev 177
Line 13... Line 13...
13
 
13
 
14
 package org.openconcerto.utils.sync;
14
 package org.openconcerto.utils.sync;
15
 
15
 
16
import org.openconcerto.utils.CollectionUtils;
16
import org.openconcerto.utils.CollectionUtils;
17
import org.openconcerto.utils.MessageDigestUtils;
17
import org.openconcerto.utils.MessageDigestUtils;
-
 
18
import org.openconcerto.utils.NetUtils;
18
import org.openconcerto.utils.StreamUtils;
19
import org.openconcerto.utils.StreamUtils;
-
 
20
import org.openconcerto.utils.net.HTTPClient;
19
 
21
 
-
 
22
import java.io.BufferedInputStream;
20
import java.io.BufferedOutputStream;
23
import java.io.BufferedOutputStream;
-
 
24
import java.io.ByteArrayOutputStream;
21
import java.io.File;
25
import java.io.File;
-
 
26
import java.io.FileInputStream;
22
import java.io.IOException;
27
import java.io.IOException;
23
import java.io.InputStream;
28
import java.io.InputStream;
24
import java.io.OutputStream;
29
import java.io.OutputStream;
25
import java.io.UnsupportedEncodingException;
-
 
26
import java.net.MalformedURLException;
-
 
27
import java.net.ProtocolException;
-
 
28
import java.net.URL;
-
 
29
import java.nio.charset.StandardCharsets;
30
import java.nio.charset.StandardCharsets;
30
import java.nio.file.Files;
31
import java.nio.file.Files;
31
import java.nio.file.Path;
32
import java.nio.file.Path;
32
import java.nio.file.StandardCopyOption;
33
import java.nio.file.StandardCopyOption;
33
import java.nio.file.attribute.FileTime;
34
import java.nio.file.attribute.FileTime;
Line 40... Line 41...
40
import java.util.List;
41
import java.util.List;
41
import java.util.Set;
42
import java.util.Set;
42
import java.util.concurrent.atomic.AtomicBoolean;
43
import java.util.concurrent.atomic.AtomicBoolean;
43
import java.util.function.BiFunction;
44
import java.util.function.BiFunction;
44
import java.util.function.Predicate;
45
import java.util.function.Predicate;
45
import java.util.zip.GZIPInputStream;
-
 
46
import java.util.zip.GZIPOutputStream;
-
 
47
 
46
 
48
import javax.net.ssl.HttpsURLConnection;
47
import javax.net.ssl.HttpsURLConnection;
49
import javax.net.ssl.SSLSocketFactory;
-
 
50
 
48
 
51
import net.minidev.json.JSONArray;
49
import net.minidev.json.JSONArray;
52
import net.minidev.json.JSONObject;
50
import net.minidev.json.JSONObject;
53
import net.minidev.json.parser.JSONParser;
51
import net.minidev.json.parser.JSONParser;
54
 
52
 
55
public final class SimpleSyncClient {
53
public final class SimpleSyncClient extends HTTPClient {
56
 
-
 
57
    private static final int MIN_GZIP_SIZE = 64;
-
 
58
 
54
 
59
    static protected final long getLong(final Object o) {
55
    static protected final long getLong(final Object o) {
60
        return ((Number) o).longValue();
56
        return ((Number) o).longValue();
61
    }
57
    }
62
 
58
 
63
    static protected final Instant getInstant(final Object o) {
59
    static protected final Instant getInstant(final Object o) {
64
        return Instant.ofEpochMilli(getLong(o));
60
        return Instant.ofEpochMilli(getLong(o));
65
    }
61
    }
66
 
62
 
67
    static public class ServerException extends RuntimeException {
-
 
68
        private final int responseCode;
-
 
69
        private final boolean authenticateError;
-
 
70
 
-
 
71
        protected ServerException(int responseCode, boolean authenticateError) {
-
 
72
            super("Response code was " + responseCode);
-
 
73
            this.responseCode = responseCode;
-
 
74
            this.authenticateError = authenticateError;
-
 
75
        }
-
 
76
 
-
 
77
        public final int getResponseCode() {
-
 
78
            return this.responseCode;
-
 
79
        }
-
 
80
 
-
 
81
        public final boolean isAuthenticateError() {
-
 
82
            return this.authenticateError;
-
 
83
        }
-
 
84
    }
-
 
85
 
-
 
86
    static public final class Response {
-
 
87
 
-
 
88
        protected final static Response create(HttpsURLConnection con, final Set<Integer> okCodes) throws IOException {
-
 
89
            final boolean success = okCodes == null ? con.getResponseCode() == 200 : okCodes.contains(con.getResponseCode());
-
 
90
            return new Response(success, con.getResponseCode(), con.getResponseMessage(), con.getContentEncoding(), con.getContentType());
-
 
91
        }
-
 
92
 
-
 
93
        private final boolean success;
-
 
94
        private final int code;
-
 
95
        private final String message;
-
 
96
        private final String contentEncoding, contentType;
-
 
97
 
-
 
98
        protected Response(boolean success, int code, String message, String contentEncoding, String contentType) {
-
 
99
            super();
-
 
100
            this.success = success;
-
 
101
            this.code = code;
-
 
102
            this.message = message;
-
 
103
            this.contentEncoding = contentEncoding;
-
 
104
            this.contentType = contentType;
-
 
105
        }
-
 
106
 
-
 
107
        public final int getCode() {
-
 
108
            return this.code;
-
 
109
        }
-
 
110
 
-
 
111
        public final boolean isSuccess() {
-
 
112
            return this.success;
-
 
113
        }
-
 
114
 
-
 
115
        public final String getMessage() {
-
 
116
            return this.message;
-
 
117
        }
-
 
118
 
-
 
119
        public final String getContentEncoding() {
-
 
120
            return this.contentEncoding;
-
 
121
        }
-
 
122
 
-
 
123
        public final String getContentType() {
-
 
124
            return this.contentType;
-
 
125
        }
-
 
126
    }
-
 
127
 
-
 
128
    static protected abstract class BaseAttrs {
63
    static protected abstract class BaseAttrs {
129
        private final String path;
64
        private final String path;
130
        private final String name;
65
        private final String name;
131
        private final Instant lastModified;
66
        private final Instant lastModified;
132
 
67
 
Line 262... Line 197...
262
            }
197
            }
263
            return res;
198
            return res;
264
        }
199
        }
265
    }
200
    }
266
 
201
 
267
    private final String url;
-
 
268
    private SSLSocketFactory socketFactory;
-
 
269
    private String token;
-
 
270
    private boolean throwException = true;
-
 
271
 
-
 
272
    public SimpleSyncClient(final String url) {
202
    public SimpleSyncClient(final String url) {
273
        this.url = url;
-
 
274
    }
-
 
275
 
-
 
276
    public final SSLSocketFactory getSocketFactory() {
-
 
277
        return this.socketFactory;
-
 
278
    }
-
 
279
 
-
 
280
    public final void setSocketFactory(SSLSocketFactory socketFactory) {
-
 
281
        this.socketFactory = socketFactory;
-
 
282
    }
-
 
283
 
-
 
284
    public final void setToken(String token) {
-
 
285
        this.token = token;
-
 
286
    }
-
 
287
 
-
 
288
    public final boolean hasToken() {
-
 
289
        return this.getToken() != null;
-
 
290
    }
-
 
291
 
-
 
292
    protected final String getToken() {
-
 
293
        return this.token;
-
 
294
    }
-
 
295
 
-
 
296
    public final void setThrowException(boolean throwException) {
-
 
297
        this.throwException = throwException;
-
 
298
    }
-
 
299
 
-
 
300
    public final boolean throwsException() {
-
 
301
        return this.throwException;
-
 
302
    }
-
 
303
 
-
 
304
    protected Response checkResponseCode(final HttpsURLConnection con) throws IOException {
-
 
305
        return checkResponseCode(con, null);
-
 
306
    }
-
 
307
 
-
 
308
    protected Response checkResponseCode(final HttpsURLConnection con, final Set<Integer> okCodes) throws IOException {
-
 
309
        final Response res = Response.create(con, okCodes);
-
 
310
        if (this.throwsException() && !res.isSuccess())
-
 
311
            throw new ServerException(res.getCode(), con.getHeaderField("WWW-Authenticate") != null);
-
 
312
        return res;
203
        super(url);
313
    }
-
 
314
 
-
 
315
    private final HttpsURLConnection openConnection(final String path) throws IOException {
-
 
316
        final HttpsURLConnection con = (HttpsURLConnection) new URL(this.url + path).openConnection();
-
 
317
        con.setRequestProperty("Accept-Encoding", "gzip");
-
 
318
        if (this.getSocketFactory() != null)
-
 
319
            con.setSSLSocketFactory(this.getSocketFactory());
-
 
320
        if (getToken() != null)
-
 
321
            con.setRequestProperty("Authorization", "Bearer " + Base64.getEncoder().encodeToString(getToken().getBytes(StandardCharsets.UTF_8)));
-
 
322
        return con;
-
 
323
    }
-
 
324
 
-
 
325
    private final InputStream getInputStream(final HttpsURLConnection con) throws IOException {
-
 
326
        return "gzip".equals(con.getContentEncoding()) ? new GZIPInputStream(con.getInputStream()) : con.getInputStream();
-
 
327
    }
-
 
328
 
-
 
329
    private final HttpsURLConnection send(final HttpsURLConnection con, final String params) throws IOException {
-
 
330
        return this.send(con, params.getBytes(StandardCharsets.UTF_8), false);
-
 
331
    }
-
 
332
 
-
 
333
    private final HttpsURLConnection send(final HttpsURLConnection con, final byte[] toSend, final boolean allowGzip) throws IOException {
-
 
334
        final boolean useGzip = allowGzip && toSend.length >= MIN_GZIP_SIZE;
-
 
335
        if (useGzip)
-
 
336
            con.setRequestProperty("Content-Encoding", "gzip");
-
 
337
        con.setRequestMethod("POST");
-
 
338
        con.setDoOutput(true);
-
 
339
        try (final OutputStream o = useGzip ? new GZIPOutputStream(con.getOutputStream()) : con.getOutputStream()) {
-
 
340
            o.write(toSend);
-
 
341
        }
-
 
342
        return con;
-
 
343
    }
204
    }
344
 
205
 
345
    public DirContent getDir(final String path) throws Exception {
206
    public DirContent getDir(final String path) throws Exception {
346
        final HttpsURLConnection con = openConnection("/getDir");
207
        final HttpsURLConnection con = openConnection("/getDir");
347
        final Response res = checkResponseCode(send(con, "rp=" + path + "&type=json"));
208
        final Response res = checkResponseCode(send(con, NetUtils.urlEncode("rp", path, "type", "json")));
348
        if (!res.isSuccess())
209
        if (!res.isSuccess())
349
            return null;
210
            return null;
350
        final JSONParser p = new JSONParser(JSONParser.MODE_STRICTEST);
211
        final JSONParser p = new JSONParser(JSONParser.MODE_STRICTEST);
351
        try (final InputStream in = getInputStream(con)) {
212
        try (final InputStream in = getInputStream(con)) {
352
            return new DirContent(path, (JSONObject) p.parse(in));
213
            return new DirContent(path, (JSONObject) p.parse(in));
Line 360... Line 221...
360
 
221
 
361
    static private final Set<Integer> GETFILE_OK_CODES = CollectionUtils.createSet(200, 404);
222
    static private final Set<Integer> GETFILE_OK_CODES = CollectionUtils.createSet(200, 404);
362
 
223
 
363
    public Response getFile(final String path, final String fileName, final FileConsumer fileConsumer) throws IOException {
224
    public Response getFile(final String path, final String fileName, final FileConsumer fileConsumer) throws IOException {
364
        final HttpsURLConnection con = openConnection("/get");
225
        final HttpsURLConnection con = openConnection("/get");
365
        send(con, "rn=" + fileName + "&rp=" + path);
226
        send(con, NetUtils.urlEncode("rn", fileName, "rp", path));
366
        final Response res = checkResponseCode(con, GETFILE_OK_CODES);
227
        final Response res = checkResponseCode(con, GETFILE_OK_CODES);
367
        if (res.getCode() == 404) {
228
        if (res.getCode() == 404) {
368
            fileConsumer.accept(null, null);
229
            fileConsumer.accept(null, null);
369
        } else if (res.getCode() == 200) {
230
        } else if (res.getCode() == 200) {
370
            final FileAttrs fileAttrs = new FileAttrs(path, fileName, Instant.ofEpochMilli(con.getLastModified()), -1, con.getHeaderField("X-SHA256"));
231
            final FileAttrs fileAttrs = new FileAttrs(path, fileName, Instant.ofEpochMilli(con.getLastModified()), -1, con.getHeaderField("X-SHA256"));
Line 389... Line 250...
389
        if (!res.isSuccess())
250
        if (!res.isSuccess())
390
            throw new IOException("Couldn't retrieve file " + fileName);
251
            throw new IOException("Couldn't retrieve file " + fileName);
391
        return !missing.get();
252
        return !missing.get();
392
    }
253
    }
393
 
254
 
394
    public Response deleteFile(final String path, final String fileName) throws MalformedURLException, IOException, ProtocolException, UnsupportedEncodingException {
255
    public Response deleteFile(final String path, final String fileName) throws IOException {
395
        final HttpsURLConnection con = openConnection("/delete");
256
        final HttpsURLConnection con = openConnection("/delete");
396
        return checkResponseCode(send(con, "rn=" + fileName + "&rp=" + path));
257
        return checkResponseCode(send(con, NetUtils.urlEncode("rn", fileName, "rp", path)));
397
    }
258
    }
398
 
259
 
-
 
260
    public final Response renameFile(final String path, final String fileName, final String newFileName) throws IOException {
-
 
261
        return this.renameFile(path, fileName, null, newFileName);
-
 
262
    }
-
 
263
 
-
 
264
    public final Response renameFile(final String path, final String fileName, final String newPath, final String newFileName) throws IOException {
-
 
265
        final HttpsURLConnection con = openConnection("/rename");
-
 
266
        return checkResponseCode(send(con, NetUtils.urlEncode("rn", fileName, "rp", path, "newPath", newPath, "newName", newFileName)));
-
 
267
    }
-
 
268
 
399
    public Response sendFile(String path, File localFile) throws Exception, MalformedURLException, IOException, ProtocolException {
269
    public Response sendFile(String path, File localFile) throws IOException {
400
        byte[] newsha256 = HashWriter.getHash(localFile);
270
        return this.sendFile(path, localFile, false);
-
 
271
    }
-
 
272
 
-
 
273
    public Response sendFile(String path, File localFile, final boolean overwrite) throws IOException {
401
        long size = localFile.length();
274
        final long size = localFile.length();
-
 
275
        if (size >= Integer.MAX_VALUE)
-
 
276
            throw new OutOfMemoryError("Required array size too large : " + size);
-
 
277
        final ByteArrayOutputStream ba = new ByteArrayOutputStream((int) size);
-
 
278
        final byte[] newsha256;
-
 
279
        // compute digest at the same time to protect against race conditions
-
 
280
        try (final InputStream ins = new BufferedInputStream(new FileInputStream(localFile))) {
-
 
281
            newsha256 = MessageDigestUtils.getHash(MessageDigestUtils.getSHA256(), ins, ba);
-
 
282
        }
402
 
283
 
403
        final HttpsURLConnection con = openConnection("/put");
284
        final HttpsURLConnection con = openConnection("/put");
404
        // We use Base64 because headers are not supporting UTF8
285
        // We use Base64 because headers are not supporting UTF8
405
        con.setRequestProperty("X_FILENAME_B64", Base64.getEncoder().encodeToString(localFile.getName().getBytes(StandardCharsets.UTF_8)));
286
        con.setRequestProperty("X_FILENAME_B64", Base64.getEncoder().encodeToString(localFile.getName().getBytes(StandardCharsets.UTF_8)));
406
        con.setRequestProperty("X_PATH_B64", Base64.getEncoder().encodeToString(path.getBytes(StandardCharsets.UTF_8)));
287
        con.setRequestProperty("X_PATH_B64", Base64.getEncoder().encodeToString(path.getBytes(StandardCharsets.UTF_8)));
-
 
288
        con.setRequestProperty("X-OVERWRITE", Boolean.toString(overwrite));
407
        con.setRequestProperty("X_FILESIZE", String.valueOf(size));
289
        con.setRequestProperty("X_FILESIZE", String.valueOf(size));
408
        con.setRequestProperty("X_SHA256", HashWriter.bytesToHex(newsha256));
290
        con.setRequestProperty("X_SHA256", HashWriter.bytesToHex(newsha256));
409
        con.setRequestProperty("X-Last-Modified-ms", String.valueOf(localFile.lastModified()));
291
        con.setRequestProperty("X-Last-Modified-ms", String.valueOf(localFile.lastModified()));
410
 
292
 
411
        return checkResponseCode(send(con, Files.readAllBytes(localFile.toPath()), true));
293
        return checkResponseCode(send(con, ba.toByteArray(), true));
412
    }
294
    }
413
}
295
}