|
20 | 20 |
|
21 | 21 | import static apoc.ApocConfig.APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM;
|
22 | 22 | import static apoc.ApocConfig.apocConfig;
|
| 23 | +import static apoc.export.util.LimitedSizeInputStream.toLimitedIStream; |
23 | 24 | import static apoc.util.Util.ERROR_BYTES_OR_STRING;
|
24 | 25 | import static apoc.util.Util.REDIRECT_LIMIT;
|
25 |
| -import static apoc.util.Util.readHttpInputStream; |
| 26 | +import static apoc.util.Util.isRedirect; |
26 | 27 |
|
27 | 28 | import apoc.ApocConfig;
|
28 | 29 | import apoc.export.util.CountingInputStream;
|
|
32 | 33 | import apoc.util.s3.S3URLConnection;
|
33 | 34 | import apoc.util.s3.S3UploadUtils;
|
34 | 35 | import java.io.BufferedOutputStream;
|
| 36 | +import java.io.BufferedWriter; |
| 37 | +import java.io.ByteArrayInputStream; |
35 | 38 | import java.io.File;
|
36 | 39 | import java.io.FileOutputStream;
|
37 | 40 | import java.io.IOException;
|
| 41 | +import java.io.InputStream; |
38 | 42 | import java.io.OutputStream;
|
| 43 | +import java.io.OutputStreamWriter; |
| 44 | +import java.io.StringWriter; |
| 45 | +import java.net.HttpURLConnection; |
39 | 46 | import java.net.MalformedURLException;
|
40 | 47 | import java.net.URI;
|
41 | 48 | import java.net.URISyntaxException;
|
42 | 49 | import java.net.URL;
|
| 50 | +import java.net.URLConnection; |
43 | 51 | import java.net.URLStreamHandler;
|
44 | 52 | import java.net.URLStreamHandlerFactory;
|
45 | 53 | import java.nio.file.NoSuchFileException;
|
46 | 54 | import java.nio.file.Path;
|
47 | 55 | import java.nio.file.Paths;
|
48 | 56 | import java.util.Map;
|
49 | 57 | import java.util.Optional;
|
| 58 | +import org.apache.commons.compress.archivers.ArchiveEntry; |
| 59 | +import org.apache.commons.compress.archivers.ArchiveInputStream; |
50 | 60 | import org.apache.commons.io.FilenameUtils;
|
| 61 | +import org.apache.commons.io.IOUtils; |
51 | 62 | import org.apache.commons.lang3.StringUtils;
|
52 | 63 | import org.neo4j.graphdb.security.URLAccessChecker;
|
53 | 64 | import org.neo4j.graphdb.security.URLAccessValidationError;
|
@@ -187,7 +198,7 @@ public static CountingInputStream inputStreamFor(
|
187 | 198 | if (input instanceof String) {
|
188 | 199 | String fileName = (String) input;
|
189 | 200 | fileName = changeFileUrlIfImportDirectoryConstrained(fileName, urlAccessChecker);
|
190 |
| - return Util.openInputStream(fileName, headers, payload, compressionAlgo, urlAccessChecker); |
| 201 | + return FileUtils.openInputStream(fileName, headers, payload, compressionAlgo, urlAccessChecker); |
191 | 202 | } else if (input instanceof byte[]) {
|
192 | 203 | return getInputStreamFromBinary((byte[]) input, compressionAlgo);
|
193 | 204 | } else {
|
@@ -345,4 +356,142 @@ public static File getLogDirectory() {
|
345 | 356 | public static CountingInputStream getInputStreamFromBinary(byte[] urlOrBinary, String compressionAlgo) {
|
346 | 357 | return CompressionAlgo.valueOf(compressionAlgo).toInputStream(urlOrBinary);
|
347 | 358 | }
|
| 359 | + |
| 360 | + public static StreamConnection readHttpInputStream( |
| 361 | + String urlAddress, |
| 362 | + Map<String, Object> headers, |
| 363 | + String payload, |
| 364 | + int redirectLimit, |
| 365 | + URLAccessChecker urlAccessChecker) |
| 366 | + throws IOException { |
| 367 | + URL url = ApocConfig.apocConfig().checkAllowedUrlAndPinToIP(urlAddress, urlAccessChecker); |
| 368 | + URLConnection con = openUrlConnection(url, headers); |
| 369 | + writePayload(con, payload); |
| 370 | + String newUrl = handleRedirect(con, urlAddress); |
| 371 | + if (newUrl != null && !urlAddress.equals(newUrl)) { |
| 372 | + con.getInputStream().close(); |
| 373 | + if (redirectLimit == 0) { |
| 374 | + throw new IOException("Redirect limit exceeded"); |
| 375 | + } |
| 376 | + return readHttpInputStream(newUrl, headers, payload, --redirectLimit, urlAccessChecker); |
| 377 | + } |
| 378 | + |
| 379 | + return new StreamConnection.UrlStreamConnection(con); |
| 380 | + } |
| 381 | + |
| 382 | + public static URLConnection openUrlConnection(URL src, Map<String, Object> headers) throws IOException { |
| 383 | + URLConnection con = src.openConnection(); |
| 384 | + con.setRequestProperty("User-Agent", "APOC Procedures for Neo4j"); |
| 385 | + if (con instanceof HttpURLConnection) { |
| 386 | + HttpURLConnection http = (HttpURLConnection) con; |
| 387 | + http.setInstanceFollowRedirects(false); |
| 388 | + if (headers != null) { |
| 389 | + Object method = headers.get("method"); |
| 390 | + if (method != null) { |
| 391 | + http.setRequestMethod(method.toString()); |
| 392 | + http.setChunkedStreamingMode(1024 * 1024); |
| 393 | + } |
| 394 | + headers.forEach((k, v) -> con.setRequestProperty(k, v == null ? "" : v.toString())); |
| 395 | + } |
| 396 | + } |
| 397 | + |
| 398 | + con.setConnectTimeout(apocConfig().getInt("apoc.http.timeout.connect", 10_000)); |
| 399 | + con.setReadTimeout(apocConfig().getInt("apoc.http.timeout.read", 60_000)); |
| 400 | + return con; |
| 401 | + } |
| 402 | + |
| 403 | + private static void writePayload(URLConnection con, String payload) throws IOException { |
| 404 | + if (payload == null) return; |
| 405 | + con.setDoOutput(true); |
| 406 | + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(con.getOutputStream(), "UTF-8")); |
| 407 | + writer.write(payload); |
| 408 | + writer.close(); |
| 409 | + } |
| 410 | + |
| 411 | + private static String handleRedirect(URLConnection con, String url) throws IOException { |
| 412 | + if (!(con instanceof HttpURLConnection)) return url; |
| 413 | + if (!isRedirect(((HttpURLConnection) con))) return url; |
| 414 | + return con.getHeaderField("Location"); |
| 415 | + } |
| 416 | + |
| 417 | + public static CountingInputStream openInputStream( |
| 418 | + Object input, |
| 419 | + Map<String, Object> headers, |
| 420 | + String payload, |
| 421 | + String compressionAlgo, |
| 422 | + URLAccessChecker urlAccessChecker) |
| 423 | + throws IOException, URISyntaxException, URLAccessValidationError { |
| 424 | + if (input instanceof String) { |
| 425 | + String urlAddress = (String) input; |
| 426 | + final ArchiveType archiveType = ArchiveType.from(urlAddress); |
| 427 | + if (archiveType.isArchive()) { |
| 428 | + return getStreamCompressedFile(urlAddress, headers, payload, archiveType, urlAccessChecker); |
| 429 | + } |
| 430 | + |
| 431 | + StreamConnection sc = getStreamConnection(urlAddress, headers, payload, urlAccessChecker); |
| 432 | + return sc.toCountingInputStream(compressionAlgo); |
| 433 | + } else if (input instanceof byte[]) { |
| 434 | + return FileUtils.getInputStreamFromBinary((byte[]) input, compressionAlgo); |
| 435 | + } else { |
| 436 | + throw new RuntimeException(ERROR_BYTES_OR_STRING); |
| 437 | + } |
| 438 | + } |
| 439 | + |
| 440 | + private static CountingInputStream getStreamCompressedFile( |
| 441 | + String urlAddress, |
| 442 | + Map<String, Object> headers, |
| 443 | + String payload, |
| 444 | + ArchiveType archiveType, |
| 445 | + URLAccessChecker urlAccessChecker) |
| 446 | + throws IOException, URISyntaxException, URLAccessValidationError { |
| 447 | + StreamConnection sc; |
| 448 | + InputStream stream; |
| 449 | + String[] tokens = urlAddress.split("!"); |
| 450 | + urlAddress = tokens[0]; |
| 451 | + String zipFileName; |
| 452 | + if (tokens.length == 2) { |
| 453 | + zipFileName = tokens[1]; |
| 454 | + sc = getStreamConnection(urlAddress, headers, payload, urlAccessChecker); |
| 455 | + stream = getFileStreamIntoCompressedFile(sc.getInputStream(), zipFileName, archiveType); |
| 456 | + stream = toLimitedIStream(stream, sc.getLength()); |
| 457 | + } else throw new IllegalArgumentException("filename can't be null or empty"); |
| 458 | + |
| 459 | + return new CountingInputStream(stream, sc.getLength()); |
| 460 | + } |
| 461 | + |
| 462 | + public static StreamConnection getStreamConnection( |
| 463 | + String urlAddress, Map<String, Object> headers, String payload, URLAccessChecker urlAccessChecker) |
| 464 | + throws IOException, URISyntaxException, URLAccessValidationError { |
| 465 | + return FileUtils.getStreamConnection( |
| 466 | + FileUtils.from(urlAddress), urlAddress, headers, payload, urlAccessChecker); |
| 467 | + } |
| 468 | + |
| 469 | + private static InputStream getFileStreamIntoCompressedFile(InputStream is, String fileName, ArchiveType archiveType) |
| 470 | + throws IOException { |
| 471 | + try (ArchiveInputStream archive = archiveType.getInputStream(is)) { |
| 472 | + ArchiveEntry archiveEntry; |
| 473 | + |
| 474 | + while ((archiveEntry = archive.getNextEntry()) != null) { |
| 475 | + if (!archiveEntry.isDirectory() && archiveEntry.getName().equals(fileName)) { |
| 476 | + return new ByteArrayInputStream(IOUtils.toByteArray(archive)); |
| 477 | + } |
| 478 | + } |
| 479 | + } |
| 480 | + |
| 481 | + return null; |
| 482 | + } |
| 483 | + |
| 484 | + public static Object getStringOrCompressedData(StringWriter writer, ExportConfig config) { |
| 485 | + try { |
| 486 | + final String compression = config.getCompressionAlgo(); |
| 487 | + final String writerString = writer.toString(); |
| 488 | + Object data = compression.equals(CompressionAlgo.NONE.name()) |
| 489 | + ? writerString |
| 490 | + : CompressionAlgo.valueOf(compression).compress(writerString, config.getCharset()); |
| 491 | + writer.getBuffer().setLength(0); |
| 492 | + return data; |
| 493 | + } catch (Exception e) { |
| 494 | + throw new RuntimeException(e); |
| 495 | + } |
| 496 | + } |
348 | 497 | }
|
0 commit comments