@loadmill/core 0.3.38 → 0.3.42
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/types.d.ts +5 -0
- package/dist/conf/types.js +6 -1
- package/dist/conf/types.js.map +1 -1
- package/dist/har/index.d.ts +8 -1
- package/dist/har/index.js.map +1 -1
- package/dist/multipart-form-data/form-data-utils.d.ts +19 -0
- package/dist/multipart-form-data/form-data-utils.js +116 -0
- package/dist/multipart-form-data/form-data-utils.js.map +1 -0
- package/dist/multipart-form-data/multipart-text-to-post-form-data.d.ts +2 -0
- package/dist/multipart-form-data/multipart-text-to-post-form-data.js +77 -0
- package/dist/multipart-form-data/multipart-text-to-post-form-data.js.map +1 -0
- package/dist/parameters/index.d.ts +5 -1
- package/dist/parameters/index.js +41 -53
- package/dist/parameters/index.js.map +1 -1
- package/dist/parameters/parameter-functions/crypto.d.ts +10 -0
- package/dist/parameters/parameter-functions/crypto.js +56 -0
- package/dist/parameters/parameter-functions/crypto.js.map +1 -0
- package/dist/parameters/parameter-functions/textual-parameter-functions.d.ts +4 -0
- package/dist/parameters/parameter-functions/textual-parameter-functions.js +24 -5
- package/dist/parameters/parameter-functions/textual-parameter-functions.js.map +1 -1
- package/dist/request/index.d.ts +12 -0
- package/dist/request/index.js +16 -2
- package/dist/request/index.js.map +1 -1
- package/package.json +9 -6
- package/src/conf/types.ts +6 -0
- package/src/har/index.ts +9 -1
- package/src/multipart-form-data/form-data-utils.ts +81 -0
- package/src/multipart-form-data/multipart-text-to-post-form-data.ts +89 -0
- package/src/parameters/index.ts +44 -56
- package/src/parameters/parameter-functions/crypto.ts +55 -0
- package/src/parameters/parameter-functions/textual-parameter-functions.ts +32 -7
- package/src/request/index.ts +20 -0
- package/test/har/is-har.spec.js +1 -0
- package/test/multipart-form-data/form-data-utils.spec.ts +121 -0
- package/test/multipart-form-data/resources/multipart-form-data-file-text-content.json +5 -0
- package/test/parameters/parameter-functions.spec.js +2 -0
- package/test/parameters/parameter-utils.spec.js +2 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const CryptoJS = require('crypto-js');
|
|
2
|
+
|
|
3
|
+
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
4
|
+
|
|
5
|
+
export const FORMATS = {
|
|
6
|
+
UTF8: 'utf-8',
|
|
7
|
+
HEX: 'hex'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const HASH = {
|
|
11
|
+
SHA1: 'SHA1',
|
|
12
|
+
SHA256: 'SHA256',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const toSha = (secret: string, alg: string, secretFormat: string) => {
|
|
16
|
+
try {
|
|
17
|
+
const hashFunction = toHashFunction(alg);
|
|
18
|
+
const hashed = hashFunction(toCryptoFormat(secret, secretFormat));
|
|
19
|
+
return hashed.toString(CryptoJS.enc.Hex);
|
|
20
|
+
} catch (_e) {
|
|
21
|
+
throw new PresentableError(`Can't digest ${secret}`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const toHMAC = (secret: string, alg: string, secretFormat: string, salt: string) => {
|
|
26
|
+
try {
|
|
27
|
+
const converted = toCryptoFormat(secret, secretFormat);
|
|
28
|
+
const hmaced = CryptoJS.algo.HMAC.create(CryptoJS.algo[(alg || HASH.SHA256)], converted);
|
|
29
|
+
salt && hmaced.update(salt);
|
|
30
|
+
const hashed = hmaced.finalize();
|
|
31
|
+
return hashed.toString(CryptoJS.enc.Hex);
|
|
32
|
+
} catch (_e) {
|
|
33
|
+
throw new PresentableError(`Can't digest ${secret}`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const toHashFunction = (type) => {
|
|
38
|
+
switch (type) {
|
|
39
|
+
case HASH.SHA1:
|
|
40
|
+
return CryptoJS.SHA1;
|
|
41
|
+
case HASH.SHA256:
|
|
42
|
+
default:
|
|
43
|
+
return CryptoJS.SHA256;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const toCryptoFormat = (from, base) => {
|
|
48
|
+
switch (base) {
|
|
49
|
+
case FORMATS.HEX:
|
|
50
|
+
return CryptoJS.enc.Hex.parse(from);
|
|
51
|
+
case FORMATS.UTF8:
|
|
52
|
+
default:
|
|
53
|
+
return CryptoJS.enc.Utf8.parse(from);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import defaultTo = require('lodash/defaultTo');
|
|
2
|
+
import isEmpty = require('lodash/isEmpty');
|
|
3
|
+
import isEqual = require('lodash/isEqual');
|
|
4
|
+
import uniqWith = require('lodash/uniqWith');
|
|
5
|
+
import escapeRegExp = require('lodash/escapeRegExp');
|
|
6
|
+
import isNaN = require('lodash/isNaN');
|
|
1
7
|
|
|
2
8
|
import { RegexExtractor } from '../extractors/regex-extractor';
|
|
3
9
|
import { CheerioExtractor } from '../extractors/cheerio-extractor';
|
|
@@ -9,21 +15,16 @@ import {
|
|
|
9
15
|
pickRandom,
|
|
10
16
|
RANDOM_SELECT_OPT,
|
|
11
17
|
} from '../value-utils';
|
|
12
|
-
import defaultTo = require('lodash/defaultTo');
|
|
13
|
-
import isEmpty = require('lodash/isEmpty');
|
|
14
|
-
import isEqual = require('lodash/isEqual');
|
|
15
|
-
import uniqWith = require('lodash/uniqWith');
|
|
16
|
-
import escapeRegExp = require('lodash/escapeRegExp');
|
|
17
|
-
import isNaN = require('lodash/isNaN');
|
|
18
18
|
import isUUID from 'validator/lib/isUUID';
|
|
19
19
|
import * as uriUtils from '@loadmill/universal/dist/uri-utils';
|
|
20
20
|
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
21
21
|
import { nodeBtoa } from '@loadmill/universal/dist/string-utils';
|
|
22
22
|
import { applyJSONSchema } from './json-schema';
|
|
23
|
+
import { toSha, toHMAC, FORMATS, HASH } from './crypto';
|
|
23
24
|
|
|
24
25
|
const MAX_NOW_LENGTH = 13; // max length UTC until 2286
|
|
25
26
|
|
|
26
|
-
const nowParameterFunction = new BasicParameterFunction((length) =>{
|
|
27
|
+
const nowParameterFunction = new BasicParameterFunction((length) => {
|
|
27
28
|
const now = Date.now();
|
|
28
29
|
if (length && +length > 0) {
|
|
29
30
|
return ('' + now).substring(0, Math.min(+length, MAX_NOW_LENGTH));
|
|
@@ -205,6 +206,30 @@ export const textualParameterFunctions = {
|
|
|
205
206
|
1
|
|
206
207
|
),
|
|
207
208
|
|
|
209
|
+
__sha1: new BasicParameterFunction(
|
|
210
|
+
(secret, secretFormat = FORMATS.UTF8) => toSha(secret, HASH.SHA1, secretFormat),
|
|
211
|
+
1,
|
|
212
|
+
2
|
|
213
|
+
),
|
|
214
|
+
|
|
215
|
+
__sha256: new BasicParameterFunction(
|
|
216
|
+
(secret, secretFormat = FORMATS.UTF8) => toSha(secret, HASH.SHA256, secretFormat),
|
|
217
|
+
1,
|
|
218
|
+
2
|
|
219
|
+
),
|
|
220
|
+
|
|
221
|
+
__hmacSha1: new BasicParameterFunction(
|
|
222
|
+
(secret, secretFormat = FORMATS.UTF8, salt = '') => toHMAC(secret, HASH.SHA1, secretFormat, salt),
|
|
223
|
+
1,
|
|
224
|
+
3
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
__hmacSha256: new BasicParameterFunction(
|
|
228
|
+
(secret, secretFormat = FORMATS.UTF8, salt = '') => toHMAC(secret, HASH.SHA256, secretFormat, salt),
|
|
229
|
+
1,
|
|
230
|
+
3
|
|
231
|
+
),
|
|
232
|
+
|
|
208
233
|
__split_pick: new BasicParameterFunction(
|
|
209
234
|
(str: string, delim: string, selection = '0') =>
|
|
210
235
|
pick(selection, str.split(delim)),
|
package/src/request/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ export class LoadmillRequest implements RequestLike {
|
|
|
38
38
|
description?: string;
|
|
39
39
|
delay?: number | string;
|
|
40
40
|
postData?: RequestPostData;
|
|
41
|
+
postFormData?: PostFormData;
|
|
41
42
|
cachePenetration?: CachePenetration;
|
|
42
43
|
|
|
43
44
|
method: HttpMethod = 'GET';
|
|
@@ -77,6 +78,7 @@ export function createRequest(from: RequestLike): LoadmillRequest {
|
|
|
77
78
|
extract,
|
|
78
79
|
headers,
|
|
79
80
|
postData,
|
|
81
|
+
postFormData,
|
|
80
82
|
stopBefore,
|
|
81
83
|
skipBefore,
|
|
82
84
|
loop,
|
|
@@ -106,6 +108,10 @@ export function createRequest(from: RequestLike): LoadmillRequest {
|
|
|
106
108
|
request.postData = { text, mimeType };
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
if (postFormData) {
|
|
112
|
+
request.postFormData = postFormData;
|
|
113
|
+
}
|
|
114
|
+
|
|
109
115
|
if (delay !== null && delay !== undefined) {
|
|
110
116
|
request.delay = delay;
|
|
111
117
|
}
|
|
@@ -346,6 +352,7 @@ export interface RequestLike {
|
|
|
346
352
|
description?: string;
|
|
347
353
|
delay?: number | string;
|
|
348
354
|
postData?: RequestPostData;
|
|
355
|
+
postFormData?: PostFormData;
|
|
349
356
|
extract?: Extractions | Extractions[];
|
|
350
357
|
headers?: LoadmillHeaders | LoadmillHeaders[];
|
|
351
358
|
expectedStatus?: HttpResponseStatus;
|
|
@@ -370,6 +377,19 @@ export type RequestPostData = {
|
|
|
370
377
|
text: string;
|
|
371
378
|
};
|
|
372
379
|
|
|
380
|
+
export type PostFormData = PostFormDataEntry[];
|
|
381
|
+
|
|
382
|
+
export type PostFormDataEntry = {
|
|
383
|
+
name: string;
|
|
384
|
+
value: string;
|
|
385
|
+
fileName?: string;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export enum BodyTypes {
|
|
389
|
+
POST_DATA = 'postData',
|
|
390
|
+
FORM_DATA = 'postFormData',
|
|
391
|
+
}
|
|
392
|
+
|
|
373
393
|
export type LoadmillHeaders = { [headerName: string]: string };
|
|
374
394
|
|
|
375
395
|
export type Extractions = { [parameter: string]: Extraction };
|
package/test/har/is-har.spec.js
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
import { suite, describe, it } from 'mocha';
|
|
4
|
+
import { expect } from 'chai';
|
|
5
|
+
|
|
6
|
+
import { PostFormData } from '../../src/request/index';
|
|
7
|
+
import { Form, FormEntry, mapToMultipartFormData, multipartFormDataTextToPostFormData } from '../../src/multipart-form-data/form-data-utils';
|
|
8
|
+
const multipartPostDataWithText = JSON.parse(fs.readFileSync(path.join(__dirname, './resources/multipart-form-data-file-text-content.json')));
|
|
9
|
+
|
|
10
|
+
const { text } = multipartPostDataWithText;
|
|
11
|
+
|
|
12
|
+
const params: FormEntry[] = [
|
|
13
|
+
{
|
|
14
|
+
'name': 'the_name_of_the_thing',
|
|
15
|
+
'contentType': 'application/vnd.mspowerpoint-sniffing.x-rar-compressed',
|
|
16
|
+
'fileName': 'some file name.ext',
|
|
17
|
+
'value': 'the_contents_of_the_file'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
'name': 'entityId',
|
|
21
|
+
'value': '123456789123456789123456789'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
'name': 'shouldDoStuff',
|
|
25
|
+
'value': 'true'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
'name': 'content-transfer-encoding',
|
|
29
|
+
'value': 'quoted-printable'
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
suite('form-data-utils - multipart form data', () => {
|
|
34
|
+
describe('Form Class - manages and manipulates multipart form data', () => {
|
|
35
|
+
it('to string', () => {
|
|
36
|
+
const boundary = '------WebKitFormBoundary12345678ASDFGHJ';
|
|
37
|
+
// eslint-disable-next-line max-len
|
|
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
|
+
const form = new Form(params, boundary);
|
|
40
|
+
const result = form.toString();
|
|
41
|
+
expect(result).equals(expectedText);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('mapToMultipartFormData', () => {
|
|
46
|
+
it ('should convert HAR parameters to PostFormData', () => {
|
|
47
|
+
const requestUnderTest = { postData: { params } } as { postData?: { params?, text?, mimeType? }, postFormData?: PostFormData };
|
|
48
|
+
const expectedResult = {
|
|
49
|
+
postFormData: [
|
|
50
|
+
{
|
|
51
|
+
name: 'the_name_of_the_thing',
|
|
52
|
+
value: '',
|
|
53
|
+
fileName: 'some file name.ext',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'entityId',
|
|
57
|
+
value: '123456789123456789123456789'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'shouldDoStuff',
|
|
61
|
+
value: 'true'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'content-transfer-encoding',
|
|
65
|
+
value: 'quoted-printable'
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
mapToMultipartFormData(requestUnderTest); // mutate request under test
|
|
70
|
+
expect(requestUnderTest.postFormData).to.have.deep.ordered.members(expectedResult.postFormData);
|
|
71
|
+
});
|
|
72
|
+
it ('should convert HAR multipart/form-data body TEXT to PostFormData', () => {
|
|
73
|
+
const requestUnderTest = { postData: { text } } as { postData?: { params?, text?, mimeType? }, postFormData?: PostFormData };
|
|
74
|
+
const expectedResult = {
|
|
75
|
+
postFormData: [
|
|
76
|
+
{
|
|
77
|
+
name: 'file',
|
|
78
|
+
value: '',
|
|
79
|
+
fileName: 'silk_sonic.jpeg',
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
mapToMultipartFormData(requestUnderTest); // mutate request under test
|
|
84
|
+
expect(requestUnderTest.postFormData).to.have.deep.ordered.members(expectedResult.postFormData);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('multipartFormDataTextToPostFormData', () => {
|
|
89
|
+
it('should return empty array when text is empty', () => {
|
|
90
|
+
const result = multipartFormDataTextToPostFormData('');
|
|
91
|
+
expect(result).to.have.deep.ordered.members([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return name-value', () => {
|
|
95
|
+
const text = '----10453916712809342873442003634\r\nContent-Disposition: form-data; name="arnon"\r\n\r\npeleg\r\n----10453916712809342873442003634--\r\n';
|
|
96
|
+
const result = multipartFormDataTextToPostFormData(text);
|
|
97
|
+
expect(result).to.have.deep.ordered.members([{ name: 'arnon', value: 'peleg' }]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should return 2 name-values', () => {
|
|
101
|
+
// eslint-disable-next-line max-len
|
|
102
|
+
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
|
+
const result = multipartFormDataTextToPostFormData(text);
|
|
104
|
+
expect(result).to.have.deep.ordered.members([{ name: 'arnon', value: 'peleg' }, { name: 'rivi', value: 'shimi' }]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return name-value-filename', () => {
|
|
108
|
+
// eslint-disable-next-line max-len
|
|
109
|
+
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
|
+
const result = multipartFormDataTextToPostFormData(text);
|
|
111
|
+
expect(result).to.have.deep.ordered.members([
|
|
112
|
+
{
|
|
113
|
+
name: 'file',
|
|
114
|
+
value: '',
|
|
115
|
+
fileName: 'silk_sonic.jpeg'
|
|
116
|
+
}
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mimeType": "multipart/form-data; boundary=---------------------------10453916712809342873442003634",
|
|
3
|
+
"params": [],
|
|
4
|
+
"text": "-----------------------------10453916712809342873442003634\r\nContent-Disposition: form-data; name=\"file\"; filename=\"silk_sonic.jpeg\"\r\nContent-Type: image/jpeg\r\n\r\nÿØÿà\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Ù\r\n-----------------------------10453916712809342873442003634--\r\n"
|
|
5
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { expect } = require('chai');
|
|
2
|
+
const { describe, it } = require('mocha');
|
|
2
3
|
|
|
3
4
|
const { parameterUtils, ParameterError } = require('../../dist/parameters');
|
|
4
5
|
const _ = require('lodash');
|
|
@@ -180,4 +181,5 @@ describe('parameter utils', () => {
|
|
|
180
181
|
expect(res).to.have.ordered.members([]);
|
|
181
182
|
});
|
|
182
183
|
});
|
|
184
|
+
|
|
183
185
|
});
|