@contrast/assess 1.30.0 → 1.32.0

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.
Files changed (156) hide show
  1. package/lib/crypto-analysis/install/crypto.test.js +146 -0
  2. package/lib/crypto-analysis/install/math.test.js +65 -0
  3. package/lib/dataflow/index.test.js +36 -0
  4. package/lib/dataflow/propagation/index.test.js +103 -0
  5. package/lib/dataflow/propagation/install/JSON/index.test.js +50 -0
  6. package/lib/dataflow/propagation/install/JSON/parse-fn.test.js +232 -0
  7. package/lib/dataflow/propagation/install/JSON/parse.test.js +968 -0
  8. package/lib/dataflow/propagation/install/JSON/stringify.test.js +265 -0
  9. package/lib/dataflow/propagation/install/array-prototype-join.test.js +106 -0
  10. package/lib/dataflow/propagation/install/buffer.test.js +109 -0
  11. package/lib/dataflow/propagation/install/contrast-methods/add.test.js +94 -0
  12. package/lib/dataflow/propagation/install/contrast-methods/index.test.js +49 -0
  13. package/lib/dataflow/propagation/install/contrast-methods/number.test.js +50 -0
  14. package/lib/dataflow/propagation/install/contrast-methods/string.test.js +148 -0
  15. package/lib/dataflow/propagation/install/contrast-methods/tag.test.js +145 -0
  16. package/lib/dataflow/propagation/install/decode-uri-component.test.js +78 -0
  17. package/lib/dataflow/propagation/install/ejs/escape-xml.test.js +69 -0
  18. package/lib/dataflow/propagation/install/ejs/template.test.js +62 -0
  19. package/lib/dataflow/propagation/install/encode-uri.test.js +83 -0
  20. package/lib/dataflow/propagation/install/escape-html.test.js +71 -0
  21. package/lib/dataflow/propagation/install/escape.test.js +73 -0
  22. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.test.js +71 -0
  23. package/lib/dataflow/propagation/install/isnumeric-0.test.js +58 -0
  24. package/lib/dataflow/propagation/install/joi/any.test.js +270 -0
  25. package/lib/dataflow/propagation/install/joi/array.test.js +912 -0
  26. package/lib/dataflow/propagation/install/joi/boolean.test.js +103 -0
  27. package/lib/dataflow/propagation/install/joi/expression.test.js +76 -0
  28. package/lib/dataflow/propagation/install/joi/index.test.js +39 -0
  29. package/lib/dataflow/propagation/install/joi/number.test.js +103 -0
  30. package/lib/dataflow/propagation/install/joi/object.test.js +119 -0
  31. package/lib/dataflow/propagation/install/joi/ref.test.js +607 -0
  32. package/lib/dataflow/propagation/install/joi/string-schema.test.js +513 -0
  33. package/lib/dataflow/propagation/install/mongoose/index.test.js +42 -0
  34. package/lib/dataflow/propagation/install/mongoose/schema-map.test.js +348 -0
  35. package/lib/dataflow/propagation/install/mongoose/schema-mixed.test.js +512 -0
  36. package/lib/dataflow/propagation/install/mongoose/schema-string.test.js +160 -0
  37. package/lib/dataflow/propagation/install/mustache-escape.test.js +62 -0
  38. package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +74 -0
  39. package/lib/dataflow/propagation/install/parse-int.test.js +48 -0
  40. package/lib/dataflow/propagation/install/path/basename.test.js +143 -0
  41. package/lib/dataflow/propagation/install/path/dirname.test.js +167 -0
  42. package/lib/dataflow/propagation/install/path/extname.test.js +141 -0
  43. package/lib/dataflow/propagation/install/path/format.test.js +250 -0
  44. package/lib/dataflow/propagation/install/path/index.test.js +45 -0
  45. package/lib/dataflow/propagation/install/path/join-and-resolve.test.js +485 -0
  46. package/lib/dataflow/propagation/install/path/normalize.test.js +176 -0
  47. package/lib/dataflow/propagation/install/path/parse.test.js +238 -0
  48. package/lib/dataflow/propagation/install/path/relative.test.js +239 -0
  49. package/lib/dataflow/propagation/install/path/toNamespacedPath.test.js +158 -0
  50. package/lib/dataflow/propagation/install/pug/index.test.js +55 -0
  51. package/lib/dataflow/propagation/install/pug-runtime-escape.test.js +69 -0
  52. package/lib/dataflow/propagation/install/querystring/escape.test.js +63 -0
  53. package/lib/dataflow/propagation/install/querystring/index.test.js +40 -0
  54. package/lib/dataflow/propagation/install/querystring/parse.test.js +272 -0
  55. package/lib/dataflow/propagation/install/querystring/stringify.test.js +301 -0
  56. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.test.js +281 -0
  57. package/lib/dataflow/propagation/install/send.test.js +63 -0
  58. package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +73 -0
  59. package/lib/dataflow/propagation/install/sequelize/sql-string.test.js +130 -0
  60. package/lib/dataflow/propagation/install/sql-template-strings.test.js +100 -0
  61. package/lib/dataflow/propagation/install/string/concat.test.js +132 -0
  62. package/lib/dataflow/propagation/install/string/format-methods.test.js +61 -0
  63. package/lib/dataflow/propagation/install/string/html-methods.test.js +164 -0
  64. package/lib/dataflow/propagation/install/string/index.test.js +103 -0
  65. package/lib/dataflow/propagation/install/string/match-all.test.js +399 -0
  66. package/lib/dataflow/propagation/install/string/match.test.js +361 -0
  67. package/lib/dataflow/propagation/install/string/replace.test.js +588 -0
  68. package/lib/dataflow/propagation/install/string/slice.test.js +265 -0
  69. package/lib/dataflow/propagation/install/string/split.test.js +500 -0
  70. package/lib/dataflow/propagation/install/string/substring.test.js +238 -0
  71. package/lib/dataflow/propagation/install/string/trim.test.js +122 -0
  72. package/lib/dataflow/propagation/install/unescape.test.js +78 -0
  73. package/lib/dataflow/propagation/install/url/domain-parsers.test.js +63 -0
  74. package/lib/dataflow/propagation/install/url/parse.test.js +391 -0
  75. package/lib/dataflow/propagation/install/url/searchParams.test.js +538 -0
  76. package/lib/dataflow/propagation/install/url/url.test.js +466 -0
  77. package/lib/dataflow/propagation/install/util-format.test.js +336 -0
  78. package/lib/dataflow/propagation/install/validator/hooks.test.js +211 -0
  79. package/lib/dataflow/sinks/index.test.js +78 -0
  80. package/lib/dataflow/sinks/install/child-process.test.js +338 -0
  81. package/lib/dataflow/sinks/install/eval.test.js +95 -0
  82. package/lib/dataflow/sinks/install/express/index.test.js +33 -0
  83. package/lib/dataflow/sinks/install/express/reflected-xss.js +55 -57
  84. package/lib/dataflow/sinks/install/express/reflected-xss.test.js +109 -0
  85. package/lib/dataflow/sinks/install/express/unvalidated-redirect.test.js +144 -0
  86. package/lib/dataflow/sinks/install/fastify/index.test.js +32 -0
  87. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.test.js +130 -0
  88. package/lib/dataflow/sinks/install/fs.test.js +138 -0
  89. package/lib/dataflow/sinks/install/function.test.js +103 -0
  90. package/lib/dataflow/sinks/install/hapi/index.test.js +32 -0
  91. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.test.js +130 -0
  92. package/lib/dataflow/sinks/install/http/index.test.js +33 -0
  93. package/lib/dataflow/sinks/install/http/request.test.js +184 -0
  94. package/lib/dataflow/sinks/install/http/server-response.test.js +162 -0
  95. package/lib/dataflow/sinks/install/koa/index.test.js +32 -0
  96. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.test.js +200 -0
  97. package/lib/dataflow/sinks/install/libxmljs.test.js +158 -0
  98. package/lib/dataflow/sinks/install/marsdb.test.js +166 -0
  99. package/lib/dataflow/sinks/install/mongodb.test.js +621 -0
  100. package/lib/dataflow/sinks/install/mssql.test.js +136 -0
  101. package/lib/dataflow/sinks/install/mysql.test.js +233 -0
  102. package/lib/dataflow/sinks/install/node-serialize.test.js +85 -0
  103. package/lib/dataflow/sinks/install/postgres.test.js +158 -0
  104. package/lib/dataflow/sinks/install/restify.test.js +142 -0
  105. package/lib/dataflow/sinks/install/sequelize.test.js +100 -0
  106. package/lib/dataflow/sinks/install/sqlite3.test.js +118 -0
  107. package/lib/dataflow/sinks/install/vm.test.js +326 -0
  108. package/lib/dataflow/sources/handler.test.js +463 -0
  109. package/lib/dataflow/sources/index.test.js +58 -0
  110. package/lib/dataflow/sources/install/body-parser1.test.js +248 -0
  111. package/lib/dataflow/sources/install/busboy.test.js +152 -0
  112. package/lib/dataflow/sources/install/cookie-parser1.test.js +143 -0
  113. package/lib/dataflow/sources/install/express/params.test.js +105 -0
  114. package/lib/dataflow/sources/install/express/parsedUrl.test.js +65 -0
  115. package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
  116. package/lib/dataflow/sources/install/fastify/fastify.test.js +210 -0
  117. package/lib/dataflow/sources/install/fastify/index.test.js +33 -0
  118. package/lib/dataflow/sources/install/formidable1.test.js +119 -0
  119. package/lib/dataflow/sources/install/hapi/hapi.test.js +172 -0
  120. package/lib/dataflow/sources/install/hapi/index.test.js +33 -0
  121. package/lib/dataflow/sources/install/http.test.js +155 -0
  122. package/lib/dataflow/sources/install/koa/index.test.js +40 -0
  123. package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +161 -0
  124. package/lib/dataflow/sources/install/koa/koa-multer.test.js +197 -0
  125. package/lib/dataflow/sources/install/koa/koa-routers.test.js +146 -0
  126. package/lib/dataflow/sources/install/koa/koa2.test.js +145 -0
  127. package/lib/dataflow/sources/install/multer1.test.js +145 -0
  128. package/lib/dataflow/sources/install/qs6.test.js +131 -0
  129. package/lib/dataflow/sources/install/querystring.test.js +82 -0
  130. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +88 -0
  131. package/lib/dataflow/sources/install/restify/index.test.js +38 -0
  132. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +144 -0
  133. package/lib/dataflow/sources/install/restify/router.test.js +83 -0
  134. package/lib/dataflow/tag-utils-complete.test.js +27 -0
  135. package/lib/dataflow/tag-utils.test.js +192 -0
  136. package/lib/dataflow/tracker.test.js +216 -0
  137. package/lib/dataflow/utils/is-safe-content-type.test.js +16 -0
  138. package/lib/dataflow/utils/is-vulnerable.test.js +115 -0
  139. package/lib/event-factory.test.js +321 -0
  140. package/lib/get-policy.test.js +194 -0
  141. package/lib/get-source-context.test.js +108 -0
  142. package/lib/index.test.js +41 -0
  143. package/lib/make-source-context.test.js +50 -0
  144. package/lib/response-scanning/handlers/index.test.js +425 -0
  145. package/lib/response-scanning/handlers/utils.test.js +391 -0
  146. package/lib/response-scanning/index.test.js +41 -0
  147. package/lib/response-scanning/install/http.test.js +175 -0
  148. package/lib/rule-scopes.test.js +27 -0
  149. package/lib/session-configuration/handlers.test.js +84 -0
  150. package/lib/session-configuration/index.test.js +36 -0
  151. package/lib/session-configuration/install/express-session.test.js +220 -0
  152. package/lib/session-configuration/install/fastify-cookie.test.js +65 -0
  153. package/lib/session-configuration/install/hapi.test.js +269 -0
  154. package/lib/session-configuration/install/koa.js +50 -44
  155. package/lib/session-configuration/install/koa.test.js +92 -0
  156. package/package.json +2 -2
