From e63d0a5ee401f3c1fe2548ef935ad816888bf212 Mon Sep 17 00:00:00 2001 From: Jon Samwell Date: Mon, 12 Oct 2015 10:51:25 +1100 Subject: [PATCH] Fixed issue with double encoding urls --- .jshintrc | 4 +- bower.json | 2 +- dist/ChangeLog.txt | 5 + dist/angular-http-batch.js | 55 +++++---- dist/angular-http-batch.min.js | 4 +- package.json | 2 +- servers/WebApi2/WebApiHttpBatchServer.v12.suo | Bin 126976 -> 129536 bytes src/services/adapters/httpAdapter.js | 16 ++- tests/index.html | 2 +- tests/services/adapters/httpAdapter.spec.js | 108 ++++++++++++++++++ 10 files changed, 168 insertions(+), 30 deletions(-) diff --git a/.jshintrc b/.jshintrc index 92b42d3..2822d9d 100644 --- a/.jshintrc +++ b/.jshintrc @@ -5,7 +5,7 @@ "noarg": true, "noempty": true, "nonew": true, - "trailing": true, + "trailing": false, "maxlen": 512, "boss": true, "eqnull": false, @@ -30,4 +30,4 @@ "it": true, "should": true } -} \ No newline at end of file +} diff --git a/bower.json b/bower.json index d2a06d0..b60f078 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-http-batcher", - "version": "1.11.3", + "version": "1.12.0", "description": "Enables transparent HTTP batch requests with Angular", "main": "dist/angular-http-batch.js", "keywords": [ diff --git a/dist/ChangeLog.txt b/dist/ChangeLog.txt index 34ee903..bd1a090 100644 --- a/dist/ChangeLog.txt +++ b/dist/ChangeLog.txt @@ -1,3 +1,8 @@ +12/10/2015 V1.12.0 +Now handles multiple JSON Vulnerability Prefixes in same response +Add unique request part names to Content-Disposition header for use with tomcat servlet 3.1 +Fix to stop multiple url encoding of query string parameters in http batch adapter + 27/08/2015 V1.11.3 Fixed error when trimming response to protect against JSON vulnerability error (pr by @magarcia https://github.com/magarcia) Use encodeURI to process query strings with spaces and other such characters in default http adapter. (pr by https://github.com/tiwariarvin) diff --git a/dist/angular-http-batch.js b/dist/angular-http-batch.js index d0f2fc8..f721d9e 100644 --- a/dist/angular-http-batch.js +++ b/dist/angular-http-batch.js @@ -1,5 +1,5 @@ /* - * angular-http-batcher - v1.11.3 - 2015-08-27 + * angular-http-batcher - v1.12.0 - 2015-10-12 * https://github.com/jonsamwell/angular-http-batcher * Copyright (c) 2015 Jon Samwell */ @@ -29,7 +29,8 @@ function HttpBatchConfigFn() { ignoredVerbs: ['head'], sendCookies: false, enabled: true, - adapter: defaultBatchAdapter + adapter: defaultBatchAdapter, + uniqueRequestName: null }; /** @@ -203,7 +204,9 @@ function HttpBatchAdapter($document, $window, httpBatchConfig) { singleSpace: ' ', forwardSlash: '/', doubleDash: '--', - colon: ':' + colon: ':', + semiColon: ';', + requestName: 'name=' }; self.key = 'httpBatchAdapter'; @@ -231,7 +234,7 @@ function HttpBatchAdapter($document, $window, httpBatchConfig) { headers: config.batchRequestHeaders || {} }, batchBody = [], - urlInfo, i, request, header; + urlInfo, i, request, header, relativeUrlParts, encodedRelativeUrl; httpConfig.headers[constants.contentType] = 'multipart/mixed; boundary=' + boundary; @@ -242,13 +245,24 @@ function HttpBatchAdapter($document, $window, httpBatchConfig) { batchBody.push(constants.doubleDash + boundary); if (config.batchPartRequestHeaders) { for (header in config.batchPartRequestHeaders) { - batchBody.push(header + constants.colon + constants.singleSpace + config.batchPartRequestHeaders[header]); + if (config.batchPartRequestHeaders.hasOwnProperty(header)) { + var currHeader = header + constants.colon + constants.singleSpace + config.batchPartRequestHeaders[header]; + if (header.toLowerCase() === "content-disposition" && config.uniqueRequestName !== null && config.uniqueRequestName !== undefined) { + currHeader += constants.semiColon + constants.singleSpace + constants.requestName + config.uniqueRequestName + i; + } + batchBody.push(currHeader); + } } } batchBody.push('Content-Type: application/http; msgtype=request', constants.emptyString); - batchBody.push(request.method + ' ' + encodeURI(urlInfo.relativeUrl) + ' ' + constants.httpVersion); + // angular would have already encoded the parameters *if* the dev passed them in via the params parameter to $http + // so we only need to url encode the url not the query string part + relativeUrlParts = urlInfo.relativeUrl.split('?'); + encodedRelativeUrl = encodeURI(relativeUrlParts[0]) + (relativeUrlParts.length > 1 ? '?' + relativeUrlParts[1] : ''); + + batchBody.push(request.method + ' ' + encodedRelativeUrl + ' ' + constants.httpVersion); batchBody.push('Host: ' + urlInfo.host); for (header in request.headers) { @@ -365,18 +379,30 @@ function HttpBatchAdapter($document, $window, httpBatchConfig) { function findResponseBoundary(contentType) { var boundaryText = 'boundary=', startIndex = contentType.indexOf(boundaryText), - boundary = contentType.substring(startIndex + boundaryText.length); + endIndex = contentType.indexOf(';', startIndex), + boundary = contentType.substring(startIndex + boundaryText.length, endIndex > 0 ? endIndex : contentType.length); // the boundary might be quoted so remove the quotes boundary = boundary.replace(/"/g, constants.emptyString); return boundary; } + /** + * see https://docs.angularjs.org/api/ng/service/$http#json-vulnerability-protection + * @param data + * @returns {*|void|string} + */ + function trimJsonProtectionVulnerability(data) { + return typeof (data) === 'string' ? data.replace(')]}\',\n', '') : data; + } + function convertDataToCorrectType(contentType, dataStr) { var data = dataStr; contentType = contentType.toLowerCase(); if (contentType.indexOf('json') > -1) { + // only remove json vulnerability prefix if we're parsing json + dataStr = trimJsonProtectionVulnerability(dataStr); data = angular.fromJson(dataStr); } @@ -559,15 +585,6 @@ function addRequestFn(request) { return true; } -/** - * see https://docs.angularjs.org/api/ng/service/$http#json-vulnerability-protection - * @param data - * @returns {*|void|string} - */ -function trimJsonProtectionVulnerability(data) { - return typeof (data) === 'string' ? data.replace(')]}\',\n', '') : data; -} - function sendFn() { var self = this, adapter = self.getAdapter(), @@ -575,11 +592,7 @@ function sendFn() { self.sendCallback(); self.$injector.get('$http')(httpBatchConfig).then(function (response) { - var batchResponses; - - response.data = trimJsonProtectionVulnerability(response.data); - - batchResponses = adapter.parseResponse(self.requests, response, self.config); + var batchResponses = adapter.parseResponse(self.requests, response, self.config); angular.forEach(batchResponses, function (batchResponse) { batchResponse.request.callback( diff --git a/dist/angular-http-batch.min.js b/dist/angular-http-batch.min.js index 73eb2d6..d6de283 100644 --- a/dist/angular-http-batch.min.js +++ b/dist/angular-http-batch.min.js @@ -1,5 +1,5 @@ /* - * angular-http-batcher - v1.11.3 - 2015-08-27 + * angular-http-batcher - v1.12.0 - 2015-10-12 * https://github.com/jonsamwell/angular-http-batcher * Copyright (c) 2015 Jon Samwell;*/ -!function(a,b){"use strict";function c(){var a=[],c="httpBatchAdapter",d={maxBatchedRequestPerCall:10,minimumBatchSize:2,batchRequestCollectionDelay:100,ignoredVerbs:["head"],sendCookies:!1,enabled:!0,adapter:c};this.setAllowedBatchEndpoint=function(e,f,g){var h=b.copy(d);void 0!==g&&(b.forEach(g,function(a,b){h[b]=a}),b.forEach(h.ignoredVerbs,function(a,b){h.ignoredVerbs[b]=a.toLowerCase()})),h.serviceUrl=e,h.batchEndpointUrl=f,h.adapter=h.adapter||c,a.push(h)},this.getBatchConfig=function(b){var c,d;for(d=0;d-1));d+=1)c=void 0;return c},this.canBatchCall=function(a,b){var c=this.getBatchConfig(a),d=c?c.canBatchRequest:void 0,e=!1;return c&&c.enabled===!0&&(e=d?d(a,b):c.batchEndpointUrl!==a&&-1===a.indexOf(c.batchEndpointUrl)&&-1===c.ignoredVerbs.indexOf(b.toLowerCase())),e},this.calculateBoundary=function(){return(new Date).getTime().toString()},this.$get=[function(){return this}]}function d(a,b,c,d,e){this.request=a,this.statusCode=b,this.statusText=c,this.data=d,this.headers=e}function e(c,d,e){function f(a,b){var d,f,g,h,i=e.calculateBoundary(),k={method:"POST",url:b.batchEndpointUrl,cache:!1,headers:b.batchRequestHeaders||{}},l=[];for(k.headers[o.contentType]="multipart/mixed; boundary="+i,f=0;f0&&l.push("Cookie: "+c[0].cookie),l.push(o.emptyString),g.data&&l.push(g.data),l.push(o.emptyString)}return l.push(o.doubleDash+i+o.doubleDash),k.data=l.join(o.newline),k}function g(a,b,c){var d,e,f=[],g=k(b.headers()["content-type"]),h=b.data.split(o.doubleDash+g+o.newline),i=0;for(d=0;d-1||a.indexOf("../")>-1){var h=document.createElement("a");h.href=a,a=h.href}return a.indexOf("://")>-1?(f=a.indexOf("://")+3,g=a.slice(f).split(o.forwardSlash),b=a.substring(0,f),c=g[0],e=function(){return delete g[0],g.join(o.forwardSlash)}()):(e=a,b=d.location.protocol,c=d.location.host),{protocol:b,host:c,relativeUrl:e}}function k(a){var b="boundary=",c=a.indexOf(b),d=a.substring(c+b.length);return d=d.replace(/"/g,o.emptyString)}function l(a,c){var d=c;return a=a.toLowerCase(),a.indexOf("json")>-1&&(d=b.fromJson(c)),d}function m(b,c,d){var e,f,g,h,j,k,m=b.split(o.newline),n={headers:{}},p=!1;for(f=0;f1&&(c+="?"+encodeURIComponent(f[1])),d>0&&(g.url+="&"),g.url+=d.toString()+"="+c;return g}function c(b,c){var d,e,f,g=[],h=c.data;for(d=0;d=this.config.maxBatchedRequestPerCall&&this.flush(),!0}function j(a){return"string"==typeof a?a.replace(")]}',\n",""):a}function k(){var a=this,c=a.getAdapter(),d=c.buildRequest(a.requests,a.config);a.sendCallback(),a.$injector.get("$http")(d).then(function(d){var e;d.data=j(d.data),e=c.parseResponse(a.requests,d,a.config),b.forEach(e,function(a){a.request.callback(a.statusCode,a.data,g(a.headers),a.statusText)})},function(c){b.forEach(a.requests,function(a){a.callback(c.statusCode,c.data,c.headers,c.statusText)})})}function l(){this.$timeout.cancel(this.currentTimeoutToken),this.currentTimeoutToken=void 0,this.send()}function m(a,c,d,e,f){var g=this;this.$injector=a,this.$timeout=c,this.adapters=d,this.config=e,this.sendCallback=f,this.requests=[],this.currentTimeoutToken=c(function(){g.currentTimeoutToken=void 0,g.requests.length-1));d+=1)c=void 0;return c},this.canBatchCall=function(a,b){var c=this.getBatchConfig(a),d=c?c.canBatchRequest:void 0,e=!1;return c&&c.enabled===!0&&(e=d?d(a,b):c.batchEndpointUrl!==a&&-1===a.indexOf(c.batchEndpointUrl)&&-1===c.ignoredVerbs.indexOf(b.toLowerCase())),e},this.calculateBoundary=function(){return(new Date).getTime().toString()},this.$get=[function(){return this}]}function d(a,b,c,d,e){this.request=a,this.statusCode=b,this.statusText=c,this.data=d,this.headers=e}function e(c,d,e){function f(a,b){var d,f,g,h,i,k,l=e.calculateBoundary(),m={method:"POST",url:b.batchEndpointUrl,cache:!1,headers:b.batchRequestHeaders||{}},n=[];for(m.headers[p.contentType]="multipart/mixed; boundary="+l,f=0;f1?"?"+i[1]:""),n.push(g.method+" "+k+" "+p.httpVersion),n.push("Host: "+d.host);for(h in g.headers)n.push(h+p.colon+p.singleSpace+g.headers[h]);b.sendCookies===!0&&c[0].cookie&&c[0].cookie.length>0&&n.push("Cookie: "+c[0].cookie),n.push(p.emptyString),g.data&&n.push(g.data),n.push(p.emptyString)}return n.push(p.doubleDash+l+p.doubleDash),m.data=n.join(p.newline),m}function g(a,b,c){var d,e,f=[],g=k(b.headers()["content-type"]),h=b.data.split(p.doubleDash+g+p.newline),i=0;for(d=0;d-1||a.indexOf("../")>-1){var h=document.createElement("a");h.href=a,a=h.href}return a.indexOf("://")>-1?(f=a.indexOf("://")+3,g=a.slice(f).split(p.forwardSlash),b=a.substring(0,f),c=g[0],e=function(){return delete g[0],g.join(p.forwardSlash)}()):(e=a,b=d.location.protocol,c=d.location.host),{protocol:b,host:c,relativeUrl:e}}function k(a){var b="boundary=",c=a.indexOf(b),d=a.indexOf(";",c),e=a.substring(c+b.length,d>0?d:a.length);return e=e.replace(/"/g,p.emptyString)}function l(a){return"string"==typeof a?a.replace(")]}',\n",""):a}function m(a,c){var d=c;return a=a.toLowerCase(),a.indexOf("json")>-1&&(c=l(c),d=b.fromJson(c)),d}function n(b,c,d){var e,f,g,h,j,k,l=b.split(p.newline),n={headers:{}},o=!1;for(f=0;f1&&(c+="?"+encodeURIComponent(f[1])),d>0&&(g.url+="&"),g.url+=d.toString()+"="+c;return g}function c(b,c){var d,e,f,g=[],h=c.data;for(d=0;d=this.config.maxBatchedRequestPerCall&&this.flush(),!0}function j(){var a=this,c=a.getAdapter(),d=c.buildRequest(a.requests,a.config);a.sendCallback(),a.$injector.get("$http")(d).then(function(d){var e=c.parseResponse(a.requests,d,a.config);b.forEach(e,function(a){a.request.callback(a.statusCode,a.data,g(a.headers),a.statusText)})},function(c){b.forEach(a.requests,function(a){a.callback(c.statusCode,c.data,c.headers,c.statusText)})})}function k(){this.$timeout.cancel(this.currentTimeoutToken),this.currentTimeoutToken=void 0,this.send()}function l(a,c,d,e,f){var g=this;this.$injector=a,this.$timeout=c,this.adapters=d,this.config=e,this.sendCallback=f,this.requests=[],this.currentTimeoutToken=c(function(){g.currentTimeoutToken=void 0,g.requests.length@0Bk*Rp%KnS}GZ)g<+U73M8N>kb}vxNR#bY|f_HZxLOXiWB*or@Ub4@Q zb7#>eg$s7*Axhb(5gcgO>As4KxZaEg{f-gfYO^M;z5vfm5S8e99HACD{-9hAvGqn)P<$eu044ICPB^CEeF#;)1Be==q}H_7J2CBpg!Ig6ffgmzw(7lSSvV^agqCA%7eV zI!M%^$$=1%E#an-;m~#ZpFGdv+SM5_{fv%Zklz>*d!vt|y(a78GVz+7@H~gXU08>mJe+`Oj%AsA#sUu@Kd}vUpg!1!2t=qD&D~(tMhjR} zUjv*vO>6$_d%STG75XouuzBAOd{ee+1*cm02-5j<5Yq6O@G-z@80x=TgaW9-5(v3% z%jzN+^64dLfs=EyvFn4gQgp`Z3M#RXl$1RYSuHF>`pyantG8iN6;|L&6PDM>OfnNp zzgi5yEmg=sLG4?G5q0D?tltU2J7-hjq%<=)8ADm;Q;2_LqqLIPIi+^%6MH6cb7$sb9|2}~7ZsZc5|l>+Xdlw42}*CbzJ zL5aT}47(hd?sk@#p)CQl QTzStYumPkp+^J*v8&58sng9R* delta 692 zcmX|;&ubGw6vy{X$Ia%K_J_7+aNg?R7~EGlZFfnKV3=+T1+#UNU&>zlat!H0SC-uKPCH?z?3Eo}H6XA?y7 ztXGn9==LCgT-Oy*;TM)lD)GlZfA?*EYQu4UQ(gqv$AGGdQB3ba5{QBfAXw>&a$!eV zjt(6-hS9@F09o`DL%1G7BGuBs*a$jlB!wJ7GDsBhBPt?}OX3#58-EXBd`^6>5a3f_x zeH^7?+@L4jB}Uz5w~JFs5xq$-Y*i@#S$qe4C3gaNM+P6+e#nm#4G+A zwp0d6y&dc1Kd_Z#&YD5xe3^=O?y5TTA*i3n>%|!OyWJ==?wHOTVb(95>z6A8URFzI z*v9Pm@GDfjMnt2l$TLJ#x@@y|&AQs;%}04~=9Xc*0ej&ZZB*XEDA=>Zu+}HO>c7R& z2{rOj2!j2w1vl*V7Q~%f#ICmBoPG8^3_{qRiNV3f-55MLRr>;zWr@tP&nXW){h`*r Y0&!L&H5268Hz0PQ26{t(0?Swa0whSt?*IS* diff --git a/src/services/adapters/httpAdapter.js b/src/services/adapters/httpAdapter.js index 73af193..425ddc0 100644 --- a/src/services/adapters/httpAdapter.js +++ b/src/services/adapters/httpAdapter.js @@ -38,7 +38,7 @@ function HttpBatchAdapter($document, $window, httpBatchConfig) { headers: config.batchRequestHeaders || {} }, batchBody = [], - urlInfo, i, request, header; + urlInfo, i, request, header, relativeUrlParts, encodedRelativeUrl; httpConfig.headers[constants.contentType] = 'multipart/mixed; boundary=' + boundary; @@ -61,7 +61,19 @@ function HttpBatchAdapter($document, $window, httpBatchConfig) { batchBody.push('Content-Type: application/http; msgtype=request', constants.emptyString); - batchBody.push(request.method + ' ' + encodeURI(urlInfo.relativeUrl) + ' ' + constants.httpVersion); + // angular would have already encoded the parameters *if* the dev passed them in via the params parameter to $http + // so we only need to url encode the url not the query string part + relativeUrlParts = urlInfo.relativeUrl.split('?'); + // do a very basic check to see if query strings are encoded as dev might + // have just added them to the url and not passed them in via the params config param to $http + if (relativeUrlParts.length > 1 && (/%[A-F0-9]{2}/gi).test(relativeUrlParts[1]) === false) { + // chances are they are not encoded so encode them + encodedRelativeUrl = encodeURI(urlInfo.relativeUrl); + } else { + encodedRelativeUrl = encodeURI(relativeUrlParts[0]) + (relativeUrlParts.length > 1 ? '?' + relativeUrlParts[1] : ''); + } + + batchBody.push(request.method + ' ' + encodedRelativeUrl + ' ' + constants.httpVersion); batchBody.push('Host: ' + urlInfo.host); for (header in request.headers) { diff --git a/tests/index.html b/tests/index.html index 2747917..8eb1236 100644 --- a/tests/index.html +++ b/tests/index.html @@ -40,7 +40,7 @@ batchRequestCollectionDelay: 500, minimumBatchSize: 1, sendCookies: true, - enabled: false + enabled: true }); // relative endpoint httpBatchConfigProvider.setAllowedBatchEndpoint( diff --git a/tests/services/adapters/httpAdapter.spec.js b/tests/services/adapters/httpAdapter.spec.js index 167d70a..91f873d 100644 --- a/tests/services/adapters/httpAdapter.spec.js +++ b/tests/services/adapters/httpAdapter.spec.js @@ -147,6 +147,114 @@ 'GET api/some-method?params=123 HTTP/1.1\r\nHost: localhost:9876\r\n\r\n\r\n--boundary123--'); }); + it('should not double encode query string parameters', function () { + var rawRequest = { + url: 'api/some-method?params=123&filter=abc%3D1', + method: 'GET' + }, + config = { + batchEndpointUrl: 'batchEndpointUrl', + batchRequestHeaders: { + 'Content-disposition': 'form-data' + }, + batchPartRequestHeaders: { + 'Content-disposition': 'form-data' + }, + uniqueRequestName: "veryUniqueRequestName" + }; + + var result = httpAdapter.buildRequest([rawRequest], config); + + expect(result.method).to.equal('POST'); + expect(result.url).to.equal(config.batchEndpointUrl); + expect(result.cache).to.equal(false); + expect(result.headers['Content-Type']).to.equal('multipart/mixed; boundary=boundary123'); + expect(result.headers['Content-disposition']).to.equal('form-data'); + expect(result.data).to.equal('--boundary123\r\nContent-disposition: form-data; name=veryUniqueRequestName0\r\nContent-Type: application/http; msgtype=request\r\n\r\n' + + 'GET api/some-method?params=123&filter=abc%3D1 HTTP/1.1\r\nHost: localhost:9876\r\n\r\n\r\n--boundary123--'); + }); + + it('should not encode query string parameters but only if needed', function () { + var rawRequest = { + url: 'api/some method?params=123&some filter=1', + method: 'GET' + }, + config = { + batchEndpointUrl: 'batchEndpointUrl', + batchRequestHeaders: { + 'Content-disposition': 'form-data' + }, + batchPartRequestHeaders: { + 'Content-disposition': 'form-data' + }, + uniqueRequestName: "veryUniqueRequestName" + }; + + var result = httpAdapter.buildRequest([rawRequest], config); + + expect(result.method).to.equal('POST'); + expect(result.url).to.equal(config.batchEndpointUrl); + expect(result.cache).to.equal(false); + expect(result.headers['Content-Type']).to.equal('multipart/mixed; boundary=boundary123'); + expect(result.headers['Content-disposition']).to.equal('form-data'); + expect(result.data).to.equal('--boundary123\r\nContent-disposition: form-data; name=veryUniqueRequestName0\r\nContent-Type: application/http; msgtype=request\r\n\r\n' + + 'GET api/some%20method?params=123&some%20filter=abc%3D1 HTTP/1.1\r\nHost: localhost:9876\r\n\r\n\r\n--boundary123--'); + }); + + it('should not double encode query string parameters but encode url', function () { + var rawRequest = { + url: 'api/some method?params=123&filter=abc%3D1', + method: 'GET' + }, + config = { + batchEndpointUrl: 'batchEndpointUrl', + batchRequestHeaders: { + 'Content-disposition': 'form-data' + }, + batchPartRequestHeaders: { + 'Content-disposition': 'form-data' + }, + uniqueRequestName: "veryUniqueRequestName" + }; + + var result = httpAdapter.buildRequest([rawRequest], config); + + expect(result.method).to.equal('POST'); + expect(result.url).to.equal(config.batchEndpointUrl); + expect(result.cache).to.equal(false); + expect(result.headers['Content-Type']).to.equal('multipart/mixed; boundary=boundary123'); + expect(result.headers['Content-disposition']).to.equal('form-data'); + expect(result.data).to.equal('--boundary123\r\nContent-disposition: form-data; name=veryUniqueRequestName0\r\nContent-Type: application/http; msgtype=request\r\n\r\n' + + 'GET api/some%20method?params=123&filter=abc%3D1 HTTP/1.1\r\nHost: localhost:9876\r\n\r\n\r\n--boundary123--'); + }); + + it('double encoding should not affect raw url', function () { + var rawRequest = { + url: 'api/some-method', + method: 'GET' + }, + config = { + batchEndpointUrl: 'batchEndpointUrl', + batchRequestHeaders: { + 'Content-disposition': 'form-data' + }, + batchPartRequestHeaders: { + 'Content-disposition': 'form-data' + }, + uniqueRequestName: "veryUniqueRequestName" + }; + + var result = httpAdapter.buildRequest([rawRequest], config); + + expect(result.method).to.equal('POST'); + expect(result.url).to.equal(config.batchEndpointUrl); + expect(result.cache).to.equal(false); + expect(result.headers['Content-Type']).to.equal('multipart/mixed; boundary=boundary123'); + expect(result.headers['Content-disposition']).to.equal('form-data'); + expect(result.data).to.equal('--boundary123\r\nContent-disposition: form-data; name=veryUniqueRequestName0\r\nContent-Type: application/http; msgtype=request\r\n\r\n' + + 'GET api/some-method HTTP/1.1\r\nHost: localhost:9876\r\n\r\n\r\n--boundary123--'); + }); + it('should ignore unique request name if content disposition is not sent', function () { var rawRequest = { url: 'api/some-method?params=123',