embulk-input-ftp 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog +6 -0
- data/README.md +48 -1
- data/build.gradle +4 -3
- data/src/main/java/org/embulk/input/FtpFileInputPlugin.java +21 -55
- data/src/main/java/org/embulk/input/ftp/SSLPlugins.java +245 -0
- data/src/main/java/org/embulk/input/ftp/TrustManagers.java +276 -0
- metadata +7 -5
- data/src/main/java/org/embulk/input/ftp/RetryExecutor.java +0 -131
- data/src/main/java/org/embulk/input/ftp/RetryableInputStream.java +0 -129
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4777eb105f91266e7be9329f4fe7c275e3aa4753
|
4
|
+
data.tar.gz: 1d797e96b71babe96627dd68291d7b8d2d046106
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa87efee4fb5dc309366b4394a958340941f704dbd03e1cbdf4c5a29419b70a87422163c8e82e5b7d4bc2b18d1a6cd6ad1eba62aa626aab9f8c12ef8703fe03a
|
7
|
+
data.tar.gz: 2794aec32f1da6644d305d585d1fda1e18a60325d1c3dc561b6b6e5767c471a21c8c0e215d363667afcdbfa325c4beebc8ea6bcd4366691422c1ed1a970777b5
|
data/ChangeLog
CHANGED
data/README.md
CHANGED
@@ -16,10 +16,26 @@
|
|
16
16
|
- **passive_mode**: use passive mode (boolean, default: true)
|
17
17
|
- **ascii_mode**: use ASCII mode instead of binary mode (boolean, default: false)
|
18
18
|
- **ssl**: use FTPS (SSL encryption). (boolean, default: false)
|
19
|
-
|
19
|
+
- **ssl_verify**: verify the certification provided by the server. By default, connection fails if the server certification is not signed by one the CAs in JVM's default trusted CA list. (boolean, default: true)
|
20
|
+
- **ssl_verify_hostname**: verify server's hostname matches with provided certificate. (boolean, default: true)
|
21
|
+
- **ssl_trusted_ca_cert_file**: if the server certification is not signed by a certificate authority, set path to the X.508 certification file (pem file) of a private CA (string, optional)
|
22
|
+
- **ssl_trusted_ca_cert_data**: similar to `ssl_trusted_ca_cert_file` but embed the contents of the PEM file as a string value instead of path to a local file (string, optional)
|
20
23
|
|
21
24
|
## Example
|
22
25
|
|
26
|
+
Simple FTP:
|
27
|
+
|
28
|
+
```yaml
|
29
|
+
in:
|
30
|
+
type: ftp
|
31
|
+
host: ftp.example.net
|
32
|
+
port: 21
|
33
|
+
user: anonymous
|
34
|
+
path_prefix: /ftp/file/path/prefix
|
35
|
+
```
|
36
|
+
|
37
|
+
FTPS encryption without server certificate verification:
|
38
|
+
|
23
39
|
```yaml
|
24
40
|
in:
|
25
41
|
type: ftp
|
@@ -28,6 +44,37 @@ in:
|
|
28
44
|
user: anonymous
|
29
45
|
password: "mypassword"
|
30
46
|
path_prefix: /ftp/file/path/prefix
|
47
|
+
|
48
|
+
ssl: true
|
49
|
+
ssl_verify: false
|
50
|
+
```
|
51
|
+
|
52
|
+
FTPS encryption with server certificate verification:
|
53
|
+
|
54
|
+
```yaml
|
55
|
+
in:
|
56
|
+
type: ftp
|
57
|
+
host: ftp.example.net
|
58
|
+
port: 21
|
59
|
+
user: anonymous
|
60
|
+
password: "mypassword"
|
61
|
+
path_prefix: /ftp/file/path/prefix
|
62
|
+
|
63
|
+
ssl: true
|
64
|
+
ssl_verify: true
|
65
|
+
|
66
|
+
ssl_verify_hostname: false # to disable server hostname verification (optional)
|
67
|
+
|
68
|
+
# if the server use self-signed certificate, or set path to the pem file (optional)
|
69
|
+
ssl_trusted_ca_cert_file: /path/to/ca_cert.pem
|
70
|
+
|
71
|
+
# or embed contents of the pem file here (optional)
|
72
|
+
ssl_trusted_ca_cert_data: |
|
73
|
+
-----BEGIN CERTIFICATE-----
|
74
|
+
MIIFV...
|
75
|
+
...
|
76
|
+
...
|
77
|
+
-----END CERTIFICATE-----
|
31
78
|
```
|
32
79
|
|
33
80
|
## Build
|
data/build.gradle
CHANGED
@@ -12,12 +12,13 @@ configurations {
|
|
12
12
|
provided
|
13
13
|
}
|
14
14
|
|
15
|
-
version = "0.1.
|
15
|
+
version = "0.1.2"
|
16
16
|
|
17
17
|
dependencies {
|
18
|
-
compile "org.embulk:embulk-core:0.6.
|
19
|
-
provided "org.embulk:embulk-core:0.6.
|
18
|
+
compile "org.embulk:embulk-core:0.6.8"
|
19
|
+
provided "org.embulk:embulk-core:0.6.8"
|
20
20
|
compile files("libs/ftp4j-1.7.2.jar")
|
21
|
+
compile "org.bouncycastle:bcpkix-jdk15on:1.52"
|
21
22
|
testCompile "junit:junit:4.+"
|
22
23
|
}
|
23
24
|
|
@@ -9,14 +9,6 @@ import java.io.IOException;
|
|
9
9
|
import java.io.InterruptedIOException;
|
10
10
|
import java.io.InputStream;
|
11
11
|
import java.nio.channels.Channels;
|
12
|
-
import java.security.SecureRandom;
|
13
|
-
import java.security.KeyManagementException;
|
14
|
-
import java.security.NoSuchAlgorithmException;
|
15
|
-
import java.security.cert.X509Certificate;
|
16
|
-
import javax.net.ssl.SSLContext;
|
17
|
-
import javax.net.ssl.SSLSocketFactory;
|
18
|
-
import javax.net.ssl.TrustManager;
|
19
|
-
import javax.net.ssl.X509TrustManager;
|
20
12
|
import org.slf4j.Logger;
|
21
13
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
22
14
|
import com.google.common.collect.ImmutableList;
|
@@ -46,11 +38,14 @@ import org.embulk.spi.Exec;
|
|
46
38
|
import org.embulk.spi.FileInputPlugin;
|
47
39
|
import org.embulk.spi.TransactionalFileInput;
|
48
40
|
import org.embulk.spi.util.InputStreamFileInput;
|
41
|
+
import org.embulk.spi.util.ResumableInputStream;
|
42
|
+
import org.embulk.spi.util.RetryExecutor.Retryable;
|
43
|
+
import org.embulk.spi.util.RetryExecutor.RetryGiveupException;
|
49
44
|
import org.embulk.input.ftp.BlockingTransfer;
|
50
|
-
import org.embulk.input.ftp.
|
51
|
-
import org.embulk.input.ftp.
|
52
|
-
import org.embulk.input.ftp.
|
53
|
-
import static org.embulk.
|
45
|
+
import org.embulk.input.ftp.SSLPlugins;
|
46
|
+
import org.embulk.input.ftp.SSLPlugins.SSLPluginTask;
|
47
|
+
import org.embulk.input.ftp.SSLPlugins.SSLPluginConfig;
|
48
|
+
import static org.embulk.spi.util.RetryExecutor.retryExecutor;
|
54
49
|
|
55
50
|
public class FtpFileInputPlugin
|
56
51
|
implements FileInputPlugin
|
@@ -58,7 +53,7 @@ public class FtpFileInputPlugin
|
|
58
53
|
private final Logger log = Exec.getLogger(FtpFileInputPlugin.class);
|
59
54
|
|
60
55
|
public interface PluginTask
|
61
|
-
extends Task
|
56
|
+
extends Task, SSLPlugins.SSLPluginTask
|
62
57
|
{
|
63
58
|
@Config("path_prefix")
|
64
59
|
public String getPathPrefix();
|
@@ -97,6 +92,9 @@ public class FtpFileInputPlugin
|
|
97
92
|
public List<String> getFiles();
|
98
93
|
public void setFiles(List<String> files);
|
99
94
|
|
95
|
+
public SSLPluginConfig getSSLConfig();
|
96
|
+
public void setSSLConfig(SSLPluginConfig config);
|
97
|
+
|
100
98
|
@ConfigInject
|
101
99
|
public BufferAllocator getBufferAllocator();
|
102
100
|
}
|
@@ -106,6 +104,8 @@ public class FtpFileInputPlugin
|
|
106
104
|
{
|
107
105
|
PluginTask task = config.loadConfig(PluginTask.class);
|
108
106
|
|
107
|
+
task.setSSLConfig(SSLPlugins.configure(task));
|
108
|
+
|
109
109
|
// list files recursively
|
110
110
|
List<String> files = listFiles(log, task);
|
111
111
|
task.setFiles(files);
|
@@ -157,7 +157,7 @@ public class FtpFileInputPlugin
|
|
157
157
|
FTPClient client = new FTPClient();
|
158
158
|
try {
|
159
159
|
if (task.getSsl()) {
|
160
|
-
client.setSSLSocketFactory(newSSLSocketFactory(task));
|
160
|
+
client.setSSLSocketFactory(SSLPlugins.newSSLSocketFactory(task.getSSLConfig(), task.getHost()));
|
161
161
|
client.setSecurity(FTPClient.SECURITY_FTPS);
|
162
162
|
}
|
163
163
|
|
@@ -242,40 +242,6 @@ public class FtpFileInputPlugin
|
|
242
242
|
}
|
243
243
|
}
|
244
244
|
|
245
|
-
private static SSLSocketFactory newSSLSocketFactory(PluginTask task)
|
246
|
-
{
|
247
|
-
// TODO certificate check
|
248
|
-
|
249
|
-
TrustManager[] trustManager = new TrustManager[] {
|
250
|
-
new X509TrustManager() {
|
251
|
-
public X509Certificate[] getAcceptedIssuers()
|
252
|
-
{
|
253
|
-
return null;
|
254
|
-
}
|
255
|
-
|
256
|
-
public void checkClientTrusted(X509Certificate[] certs, String authType)
|
257
|
-
{
|
258
|
-
}
|
259
|
-
|
260
|
-
public void checkServerTrusted(X509Certificate[] certs, String authType)
|
261
|
-
{
|
262
|
-
}
|
263
|
-
}
|
264
|
-
};
|
265
|
-
|
266
|
-
try {
|
267
|
-
SSLContext context = SSLContext.getInstance("TLS");
|
268
|
-
context.init(null, trustManager, new SecureRandom());
|
269
|
-
return context.getSocketFactory();
|
270
|
-
|
271
|
-
} catch (NoSuchAlgorithmException ex) {
|
272
|
-
throw new RuntimeException(ex);
|
273
|
-
|
274
|
-
} catch (KeyManagementException ex) {
|
275
|
-
throw new RuntimeException(ex);
|
276
|
-
}
|
277
|
-
}
|
278
|
-
|
279
245
|
private List<String> listFiles(Logger log, PluginTask task)
|
280
246
|
{
|
281
247
|
FTPClient client = newFTPClient(log, task);
|
@@ -507,15 +473,15 @@ public class FtpFileInputPlugin
|
|
507
473
|
return Channels.newInputStream(t.getReaderChannel());
|
508
474
|
}
|
509
475
|
|
510
|
-
private static class
|
511
|
-
implements
|
476
|
+
private static class FtpInputStreamReopener
|
477
|
+
implements ResumableInputStream.Reopener
|
512
478
|
{
|
513
479
|
private final Logger log;
|
514
480
|
private final FTPClient client;
|
515
481
|
private final ExecutorService executor;
|
516
482
|
private final String path;
|
517
483
|
|
518
|
-
public
|
484
|
+
public FtpInputStreamReopener(Logger log, FTPClient client, ExecutorService executor, String path)
|
519
485
|
{
|
520
486
|
this.log = log;
|
521
487
|
this.client = client;
|
@@ -524,7 +490,7 @@ public class FtpFileInputPlugin
|
|
524
490
|
}
|
525
491
|
|
526
492
|
@Override
|
527
|
-
public InputStream
|
493
|
+
public InputStream reopen(final long offset, final Exception closedCause) throws IOException
|
528
494
|
{
|
529
495
|
try {
|
530
496
|
return retryExecutor()
|
@@ -535,7 +501,7 @@ public class FtpFileInputPlugin
|
|
535
501
|
@Override
|
536
502
|
public InputStream call() throws InterruptedIOException
|
537
503
|
{
|
538
|
-
log.warn(String.format("FTP read failed. Retrying GET request with %,d bytes offset", offset),
|
504
|
+
log.warn(String.format("FTP read failed. Retrying GET request with %,d bytes offset", offset), closedCause);
|
539
505
|
return startDownload(log, client, path, offset, executor);
|
540
506
|
}
|
541
507
|
|
@@ -603,9 +569,9 @@ public class FtpFileInputPlugin
|
|
603
569
|
}
|
604
570
|
opened = true;
|
605
571
|
|
606
|
-
return new
|
572
|
+
return new ResumableInputStream(
|
607
573
|
startDownload(log, client, path, 0L, executor),
|
608
|
-
new
|
574
|
+
new FtpInputStreamReopener(log, client, executor, path));
|
609
575
|
}
|
610
576
|
|
611
577
|
@Override
|
@@ -0,0 +1,245 @@
|
|
1
|
+
package org.embulk.input.ftp;
|
2
|
+
|
3
|
+
import java.util.List;
|
4
|
+
import java.io.Reader;
|
5
|
+
import java.io.FileReader;
|
6
|
+
import java.io.StringReader;
|
7
|
+
import java.io.ByteArrayInputStream;
|
8
|
+
import java.io.IOException;
|
9
|
+
import java.io.FileNotFoundException;
|
10
|
+
import java.security.KeyStore;
|
11
|
+
import java.security.NoSuchAlgorithmException;
|
12
|
+
import java.security.GeneralSecurityException;
|
13
|
+
import java.security.cert.CertificateFactory;
|
14
|
+
import java.security.cert.X509Certificate;
|
15
|
+
import java.security.cert.CertificateException;
|
16
|
+
import java.security.cert.CertificateEncodingException;
|
17
|
+
import java.security.KeyManagementException;
|
18
|
+
import javax.net.ssl.SSLSocketFactory;
|
19
|
+
import javax.net.ssl.X509TrustManager;
|
20
|
+
import com.google.common.base.Optional;
|
21
|
+
import com.google.common.base.Function;
|
22
|
+
import com.google.common.collect.ImmutableList;
|
23
|
+
import com.google.common.collect.Lists;
|
24
|
+
import com.fasterxml.jackson.annotation.JsonCreator;
|
25
|
+
import com.fasterxml.jackson.annotation.JsonProperty;
|
26
|
+
import com.fasterxml.jackson.annotation.JsonIgnore;
|
27
|
+
import org.embulk.config.Config;
|
28
|
+
import org.embulk.config.ConfigDefault;
|
29
|
+
import org.embulk.config.ConfigException;
|
30
|
+
|
31
|
+
public class SSLPlugins
|
32
|
+
{
|
33
|
+
// SSLPlugins is only for SSL clients. SSL server implementation is out ouf scope.
|
34
|
+
|
35
|
+
public interface SSLPluginTask
|
36
|
+
{
|
37
|
+
@Config("ssl_verify")
|
38
|
+
@ConfigDefault("null")
|
39
|
+
public Optional<Boolean> getSslVerify();
|
40
|
+
|
41
|
+
@Config("ssl_verify_hostname")
|
42
|
+
@ConfigDefault("true")
|
43
|
+
public boolean getSslVerifyHostname();
|
44
|
+
|
45
|
+
@Config("ssl_trusted_ca_cert_file")
|
46
|
+
@ConfigDefault("null")
|
47
|
+
public Optional<String> getSslTrustedCaCertFile();
|
48
|
+
|
49
|
+
@Config("ssl_trusted_ca_cert_data")
|
50
|
+
@ConfigDefault("null")
|
51
|
+
public Optional<String> getSslTrustedCaCertData();
|
52
|
+
}
|
53
|
+
|
54
|
+
private static enum VerifyMode
|
55
|
+
{
|
56
|
+
NO_VERIFY,
|
57
|
+
CERTIFICATES,
|
58
|
+
JVM_DEFAULT;
|
59
|
+
}
|
60
|
+
|
61
|
+
public static class SSLPluginConfig
|
62
|
+
{
|
63
|
+
static SSLPluginConfig NO_VERIFY = new SSLPluginConfig(VerifyMode.NO_VERIFY, false, ImmutableList.<byte[]>of());
|
64
|
+
|
65
|
+
private final VerifyMode verifyMode;
|
66
|
+
private final boolean verifyHostname;
|
67
|
+
private final List<X509Certificate> certificates;
|
68
|
+
|
69
|
+
@JsonCreator
|
70
|
+
private SSLPluginConfig(
|
71
|
+
@JsonProperty("verifyMode") VerifyMode verifyMode,
|
72
|
+
@JsonProperty("verifyHostname") boolean verifyHostname,
|
73
|
+
@JsonProperty("certificates") List<byte[]> certificates)
|
74
|
+
{
|
75
|
+
this.verifyMode = verifyMode;
|
76
|
+
this.verifyHostname = verifyHostname;
|
77
|
+
this.certificates = ImmutableList.copyOf(
|
78
|
+
Lists.transform(certificates, new Function<byte[], X509Certificate>() {
|
79
|
+
public X509Certificate apply(byte[] data)
|
80
|
+
{
|
81
|
+
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
|
82
|
+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
83
|
+
return (X509Certificate) cf.generateCertificate(in);
|
84
|
+
} catch (IOException | CertificateException ex) {
|
85
|
+
throw new RuntimeException(ex);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
})
|
89
|
+
);
|
90
|
+
}
|
91
|
+
|
92
|
+
SSLPluginConfig(List<X509Certificate> certificates, boolean verifyHostname)
|
93
|
+
{
|
94
|
+
this.verifyMode = VerifyMode.CERTIFICATES;
|
95
|
+
this.verifyHostname = verifyHostname;
|
96
|
+
this.certificates = certificates;
|
97
|
+
}
|
98
|
+
|
99
|
+
static SSLPluginConfig useJvmDefault(boolean verifyHostname)
|
100
|
+
{
|
101
|
+
return new SSLPluginConfig(VerifyMode.JVM_DEFAULT, verifyHostname, ImmutableList.<byte[]>of());
|
102
|
+
}
|
103
|
+
|
104
|
+
@JsonProperty("verifyMode")
|
105
|
+
private VerifyMode getVerifyMode()
|
106
|
+
{
|
107
|
+
return verifyMode;
|
108
|
+
}
|
109
|
+
|
110
|
+
@JsonProperty("verifyHostname")
|
111
|
+
private boolean getVerifyHostname()
|
112
|
+
{
|
113
|
+
return verifyHostname;
|
114
|
+
}
|
115
|
+
|
116
|
+
@JsonProperty("certificates")
|
117
|
+
private List<byte[]> getCertData()
|
118
|
+
{
|
119
|
+
return Lists.transform(certificates, new Function<X509Certificate, byte[]>() {
|
120
|
+
public byte[] apply(X509Certificate cert)
|
121
|
+
{
|
122
|
+
try {
|
123
|
+
return cert.getEncoded();
|
124
|
+
} catch (CertificateEncodingException ex) {
|
125
|
+
throw new RuntimeException(ex);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
});
|
129
|
+
}
|
130
|
+
|
131
|
+
@JsonIgnore
|
132
|
+
public X509TrustManager[] newTrustManager()
|
133
|
+
{
|
134
|
+
try {
|
135
|
+
switch (verifyMode) {
|
136
|
+
case NO_VERIFY:
|
137
|
+
return new X509TrustManager[] { getNoVerifyTrustManager() };
|
138
|
+
case CERTIFICATES:
|
139
|
+
return TrustManagers.newTrustManager(certificates);
|
140
|
+
default: // JVM_DEFAULT
|
141
|
+
return TrustManagers.newDefaultJavaTrustManager();
|
142
|
+
}
|
143
|
+
} catch (IOException | GeneralSecurityException ex) {
|
144
|
+
throw new RuntimeException(ex);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
public static enum DefaultVerifyMode
|
150
|
+
{
|
151
|
+
VERIFY_BY_JVM_TRUSTED_CA_CERTS,
|
152
|
+
NO_VERIFY;
|
153
|
+
};
|
154
|
+
|
155
|
+
public static SSLPluginConfig configure(SSLPluginTask task)
|
156
|
+
{
|
157
|
+
return configure(task, DefaultVerifyMode.VERIFY_BY_JVM_TRUSTED_CA_CERTS);
|
158
|
+
}
|
159
|
+
|
160
|
+
public static SSLPluginConfig configure(SSLPluginTask task, DefaultVerifyMode defaultVerifyMode)
|
161
|
+
{
|
162
|
+
boolean verify = task.getSslVerify().or(defaultVerifyMode != DefaultVerifyMode.NO_VERIFY);
|
163
|
+
if (verify) {
|
164
|
+
Optional<List<X509Certificate>> certs = readTrustedCertificates(task);
|
165
|
+
if (certs.isPresent()) {
|
166
|
+
return new SSLPluginConfig(certs.get(), task.getSslVerifyHostname());
|
167
|
+
} else {
|
168
|
+
return SSLPluginConfig.useJvmDefault(task.getSslVerifyHostname());
|
169
|
+
}
|
170
|
+
} else {
|
171
|
+
return SSLPluginConfig.NO_VERIFY;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
private static Optional<List<X509Certificate>> readTrustedCertificates(SSLPluginTask task)
|
176
|
+
{
|
177
|
+
String optionName;
|
178
|
+
Reader reader;
|
179
|
+
if (task.getSslTrustedCaCertData().isPresent()) {
|
180
|
+
optionName = "ssl_trusted_ca_cert_data";
|
181
|
+
reader = new StringReader(task.getSslTrustedCaCertData().get());
|
182
|
+
} else if (task.getSslTrustedCaCertFile().isPresent()) {
|
183
|
+
optionName = "ssl_trusted_ca_cert_file '" + task.getSslTrustedCaCertFile().get() + "'";
|
184
|
+
try {
|
185
|
+
reader = new FileReader(task.getSslTrustedCaCertFile().get());
|
186
|
+
} catch (IOException ex) {
|
187
|
+
throw new ConfigException("Failed to open "+optionName, ex);
|
188
|
+
}
|
189
|
+
} else {
|
190
|
+
return Optional.absent();
|
191
|
+
}
|
192
|
+
|
193
|
+
List<X509Certificate> certs;
|
194
|
+
try (Reader r = reader) {
|
195
|
+
certs = TrustManagers.readPemEncodedX509Certificates(r);
|
196
|
+
if (certs.isEmpty()) {
|
197
|
+
throw new ConfigException(optionName + " does not include valid X.509 PEM certificates");
|
198
|
+
}
|
199
|
+
} catch (CertificateException | IOException ex) {
|
200
|
+
throw new ConfigException("Failed to read "+optionName, ex);
|
201
|
+
}
|
202
|
+
|
203
|
+
return Optional.of(certs);
|
204
|
+
}
|
205
|
+
|
206
|
+
public static SSLSocketFactory newSSLSocketFactory(SSLPluginConfig config, String hostname)
|
207
|
+
{
|
208
|
+
try {
|
209
|
+
return TrustManagers.newSSLSocketFactory(
|
210
|
+
null, // TODO sending client certificate is not implemented yet
|
211
|
+
config.newTrustManager(),
|
212
|
+
config.getVerifyHostname() ? hostname : null);
|
213
|
+
} catch (KeyManagementException ex) {
|
214
|
+
throw new RuntimeException(ex);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
private static class NoVerifyTrustManager
|
219
|
+
implements X509TrustManager
|
220
|
+
{
|
221
|
+
static final NoVerifyTrustManager INSTANCE = new NoVerifyTrustManager();
|
222
|
+
|
223
|
+
private NoVerifyTrustManager()
|
224
|
+
{ }
|
225
|
+
|
226
|
+
@Override
|
227
|
+
public X509Certificate[] getAcceptedIssuers()
|
228
|
+
{
|
229
|
+
return null;
|
230
|
+
}
|
231
|
+
|
232
|
+
@Override
|
233
|
+
public void checkClientTrusted(X509Certificate[] certs, String authType)
|
234
|
+
{ }
|
235
|
+
|
236
|
+
@Override
|
237
|
+
public void checkServerTrusted(X509Certificate[] certs, String authType)
|
238
|
+
{ }
|
239
|
+
}
|
240
|
+
|
241
|
+
private static X509TrustManager getNoVerifyTrustManager()
|
242
|
+
{
|
243
|
+
return NoVerifyTrustManager.INSTANCE;
|
244
|
+
}
|
245
|
+
}
|
@@ -0,0 +1,276 @@
|
|
1
|
+
package org.embulk.input.ftp;
|
2
|
+
|
3
|
+
import java.util.List;
|
4
|
+
import java.util.ArrayList;
|
5
|
+
import java.io.File;
|
6
|
+
import java.io.FileInputStream;
|
7
|
+
import java.io.Reader;
|
8
|
+
import java.io.IOException;
|
9
|
+
import java.net.Socket;
|
10
|
+
import java.net.InetAddress;
|
11
|
+
import java.net.UnknownHostException;
|
12
|
+
import java.security.KeyStore;
|
13
|
+
import java.security.SecureRandom;
|
14
|
+
import java.security.KeyStoreException;
|
15
|
+
import java.security.KeyManagementException;
|
16
|
+
import java.security.NoSuchAlgorithmException;
|
17
|
+
import java.security.InvalidAlgorithmParameterException;
|
18
|
+
import java.security.cert.Certificate;
|
19
|
+
import java.security.cert.TrustAnchor;
|
20
|
+
import java.security.cert.PKIXParameters;
|
21
|
+
import java.security.cert.X509Certificate;
|
22
|
+
import java.security.cert.CertificateException;
|
23
|
+
import java.security.cert.CertificateParsingException;
|
24
|
+
import javax.net.ssl.SSLContext;
|
25
|
+
import javax.net.ssl.SSLSession;
|
26
|
+
import javax.net.ssl.SSLParameters;
|
27
|
+
import javax.net.ssl.TrustManager;
|
28
|
+
import javax.net.ssl.KeyManager;
|
29
|
+
import javax.net.ssl.X509TrustManager;
|
30
|
+
import javax.net.ssl.TrustManagerFactory;
|
31
|
+
import javax.net.ssl.SSLSocket;
|
32
|
+
import javax.net.ssl.SSLSocketFactory;
|
33
|
+
import javax.net.ssl.HostnameVerifier;
|
34
|
+
import org.bouncycastle.openssl.PEMParser;
|
35
|
+
import org.bouncycastle.openssl.PEMException;
|
36
|
+
import org.bouncycastle.cert.X509CertificateHolder;
|
37
|
+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
38
|
+
import sun.security.ssl.SSLSocketImpl;
|
39
|
+
|
40
|
+
public class TrustManagers
|
41
|
+
{
|
42
|
+
public static KeyStore readDefaultJavaKeyStore()
|
43
|
+
throws IOException, KeyStoreException, CertificateException
|
44
|
+
{
|
45
|
+
String path = (System.getProperty("java.home") + "/lib/security/cacerts").replace('/', File.separatorChar);
|
46
|
+
try {
|
47
|
+
KeyStore keyStore = KeyStore.getInstance("JKS");
|
48
|
+
try (FileInputStream in = new FileInputStream(path)) {
|
49
|
+
keyStore.load(in, null); // password=null because cacerts file is not encrypted
|
50
|
+
}
|
51
|
+
return keyStore;
|
52
|
+
} catch (NoSuchAlgorithmException ex) {
|
53
|
+
throw new RuntimeException(ex); // TODO assertion exception?
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
public static List<X509Certificate> readDefaultJavaTrustedCertificates()
|
58
|
+
throws IOException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException
|
59
|
+
{
|
60
|
+
KeyStore keyStore = readDefaultJavaKeyStore();
|
61
|
+
PKIXParameters params = new PKIXParameters(keyStore);
|
62
|
+
List<X509Certificate> certs = new ArrayList<>();
|
63
|
+
for (TrustAnchor trustAnchor : params.getTrustAnchors() ) {
|
64
|
+
certs.add(trustAnchor.getTrustedCert());
|
65
|
+
}
|
66
|
+
return certs;
|
67
|
+
}
|
68
|
+
|
69
|
+
public static List<X509Certificate> readPemEncodedX509Certificates(Reader reader)
|
70
|
+
throws IOException, CertificateException
|
71
|
+
{
|
72
|
+
// this method abuses CertificateParsingException because its javadoc says
|
73
|
+
// CertificateParsingException is only for DER-encoded formats.
|
74
|
+
|
75
|
+
JcaX509CertificateConverter conv = new JcaX509CertificateConverter();
|
76
|
+
List<X509Certificate> certs = new ArrayList<>();
|
77
|
+
|
78
|
+
try {
|
79
|
+
PEMParser pemParser = new PEMParser(reader);
|
80
|
+
// PEMParser#close is unnecessary because it just closes underlying reader
|
81
|
+
|
82
|
+
while (true) {
|
83
|
+
Object pem = pemParser.readObject();
|
84
|
+
|
85
|
+
if (pem == null) {
|
86
|
+
break;
|
87
|
+
}
|
88
|
+
|
89
|
+
if (pem instanceof X509CertificateHolder) {
|
90
|
+
X509Certificate cert = conv.getCertificate((X509CertificateHolder) pem);
|
91
|
+
certs.add(cert);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
} catch (PEMException ex) {
|
96
|
+
// throw when parsing PemObject to Object fails
|
97
|
+
throw new CertificateParsingException(ex);
|
98
|
+
|
99
|
+
} catch (IOException ex) {
|
100
|
+
if (ex.getClass().equals(IOException.class)) {
|
101
|
+
String message = ex.getMessage();
|
102
|
+
if (message.startsWith("unrecognised object: ")) {
|
103
|
+
// thrown at org.bouncycastle.openssl.PemParser.readObject when key type (header of a pem) is
|
104
|
+
// unknown.
|
105
|
+
throw new CertificateParsingException(ex);
|
106
|
+
} else if (message.startsWith("-----END ") && message.endsWith(" not found")) {
|
107
|
+
// thrown at org.bouncycastle.util.io.pem.PemReader.loadObject when a pem file format is invalid
|
108
|
+
throw new CertificateParsingException(ex);
|
109
|
+
}
|
110
|
+
} else {
|
111
|
+
throw ex;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
return certs;
|
116
|
+
}
|
117
|
+
|
118
|
+
public static KeyStore buildKeyStoreFromTrustedCertificates(List<X509Certificate> certificates)
|
119
|
+
throws KeyStoreException
|
120
|
+
{
|
121
|
+
KeyStore keyStore = KeyStore.getInstance("JKS");
|
122
|
+
try {
|
123
|
+
keyStore.load(null);
|
124
|
+
} catch (IOException | CertificateException | NoSuchAlgorithmException ex) {
|
125
|
+
throw new RuntimeException(ex);
|
126
|
+
}
|
127
|
+
int i = 0;
|
128
|
+
for (X509Certificate cert : certificates) {
|
129
|
+
keyStore.setCertificateEntry("cert_" + i, cert);
|
130
|
+
i++;
|
131
|
+
}
|
132
|
+
return keyStore;
|
133
|
+
}
|
134
|
+
|
135
|
+
public static X509TrustManager[] newTrustManager(List<X509Certificate> trustedCertificates)
|
136
|
+
throws KeyStoreException
|
137
|
+
{
|
138
|
+
try {
|
139
|
+
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
140
|
+
KeyStore keyStore = buildKeyStoreFromTrustedCertificates(trustedCertificates);
|
141
|
+
factory.init(keyStore);
|
142
|
+
List<X509TrustManager> tms = new ArrayList<>();
|
143
|
+
for (TrustManager tm : factory.getTrustManagers()) {
|
144
|
+
if (tm instanceof X509TrustManager) {
|
145
|
+
tms.add((X509TrustManager) tm);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
return tms.toArray(new X509TrustManager[tms.size()]);
|
149
|
+
} catch (NoSuchAlgorithmException ex) {
|
150
|
+
throw new RuntimeException(ex); // TODO assertion exception?
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
public static X509TrustManager[] newDefaultJavaTrustManager()
|
155
|
+
throws IOException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException
|
156
|
+
{
|
157
|
+
return newTrustManager(readDefaultJavaTrustedCertificates());
|
158
|
+
}
|
159
|
+
|
160
|
+
public static SSLContext newSSLContext(KeyManager[] keyManager, X509TrustManager[] trustManager)
|
161
|
+
throws KeyManagementException
|
162
|
+
{
|
163
|
+
try {
|
164
|
+
SSLContext context = SSLContext.getInstance("TLS");
|
165
|
+
context.init(
|
166
|
+
keyManager,
|
167
|
+
trustManager,
|
168
|
+
new SecureRandom());
|
169
|
+
return context;
|
170
|
+
|
171
|
+
} catch (NoSuchAlgorithmException ex) {
|
172
|
+
throw new RuntimeException(ex);
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
public static SSLSocketFactory newSSLSocketFactory(KeyManager[] keyManager, X509TrustManager[] trustManager, String verifyHostname)
|
177
|
+
throws KeyManagementException
|
178
|
+
{
|
179
|
+
SSLContext context = newSSLContext(keyManager, trustManager);
|
180
|
+
SSLSocketFactory factory = context.getSocketFactory();
|
181
|
+
if (verifyHostname == null) {
|
182
|
+
return factory;
|
183
|
+
} else {
|
184
|
+
return new VerifyHostNameSSLSocketFactory(factory, verifyHostname);
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
private static class VerifyHostNameSSLSocketFactory
|
189
|
+
extends SSLSocketFactory
|
190
|
+
{
|
191
|
+
private final SSLSocketFactory next;
|
192
|
+
private final String hostname;
|
193
|
+
|
194
|
+
public VerifyHostNameSSLSocketFactory(SSLSocketFactory next, String hostname)
|
195
|
+
{
|
196
|
+
this.next = next;
|
197
|
+
this.hostname = hostname;
|
198
|
+
}
|
199
|
+
|
200
|
+
@Override
|
201
|
+
public String[] getDefaultCipherSuites()
|
202
|
+
{
|
203
|
+
return next.getDefaultCipherSuites();
|
204
|
+
}
|
205
|
+
|
206
|
+
@Override
|
207
|
+
public String[] getSupportedCipherSuites()
|
208
|
+
{
|
209
|
+
return next.getSupportedCipherSuites();
|
210
|
+
}
|
211
|
+
|
212
|
+
@Override
|
213
|
+
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
|
214
|
+
throws IOException
|
215
|
+
{
|
216
|
+
Socket sock = next.createSocket(s, host, port, autoClose);
|
217
|
+
setSSLParameters(sock, false);
|
218
|
+
return sock;
|
219
|
+
}
|
220
|
+
|
221
|
+
@Override
|
222
|
+
public Socket createSocket(String host, int port)
|
223
|
+
throws IOException, UnknownHostException
|
224
|
+
{
|
225
|
+
Socket sock = next.createSocket(host, port);
|
226
|
+
setSSLParameters(sock, false);
|
227
|
+
return sock;
|
228
|
+
}
|
229
|
+
|
230
|
+
@Override
|
231
|
+
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
|
232
|
+
throws IOException, UnknownHostException
|
233
|
+
{
|
234
|
+
Socket sock = next.createSocket(host, port, localHost, localPort);
|
235
|
+
setSSLParameters(sock, false);
|
236
|
+
return sock;
|
237
|
+
}
|
238
|
+
|
239
|
+
@Override
|
240
|
+
public Socket createSocket(InetAddress host, int port)
|
241
|
+
throws IOException
|
242
|
+
{
|
243
|
+
Socket sock = next.createSocket(host, port);
|
244
|
+
setSSLParameters(sock, true);
|
245
|
+
return sock;
|
246
|
+
}
|
247
|
+
|
248
|
+
@Override
|
249
|
+
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
|
250
|
+
throws IOException
|
251
|
+
{
|
252
|
+
Socket sock = next.createSocket(address, port, localAddress, localPort);
|
253
|
+
setSSLParameters(sock, true);
|
254
|
+
return sock;
|
255
|
+
}
|
256
|
+
|
257
|
+
private void setSSLParameters(Socket sock, boolean setHostname)
|
258
|
+
{
|
259
|
+
if (sock instanceof SSLSocket) {
|
260
|
+
SSLSocket s = (SSLSocket) sock;
|
261
|
+
String identAlgorithm = s.getSSLParameters().getEndpointIdentificationAlgorithm();
|
262
|
+
if (identAlgorithm != null && identAlgorithm.equalsIgnoreCase("HTTPS")) {
|
263
|
+
// hostname verification is already configured.
|
264
|
+
} else {
|
265
|
+
if (setHostname && s instanceof SSLSocketImpl) {
|
266
|
+
((SSLSocketImpl) s).setHost(hostname);
|
267
|
+
}
|
268
|
+
SSLParameters params = s.getSSLParameters();
|
269
|
+
params.setEndpointIdentificationAlgorithm("HTTPS");
|
270
|
+
s.setSSLParameters(params);
|
271
|
+
// s.startHandshake
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-ftp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -57,10 +57,12 @@ files:
|
|
57
57
|
- libs/ftp4j-1.7.2.jar
|
58
58
|
- src/main/java/org/embulk/input/FtpFileInputPlugin.java
|
59
59
|
- src/main/java/org/embulk/input/ftp/BlockingTransfer.java
|
60
|
-
- src/main/java/org/embulk/input/ftp/
|
61
|
-
- src/main/java/org/embulk/input/ftp/
|
60
|
+
- src/main/java/org/embulk/input/ftp/SSLPlugins.java
|
61
|
+
- src/main/java/org/embulk/input/ftp/TrustManagers.java
|
62
62
|
- src/test/java/org/embulk/input/TestFtpFileInputPlugin.java
|
63
|
-
- classpath/
|
63
|
+
- classpath/bcpkix-jdk15on-1.52.jar
|
64
|
+
- classpath/bcprov-jdk15on-1.52.jar
|
65
|
+
- classpath/embulk-input-ftp-0.1.2.jar
|
64
66
|
- classpath/ftp4j-1.7.2.jar
|
65
67
|
homepage: https://github.com/embulk/embulk-input-ftp
|
66
68
|
licenses:
|
@@ -1,131 +0,0 @@
|
|
1
|
-
// TODO copied from S3FileInputPlugin. This should be moved to org.embulk.
|
2
|
-
package org.embulk.input.ftp;
|
3
|
-
|
4
|
-
import java.util.concurrent.Callable;
|
5
|
-
import java.util.concurrent.ExecutionException;
|
6
|
-
|
7
|
-
public class RetryExecutor
|
8
|
-
{
|
9
|
-
public static RetryExecutor retryExecutor()
|
10
|
-
{
|
11
|
-
// TODO default configuration
|
12
|
-
return new RetryExecutor(3, 500, 30*60*1000);
|
13
|
-
}
|
14
|
-
|
15
|
-
public static class RetryGiveupException
|
16
|
-
extends ExecutionException
|
17
|
-
{
|
18
|
-
public RetryGiveupException(String message, Exception cause)
|
19
|
-
{
|
20
|
-
super(cause);
|
21
|
-
}
|
22
|
-
|
23
|
-
public RetryGiveupException(Exception cause)
|
24
|
-
{
|
25
|
-
super(cause);
|
26
|
-
}
|
27
|
-
|
28
|
-
public Exception getCause()
|
29
|
-
{
|
30
|
-
return (Exception) super.getCause();
|
31
|
-
}
|
32
|
-
}
|
33
|
-
|
34
|
-
public static interface Retryable<T>
|
35
|
-
extends Callable<T>
|
36
|
-
{
|
37
|
-
public T call()
|
38
|
-
throws Exception;
|
39
|
-
|
40
|
-
public boolean isRetryableException(Exception exception);
|
41
|
-
|
42
|
-
public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait)
|
43
|
-
throws RetryGiveupException;
|
44
|
-
|
45
|
-
public void onGiveup(Exception firstException, Exception lastException)
|
46
|
-
throws RetryGiveupException;
|
47
|
-
}
|
48
|
-
|
49
|
-
private final int retryLimit;
|
50
|
-
private final int initialRetryWait;
|
51
|
-
private final int maxRetryWait;
|
52
|
-
|
53
|
-
private RetryExecutor(int retryLimit, int initialRetryWait, int maxRetryWait)
|
54
|
-
{
|
55
|
-
this.retryLimit = retryLimit;
|
56
|
-
this.initialRetryWait = initialRetryWait;
|
57
|
-
this.maxRetryWait = maxRetryWait;
|
58
|
-
}
|
59
|
-
|
60
|
-
public RetryExecutor withRetryLimit(int count)
|
61
|
-
{
|
62
|
-
return new RetryExecutor(count, initialRetryWait, maxRetryWait);
|
63
|
-
}
|
64
|
-
|
65
|
-
public RetryExecutor withInitialRetryWait(int msec)
|
66
|
-
{
|
67
|
-
return new RetryExecutor(retryLimit, msec, maxRetryWait);
|
68
|
-
}
|
69
|
-
|
70
|
-
public RetryExecutor withMaxRetryWait(int msec)
|
71
|
-
{
|
72
|
-
return new RetryExecutor(retryLimit, initialRetryWait, msec);
|
73
|
-
}
|
74
|
-
|
75
|
-
public <T> T runInterruptible(Retryable<T> op)
|
76
|
-
throws InterruptedException, RetryGiveupException
|
77
|
-
{
|
78
|
-
return run(op, true);
|
79
|
-
}
|
80
|
-
|
81
|
-
public <T> T run(Retryable<T> op)
|
82
|
-
throws RetryGiveupException
|
83
|
-
{
|
84
|
-
try {
|
85
|
-
return run(op, false);
|
86
|
-
} catch (InterruptedException ex) {
|
87
|
-
throw new RetryGiveupException("Unexpected interruption", ex);
|
88
|
-
}
|
89
|
-
}
|
90
|
-
|
91
|
-
private <T> T run(Retryable<T> op, boolean interruptible)
|
92
|
-
throws InterruptedException, RetryGiveupException
|
93
|
-
{
|
94
|
-
int retryWait = initialRetryWait;
|
95
|
-
int retryCount = 0;
|
96
|
-
|
97
|
-
Exception firstException = null;
|
98
|
-
|
99
|
-
while(true) {
|
100
|
-
try {
|
101
|
-
return op.call();
|
102
|
-
} catch (Exception exception) {
|
103
|
-
if (firstException == null) {
|
104
|
-
firstException = exception;
|
105
|
-
}
|
106
|
-
if (!op.isRetryableException(exception) || retryCount >= retryLimit) {
|
107
|
-
op.onGiveup(firstException, exception);
|
108
|
-
throw new RetryGiveupException(firstException);
|
109
|
-
}
|
110
|
-
|
111
|
-
retryCount++;
|
112
|
-
op.onRetry(exception, retryCount, retryLimit, retryWait);
|
113
|
-
|
114
|
-
try {
|
115
|
-
Thread.sleep(retryWait);
|
116
|
-
} catch (InterruptedException ex) {
|
117
|
-
if (interruptible) {
|
118
|
-
throw ex;
|
119
|
-
}
|
120
|
-
}
|
121
|
-
|
122
|
-
// exponential back-off with hard limit
|
123
|
-
retryWait *= 2;
|
124
|
-
if (retryWait > maxRetryWait) {
|
125
|
-
retryWait = maxRetryWait;
|
126
|
-
}
|
127
|
-
}
|
128
|
-
}
|
129
|
-
}
|
130
|
-
}
|
131
|
-
|
@@ -1,129 +0,0 @@
|
|
1
|
-
// TODO copied from S3FileInputPlugin. This should be moved to org.embulk.
|
2
|
-
package org.embulk.input.ftp;
|
3
|
-
|
4
|
-
import java.io.InputStream;
|
5
|
-
import java.io.IOException;
|
6
|
-
|
7
|
-
public class RetryableInputStream
|
8
|
-
extends InputStream
|
9
|
-
{
|
10
|
-
public interface Opener
|
11
|
-
{
|
12
|
-
public InputStream open(long offset, Exception exception) throws IOException;
|
13
|
-
}
|
14
|
-
|
15
|
-
private final Opener opener;
|
16
|
-
protected InputStream in;
|
17
|
-
private long offset;
|
18
|
-
private long markedOffset;
|
19
|
-
|
20
|
-
public RetryableInputStream(InputStream initialInputStream, Opener reopener)
|
21
|
-
{
|
22
|
-
this.opener = reopener;
|
23
|
-
this.in = initialInputStream;
|
24
|
-
this.offset = 0L;
|
25
|
-
this.markedOffset = 0L;
|
26
|
-
}
|
27
|
-
|
28
|
-
public RetryableInputStream(Opener opener) throws IOException
|
29
|
-
{
|
30
|
-
this(opener.open(0, null), opener);
|
31
|
-
}
|
32
|
-
|
33
|
-
private void reopen(Exception exception) throws IOException
|
34
|
-
{
|
35
|
-
if (in != null) {
|
36
|
-
in.close();
|
37
|
-
in = null;
|
38
|
-
}
|
39
|
-
in = opener.open(offset, exception);
|
40
|
-
}
|
41
|
-
|
42
|
-
@Override
|
43
|
-
public int read() throws IOException
|
44
|
-
{
|
45
|
-
while (true) {
|
46
|
-
try {
|
47
|
-
int v = in.read();
|
48
|
-
offset += 1;
|
49
|
-
return v;
|
50
|
-
} catch (IOException | RuntimeException ex) {
|
51
|
-
reopen(ex);
|
52
|
-
}
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
@Override
|
57
|
-
public int read(byte[] b) throws IOException
|
58
|
-
{
|
59
|
-
while (true) {
|
60
|
-
try {
|
61
|
-
int r = in.read(b);
|
62
|
-
offset += r;
|
63
|
-
return r;
|
64
|
-
} catch (IOException | RuntimeException ex) {
|
65
|
-
reopen(ex);
|
66
|
-
}
|
67
|
-
}
|
68
|
-
}
|
69
|
-
|
70
|
-
@Override
|
71
|
-
public int read(byte[] b, int off, int len) throws IOException
|
72
|
-
{
|
73
|
-
while (true) {
|
74
|
-
try {
|
75
|
-
int r = in.read(b, off, len);
|
76
|
-
offset += r;
|
77
|
-
return r;
|
78
|
-
} catch (IOException | RuntimeException ex) {
|
79
|
-
reopen(ex);
|
80
|
-
}
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
@Override
|
85
|
-
public long skip(long n) throws IOException
|
86
|
-
{
|
87
|
-
while (true) {
|
88
|
-
try {
|
89
|
-
long r = in.skip(n);
|
90
|
-
offset += r;
|
91
|
-
return r;
|
92
|
-
} catch (IOException | RuntimeException ex) {
|
93
|
-
reopen(ex);
|
94
|
-
}
|
95
|
-
}
|
96
|
-
}
|
97
|
-
|
98
|
-
@Override
|
99
|
-
public int available() throws IOException
|
100
|
-
{
|
101
|
-
return in.available();
|
102
|
-
}
|
103
|
-
|
104
|
-
@Override
|
105
|
-
public void close() throws IOException
|
106
|
-
{
|
107
|
-
in.close();
|
108
|
-
}
|
109
|
-
|
110
|
-
@Override
|
111
|
-
public void mark(int readlimit)
|
112
|
-
{
|
113
|
-
in.mark(readlimit);
|
114
|
-
markedOffset = offset;
|
115
|
-
}
|
116
|
-
|
117
|
-
@Override
|
118
|
-
public void reset() throws IOException
|
119
|
-
{
|
120
|
-
in.reset();
|
121
|
-
offset = markedOffset;
|
122
|
-
}
|
123
|
-
|
124
|
-
@Override
|
125
|
-
public boolean markSupported()
|
126
|
-
{
|
127
|
-
return in.markSupported();
|
128
|
-
}
|
129
|
-
}
|