@@ -0,0 +1,588 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const { inspect } = require('util');
5
+ const { initAssessFixture } = require('@contrast/test/fixtures');
6
+
7
+ describe('assess dataflow propagation string replace', function () {
8
+ let core, simulateRequestScope, trackString, tracker;
9
+
10
+ beforeEach(function () {
11
+ ({
12
+ core,
13
+ trackString,
14
+ simulateRequestScope
15
+ } = initAssessFixture());
16
+
17
+ tracker = core.assess.dataflow.tracker;
18
+
19
+ core.assess.dataflow.propagation.stringInstrumentation.substring.install();
20
+ core.assess.dataflow.propagation.stringInstrumentation.replace.install();
21
+ });
22
+
23
+ afterEach(function () {
24
+ core.assess.dataflow.propagation.stringInstrumentation.replace.uninstall();
25
+ });
26
+
27
+ ['string', 'function'].forEach((replacerType) => {
28
+ function getReplacement(replacerType, replacement) {
29
+ return replacerType === 'function' ? () => replacement : replacement;
30
+ }
31
+ describe(`string-arg custom ${replacerType} replacer`, function () {
32
+ it('untracked string replace with untracked string', function () {
33
+ simulateRequestScope(() => {
34
+ const val = '?bcd';
35
+ const ret = val.replace('?', getReplacement(replacerType, 'a'));
36
+ expect(ret).to.equal('abcd');
37
+ expect(tracker.getData(ret)).to.be.null;
38
+ });
39
+ });
40
+
41
+ [
42
+ [],
43
+ [undefined],
44
+ [null]
45
+ ].forEach((rest) => {
46
+ it(`full replacement with nullish value: ${inspect(rest[0])}`, function () {
47
+ simulateRequestScope(() => {
48
+ const val = 'abcd';
49
+ const extern = trackString(val);
50
+ const ret = extern.replace(val, getReplacement(replacerType, rest[0]));
51
+ expect(ret).to.equal(String(rest[0]));
52
+ expect(tracker.getData(ret)).to.be.null;
53
+ });
54
+ });
55
+ });
56
+
57
+ it('empty match string', function () {
58
+ simulateRequestScope(() => {
59
+ const extern = trackString('bcde', {
60
+ tags: {
61
+ UNTRUSTED: [0, 3]
62
+ }
63
+ });
64
+ const ret = extern.replace('', getReplacement(replacerType, 'a'));
65
+ expect(ret).to.equal('abcde');
66
+ expect(tracker.getData(ret).tags).to.deep.equal({
67
+ UNTRUSTED: [1, 4]
68
+ });
69
+ });
70
+ });
71
+
72
+ it('empty replacer string', function () {
73
+ simulateRequestScope(() => {
74
+ const extern = trackString('abcd', {
75
+ tags: {
76
+ UNTRUSTED: [0, 3]
77
+ }
78
+ });
79
+ const ret = extern.replace('', getReplacement(replacerType, ''));
80
+ expect(ret).to.equal('abcd');
81
+ expect(tracker.getData(ret).tags).to.deep.equal({
82
+ UNTRUSTED: [0, 3]
83
+ });
84
+ });
85
+ });
86
+
87
+ it('untracked string replaced with tracked', function () {
88
+ simulateRequestScope(() => {
89
+ const val = '?bcd';
90
+ const replacement = trackString('a', {
91
+ tags: {
92
+ UNTRUSTED: [0, 0]
93
+ }
94
+ });
95
+ const ret = val.replace('?', getReplacement(replacerType, replacement));
96
+ expect(ret).to.equal('abcd');
97
+ expect(tracker.getData(ret).tags).to.deep.equal({
98
+ UNTRUSTED: [0, 0]
99
+ });
100
+ });
101
+ });
102
+
103
+ it('does not track return with all tracked values removed', function () {
104
+ simulateRequestScope(() => {
105
+ const extern = trackString('_TT_', {
106
+ tags: {
107
+ UNTRUSTED: [1, 2]
108
+ }
109
+ });
110
+ const ret = extern.replace('TT', getReplacement(replacerType, '__'));
111
+ expect(ret).to.equal('____');
112
+ expect(tracker.getData(ret)).to.be.null;
113
+ });
114
+ });
115
+
116
+ it('tracks result highspan', function () {
117
+ simulateRequestScope(() => {
118
+ const extern = trackString('_TT_', {
119
+ tags: {
120
+ UNTRUSTED: [1, 2],
121
+ history: [{ mock: 'sourceEvent' }]
122
+ }
123
+ });
124
+ const ret = extern.replace('_T', getReplacement(replacerType, '__'));
125
+ expect(ret).to.equal('__T_');
126
+ expect(tracker.getData(ret).tags).to.deep.equal({
127
+ UNTRUSTED: [2, 2]
128
+ });
129
+ });
130
+ });
131
+
132
+ it('tracks result lospan', function () {
133
+ simulateRequestScope(() => {
134
+ const extern = trackString('_TT_', {
135
+ tags: {
136
+ UNTRUSTED: [1, 2],
137
+ history: [{ mock: 'sourceEvent' }]
138
+ }
139
+ });
140
+ const ret = extern.replace('T_', getReplacement(replacerType, '__'));
141
+ expect(ret).to.equal('_T__');
142
+ expect(tracker.getData(ret).tags).to.deep.equal({
143
+ UNTRUSTED: [1, 1]
144
+ });
145
+ });
146
+ });
147
+
148
+ it('tracks result highspan tracked replacement', function () {
149
+ simulateRequestScope(() => {
150
+ const extern = trackString('_TT_', {
151
+ tags: {
152
+ UNTRUSTED: [1, 2]
153
+ }
154
+ });
155
+ const replacement = trackString('TT', {
156
+ tags: {
157
+ UNTRUSTED: [0, 1]
158
+ }
159
+ });
160
+ const ret = extern.replace('T_', getReplacement(replacerType, replacement));
161
+ expect(ret).to.equal('_TTT');
162
+ expect(tracker.getData(ret).tags).to.deep.equal({
163
+ UNTRUSTED: [1, 3]
164
+ });
165
+ });
166
+ });
167
+
168
+ it('keeps track of multiple tag ranges', function () {
169
+ simulateRequestScope(() => {
170
+ const extern = trackString('a?cd', {
171
+ tags: {
172
+ UNTRUSTED: [0, 3],
173
+ foo: [1, 2]
174
+ }
175
+ });
176
+ const replacement = trackString('b', {
177
+ tags: {
178
+ foo: [0, 0],
179
+ bar: [0, 0]
180
+ }
181
+ });
182
+ const ret = extern.replace('?', getReplacement(replacerType, replacement));
183
+ expect(ret).to.equal('abcd');
184
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
185
+ UNTRUSTED: [0, 0, 2, 3],
186
+ foo: [1, 2],
187
+ bar: [1, 1]
188
+ });
189
+ });
190
+ });
191
+ });
192
+
193
+ describe(`regEx custom ${replacerType} replacer`, function () {
194
+ it('keeps track of tagged strings not replaced', function () {
195
+ simulateRequestScope(() => {
196
+ const extern = trackString('abcd');
197
+ const ret = extern.replace(/e/g, getReplacement(replacerType, 'e'));
198
+ expect(ret).to.equal('abcd');
199
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
200
+ UNTRUSTED: [0, 3]
201
+ });
202
+ });
203
+ });
204
+
205
+ it('keeps track of tagged strings replaced by untracked strings', function () {
206
+ simulateRequestScope(() => {
207
+ const extern = trackString('a?a?');
208
+ const ret = extern.replace(/\?/g, getReplacement(replacerType, 'b'));
209
+ expect(ret).to.equal('abab');
210
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
211
+ UNTRUSTED: [0, 0, 2, 2]
212
+ });
213
+ });
214
+ });
215
+
216
+ it('keeps track of tagged strings replaced by larger untracked strings', function () {
217
+ simulateRequestScope(() => {
218
+ const extern = trackString('a?a?');
219
+ const ret = extern.replace(/\?/g, getReplacement(replacerType, 'bbb'));
220
+ expect(ret).to.equal('abbbabbb');
221
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
222
+ UNTRUSTED: [0, 0, 4, 4]
223
+ });
224
+ });
225
+ });
226
+
227
+ it('keeps track of tagged strings replaced by smaller untracked strings', function () {
228
+ simulateRequestScope(() => {
229
+ const extern = trackString('a???a???');
230
+ const ret = extern.replace(/\?\?\?/g, getReplacement(replacerType, 'b'));
231
+ expect(ret).to.equal('abab');
232
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
233
+ UNTRUSTED: [0, 0, 2, 2]
234
+ });
235
+ });
236
+ });
237
+
238
+ it('keeps track of tagged strings replaced by tracked strings', function () {
239
+ simulateRequestScope(() => {
240
+ const extern = trackString('a?a?', {
241
+ tags: {
242
+ UNTRUSTED: [0, 3]
243
+ }
244
+ });
245
+ const replacement = trackString('b', {
246
+ tags: {
247
+ UNTRUSTED: [0, 0]
248
+ }
249
+ });
250
+ const ret = extern.replace(/\?/g, getReplacement(replacerType, replacement));
251
+ expect(ret).to.equal('abab');
252
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
253
+ UNTRUSTED: [0, 3]
254
+ });
255
+ });
256
+ });
257
+
258
+ it('keeps track of tagged strings replaced by larger tracked strings', function () {
259
+ simulateRequestScope(() => {
260
+ const extern = trackString('a?a?', {
261
+ tags: {
262
+ UNTRUSTED: [0, 3]
263
+ }
264
+ });
265
+ const replacement = trackString('bbb', {
266
+ tags: {
267
+ UNTRUSTED: [0, 2]
268
+ }
269
+ });
270
+ const ret = extern.replace(/\?/g, getReplacement(replacerType, replacement));
271
+ expect(ret).to.equal('abbbabbb');
272
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
273
+ UNTRUSTED: [0, 7]
274
+ });
275
+ });
276
+ });
277
+
278
+ it('keeps track of tagged strings replaced by smaller tracked strings', function () {
279
+ simulateRequestScope(() => {
280
+ const extern = trackString('a???a???', {
281
+ tags: {
282
+ UNTRUSTED: [0, 7]
283
+ }
284
+ });
285
+ const replacement = trackString('b', {
286
+ tags: {
287
+ UNTRUSTED: [0, 0]
288
+ }
289
+ });
290
+ const ret = extern.replace(/\?\?\?/g, getReplacement(replacerType, replacement));
291
+ expect(ret).to.equal('abab');
292
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
293
+ UNTRUSTED: [0, 3]
294
+ });
295
+ });
296
+ });
297
+
298
+ it('keeps track of multiple tag ranges', function () {
299
+ simulateRequestScope(() => {
300
+ const extern = trackString('a??a??a', {
301
+ tags: {
302
+ UNTRUSTED: [0, 6],
303
+ foo: [0, 2],
304
+ bar: [4, 6]
305
+ }
306
+ });
307
+ const replacement = trackString('b', {
308
+ tags: {
309
+ foo: [0, 0],
310
+ bar: [0, 0]
311
+ }
312
+ });
313
+ const ret = extern.replace(/\?/g, replacement);
314
+ expect(ret).to.equal('abbabba');
315
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
316
+ UNTRUSTED: [0, 0, 3, 3, 6, 6],
317
+ foo: [0, 2, 4, 5],
318
+ bar: [1, 2, 4, 6]
319
+ });
320
+ });
321
+ });
322
+ });
323
+ });
324
+
325
+ describe('complex/edge cases', function () {
326
+ it('replacer function returns a non-string', function () {
327
+ simulateRequestScope(() => {
328
+ const extern = trackString('123?', {
329
+ tags: {
330
+ UNTRUSTED: [0, 3]
331
+ }
332
+ });
333
+ const ret = extern.replace('?', (match, offset) => offset + 1);
334
+ expect(ret).to.equal('1234');
335
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
336
+ UNTRUSTED: [0, 2]
337
+ });
338
+ });
339
+ });
340
+
341
+ it('replacer operates on capture groups', function () {
342
+ simulateRequestScope(() => {
343
+ const extern = trackString('AaaBbbCcc', {
344
+ tags: {
345
+ UNTRUSTED: [0, 8],
346
+ groupOne: [0, 2],
347
+ groupTwo: [3, 5],
348
+ groupThree: [6, 8]
349
+ }
350
+ });
351
+ const ret = extern.replace(/(A)|(B)|(C)/g, (match => match.toLowerCase()));
352
+ expect(ret).to.equal('aaabbbccc');
353
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
354
+ UNTRUSTED: [1, 2, 4, 5, 7, 8],
355
+ groupOne: [1, 2],
356
+ groupTwo: [4, 5],
357
+ groupThree: [7, 8]
358
+ });
359
+ });
360
+ });
361
+
362
+ it('handles edge case where we have a function that sets the replacement to be a special value',
363
+ function () {
364
+ simulateRequestScope(() => {
365
+ let counter = 0;
366
+ const extern = trackString('SELECT ? FROM ?', {
367
+ tags: {
368
+ UNTRUSTED: [0, 14],
369
+ groupOne: [9, 12],
370
+ }
371
+ });
372
+ const ret = extern.replace(/(\?)/g, () => `$${counter++}`);
373
+ expect(ret).to.equal('SELECT $0 FROM $1');
374
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
375
+ UNTRUSTED: [0, 6, 9, 14],
376
+ groupOne: [10, 13],
377
+ });
378
+ });
379
+ });
380
+
381
+ it('replacer operates on named capture groups', function () {
382
+ simulateRequestScope(() => {
383
+ const extern = trackString('AaaBbbCcc', {
384
+ tags: {
385
+ UNTRUSTED: [0, 8],
386
+ groupOne: [0, 2],
387
+ groupTwo: [3, 5],
388
+ groupThree: [6, 8]
389
+ }
390
+ });
391
+ const ret = extern.replace(/(?<g1>A)|(?<g2>B)|(?<g3>C)/g, (match => match.toLowerCase()));
392
+ expect(ret).to.equal('aaabbbccc');
393
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
394
+ UNTRUSTED: [1, 2, 4, 5, 7, 8],
395
+ groupOne: [1, 2],
396
+ groupTwo: [4, 5],
397
+ groupThree: [7, 8]
398
+ });
399
+ });
400
+ });
401
+
402
+ it('keeps track of multiple overlapping tag ranges over multiple calls', function () {
403
+ simulateRequestScope(() => {
404
+ let ret;
405
+ const extern = trackString('a??cd?g?', {
406
+ tags: {
407
+ UNTRUSTED: [0, 7],
408
+ foo: [0, 2],
409
+ bar: [1, 3],
410
+ fizz: [4, 7],
411
+ buzz: [1, 6]
412
+ }
413
+ });
414
+ const replB = trackString('b', {
415
+ tags: {
416
+ foo: [0, 0],
417
+ bar: [0, 0]
418
+ }
419
+ });
420
+ const replEf = trackString('ef', {
421
+ tags: {
422
+ fizz: [0, 0],
423
+ buzz: [1, 1]
424
+ }
425
+ });
426
+ const replH = trackString('h', {
427
+ tags: {
428
+ foobar: [0, 0]
429
+ }
430
+ });
431
+ ret = extern.replace('??', replB);
432
+ ret = ret.replace('?', () => replEf);
433
+ ret = ret.replace(/\?/g, replH);
434
+ expect(ret).to.be.equal('abcdefgh');
435
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
436
+ UNTRUSTED: [0, 0, 2, 3, 6, 6],
437
+ foo: [0, 1],
438
+ bar: [1, 2],
439
+ fizz: [3, 4, 6, 6],
440
+ buzz: [2, 3, 5, 6],
441
+ foobar: [7, 7]
442
+ });
443
+ });
444
+ });
445
+
446
+ it('keeps track of replacement history', function () {
447
+ simulateRequestScope(() => {
448
+ const extern = trackString('aa??');
449
+ const replacementOne = trackString('bb');
450
+ const replacementTwo = trackString('cc');
451
+ const replacements = ['', '', replacementOne, replacementTwo];
452
+ const ret = extern.replace(/\?/g, (match, offset) => replacements[offset]);
453
+ expect(ret).to.equal('aabbcc');
454
+ expect(tracker.getData(ret).history.length).to.equal(3);
455
+ expect(tracker.getData(ret).history).to.be.like([
456
+ { value: 'aa??', tags: { UNTRUSTED: [0, 3] } },
457
+ { value: 'bb', tags: { UNTRUSTED: [0, 1] } },
458
+ { value: 'cc', tags: { UNTRUSTED: [0, 1] } }
459
+ ]);
460
+ });
461
+ });
462
+ });
463
+
464
+ describe('special characters', function () {
465
+ it('replaces and handles $$', function () {
466
+ simulateRequestScope(() => {
467
+ const extern = trackString('foo?foo');
468
+ const ret = extern.replace('?', '$$ $$');
469
+ expect(ret).to.equal('foo$ $foo');
470
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
471
+ UNTRUSTED: [0, 2, 6, 8]
472
+ });
473
+ });
474
+ });
475
+
476
+ it('replaces and handles untracked $&', function () {
477
+ simulateRequestScope(() => {
478
+ const extern = trackString('foo bar');
479
+ const ret = extern.replace('bar', '[$&]-[$&]');
480
+ expect(ret).to.equal('foo [bar]-[bar]');
481
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
482
+ UNTRUSTED: [0, 3]
483
+ });
484
+ });
485
+ });
486
+
487
+ it('replaces and handles tracked $&', function () {
488
+ simulateRequestScope(() => {
489
+ const extern = trackString('foo bar');
490
+ const replacement = trackString('bar');
491
+ const ret = extern.replace(replacement, '[$&]-[$&]');
492
+ expect(ret).to.equal('foo [bar]-[bar]');
493
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
494
+ UNTRUSTED: [0, 3, 5, 7, 11, 13]
495
+ });
496
+ });
497
+ });
498
+
499
+ it('replaces and handles $`', function () {
500
+ simulateRequestScope(() => {
501
+ const extern = trackString('foobar');
502
+ const ret = extern.replace('bar', '$`$`');
503
+ expect(ret).to.equal('foofoofoo');
504
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
505
+ UNTRUSTED: [0, 8]
506
+ });
507
+ });
508
+ });
509
+
510
+ it('replaces and handles $\'', function () {
511
+ simulateRequestScope(() => {
512
+ const extern = trackString('foobar');
513
+ const ret = extern.replace('foo', '$\'$\'');
514
+ expect(ret).to.equal('barbarbar');
515
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
516
+ UNTRUSTED: [0, 8]
517
+ });
518
+ });
519
+ });
520
+
521
+ it('replaces and handles $N', function () {
522
+ simulateRequestScope(() => {
523
+ const extern = trackString('aabbcc');
524
+ const ret = extern.replace(/(aa)(bb)/g, '$2$1');
525
+ expect(ret).to.equal('bbaacc');
526
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
527
+ UNTRUSTED: [4, 5]
528
+ });
529
+ });
530
+ });
531
+
532
+ it('replaces and handles $name', function () {
533
+ simulateRequestScope(() => {
534
+ const extern = trackString('aabbcc');
535
+ const ret = extern.replace(/(?<g1>aa)(?<g2>bb)/g, '$g2$g1');
536
+ expect(ret).to.equal('bbaacc');
537
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
538
+ UNTRUSTED: [4, 5]
539
+ });
540
+ });
541
+ });
542
+
543
+ it('replaces and handles strings containing multiple special characters', function () {
544
+ simulateRequestScope(() => {
545
+ const extern = trackString('aabbcc');
546
+ const ret = extern.replace('bb', '($&)-($\')-($`)');
547
+ expect(ret).to.equal('aa(bb)-(cc)-(aa)cc');
548
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
549
+ UNTRUSTED: [0, 1, 8, 9, 13, 14, 16, 17]
550
+ });
551
+ });
552
+ });
553
+
554
+ it('replaces and handles strings containing multiple special characters that replace multiple strings', function () {
555
+ simulateRequestScope(() => {
556
+ const extern = trackString('aa?bb?cc?');
557
+ const ret = extern.replace(/(\?)/g, '$$$1');
558
+ expect(ret).to.equal('aa$?bb$?cc$?');
559
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
560
+ UNTRUSTED: [0, 1, 4, 5, 8, 9]
561
+ });
562
+ });
563
+ });
564
+
565
+ it('replaces and handles multiple special characters and tracked strings', function () {
566
+ simulateRequestScope(() => {
567
+ const extern = trackString('foobar');
568
+ const replacement = trackString('bar');
569
+ const ret = extern.replace(replacement, '$$$&$$$&');
570
+ expect(ret).to.equal('foo$bar$bar');
571
+ expect(tracker.getData(ret).tags).to.be.deep.equal({
572
+ UNTRUSTED: [0, 2, 4, 6, 8, 10]
573
+ });
574
+ });
575
+ });
576
+
577
+ // example is from shell-quote
578
+ it('replaces and handles special characters of form $\\d when capture group exists or doesn\'t', function() {
579
+ const s = 'http://example.com?a=1&a=2';
580
+ const expected = s.replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2');
581
+ simulateRequestScope(() => {
582
+ const result = s.replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2');
583
+ // http\://example.com\?a\=1\&a\=2
584
+ expect(result).to.equal(expected);
585
+ });
586
+ });
587
+ });
588
+ });