embulk-input-ftp 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
}
|