|
| 1 | +/* |
| 2 | + * FormData polyfill for k6 |
| 3 | + * Copyright (C) 2021 Load Impact |
| 4 | + * License: MIT |
| 5 | + * |
| 6 | + * This simplifies the creation of multipart/form-data requests from k6 scripts. |
| 7 | + * It was adapted from the original version by Rob Wu[1] to remove references of |
| 8 | + * XMLHttpRequest and File related code which isn't supported in k6. |
| 9 | + * |
| 10 | + * [1]: https://gist.github.com/Rob--W/8b5adedd84c0d36aba64 |
| 11 | + **/ |
| 12 | + |
| 13 | +if (exports.FormData) { |
| 14 | + // Don't replace FormData if it already exists |
| 15 | + return; |
| 16 | +} |
| 17 | + |
| 18 | +// Export variable to the global scope |
| 19 | +exports.FormData = FormData; |
| 20 | + |
| 21 | +function FormData() { |
| 22 | + // Force a Constructor |
| 23 | + if (!(this instanceof FormData)) return new FormData(); |
| 24 | + // Generate a random boundary - This must be unique with respect to the |
| 25 | + // form's contents. |
| 26 | + this.boundary = '------RWWorkerFormDataBoundary' + Math.random().toString(36); |
| 27 | + this.parts = []; |
| 28 | + |
| 29 | + /** |
| 30 | + * Internal method. Convert input to a byte array. |
| 31 | + * @param inp String | ArrayBuffer | Uint8Array Input |
| 32 | + */ |
| 33 | + this.__toByteArray = function(inp) { |
| 34 | + var arr = []; |
| 35 | + var i = 0, len; |
| 36 | + if (typeof inp === 'string') { |
| 37 | + for (len = inp.length; i < len; ++i) |
| 38 | + arr.push(inp.charCodeAt(i) & 0xff); |
| 39 | + } else if (inp && inp.byteLength) {/*If ArrayBuffer or typed array */ |
| 40 | + if (!('byteOffset' in inp)) /* If ArrayBuffer, wrap in view */ |
| 41 | + inp = new Uint8Array(inp); |
| 42 | + for (len = inp.byteLength; i < len; ++i) |
| 43 | + arr.push(inp[i] & 0xff); |
| 44 | + } |
| 45 | + return arr; |
| 46 | + }; |
| 47 | +} |
| 48 | + |
| 49 | +/** |
| 50 | + * @param fieldName String Form field name |
| 51 | + * @param data object|string An object or string field value. |
| 52 | + * |
| 53 | + * If data is an object, it should match the structure of k6's http.FileData |
| 54 | + * object (returned by http.file()) and consist of: |
| 55 | + * @param data.data String|Array|ArrayBuffer File data |
| 56 | + * @param data.filename String Optional file name |
| 57 | + * @param data.content_type String Optional content type, default is application/octet-stream |
| 58 | + **/ |
| 59 | +FormData.prototype.append = function(fieldName, data) { |
| 60 | + if (arguments.length < 2) { |
| 61 | + throw new SyntaxError('Not enough arguments'); |
| 62 | + } |
| 63 | + var file = data; |
| 64 | + if (typeof data === 'string') { |
| 65 | + file = {data: data, content_type: 'text/plain'}; |
| 66 | + } |
| 67 | + this.parts.push({field: fieldName, file: file}); |
| 68 | +}; |
| 69 | + |
| 70 | +/** |
| 71 | + * Return the assembled request body as an ArrayBuffer. |
| 72 | + **/ |
| 73 | +FormData.prototype.body = function() { |
| 74 | + var body = []; |
| 75 | + var barr = this.__toByteArray('--' + this.boundary + '\r\n'); |
| 76 | + for (var i=0; i < this.parts.length; i++) { |
| 77 | + Array.prototype.push.apply(body, barr); |
| 78 | + var p = this.parts[i]; |
| 79 | + var cd = 'Content-Disposition: form-data; name="' + p.field + '"'; |
| 80 | + if (p.file.filename) { |
| 81 | + cd += '; filename="' + p.file.filename.replace(/"/g,'%22') + '"'; |
| 82 | + } |
| 83 | + cd += '\r\nContent-Type: ' |
| 84 | + + (p.file.content_type || 'application/octet-stream') |
| 85 | + + '\r\n\r\n'; |
| 86 | + Array.prototype.push.apply(body, this.__toByteArray(cd)); |
| 87 | + var data = Array.isArray(p.file.data) ? p.file.data : this.__toByteArray(p.file.data); |
| 88 | + Array.prototype.push.apply(body, data); |
| 89 | + Array.prototype.push.apply(body, this.__toByteArray('\r\n')); |
| 90 | + } |
| 91 | + Array.prototype.push.apply(body, this.__toByteArray('--' + this.boundary + '--\r\n')); |
| 92 | + return new Uint8Array(body).buffer; |
| 93 | +}; |
0 commit comments