Browse Source

Fix url encoding issue #44

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

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

72
             case SingleFile:
72
             case SingleFile:
73
                 writeOctetData(sink);
73
                 writeOctetData(sink);
74
                 break;
74
                 break;
75
-			case Encoded:
76
-				writeEncodedData(sink);
75
+            case AsIs:
76
+				writeRawData(sink);
77
 				break;
77
 				break;
78
         }
78
         }
79
         buffer.flush();
79
         buffer.flush();
190
 
190
 
191
 
191
 
192
 	/**
192
 	/**
193
-     * Write form-encoded data to request body
193
+     * Write data to request body as-is
194
      * @param sink
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
 		sink.write(rawBody.getBytes());
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
 public class RNFetchBlobConst {
8
 public class RNFetchBlobConst {
9
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
9
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
11
+    public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
11
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
12
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
12
     public static final MediaType MIME_OCTET = MediaType.parse("application/octet-stream");
13
     public static final MediaType MIME_OCTET = MediaType.parse("application/octet-stream");
13
     public static final MediaType MIME_MULTIPART = MediaType.parse("multipart/form-data");
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
 import com.facebook.react.bridge.ReadableMap;
19
 import com.facebook.react.bridge.ReadableMap;
20
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
20
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
21
 import com.facebook.react.bridge.WritableMap;
21
 import com.facebook.react.bridge.WritableMap;
22
+import com.facebook.react.modules.core.DeviceEventManagerModule;
22
 
23
 
23
 import java.io.ByteArrayInputStream;
24
 import java.io.ByteArrayInputStream;
24
 import java.io.File;
25
 import java.io.File;
41
 import okhttp3.ResponseBody;
42
 import okhttp3.ResponseBody;
42
 import okhttp3.FormBody;
43
 import okhttp3.FormBody;
43
 import okhttp3.internal.framed.Header;
44
 import okhttp3.internal.framed.Header;
45
+import okhttp3.internal.http.OkHeaders;
44
 
46
 
45
 /**
47
 /**
46
  * Created by wkh237 on 2016/6/21.
48
  * Created by wkh237 on 2016/6/21.
49
 
51
 
50
     enum RequestType  {
52
     enum RequestType  {
51
         Form,
53
         Form,
52
-		Encoded,
53
         SingleFile,
54
         SingleFile,
55
+        AsIs,
54
         WithoutBody,
56
         WithoutBody,
55
         Others
57
         Others
56
     };
58
     };
88
         this.rawRequestBody = body;
90
         this.rawRequestBody = body;
89
         this.rawRequestBodyArray = arrayBody;
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
             responseType = ResponseType.FileStorage;
94
             responseType = ResponseType.FileStorage;
93
         else
95
         else
94
             responseType = ResponseType.KeepInMemory;
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
             requestType = RequestType.SingleFile;
100
             requestType = RequestType.SingleFile;
100
         else if (arrayBody != null)
101
         else if (arrayBody != null)
101
             requestType = RequestType.Form;
102
             requestType = RequestType.Form;
182
             } catch (MalformedURLException e) {
183
             } catch (MalformedURLException e) {
183
                 e.printStackTrace();
184
                 e.printStackTrace();
184
             }
185
             }
186
+
187
+            HashMap<String, String> mheaders = new HashMap<>();
185
             // set headers
188
             // set headers
186
             if (headers != null) {
189
             if (headers != null) {
187
                 ReadableMapKeySetIterator it = headers.keySetIterator();
190
                 ReadableMapKeySetIterator it = headers.keySetIterator();
189
                     String key = it.nextKey();
192
                     String key = it.nextKey();
190
                     String value = headers.getString(key);
193
                     String value = headers.getString(key);
191
                     builder.header(key, value);
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
             // set request body
222
             // set request body
196
             switch (requestType) {
223
             switch (requestType) {
197
                 case SingleFile:
224
                 case SingleFile:
198
                     builder.method(method, new RNFetchBlobBody(
225
                     builder.method(method, new RNFetchBlobBody(
199
                             taskId,
226
                             taskId,
200
-                            RequestType.SingleFile,
227
+                            requestType,
201
                             rawRequestBody,
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
                     break;
239
                     break;
205
                 case Form:
240
                 case Form:
206
                     builder.method(method, new RNFetchBlobBody(
241
                     builder.method(method, new RNFetchBlobBody(
207
                             taskId,
242
                             taskId,
208
-                            RequestType.Form,
243
+                            requestType,
209
                             rawRequestBodyArray,
244
                             rawRequestBodyArray,
210
                             MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
245
                             MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
211
                     ));
246
                     ));
212
                     break;
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
                 case WithoutBody:
249
                 case WithoutBody:
229
                     builder.method(method, null);
250
                     builder.method(method, null);
230
                     break;
251
                     break;
309
      * @param resp OkHttp response object
330
      * @param resp OkHttp response object
310
      */
