Browse Source

Fix url encoding issue #44

Fix missing content type handling #44
Ben Hsieh 7 years ago
parent
commit
5c6acb8bf9

+ 4
- 22
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

@@ -72,8 +72,8 @@ public class RNFetchBlobBody extends RequestBody{
72 72
             case SingleFile:
73 73
                 writeOctetData(sink);
74 74
                 break;
75
-			case Encoded:
76
-				writeEncodedData(sink);
75
+            case AsIs:
76
+				writeRawData(sink);
77 77
 				break;
78 78
         }
79 79
         buffer.flush();
@@ -190,29 +190,11 @@ public class RNFetchBlobBody extends RequestBody{
190 190
 
191 191
 
192 192
 	/**
193
-     * Write form-encoded data to request body
193
+     * Write data to request body as-is
194 194
      * @param sink
195 195
      */
196
-	 // This is NOT working since sink.write, creates a Transfer-Encoding: chunked
197
-	 // header, which is not expected from servers
198
-	private void writeEncodedData(BufferedSink sink) throws IOException {
199
-		FormBody.Builder body = new FormBody.Builder();
200
-
201
-		// String[] pairs = rawBody.split("&");
202
-		// for ( String pair : pairs ) {
203
-		// 	String[] kv = pair.split("=");
204
-		// 	body.add(kv[0], kv[1]);
205
-		// }
206
-		// body.build().writeTo(sink);
207
-
208
-		// String header = "Content-Disposition: form-data; \r\n";
209
-		// String header = "Content-Type: application/x-www-form-urlencoded\r\n\r\n";
210
-		// sink.write(header.getBytes());
211
-		// byte[] fieldData = field.data.getBytes();
212
-		// bytesWritten += fieldData.length;
196
+	private void writeRawData(BufferedSink sink) throws IOException {
213 197
 		sink.write(rawBody.getBytes());
214
-
215
-
216 198
 	}
217 199
 
218 200
     /**

+ 1
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java View File

@@ -8,6 +8,7 @@ import okhttp3.MediaType;
8 8
 public class RNFetchBlobConst {
9 9
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
10 10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
11
+    public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
11 12
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
12 13
     public static final MediaType MIME_OCTET = MediaType.parse("application/octet-stream");
13 14
     public static final MediaType MIME_MULTIPART = MediaType.parse("multipart/form-data");

+ 54
- 35
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -19,6 +19,7 @@ import com.facebook.react.bridge.ReadableArray;
19 19
 import com.facebook.react.bridge.ReadableMap;
20 20
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
21 21
 import com.facebook.react.bridge.WritableMap;
22
+import com.facebook.react.modules.core.DeviceEventManagerModule;
22 23
 
23 24
 import java.io.ByteArrayInputStream;
24 25
 import java.io.File;
@@ -41,6 +42,7 @@ import okhttp3.Response;
41 42
 import okhttp3.ResponseBody;
42 43
 import okhttp3.FormBody;
43 44
 import okhttp3.internal.framed.Header;
45
+import okhttp3.internal.http.OkHeaders;
44 46
 
45 47
 /**
46 48
  * Created by wkh237 on 2016/6/21.
@@ -49,8 +51,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
49 51
 
50 52
     enum RequestType  {
51 53
         Form,
52
-		Encoded,
53 54
         SingleFile,
55
+        AsIs,
54 56
         WithoutBody,
55 57
         Others
56 58
     };
@@ -88,14 +90,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
88 90
         this.rawRequestBody = body;
89 91
         this.rawRequestBodyArray = arrayBody;
90 92
 
91
-        if(this.options.fileCache == true || this.options.path != null)
93
+        if(this.options.fileCache || this.options.path != null)
92 94
             responseType = ResponseType.FileStorage;
93 95
         else
94 96
             responseType = ResponseType.KeepInMemory;
95 97
 
96
-        if (body != null && headers.hasKey("content-type") && "application/x-www-form-urlencoded".equals(headers.getString("content-type")))
97
-			requestType = RequestType.Encoded;
98
-		else if (body != null)
98
+
99
+		if (body != null)
99 100
             requestType = RequestType.SingleFile;
100 101
         else if (arrayBody != null)
101 102
             requestType = RequestType.Form;
@@ -182,6 +183,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
182 183
             } catch (MalformedURLException e) {
183 184
                 e.printStackTrace();
184 185
             }
186
+
187
+            HashMap<String, String> mheaders = new HashMap<>();
185 188
             // set headers
186 189
             if (headers != null) {
187 190
                 ReadableMapKeySetIterator it = headers.keySetIterator();
@@ -189,42 +192,60 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
189 192
                     String key = it.nextKey();
190 193
                     String value = headers.getString(key);
191 194
                     builder.header(key, value);
195
+                    mheaders.put(key,value);
192 196
                 }
193 197
             }
194 198
 
199
+            if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
200
+                String cType = getHeaderIgnoreCases(mheaders, "content-type").toLowerCase();
201
+
202
+                if(cType == null) {
203
+                    builder.header("Content-Type", "application/octet-stream");
204
+                    requestType = RequestType.SingleFile;
205
+                }
206
+                if(rawRequestBody != null) {
207
+                    if(rawRequestBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
208
+                        requestType = RequestType.SingleFile;
209
+                    }
210
+                    else if (cType.contains(";base64") || cType.startsWith("application/octet")) {
211
+                        requestType = RequestType.SingleFile;
212
+                    } else {
213
+                        requestType = RequestType.AsIs;
214
+                    }
215
+                }
216
+            }
217
+            else {
218
+                requestType = RequestType.WithoutBody;
219
+            }
220
+
221
+
195 222
             // set request body
196 223
             switch (requestType) {
197 224
                 case SingleFile:
198 225
                     builder.method(method, new RNFetchBlobBody(
199 226
                             taskId,
200
-                            RequestType.SingleFile,
227
+                            requestType,
201 228
                             rawRequestBody,
202
-                            RNFetchBlobConst.MIME_OCTET
229
+                            MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
230
+                    ));
231
+                    break;
232
+                case AsIs:
233
+                    builder.method(method, new RNFetchBlobBody(
234
+                            taskId,
235
+                            requestType,
236
+                            rawRequestBody,
237
+                            MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
203 238
                     ));
204 239
                     break;
205 240
                 case Form:
206 241
                     builder.method(method, new RNFetchBlobBody(
207 242
                             taskId,
208
-                            RequestType.Form,
243
+                            requestType,
209 244
                             rawRequestBodyArray,
210 245
                             MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
211 246
                     ));
212 247
                     break;
213
-				case Encoded:
214
-					// rawRequestBody has an expected format of
215
-					// key1=value1&key2=value&...
216
-					FormBody.Builder formBuilder = new FormBody.Builder();
217 248
 
218
-					String[] pairs = rawRequestBody.split("&");
219
-					for ( String pair : pairs ) {
220
-						String[] kv = pair.split("=");
221
-						formBuilder.add(kv[0], kv[1]);
222
-					}
223
-
224
-					RequestBody body = formBuilder.build();
225
-
226
-					builder.method(method, body);
227
-					break;
228 249
                 case WithoutBody:
229 250
                     builder.method(method, null);
230 251
                     break;
@@ -309,6 +330,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
309 330
      * @param resp OkHttp response object
310 331
      */
311 332
     private void done(Response resp) {
333
+        emitStateEvent(getResponseInfo(resp));
312 334
         switch (responseType) {
313 335
             case KeepInMemory:
314 336
                 try {
@@ -402,18 +424,15 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
402 424
         return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
403 425
     }
404 426
 
405
-    /**
406
-     * Build request body by given string
407
-     * @param body Content of request body in UTF8 string format.
408
-     * @return
409
-     */
410
-    RequestBody buildRawBody(String body) {
411
-        if(body != null) {
412
-            this.contentType = MediaType.parse(options.mime);
413
-            return RequestBody.create(this.contentType, body);
414
-        }
415
-        return null;
427
+    private String getHeaderIgnoreCases(HashMap<String,String> headers, String field) {
428
+        String val = headers.get(field);
429
+        if(val != null) return val;
430
+        return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
431
+    }
416 432
 
433
+    private void emitStateEvent(WritableMap args) {
434
+        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
435
+                .emit(RNFetchBlobConst.EVENT_HTTP_STATE, args);
417 436
     }
418 437
 
419 438
     @Override
@@ -436,10 +455,10 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
436 455
                         cursor.moveToFirst();
437 456
                         String filePath = cursor.getString(0);
438 457
                         cursor.close();
439
-                        this.callback.invoke(null, filePath);
458
+                        this.callback.invoke(null, null, filePath);
440 459
                     }
441 460
                     else
442
-                        this.callback.invoke(null, null);
461
+                        this.callback.invoke(null, null, null);
443 462
                 }
444 463
             }
445 464
         }

+ 1
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java View File

@@ -16,6 +16,7 @@ import okhttp3.OkHttpClient;
16 16
  * Created by wkh237 on 2016/7/11.
17 17
  */
18 18
 public class RNFetchBlobUtils {
19
+
19 20
     public static String getMD5(String input) {
20 21
         String result = null;
21 22
 

+ 0
- 1
src/fs.js View File

@@ -173,7 +173,6 @@ function writeFile(path:string, data:string | Array<number>, encoding:?string):P
173 173
 
174 174
 function appendFile(path:string, data:string | Array<number>, encoding:?string):Promise {
175 175
   encoding = encoding || 'utf8'
176
-  console.log('fs append file', data, encoding)
177 176
   if(typeof path !== 'string')
178 177
     return Promise.reject('Invalid argument "path" ')
179 178
   if(encoding.toLocaleLowerCase() === 'ascii') {

+ 4
- 1
src/index.js View File

@@ -143,6 +143,7 @@ function fetch(...args:any):Promise {
143 143
     }
144 144
 
145 145
     let req = RNFetchBlob[nativeMethodName]
146
+
146 147
     req(options, taskId, method, url, headers || {}, body, (err, info, data) => {
147 148
 
148 149
       // task done, remove event listener
@@ -160,7 +161,8 @@ function fetch(...args:any):Promise {
160 161
           if(options.session)
161 162
             session(options.session).add(data)
162 163
         }
163
-        info = Object.assign({}, info, { rnfbEncode })
164
+        info = info || {}
165
+        info.rnfbEncode = rnfbEncode
164 166
 
165 167
         resolve(new FetchBlobResponse(taskId, info, data))
166 168
       }
@@ -189,6 +191,7 @@ function fetch(...args:any):Promise {
189 191
     stateEvent.remove()
190 192
     RNFetchBlob.cancelRequest(taskId, fn)
191 193
   }
194
+  promise.taskId = taskId
192 195
 
193 196
   return promise
194 197
 

+ 1
- 1
src/ios/RNFetchBlobFS.m View File

@@ -234,7 +234,7 @@ NSMutableDictionary *fileStreams = nil;
234 234
             return;
235 235
         }
236 236
         else {
237
-            content = [data dataUsingEncoding:NSUTF8StringEncoding];
237
+            content = [data dataUsingEncoding:NSISOLatin1StringEncoding];
238 238
         }
239 239
         if(append == YES) {
240 240
             [fileHandle seekToEndOfFile];

+ 7
- 5
src/ios/RNFetchBlobReqBuilder.m View File

@@ -30,7 +30,9 @@
30 30
                          form:(NSArray *)form
31 31
                    onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete
32 32
 {
33
-    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
33
+    //    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
34
+    NSString * encodedUrl = url;
35
+    
34 36
     // send request
35 37
     NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString: encodedUrl]];
36 38
     NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
@@ -71,11 +73,11 @@
71 73
                      body:(NSString *)body
72 74
                onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete
73 75
 {
74
-    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
76
+//    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
77
+    NSString * encodedUrl = url;
75 78
     // send request
76 79
     NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
77
-                                    initWithURL:[NSURL
78
-                                                 URLWithString: encodedUrl]];
80
+                                    initWithURL:[NSURL URLWithString: encodedUrl]];
79 81
 
80 82
     NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
81 83
     // move heavy task to another thread
@@ -115,7 +117,7 @@
115 117
                     
116 118
                     NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders];
117 119
                     // when content-type is application/octet* decode body string using BASE64 decoder
118
-                    if([[cType lowercaseString] hasPrefix:@"application/octet"])
120
+                    if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] containsString:@";base64"])
119 121
                     {
120 122
                         blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
121 123
                         [request setHTTPBody:blobData];

+ 9
- 9
src/polyfill/XMLHttpRequest.js View File

@@ -153,15 +153,15 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
153 153
 
154 154
   getResponseHeader(field:string):string | null {
155 155
     log.verbose('XMLHttpRequest get header', field)
156
-    if(!this.responseHeaders)
156
+    if(!this._responseHeaders)
157 157
       return null
158 158
     return this.responseHeaders[field] || null
159 159
 
160 160
   }
161 161
 
162 162
   getAllResponseHeaders():string | null {
163
-    log.verbose('XMLHttpRequest get all headers')
164
-    if(!this.responseHeaders)
163
+    log.verbose('XMLHttpRequest get all headers',this._task.taskId, this._responseHeaders)
164
+    if(!this._responseHeaders)
165 165
       return null
166 166
     let result = ''
167 167
     let respHeaders = this.responseHeaders
@@ -172,14 +172,14 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
172 172
   }
173 173
 
174 174
   _headerReceived(e) {
175
-    log.verbose('header received ', e)
175
+    log.verbose('header received ', this._task.taskId, e)
176 176
     this.responseURL = this._url
177 177
     if(e.state === "2") {
178
-      this.readyState = XMLHttpRequest.HEADERS_RECEIVED
179
-      this.responseHeaders = e.headers
178
+      this._readyState = XMLHttpRequest.HEADERS_RECEIVED
179
+      this._responseHeaders = e.headers
180 180
       this._responseText = e.status
181 181
       this._responseType = e.respType || ''
182
-      this.status = Math.floor(e.status)
182
+      this._status = Math.floor(e.status)
183 183
     }
184 184
   }
185 185
 
@@ -221,7 +221,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
221 221
   }
222 222
 
223 223
   _onDone(resp) {
224
-    log.verbose('XMLHttpRequest done', this)
224
+    log.verbose('XMLHttpRequest done', this._task.taskId, this)
225 225
     this.statusText = '200 OK'
226 226
     this._status = 200
227 227
     switch(resp.type) {
@@ -306,7 +306,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
306 306
   }
307 307
 
308 308
   get responseHeaders() {
309
-    log.verbose('get responseHeaders', this._responseHeaders)
309
+    log.verbose('get responseHeaders', this._task.taskId, this._responseHeaders)
310 310
     return this._responseHeaders
311 311
   }
312 312