Skip to content

Commit 1d1ef79

Browse files
committed
feat: objc/native full support (multipart)
1 parent c07a198 commit 1d1ef79

15 files changed

Lines changed: 304 additions & 129 deletions

File tree

src/index.js

Lines changed: 94 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,66 @@
1-
'use strict';
2-
3-
var debug = require('debug')('httpsnippet');
4-
var es = require('event-stream');
5-
var FormData = require('form-data');
6-
var qs = require('querystring');
7-
var reducer = require('./reducer');
8-
var targets = require('./targets');
9-
var url = require('url');
10-
var util = require('util');
11-
var validate = require('har-validator');
1+
'use strict'
2+
3+
var debug = require('debug')('httpsnippet')
4+
var es = require('event-stream')
5+
var FormData = require('form-data')
6+
var qs = require('querystring')
7+
var reducer = require('./reducer')
8+
var targets = require('./targets')
9+
var url = require('url')
10+
var util = require('util')
11+
var validate = require('har-validator')
1212

1313
// constructor
1414
var HTTPSnippet = function (req, lang) {
15-
this.source = util._extend({}, req);
15+
this.source = util._extend({}, req)
1616

1717
// add optional properties to make validation successful
18-
this.source.httpVersion = this.source.httpVersion || 'HTTP/1.1';
19-
this.source.queryString = this.source.queryString || [];
20-
this.source.headers = this.source.headers || [];
21-
this.source.cookies = this.source.cookies || [];
22-
this.source.postData = this.source.postData || {};
23-
this.source.postData.mimeType = this.source.postData.mimeType || 'application/octet-stream';
18+
this.source.httpVersion = this.source.httpVersion || 'HTTP/1.1'
19+
this.source.queryString = this.source.queryString || []
20+
this.source.headers = this.source.headers || []
21+
this.source.cookies = this.source.cookies || []
22+
this.source.postData = this.source.postData || {}
23+
this.source.postData.mimeType = this.source.postData.mimeType || 'application/octet-stream'
2424

25-
this.source.bodySize = 0;
26-
this.source.headersSize = 0;
27-
this.source.postData.size = 0;
25+
this.source.bodySize = 0
26+
this.source.headersSize = 0
27+
this.source.postData.size = 0
2828

2929
validate.request(this.source, function (err, valid) {
3030
if (!valid) {
31-
throw err;
31+
throw err
3232
}
3333

3434
// construct query string object
35-
this.source.queryObj = {};
36-
this.source.headersObj = {};
37-
this.source.allHeaders = {};
38-
this.source.postData.jsonObj = false;
39-
this.source.postData.paramsObj = false;
35+
this.source.queryObj = {}
36+
this.source.headersObj = {}
37+
this.source.allHeaders = {}
38+
this.source.postData.jsonObj = false
39+
this.source.postData.paramsObj = false
4040

4141
// construct query objects
4242
if (this.source.queryString && this.source.queryString.length) {
43-
debug('queryString found, constructing queryString pair map');
43+
debug('queryString found, constructing queryString pair map')
4444

45-
this.source.queryObj = this.source.queryString.reduce(reducer, {});
45+
this.source.queryObj = this.source.queryString.reduce(reducer, {})
4646
}
4747

4848
// construct headers objects
4949
if (this.source.headers && this.source.headers.length) {
5050
// loweCase header keys
5151
this.source.headersObj = this.source.headers.reduceRight(function (headers, header) {
52-
headers[header.name.toLowerCase()] = header.value;
53-
return headers;
54-
}, {});
52+
headers[header.name.toLowerCase()] = header.value
53+
return headers
54+
}, {})
5555
}
5656

5757
// construct Cookie header
5858
var cookies = this.source.cookies.map(function (cookie) {
59-
return encodeURIComponent(cookie.name) + '=' + encodeURIComponent(cookie.value);
60-
});
59+
return encodeURIComponent(cookie.name) + '=' + encodeURIComponent(cookie.value)
60+
})
6161

6262
if (cookies.length) {
63-
this.source.allHeaders.cookie = cookies.join('; ');
63+
this.source.allHeaders.cookie = cookies.join('; ')
6464
}
6565

6666
switch (this.source.postData.mimeType) {
@@ -69,143 +69,146 @@ var HTTPSnippet = function (req, lang) {
6969
case 'multipart/form-data':
7070
case 'multipart/alternative':
7171
// reset values
72-
this.source.postData.text = '';
73-
this.source.postData.mimeType = 'multipart/form-data';
72+
this.source.postData.text = ''
73+
this.source.postData.mimeType = 'multipart/form-data'
7474

75-
var form = new FormData();
75+
var form = new FormData()
7676

7777
// easter egg
78-
form._boundary = '---011000010111000001101001';
78+
form._boundary = '---011000010111000001101001'
7979

8080
this.source.postData.params.map(function (param) {
8181
form.append(param.name, param.value || '', {
8282
filename: param.fileName || null,
8383
contentType: param.contentType || null
84-
});
85-
});
84+
})
85+
})
8686

