-
Notifications
You must be signed in to change notification settings - Fork 965
/
CapacitorHttpUrlConnection.java
381 lines (355 loc) · 13.8 KB
/
CapacitorHttpUrlConnection.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
package com.getcapacitor.plugin.util;
import android.os.Build;
import android.os.LocaleList;
import android.text.TextUtils;
import com.getcapacitor.Bridge;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.JSValue;
import com.getcapacitor.PluginCall;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.UnknownServiceException;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import org.json.JSONException;
public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
private final HttpURLConnection connection;
/**
* Make a new CapacitorHttpUrlConnection instance, which wraps around HttpUrlConnection
* and provides some helper functions for setting request headers and the request body
* @param conn the base HttpUrlConnection. You can pass the value from
* {@code (HttpUrlConnection) URL.openConnection()}
*/
public CapacitorHttpUrlConnection(HttpURLConnection conn) {
connection = conn;
this.setDefaultRequestProperties();
}
/**
* Returns the underlying HttpUrlConnection value
* @return the underlying HttpUrlConnection value
*/
public HttpURLConnection getHttpConnection() {
return connection;
}
/**
* Set the value of the {@code allowUserInteraction} field of
* this {@code URLConnection}.
*
* @param isAllowedInteraction the new value.
* @throws IllegalStateException if already connected
*/
public void setAllowUserInteraction(boolean isAllowedInteraction) {
connection.setAllowUserInteraction(isAllowedInteraction);
}
/**
* Set the method for the URL request, one of:
* <UL>
* <LI>GET
* <LI>POST
* <LI>HEAD
* <LI>OPTIONS
* <LI>PUT
* <LI>DELETE
* <LI>TRACE
* </UL> are legal, subject to protocol restrictions. The default
* method is GET.
*
* @param method the HTTP method
* @exception ProtocolException if the method cannot be reset or if
* the requested method isn't valid for HTTP.
* @exception SecurityException if a security manager is set and the
* method is "TRACE", but the "allowHttpTrace"
* NetPermission is not granted.
*/
public void setRequestMethod(String method) throws ProtocolException {
connection.setRequestMethod(method);
}
/**
* Sets a specified timeout value, in milliseconds, to be used
* when opening a communications link to the resource referenced
* by this URLConnection. If the timeout expires before the
* connection can be established, a
* java.net.SocketTimeoutException is raised. A timeout of zero is
* interpreted as an infinite timeout.
*
* <p><strong>Warning</strong>: If the hostname resolves to multiple IP
* addresses, Android's default implementation of {@link HttpURLConnection}
* will try each in
* <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order. If
* connecting to each of these addresses fails, multiple timeouts will
* elapse before the connect attempt throws an exception. Host names
* that support both IPv6 and IPv4 always have at least 2 IP addresses.
*
* @param timeout an {@code int} that specifies the connect
* timeout value in milliseconds
* @throws IllegalArgumentException if the timeout parameter is negative
*/
public void setConnectTimeout(int timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("timeout can not be negative");
}
connection.setConnectTimeout(timeout);
}
/**
* Sets the read timeout to a specified timeout, in
* milliseconds. A non-zero value specifies the timeout when
* reading from Input stream when a connection is established to a
* resource. If the timeout expires before there is data available
* for read, a java.net.SocketTimeoutException is raised. A
* timeout of zero is interpreted as an infinite timeout.
*
* @param timeout an {@code int} that specifies the timeout
* value to be used in milliseconds
* @throws IllegalArgumentException if the timeout parameter is negative
*/
public void setReadTimeout(int timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("timeout can not be negative");
}
connection.setReadTimeout(timeout);
}
/**
* Sets whether automatic HTTP redirects should be disabled
* @param disableRedirects the flag to determine if redirects should be followed
*/
public void setDisableRedirects(boolean disableRedirects) {
connection.setInstanceFollowRedirects(!disableRedirects);
}
/**
* Sets the request headers given a JSObject of key-value pairs
* @param headers the JSObject values to map to the HttpUrlConnection request headers
*/
public void setRequestHeaders(JSObject headers) {
Iterator<String> keys = headers.keys();
while (keys.hasNext()) {
String key = keys.next();
String value = headers.getString(key);
connection.setRequestProperty(key, value);
}
}
/**
* Sets the value of the {@code doOutput} field for this
* {@code URLConnection} to the specified value.
* <p>
* A URL connection can be used for input and/or output. Set the DoOutput
* flag to true if you intend to use the URL connection for output,
* false if not. The default is false.
*
* @param shouldDoOutput the new value.
* @throws IllegalStateException if already connected
*/
public void setDoOutput(boolean shouldDoOutput) {
connection.setDoOutput(shouldDoOutput);
}
/**
*
* @param call
* @throws JSONException
* @throws IOException
*/
public void setRequestBody(PluginCall call, JSValue body) throws JSONException, IOException {
String contentType = connection.getRequestProperty("Content-Type");
String dataString = "";
if (contentType == null || contentType.isEmpty()) return;
if (contentType.contains("application/json")) {
JSArray jsArray = null;
if (body != null) {
dataString = body.toString();
} else {
jsArray = call.getArray("data", null);
}
if (jsArray != null) {
dataString = jsArray.toString();
} else if (body == null) {
dataString = call.getString("data");
}
this.writeRequestBody(dataString != null ? dataString : "");
} else {
this.writeRequestBody(body.toString());
}
}
/**
* Writes the provided string to the HTTP connection managed by this instance.
*
* @param body The string value to write to the connection stream.
*/
private void writeRequestBody(String body) throws IOException {
try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
os.write(body.getBytes(StandardCharsets.UTF_8));
os.flush();
}
}
/**
* Opens a communications link to the resource referenced by this
* URL, if such a connection has not already been established.
* <p>
* If the {@code connect} method is called when the connection
* has already been opened (indicated by the {@code connected}
* field having the value {@code true}), the call is ignored.
* <p>
* URLConnection objects go through two phases: first they are
* created, then they are connected. After being created, and
* before being connected, various options can be specified
* (e.g., doInput and UseCaches). After connecting, it is an
* error to try to set them. Operations that depend on being
* connected, like getContentLength, will implicitly perform the
* connection, if necessary.
*
* @throws SocketTimeoutException if the timeout expires before
* the connection can be established
* @exception IOException if an I/O error occurs while opening the
* connection.
*/
public void connect() throws IOException {
connection.connect();
}
/**
* Gets the status code from an HTTP response message.
* For example, in the case of the following status lines:
* <PRE>
* HTTP/1.0 200 OK
* HTTP/1.0 401 Unauthorized
* </PRE>
* It will return 200 and 401 respectively.
* Returns -1 if no code can be discerned
* from the response (i.e., the response is not valid HTTP).
* @throws IOException if an error occurred connecting to the server.
* @return the HTTP Status-Code, or -1
*/
public int getResponseCode() throws IOException {
return connection.getResponseCode();
}
/**
* Returns the value of this {@code URLConnection}'s {@code URL}
* field.
*
* @return the value of this {@code URLConnection}'s {@code URL}
* field.
*/
public URL getURL() {
return connection.getURL();
}
/**
* Returns the error stream if the connection failed
* but the server sent useful data nonetheless. The
* typical example is when an HTTP server responds
* with a 404, which will cause a FileNotFoundException
* to be thrown in connect, but the server sent an HTML
* help page with suggestions as to what to do.
*
* <p>This method will not cause a connection to be initiated. If
* the connection was not connected, or if the server did not have
* an error while connecting or if the server had an error but
* no error data was sent, this method will return null. This is
* the default.
*
* @return an error stream if any, null if there have been no
* errors, the connection is not connected or the server sent no
* useful data.
*/
@Override
public InputStream getErrorStream() {
return connection.getErrorStream();
}
/**
* Returns the value of the named header field.
* <p>
* If called on a connection that sets the same header multiple times
* with possibly different values, only the last value is returned.
*
*
* @param name the name of a header field.
* @return the value of the named header field, or {@code null}
* if there is no such field in the header.
*/
@Override
public String getHeaderField(String name) {
return connection.getHeaderField(name);
}
/**
* Returns an input stream that reads from this open connection.
*
* A SocketTimeoutException can be thrown when reading from the
* returned input stream if the read timeout expires before data
* is available for read.
*
* @return an input stream that reads from this open connection.
* @exception IOException if an I/O error occurs while
* creating the input stream.
* @exception UnknownServiceException if the protocol does not support
* input.
* @see #setReadTimeout(int)
*/
@Override
public InputStream getInputStream() throws IOException {
return connection.getInputStream();
}
/**
* Returns an unmodifiable Map of the header fields.
* The Map keys are Strings that represent the
* response-header field names. Each Map value is an
* unmodifiable List of Strings that represents
* the corresponding field values.
*
* @return a Map of header fields
*/
public Map<String, List<String>> getHeaderFields() {
return connection.getHeaderFields();
}
/**
* Sets the default request properties on the newly created connection.
* This is called as early as possible to allow overrides by user-provided values.
*/
private void setDefaultRequestProperties() {
String acceptLanguage = buildDefaultAcceptLanguageProperty();
if (!TextUtils.isEmpty(acceptLanguage)) {
connection.setRequestProperty("Accept-Language", acceptLanguage);
}
}
/**
* Builds and returns a locale string describing the device's current locale preferences.
*/
private String buildDefaultAcceptLanguageProperty() {
Locale locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = LocaleList.getDefault().get(0);
} else {
locale = Locale.getDefault();
}
String result = "";
String lang = locale.getLanguage();
String country = locale.getCountry();
if (!TextUtils.isEmpty(lang)) {
if (!TextUtils.isEmpty(country)) {
result = String.format("%s-%s,%s;q=0.5", lang, country, lang);
} else {
result = String.format("%s;q=0.5", lang);
}
}
return result;
}
public void setSSLSocketFactory(Bridge bridge) {
// Attach SSL Certificates if Enterprise Plugin is available
try {
Class<?> sslPinningImpl = Class.forName("io.ionic.sslpinning.SSLPinning");
Method method = sslPinningImpl.getDeclaredMethod("getSSLSocketFactory", Bridge.class);
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) method.invoke(sslPinningImpl.newInstance(), bridge);
if (sslSocketFactory != null) {
((HttpsURLConnection) this.connection).setSSLSocketFactory(sslSocketFactory);
}
} catch (Exception ignored) {}
}
}