diff --git a/lib/body_parser.dart b/lib/body_parser.dart index f2e61274..37946a90 100644 --- a/lib/body_parser.dart +++ b/lib/body_parser.dart @@ -12,6 +12,9 @@ class BodyParseResult { /// The parsed query string. Map query = {}; + + /// All files uploaded within this request. + List files = []; } /// Grabs data from an incoming request. @@ -39,14 +42,48 @@ Future parseBody(HttpRequest request) async { buildMapFromUri(result.query, queryMatch.group(1)); } + // Accept file + if (contentType != null && request.method == 'POST') { + RegExp parseBoundaryRgx = new RegExp( + r'^multipart\/form-data;\s*boundary=(.+)$'); + if (parseBoundaryRgx.hasMatch(contentType.toString())) { + Match boundaryMatch = parseBoundaryRgx.firstMatch(contentType.toString()); + String body = await request.transform(UTF8.decoder).join(); + for (String chunk in body.split(boundaryMatch.group(1))) { + var fileData = getFileDataFromChunk(chunk); + if (fileData != null) + result.files.add(fileData); + } + } + } + return result; } +/// Parses file data from a multipart/form-data chunk. +Map getFileDataFromChunk(String chunk) { + Map result = {}; + RegExp isFormDataRgx = new RegExp( + r'Content-Dispositition:\s*form-data;\s*name="([^"]+)"'); + + if (isFormDataRgx.hasMatch(chunk)) { + result['name'] = isFormDataRgx.firstMatch(chunk).group(1); + + RegExp contentTypeRgx = new RegExp(r'Content-Type:\s*([^\n]+)'); + if (contentTypeRgx.hasMatch(chunk)) { + result['type'] = contentTypeRgx.firstMatch(chunk).group(1); + return result; + } + } + + return null; +} + /// Parses a URI-encoded string into real data! **Wow!** /// /// Whichever map you provide will be automatically populated from the urlencoded body string you provide. buildMapFromUri(Map map, String body) { - RegExp parseArray = new RegExp(r'^(.+)\[\]$'); + RegExp parseArrayRgx = new RegExp(r'^(.+)\[\]$'); for (String keyValuePair in body.split('&')) { if (keyValuePair.contains('=')) { @@ -54,15 +91,15 @@ buildMapFromUri(Map map, String body) { String key = Uri.decodeQueryComponent(split[0]); String value = Uri.decodeQueryComponent(split[1]); - if (parseArray.hasMatch(key)) { - Match queryMatch = parseArray.firstMatch(key); + if (parseArrayRgx.hasMatch(key)) { + Match queryMatch = parseArrayRgx.firstMatch(key); key = queryMatch.group(1); if (!(map[key] is List)) { map[key] = []; } map[key].add(getValue(value)); - } else if(key.contains('.')) { + } else if (key.contains('.')) { // i.e. map.foo.bar => [map, foo, bar] List keys = key.split('.'); diff --git a/test/all_tests.dart b/test/all_tests.dart index 770b2101..61a71c2b 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart @@ -38,7 +38,7 @@ main() { print('GET $url/?hello=world'); var response = await client.get('$url/?hello=world'); print('Response: ${response.body}'); - expect(response.body, equals('{"body":{},"query":{"hello":"world"}}')); + expect(response.body, equals('{"body":{},"query":{"hello":"world"},"files":[]}')); }); test('GET Complex', () async { @@ -64,7 +64,7 @@ main() { var response = await client.post( url, headers: headers, body: 'hello=world'); print('Response: ${response.body}'); - expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); + expect(response.body, equals('{"body":{"hello":"world"},"query":{},"files":[]}')); }); test('Post Complex', () async { @@ -91,7 +91,7 @@ main() { var response = await client.post( url, headers: headers, body: postData); print('Response: ${response.body}'); - expect(response.body, equals('{"body":{"hello":"world"},"query":{}}')); + expect(response.body, equals('{"body":{"hello":"world"},"query":{},"files":[]}')); }); test('Post Complex', () async { @@ -114,5 +114,28 @@ main() { expect(body['map']['foo'], equals({'bar': 'baz'})); }); }); + + group('File', () { + test('Single upload', () async { + String boundary = '----myBoundary'; + Map headers = { + HttpHeaders.CONTENT_TYPE: 'multipart/form-data; boundary=$boundary' + }; + String postData = ''' + + $boundary + Content-Dispositition: form-data; name="file" + Content-Type: text/plain + + Hello world + $boundary-- + '''; + + print('Form Data: \n$postData'); + var response = await client.post(url, headers: headers, body: postData); + print('Response: ${response.body}'); + var body = god.deserialize(response.body)['body']; + }); + }); }); } \ No newline at end of file