8787
form.pipe(es.map(function (data, cb) {
88-
this.source.postData.text += data;
89-
}.bind(this)));
88+
this.source.postData.text += data
89+
}.bind(this)))
9090

91-
this.source.headersObj['content-type'] = 'multipart/form-data; boundary=' + form.getBoundary();
92-
break;
91+
this.source.headersObj['content-type'] = 'multipart/form-data; boundary=' + form.getBoundary()
92+
break
9393

9494
case 'application/x-www-form-urlencoded':
9595
if (!this.source.postData.params) {
96-
this.source.postData.text = '';
96+
this.source.postData.text = ''
9797
} else {
98-
this.source.postData.paramsObj = this.source.postData.params.reduce(reducer, {});
98+
this.source.postData.paramsObj = this.source.postData.params.reduce(reducer, {})
9999

100100
// always overwrite
101-
this.source.postData.text = qs.stringify(this.source.postData.paramsObj);
101+
this.source.postData.text = qs.stringify(this.source.postData.paramsObj)
102102
}
103-
break;
103+
break
104104

105105
case 'text/json':
106106
case 'text/x-json':
107107
case 'application/json':
108108
case 'application/x-json':
109-
this.source.postData.mimeType = 'application/json';
109+
this.source.postData.mimeType = 'application/json'
110110

111111
if (this.source.postData.text) {
112112
try {
113-
this.source.postData.jsonObj = JSON.parse(this.source.postData.text);
113+
this.source.postData.jsonObj = JSON.parse(this.source.postData.text)
114114
} catch (e) {
115-
debug(e);
115+
debug(e)
116116

117117
// force back to text/plain
118118
// if headers have proper content-type value, then this should also work
119-
this.source.postData.mimeType = 'text/plain';
119+
this.source.postData.mimeType = 'text/plain'
120120
}
121121
}
122-
break;
122+
break
123123
}
124124

125125
// create allHeaders object
126-
this.source.allHeaders = util._extend(this.source.allHeaders, this.source.headersObj);
126+
this.source.allHeaders = util._extend(this.source.allHeaders, this.source.headersObj)
127127

128128
// deconstruct the uri
129-
this.source.uriObj = url.parse(this.source.url, true, true);
129+
this.source.uriObj = url.parse(this.source.url, true, true)
130130

131131
// merge all possible queryString values
132-
this.source.queryObj = util._extend(this.source.queryObj, this.source.uriObj.query);
132+
this.source.queryObj = util._extend(this.source.queryObj, this.source.uriObj.query)
133133

134134
// reset uriObj values for a clean url
135-
this.source.uriObj.query = null;
136-
this.source.uriObj.search = null;
137-
this.source.uriObj.path = this.source.uriObj.pathname;
135+
this.source.uriObj.query = null
136+
this.source.uriObj.search = null
137+
this.source.uriObj.path = this.source.uriObj.pathname
138138

139139
// keep the base url clean of queryString
140-
this.source.url = url.format(this.source.uriObj);
140+
this.source.url = url.format(this.source.uriObj)
141141

142142
// update the uri object
143-
this.source.uriObj.query = this.source.queryObj;
144-
this.source.uriObj.search = qs.stringify(this.source.queryObj);
145-
this.source.uriObj.path = this.source.uriObj.pathname + '?' + this.source.uriObj.search;
143+
this.source.uriObj.query = this.source.queryObj
144+
this.source.uriObj.search = qs.stringify(this.source.queryObj)
145+
146+
if (this.source.uriObj.search) {
147+
this.source.uriObj.path = this.source.uriObj.pathname + '?' + this.source.uriObj.search
148+
}
146149

147150
// construct a full url
148-
this.source.fullUrl = url.format(this.source.uriObj);
149-
}.bind(this));
150-
};
151+
this.source.fullUrl = url.format(this.source.uriObj)
152+
}.bind(this))
153+
}
151154

152155
HTTPSnippet.prototype.convert = function (target, client, opts) {
153156
if (!opts && client) {
154-
opts = client;
157+
opts = client
155158
}
156159

157-
var func = this._matchTarget(target, client);
160+
var func = this._matchTarget(target, client)
158161

159162
if (func) {
160-
return func.call(this, this.source, opts);
163+
return func.call(this, this.source, opts)
161164
}
162165

163-
return false;
164-
};
166+
return false
167+
}
165168

