@loadmill/core 0.3.50 → 0.3.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conf/extrema.d.ts +1 -1
- package/dist/conf/extrema.js +2 -2
- package/dist/conf/extrema.js.map +1 -1
- package/dist/conf/types.d.ts +12 -0
- package/dist/conf/validate.d.ts +2 -1
- package/dist/conf/validate.js +6 -1
- package/dist/conf/validate.js.map +1 -1
- package/dist/multipart-form-data/form-data-utils.js +2 -2
- package/dist/multipart-form-data/form-data-utils.js.map +1 -1
- package/dist/multipart-form-data/is-binary-file.d.ts +2 -0
- package/dist/multipart-form-data/is-binary-file.js +215 -0
- package/dist/multipart-form-data/is-binary-file.js.map +1 -0
- package/dist/multipart-form-data/multipart-text-to-post-form-data.d.ts +5 -1
- package/dist/multipart-form-data/multipart-text-to-post-form-data.js +96 -35
- package/dist/multipart-form-data/multipart-text-to-post-form-data.js.map +1 -1
- package/dist/parameters/extractions.d.ts +2 -1
- package/dist/parameters/extractions.js.map +1 -1
- package/dist/parameters/index.d.ts +1 -1
- package/dist/parameters/index.js +4 -0
- package/dist/parameters/index.js.map +1 -1
- package/dist/request/index.d.ts +1 -0
- package/dist/request/index.js.map +1 -1
- package/package.json +7 -2
- package/src/conf/extrema.ts +1 -0
- package/src/conf/types.ts +14 -0
- package/src/conf/validate.ts +12 -1
- package/src/multipart-form-data/form-data-utils.ts +4 -4
- package/src/multipart-form-data/is-binary-file.ts +206 -0
- package/src/multipart-form-data/multipart-text-to-post-form-data.ts +107 -47
- package/src/parameters/extractions.ts +10 -8
- package/src/parameters/index.ts +4 -1
- package/src/request/index.ts +1 -0
- package/test/multipart-form-data/form-data-utils.spec.ts +28 -7
|
@@ -1,26 +1,25 @@
|
|
|
1
|
+
import { extension } from 'mime-types';
|
|
2
|
+
import { isBase64 } from 'validator';
|
|
3
|
+
|
|
4
|
+
import { HarParam } from '../har';
|
|
1
5
|
import { PostFormData, PostFormDataEntry } from '../request';
|
|
6
|
+
import { isBinaryFile } from './is-binary-file';
|
|
2
7
|
|
|
3
|
-
export const multipartFormDataTextToPostFormData = (multipartFormDataText: string): PostFormData =>
|
|
4
|
-
|
|
8
|
+
export const multipartFormDataTextToPostFormData = (multipartFormDataText: string = ''): PostFormData =>
|
|
9
|
+
getLines(multipartFormDataText)
|
|
10
|
+
.map(line => extractPostFormDataEntry(line));
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const getLines = (multipartFormDataText: string) => {
|
|
15
|
-
const boundary = getBoundary(multipartFormDataText);
|
|
16
|
-
const boundaryRegex = splitByBoundaryRegex(boundary);
|
|
17
|
-
const linesSplittedByBoundary = multipartFormDataText.split(boundaryRegex);
|
|
18
|
-
return removeEmptyLines(linesSplittedByBoundary);
|
|
19
|
-
};
|
|
12
|
+
const getLines = (multipartFormDataText: string = ''): string[] =>
|
|
13
|
+
removeEmptyLines(
|
|
14
|
+
multipartFormDataText.split(
|
|
15
|
+
splitByBoundaryRegex(
|
|
16
|
+
getBoundary(multipartFormDataText)
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
20
|
|
|
21
|
-
const getBoundary = (multipartFormDataText: string): string =>
|
|
22
|
-
|
|
23
|
-
};
|
|
21
|
+
const getBoundary = (multipartFormDataText: string): string =>
|
|
22
|
+
multipartFormDataText.substring(0, multipartFormDataText.indexOf('\r\n'));
|
|
24
23
|
|
|
25
24
|
const splitByBoundaryRegex = (boundary: string) => {
|
|
26
25
|
const normalBoundary = boundary + '\r\n';
|
|
@@ -28,7 +27,7 @@ const splitByBoundaryRegex = (boundary: string) => {
|
|
|
28
27
|
return new RegExp(`${normalBoundary}|${lastBoundary}`);
|
|
29
28
|
};
|
|
30
29
|
|
|
31
|
-
const removeEmptyLines = (lines: string[]): string[] => lines.filter(Boolean);
|
|
30
|
+
const removeEmptyLines = (lines: string[] = []): string[] => lines.filter(Boolean);
|
|
32
31
|
|
|
33
32
|
const extractPostFormDataEntry = (line: string): PostFormDataEntry => {
|
|
34
33
|
const indexOfFirst2Newlines = line.indexOf('\r\n\r\n');
|
|
@@ -38,52 +37,113 @@ const extractPostFormDataEntry = (line: string): PostFormDataEntry => {
|
|
|
38
37
|
|
|
39
38
|
const postFormDataEntry: PostFormDataEntry = { name: fields.name, value };
|
|
40
39
|
fields.fileName && (postFormDataEntry.fileName = fields.fileName);
|
|
40
|
+
fields.contentType && (postFormDataEntry.contentType = fields.contentType);
|
|
41
41
|
|
|
42
42
|
return postFormDataEntry;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
type Fields =
|
|
45
|
+
type Fields = HarParam;
|
|
46
46
|
|
|
47
47
|
const extractFields = (line: string, indexOfFirst2Newlines: number): Fields => {
|
|
48
48
|
const rawFields = extractRawFields(line, indexOfFirst2Newlines);
|
|
49
49
|
const res: Fields = { name: '' };
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const [k, v] = token.split('=');
|
|
54
|
-
if (k === 'name') {
|
|
55
|
-
res.name = removeDoubleQuotes(v);
|
|
56
|
-
} else if (k === 'filename') {
|
|
57
|
-
res.fileName = removeDoubleQuotes(v);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
50
|
+
const fieldsLines = rawFields.split('\r\n');
|
|
51
|
+
addNameFields(fieldsLines[0], res);
|
|
52
|
+
fieldsLines.length > 1 && addContentType(fieldsLines[1], res);
|
|
60
53
|
return res;
|
|
61
54
|
};
|
|
62
55
|
|
|
63
|
-
const extractRawFields = (line: string, indexOfFieldsValueSeperator: number): string =>
|
|
64
|
-
|
|
56
|
+
const extractRawFields = (line: string, indexOfFieldsValueSeperator: number): string =>
|
|
57
|
+
line.substring(0, indexOfFieldsValueSeperator);
|
|
58
|
+
|
|
59
|
+
const removeDoubleQuotes = (v: string): string =>
|
|
60
|
+
v.replace(/"/g, '');
|
|
61
|
+
|
|
62
|
+
const calculateValue = ({ fileName, contentType }: Fields, line: string, indexOfFirst2Newlines: number): string | '' => {
|
|
63
|
+
const value = extractValue(line, indexOfFirst2Newlines);
|
|
64
|
+
return getBinaryOrTextValue({ value, fileName, contentType });
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const getBinaryOrTextValue = (fields: Omit<Fields, 'name'>): string => {
|
|
68
|
+
const { value = '', fileName } = fields;
|
|
69
|
+
if (fileName) {
|
|
70
|
+
const isBass64 = isBase64(value);
|
|
71
|
+
const toBinary = isToBinary(fields);
|
|
72
|
+
if (isBass64 || toBinary) {
|
|
73
|
+
return myAtob(value, isBass64, toBinary);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
65
77
|
};
|
|
66
78
|
|
|
67
|
-
const
|
|
68
|
-
|
|
79
|
+
const myAtob = (target = '', fromBase64?: boolean, toBinary?: boolean) => {
|
|
80
|
+
const defaultEncoding = 'utf8';
|
|
81
|
+
const encodingFrom = fromBase64 ? 'base64' : defaultEncoding;
|
|
82
|
+
const encodingTo = toBinary ? 'binary' : defaultEncoding;
|
|
83
|
+
return Buffer.from(target, encodingFrom).toString(encodingTo);
|
|
69
84
|
};
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
const isToBinary = (fields: Omit<Fields, 'name'>): boolean => {
|
|
87
|
+
const { value = '', fileName, contentType } = fields;
|
|
88
|
+
if (isAlreadyBinary(value)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (fileName) {
|
|
92
|
+
const ext = getExtension(fileName);
|
|
93
|
+
if (ext) {
|
|
94
|
+
return isBinaryFileExtension(ext);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (contentType) {
|
|
98
|
+
return isBinaryContentType(contentType);
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
};
|
|
74
102
|
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
103
|
+
const isAlreadyBinary = (target = '') => {
|
|
104
|
+
const bytes = Buffer.from(target);
|
|
105
|
+
const size = target.length;
|
|
106
|
+
return isBinaryFile(bytes, size);
|
|
78
107
|
};
|
|
79
108
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
const getExtension = (fileName: string): string | undefined =>
|
|
110
|
+
fileName.split('.').pop();
|
|
111
|
+
|
|
112
|
+
const isBinaryFileExtension = (ext: string): boolean =>
|
|
113
|
+
['docx', 'pdf', 'doc', 'jpg', 'jpeg', 'png'].includes(ext);
|
|
114
|
+
|
|
115
|
+
const isBinaryContentType = (contentType: string): boolean =>
|
|
116
|
+
['docx', 'pdf', 'bin'].includes(extension(contentType) || '');
|
|
117
|
+
|
|
118
|
+
const extractValue = (line: string, indexOfFieldsValueSeperator: number): string =>
|
|
119
|
+
removeEndingNewline(
|
|
120
|
+
line.substring(indexOfFieldsValueSeperator + 4)
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const removeEndingNewline = (s: string): string =>
|
|
124
|
+
s.endsWith('\r\n') ? removeLast2Chars(s) : s;
|
|
125
|
+
|
|
126
|
+
const removeLast2Chars = (s: string): string =>
|
|
127
|
+
s.substring(0, s.length - 2);
|
|
128
|
+
|
|
129
|
+
const addNameFields = (nameFieldsLine: string, res: HarParam) => {
|
|
130
|
+
const tokens = extractTokens(nameFieldsLine);
|
|
131
|
+
for (const token of tokens) {
|
|
132
|
+
const [k, v] = token.split('=');
|
|
133
|
+
if (k === 'name') {
|
|
134
|
+
res.name = removeDoubleQuotes(v);
|
|
135
|
+
} else if (k === 'filename') {
|
|
136
|
+
res.fileName = removeDoubleQuotes(v);
|
|
137
|
+
}
|
|
83
138
|
}
|
|
84
|
-
return s;
|
|
85
139
|
};
|
|
86
140
|
|
|
87
|
-
const
|
|
88
|
-
|
|
141
|
+
const extractTokens = (nameFieldsLine: string): string[] =>
|
|
142
|
+
nameFieldsLine.split(';').map(t => t.trim());
|
|
143
|
+
|
|
144
|
+
const addContentType = (contentTypeLine: string, res: HarParam) => {
|
|
145
|
+
const [k, v] = contentTypeLine.split(': ');
|
|
146
|
+
if (k === 'content-type') {
|
|
147
|
+
res.contentType = v;
|
|
148
|
+
}
|
|
89
149
|
};
|
|
@@ -17,14 +17,7 @@ const { getParameterName } = parameterUtils;
|
|
|
17
17
|
* @param ext The extraction object to take a part
|
|
18
18
|
* @returns The spreaded parts of the given extraction object
|
|
19
19
|
*/
|
|
20
|
-
export function getExtractionParts(ext: Extractions): {
|
|
21
|
-
name: string,
|
|
22
|
-
value: string | {
|
|
23
|
-
query: string;
|
|
24
|
-
attr?: string;
|
|
25
|
-
}
|
|
26
|
-
type?: string,
|
|
27
|
-
} {
|
|
20
|
+
export function getExtractionParts(ext: Extractions): ExtractionParts {
|
|
28
21
|
const name = getParameterName(ext);
|
|
29
22
|
const extContent = ext[name];
|
|
30
23
|
if (typeof extContent === 'string') {
|
|
@@ -35,6 +28,15 @@ export function getExtractionParts(ext: Extractions): {
|
|
|
35
28
|
return { name, value, type };
|
|
36
29
|
}
|
|
37
30
|
|
|
31
|
+
export type ExtractionParts = {
|
|
32
|
+
name: string;
|
|
33
|
+
value: string | {
|
|
34
|
+
query: string;
|
|
35
|
+
attr?: string;
|
|
36
|
+
};
|
|
37
|
+
type?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
38
40
|
export function requestsExtractionsArr(requests, toIndex: number) {
|
|
39
41
|
const extractionParamNames = [] as string[];
|
|
40
42
|
for (let i = 0; i <= toIndex; i++) {
|
package/src/parameters/index.ts
CHANGED
|
@@ -177,6 +177,9 @@ export const parameterUtils = {
|
|
|
177
177
|
},
|
|
178
178
|
|
|
179
179
|
findIndex(key, arr) {
|
|
180
|
+
if (!arr) {
|
|
181
|
+
return -1;
|
|
182
|
+
}
|
|
180
183
|
return arr.findIndex((obj) => {
|
|
181
184
|
return Object.keys(obj)[0] === key;
|
|
182
185
|
});
|
|
@@ -244,7 +247,7 @@ export const parameterUtils = {
|
|
|
244
247
|
return this.getUsedConfParams({ requests: [request] }, parameters);
|
|
245
248
|
},
|
|
246
249
|
|
|
247
|
-
getValueByKeyFromArr(key: string, parameters: Parameters[], returnArray: boolean = false) {
|
|
250
|
+
getValueByKeyFromArr(key: string, parameters: Parameters[] | undefined = [], returnArray: boolean = false) {
|
|
248
251
|
let res;
|
|
249
252
|
const param = getParameterByKey(key, parameters);
|
|
250
253
|
if (param) {
|
package/src/request/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
const fs = require('fs');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
import { suite, describe, it } from 'mocha';
|
|
@@ -34,7 +35,6 @@ suite('form-data-utils - multipart form data', () => {
|
|
|
34
35
|
describe('Form Class - manages and manipulates multipart form data', () => {
|
|
35
36
|
it('to string', () => {
|
|
36
37
|
const boundary = '------WebKitFormBoundary12345678ASDFGHJ';
|
|
37
|
-
// eslint-disable-next-line max-len
|
|
38
38
|
const expectedText = '--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="the_name_of_the_thing"; filename="some file name.ext"\r\ncontent-type: application/vnd.mspowerpoint-sniffing.x-rar-compressed\r\n\r\nthe_contents_of_the_file\r\n--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="entityId"\r\n\r\n123456789123456789123456789\r\n--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="shouldDoStuff"\r\n\r\ntrue\r\n--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="content-transfer-encoding"\r\n\r\nquoted-printable\r\n--------WebKitFormBoundary12345678ASDFGHJ--\r\n';
|
|
39
39
|
const form = new Form(params, boundary);
|
|
40
40
|
const result = form.toString();
|
|
@@ -49,7 +49,7 @@ suite('form-data-utils - multipart form data', () => {
|
|
|
49
49
|
postFormData: [
|
|
50
50
|
{
|
|
51
51
|
name: 'the_name_of_the_thing',
|
|
52
|
-
value: '',
|
|
52
|
+
value: 'the_contents_of_the_file',
|
|
53
53
|
fileName: 'some file name.ext',
|
|
54
54
|
},
|
|
55
55
|
{
|
|
@@ -75,7 +75,7 @@ suite('form-data-utils - multipart form data', () => {
|
|
|
75
75
|
postFormData: [
|
|
76
76
|
{
|
|
77
77
|
name: 'file',
|
|
78
|
-
value: '',
|
|
78
|
+
value: 'ÿØÿà\u0000\u0010JFIF\u0000\u0001\u0001\u0000\u0000\u0001\u0000\u0001\u0000\u0000ÿÛ\u0000\u0000\u0005\u0003\u0004\t\t\b\t\t\t\t\t\t\t\t\t\u0005u001c\u0017\t\b\u001a\t\t\u0006\u0018!\u0018\u001a\u001d\u001d\u001f\u001f\u001f\u0007\u000b"\u0018"\u001e\u0018\u001c\u001e\u001f\u001e\u0001\u0005\u0005\u0005\b\u0007\b\u000e\b\b\r\u0012\r\u000e\r\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012R#B?\u00005Ù',
|
|
79
79
|
fileName: 'silk_sonic.jpeg',
|
|
80
80
|
},
|
|
81
81
|
]
|
|
@@ -98,24 +98,45 @@ suite('form-data-utils - multipart form data', () => {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
it('should return 2 name-values', () => {
|
|
101
|
-
// eslint-disable-next-line max-len
|
|
102
101
|
const text = '----10453916712\r\nContent-Disposition: form-data; name="arnon"\r\n\r\npeleg\r\n----10453916712\r\nContent-Disposition: form-data; name="rivi"\r\n\r\nshimi\r\n----10453916712--\r\n';
|
|
103
102
|
const result = multipartFormDataTextToPostFormData(text);
|
|
104
103
|
expect(result).to.have.deep.ordered.members([{ name: 'arnon', value: 'peleg' }, { name: 'rivi', value: 'shimi' }]);
|
|
105
104
|
});
|
|
106
105
|
|
|
107
106
|
it('should return name-value-filename', () => {
|
|
108
|
-
// eslint-disable-next-line max-len
|
|
109
107
|
const text = '-----------------------------10453916712809342873442003634\r\nContent-Disposition: form-data; name="file"; filename="silk_sonic.jpeg"\r\nContent-Type: image/jpeg\r\n\r\nXCXCXCXCXCXCXCXCXCXCXCXCXCXC\t\r\n\t \n\n\nXXC101010101010101101010101010\r\n-----------------------------10453916712809342873442003634--\r\n';
|
|
110
108
|
const result = multipartFormDataTextToPostFormData(text);
|
|
111
109
|
expect(result).to.have.deep.ordered.members([
|
|
112
110
|
{
|
|
113
111
|
name: 'file',
|
|
114
|
-
value: '',
|
|
112
|
+
value: 'XCXCXCXCXCXCXCXCXCXCXCXCXCXC\t\r\n\t \n\n\nXXC101010101010101101010101010',
|
|
115
113
|
fileName: 'silk_sonic.jpeg'
|
|
116
114
|
}
|
|
117
115
|
]);
|
|
118
116
|
});
|
|
119
|
-
});
|
|
120
117
|
|
|
118
|
+
it('should return also the value of a simple file', () => {
|
|
119
|
+
const text = '------WebKitFormBoundaryntAaM2MokVD4YVOO\r\nContent-Disposition: form-data; name="file"; filename="Cars.csv"\r\nContent-Type: text/csv\r\n\r\nID,First_Name,Last_Name,Car_Make,Car_Model,Car_Color,Car_Model Year,Car_VIN\n1,Lenora,Hegdonne,Lincoln,Town Car,Blue,1994,WAUUL98E68A703943\n2,Jermaine,Bradane,Ford,GT,Pink,2005,1N6AA0EC1FN193806\r\n------WebKitFormBoundaryntAaM2MokVD4YVOO--\r\n';
|
|
120
|
+
const result = multipartFormDataTextToPostFormData(text);
|
|
121
|
+
expect(result).to.have.deep.ordered.members([
|
|
122
|
+
{
|
|
123
|
+
name: 'file',
|
|
124
|
+
value: 'ID,First_Name,Last_Name,Car_Make,Car_Model,Car_Color,Car_Model Year,Car_VIN\n1,Lenora,Hegdonne,Lincoln,Town Car,Blue,1994,WAUUL98E68A703943\n2,Jermaine,Bradane,Ford,GT,Pink,2005,1N6AA0EC1FN193806',
|
|
125
|
+
fileName: 'Cars.csv'
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
it('should detect content-type', () => {
|
|
130
|
+
const text = '----------------------------148649485734387205386089\r\nContent-Disposition: form-data; name="file"; filename="Bell data engineer.docx"\r\ncontent-type: application/vnd.openxmlformats-officedocument.wordprocessingml.document\r\n\r\nUEsDBBQABgAIAAAAIQCTkoLJhQEAACkHAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo\r\n----------------------------148649485734387205386089--\r\n';
|
|
131
|
+
const result = multipartFormDataTextToPostFormData(text);
|
|
132
|
+
expect(result).to.have.deep.ordered.members([
|
|
133
|
+
{
|
|
134
|
+
name: 'file',
|
|
135
|
+
value: 'PK\u0003\u0004\u0014\u0000\u0006\u0000\b\u0000\u0000\u0000!\u0000É
\u0001\u0000\u0000)\u0007\u0000\u0000\u0013\u0000\b\u0002[Content_Types].xml ¢\u0004\u0002(',
|
|
136
|
+
fileName: 'Bell data engineer.docx',
|
|
137
|
+
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
138
|
+
}
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
121
142
|
});
|