331
      */
311
     private void done(Response resp) {
332
     private void done(Response resp) {
333
+        emitStateEvent(getResponseInfo(resp));
312
         switch (responseType) {
334
         switch (responseType) {
313
             case KeepInMemory:
335
             case KeepInMemory:
314
                 try {
336
                 try {
402
         return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
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
     @Override
438
     @Override
436
                         cursor.moveToFirst();
455
                         cursor.moveToFirst();
437
                         String filePath = cursor.getString(0);
456
                         String filePath = cursor.getString(0);
438
                         cursor.close();
457
                         cursor.close();
439
-                        this.callback.invoke(null, filePath);
458
+                        this.callback.invoke(null, null, filePath);
440
                     }
459
                     }
441
                     else
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
  * Created by wkh237 on 2016/7/11.
16
  * Created by wkh237 on 2016/7/11.
17
  */
17
  */
18
 public class RNFetchBlobUtils {
18
 public class RNFetchBlobUtils {
19
+
19
     public static String getMD5(String input) {
20
     public static String getMD5(String input) {
20
         String result = null;
21
         String result = null;
21
 
22
 

+ 0
- 1
src/fs.js View File

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

+ 4
- 1
src/index.js View File

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

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

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

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

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

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

153
 
153
 
154
   getResponseHeader(field:string):string | null {
154
   getResponseHeader(field:string):string | null {
155
     log.verbose('XMLHttpRequest get header', field)
155
     log.verbose('XMLHttpRequest get header', field)
156
-    if(!this.responseHeaders)
156
+    if(!this._responseHeaders)
157
       return null
157
       return null
158
     return this.responseHeaders[field] || null
158
     return this.responseHeaders[field] || null
159
 
159
 
160
   }
160
   }
161
 
161
 
162
   getAllResponseHeaders():string | null {
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
       return null
165
       return null
166
     let result = ''
166
     let result = ''
167
     let respHeaders = this.responseHeaders
167
     let respHeaders = this.responseHeaders
172
   }
172
   }
173
 
173
 
174
   _headerReceived(e) {
174
   _headerReceived(e) {
175
-    log.verbose('header received ', e)
175
+    log.verbose('header received ', this._task.taskId, e)
176
     this.responseURL = this._url
176
     this.responseURL = this._url
177
     if(e.state === "2") {
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
       this._responseText = e.status
180
       this._responseText = e.status
181
       this._responseType = e.respType || ''
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
   }
221
   }
222
 
222
 
223
   _onDone(resp) {
223
   _onDone(resp) {
224
-    log.verbose('XMLHttpRequest done', this)
224
+    log.verbose('XMLHttpRequest done', this._task.taskId, this)
225
     this.statusText = '200 OK'
225
     this.statusText = '200 OK'
226
     this._status = 200
226
     this._status = 200
227
     switch(resp.type) {
227
     switch(resp.type) {
306
   }
306
   }
307
 
307
 
308
   get responseHeaders() {
308
   get responseHeaders() {
309
-    log.verbose('get responseHeaders', this._responseHeaders)
309
+    log.verbose('get responseHeaders', this._task.taskId, this._responseHeaders)
310
     return this._responseHeaders
310
     return this._responseHeaders
311
   }
311
   }
312
 
312