166169
HTTPSnippet.prototype._matchTarget = function (target, client) {
167170
// does it exist?
168171
if (!targets.hasOwnProperty(target)) {
169-
return false;
172+
return false
170173
}
171174

172175
if (typeof targets[target] === 'function') {
173-
return targets[target];
176+
return targets[target]
174177
}
175178

176179
// shorthand
177180
if (typeof client === 'string' && typeof targets[target][client] === 'function') {
178-
return targets[target][client];
181+
return targets[target][client]
179182
}
180183

181184
// default target
182-
return targets[target][targets[target].info.default];
183-
};
185+
return targets[target][targets[target].info.default]
186+
}
184187

185188
// exports
186-
module.exports = HTTPSnippet;
189+
module.exports = HTTPSnippet
187190

188191
module.exports.availableTargets = function () {
189192
return Object.keys(targets).map(function (key) {
190-
var target = util._extend({}, targets[key].info);
193+
var target = util._extend({}, targets[key].info)
191194
var clients = Object.keys(targets[key])
192195

193-
.filter(function (prop) {
194-
return !~['info', 'index'].indexOf(prop);
195-
})
196+
.filter(function (prop) {
197+
return !~['info', 'index'].indexOf(prop)
198+
})
196199

197-
.map(function (client) {
198-
return targets[key][client].info;
199-
});
200+
.map(function (client) {
201+
return targets[key][client].info
202+
})
200203

201204
if (clients.length) {
202-
target.clients = clients;
205+
target.clients = clients
203206
}
204207

205-
return target;
206-
});
207-
};
208+
return target
209+
})
210+
}
208211

209212
module.exports.extname = function (target) {
210-
return targets[target] ? targets[target].info.extname : '';
211-
};
213+
return targets[target] ? targets[target].info.extname : ''
214+
}

src/targets/objc/helpers.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,41 @@ module.exports = {
3737
return dicOpening + dicLiteral + ';';
3838
},
3939

40+
/**
41+
* Similar to nsDictionaryBuilder but for NSArray literals.
42+
* @see nsDictionaryBuilder
43+
*/
44+
nsArrayBuilder: function (name, parameters, indent) {
45+
var dicOpening = 'NSArray *' + name + ' = ';
46+
var dicLiteral = this.literalRepresentation(parameters, indent ? dicOpening.length : null);
47+
return dicOpening + dicLiteral + ';';
48+
},
49+
4050
/**
4151
* Create a valid Objective-C string of a literal value according to its type.
4252
*
4353
* @param {*} value Any JavaScript literal
4454
* @return {string}
4555
*/
4656
literalRepresentation: function (value, indentation) {
57+
var join = indentation === undefined ? ', ' : ',\n ' + this.blankString(indentation);
58+
4759
switch (Object.prototype.toString.call(value)) {
4860
case '[object Number]':
4961
return '@' + value;
5062
case '[object Array]':
5163
var values_representation = value.map(function (v) {
5264
return this.literalRepresentation(v);
5365
}.bind(this));
54-
return '@[ ' + values_representation.join(', ') + ' ]';
66+
return '@[ ' + values_representation.join(join) + ' ]';
5567
case '[object Object]':
5668
var keyValuePairs = [];
5769
for (var k in value) {
5870
keyValuePairs.push(util.format('@"%s": %s', k, this.literalRepresentation(value[k])));
5971
}
60-
61-
var join = indentation === undefined ? ', ' : ',\n ' + this.blankString(indentation);
62-
6372
return '@{ ' + keyValuePairs.join(join) + ' }';
6473
default:
6574
return '@"' + value.replace(/"/g, '\\"') + '"';
6675
}
6776
}
68-
}
77+
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
curl --request POST \
22
--url "http://mockbin.com/har" \
3-
--header "Content-Type: application/json" \
3+
--header "content-type: application/json" \
44
--data "{\"number\": 1, \"string\": \"f\\\"oo\", \"arr\": [1, 2, 3], \"nested\": {\"a\": \"b\"}, \"arr_mix\": [1, \"a\", {\"arr_mix_nested\": {}}] }"

test/fixtures/output/curl/http1.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
curl --request GET \
2+
--url "http://mockbin.com/request" \
3+
--http1.0

test/fixtures/output/objc/native/application-form-encoded.m

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#import <Foundation/Foundation.h>
22

3-
NSDictionary *headers = @{ @"Content-Type": @"application/x-www-form-urlencoded" };
3+
NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded" };
44

55
NSMutableData *postData = [[NSMutableData alloc] initWithData:[@"foo=bar" dataUsingEncoding:NSUTF8StringEncoding]];
66
[postData appendData:[@"&hello=world" dataUsingEncoding:NSUTF8StringEncoding]];
@@ -15,7 +15,11 @@
1515
NSURLSession *session = [NSURLSession sharedSession];
1616
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
1717
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
18-
19-
}];
20-
18+
if (error) {
19+
NSLog(@"%@", error);
20+
} else {
21+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
22+
NSLog(@"%@", httpResponse);
23+
}
24+
}];
2125
[dataTask resume];

0 commit comments

Comments
 (0)