urlencoded.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. 'use strict';
  2. const { Writable } = require('stream');
  3. const { getDecoder } = require('../utils.js');
  4. class URLEncoded extends Writable {
  5. constructor(cfg) {
  6. const streamOpts = {
  7. autoDestroy: true,
  8. emitClose: true,
  9. highWaterMark: (typeof cfg.highWaterMark === 'number'
  10. ? cfg.highWaterMark
  11. : undefined),
  12. };
  13. super(streamOpts);
  14. let charset = (cfg.defCharset || 'utf8');
  15. if (cfg.conType.params && typeof cfg.conType.params.charset === 'string')
  16. charset = cfg.conType.params.charset;
  17. this.charset = charset;
  18. const limits = cfg.limits;
  19. this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
  20. ? limits.fieldSize
  21. : 1 * 1024 * 1024);
  22. this.fieldsLimit = (limits && typeof limits.fields === 'number'
  23. ? limits.fields
  24. : Infinity);
  25. this.fieldNameSizeLimit = (
  26. limits && typeof limits.fieldNameSize === 'number'
  27. ? limits.fieldNameSize
  28. : 100
  29. );
  30. this._inKey = true;
  31. this._keyTrunc = false;
  32. this._valTrunc = false;
  33. this._bytesKey = 0;
  34. this._bytesVal = 0;
  35. this._fields = 0;
  36. this._key = '';
  37. this._val = '';
  38. this._byte = -2;
  39. this._lastPos = 0;
  40. this._encode = 0;
  41. this._decoder = getDecoder(charset);
  42. }
  43. static detect(conType) {
  44. return (conType.type === 'application'
  45. && conType.subtype === 'x-www-form-urlencoded');
  46. }
  47. _write(chunk, enc, cb) {
  48. if (this._fields >= this.fieldsLimit)
  49. return cb();
  50. let i = 0;
  51. const len = chunk.length;
  52. this._lastPos = 0;
  53. // Check if we last ended mid-percent-encoded byte
  54. if (this._byte !== -2) {
  55. i = readPctEnc(this, chunk, i, len);
  56. if (i === -1)
  57. return cb(new Error('Malformed urlencoded form'));
  58. if (i >= len)
  59. return cb();
  60. if (this._inKey)
  61. ++this._bytesKey;
  62. else
  63. ++this._bytesVal;
  64. }
  65. main:
  66. while (i < len) {
  67. if (this._inKey) {
  68. // Parsing key
  69. i = skipKeyBytes(this, chunk, i, len);
  70. while (i < len) {
  71. switch (chunk[i]) {
  72. case 61: // '='
  73. if (this._lastPos < i)
  74. this._key += chunk.latin1Slice(this._lastPos, i);
  75. this._lastPos = ++i;
  76. this._key = this._decoder(this._key, this._encode);
  77. this._encode = 0;
  78. this._inKey = false;
  79. continue main;
  80. case 38: // '&'
  81. if (this._lastPos < i)
  82. this._key += chunk.latin1Slice(this._lastPos, i);
  83. this._lastPos = ++i;
  84. this._key = this._decoder(this._key, this._encode);
  85. this._encode = 0;
  86. if (this._bytesKey > 0) {
  87. this.emit(
  88. 'field',
  89. this._key,
  90. '',
  91. { nameTruncated: this._keyTrunc,
  92. valueTruncated: false,
  93. encoding: this.charset,
  94. mimeType: 'text/plain' }
  95. );
  96. }
  97. this._key = '';
  98. this._val = '';
  99. this._keyTrunc = false;
  100. this._valTrunc = false;
  101. this._bytesKey = 0;
  102. this._bytesVal = 0;
  103. if (++this._fields >= this.fieldsLimit) {
  104. this.emit('fieldsLimit');
  105. return cb();
  106. }
  107. continue;
  108. case 43: // '+'
  109. if (this._lastPos < i)
  110. this._key += chunk.latin1Slice(this._lastPos, i);
  111. this._key += ' ';
  112. this._lastPos = i + 1;
  113. break;
  114. case 37: // '%'
  115. if (this._encode === 0)
  116. this._encode = 1;
  117. if (this._lastPos < i)
  118. this._key += chunk.latin1Slice(this._lastPos, i);
  119. this._lastPos = i + 1;
  120. this._byte = -1;
  121. i = readPctEnc(this, chunk, i + 1, len);
  122. if (i === -1)
  123. return cb(new Error('Malformed urlencoded form'));
  124. if (i >= len)
  125. return cb();
  126. ++this._bytesKey;
  127. i = skipKeyBytes(this, chunk, i, len);
  128. continue;
  129. }
  130. ++i;
  131. ++this._bytesKey;
  132. i = skipKeyBytes(this, chunk, i, len);
  133. }
  134. if (this._lastPos < i)
  135. this._key += chunk.latin1Slice(this._lastPos, i);
  136. } else {
  137. // Parsing value
  138. i = skipValBytes(this, chunk, i, len);
  139. while (i < len) {
  140. switch (chunk[i]) {
  141. case 38: // '&'
  142. if (this._lastPos < i)
  143. this._val += chunk.latin1Slice(this._lastPos, i);
  144. this._lastPos = ++i;
  145. this._inKey = true;
  146. this._val = this._decoder(this._val, this._encode);
  147. this._encode = 0;
  148. if (this._bytesKey > 0 || this._bytesVal > 0) {
  149. this.emit(
  150. 'field',
  151. this._key,
  152. this._val,
  153. { nameTruncated: this._keyTrunc,
  154. valueTruncated: this._valTrunc,
  155. encoding: this.charset,
  156. mimeType: 'text/plain' }
  157. );
  158. }
  159. this._key = '';
  160. this._val = '';
  161. this._keyTrunc = false;
  162. this._valTrunc = false;
  163. this._bytesKey = 0;
  164. this._bytesVal = 0;
  165. if (++this._fields >= this.fieldsLimit) {
  166. this.emit('fieldsLimit');
  167. return cb();
  168. }
  169. continue main;
  170. case 43: // '+'
  171. if (this._lastPos < i)
  172. this._val += chunk.latin1Slice(this._lastPos, i);
  173. this._val += ' ';
  174. this._lastPos = i + 1;
  175. break;
  176. case 37: // '%'
  177. if (this._encode === 0)
  178. this._encode = 1;
  179. if (this._lastPos < i)
  180. this._val += chunk.latin1Slice(this._lastPos, i);
  181. this._lastPos = i + 1;
  182. this._byte = -1;
  183. i = readPctEnc(this, chunk, i + 1, len);
  184. if (i === -1)
  185. return cb(new Error('Malformed urlencoded form'));
  186. if (i >= len)
  187. return cb();
  188. ++this._bytesVal;
  189. i = skipValBytes(this, chunk, i, len);
  190. continue;
  191. }
  192. ++i;
  193. ++this._bytesVal;
  194. i = skipValBytes(this, chunk, i, len);
  195. }
  196. if (this._lastPos < i)
  197. this._val += chunk.latin1Slice(this._lastPos, i);
  198. }
  199. }
  200. cb();
  201. }
  202. _final(cb) {
  203. if (this._byte !== -2)
  204. return cb(new Error('Malformed urlencoded form'));
  205. if (!this._inKey || this._bytesKey > 0 || this._bytesVal > 0) {
  206. if (this._inKey)
  207. this._key = this._decoder(this._key, this._encode);
  208. else
  209. this._val = this._decoder(this._val, this._encode);
  210. this.emit(
  211. 'field',
  212. this._key,
  213. this._val,
  214. { nameTruncated: this._keyTrunc,
  215. valueTruncated: this._valTrunc,
  216. encoding: this.charset,
  217. mimeType: 'text/plain' }
  218. );
  219. }
  220. cb();
  221. }
  222. }
  223. function readPctEnc(self, chunk, pos, len) {
  224. if (pos >= len)
  225. return len;
  226. if (self._byte === -1) {
  227. // We saw a '%' but no hex characters yet
  228. const hexUpper = HEX_VALUES[chunk[pos++]];
  229. if (hexUpper === -1)
  230. return -1;
  231. if (hexUpper >= 8)
  232. self._encode = 2; // Indicate high bits detected
  233. if (pos < len) {
  234. // Both hex characters are in this chunk
  235. const hexLower = HEX_VALUES[chunk[pos++]];
  236. if (hexLower === -1)
  237. return -1;
  238. if (self._inKey)
  239. self._key += String.fromCharCode((hexUpper << 4) + hexLower);
  240. else
  241. self._val += String.fromCharCode((hexUpper << 4) + hexLower);
  242. self._byte = -2;
  243. self._lastPos = pos;
  244. } else {
  245. // Only one hex character was available in this chunk
  246. self._byte = hexUpper;
  247. }
  248. } else {
  249. // We saw only one hex character so far
  250. const hexLower = HEX_VALUES[chunk[pos++]];
  251. if (hexLower === -1)
  252. return -1;
  253. if (self._inKey)
  254. self._key += String.fromCharCode((self._byte << 4) + hexLower);
  255. else
  256. self._val += String.fromCharCode((self._byte << 4) + hexLower);
  257. self._byte = -2;
  258. self._lastPos = pos;
  259. }
  260. return pos;
  261. }
  262. function skipKeyBytes(self, chunk, pos, len) {
  263. // Skip bytes if we've truncated
  264. if (self._bytesKey > self.fieldNameSizeLimit) {
  265. if (!self._keyTrunc) {
  266. if (self._lastPos < pos)
  267. self._key += chunk.latin1Slice(self._lastPos, pos - 1);
  268. }
  269. self._keyTrunc = true;
  270. for (; pos < len; ++pos) {
  271. const code = chunk[pos];
  272. if (code === 61/* '=' */ || code === 38/* '&' */)
  273. break;
  274. ++self._bytesKey;
  275. }
  276. self._lastPos = pos;
  277. }
  278. return pos;
  279. }
  280. function skipValBytes(self, chunk, pos, len) {
  281. // Skip bytes if we've truncated
  282. if (self._bytesVal > self.fieldSizeLimit) {
  283. if (!self._valTrunc) {
  284. if (self._lastPos < pos)
  285. self._val += chunk.latin1Slice(self._lastPos, pos - 1);
  286. }
  287. self._valTrunc = true;
  288. for (; pos < len; ++pos) {
  289. if (chunk[pos] === 38/* '&' */)
  290. break;
  291. ++self._bytesVal;
  292. }
  293. self._lastPos = pos;
  294. }
  295. return pos;
  296. }
  297. /* eslint-disable no-multi-spaces */
  298. const HEX_VALUES = [
  299. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  300. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  301. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  302. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
  303. -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  304. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  305. -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  306. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  307. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  308. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  309. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  310. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  311. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  312. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  313. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  314. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  315. ];
  316. /* eslint-enable no-multi-spaces */
  317. module.exports = URLEncoded;