View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.regexp;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  
19  import org.apache.commons.io.IOUtils;
20  import org.htmlunit.WebDriverTestCase;
21  import org.htmlunit.junit.annotation.Alerts;
22  import org.junit.jupiter.api.Test;
23  import org.junit.jupiter.api.Timeout;
24  
25  /**
26   * Tests for {@link HtmlUnitRegExpProxy}.
27   *
28   * @author Marc Guillemot
29   * @author Ahmed Ashour
30   * @author Frank Danek
31   * @author Ronald Brill
32   * @author Carsten Steurl
33   * @author Leszek Hoppe
34   * @author Atsushi Nakagawa
35   * @author Lai Quang Duong
36   */
37  public class RegExp3Test extends WebDriverTestCase {
38  
39      private static final String SCRIPT_TEST_MATCH = "function arrayToString(_arr) {\n"
40          + "  if (_arr == null) return null;\n"
41          + "  var s = '[';\n"
42          + "  for (var i = 0; i < _arr.length; i++)\n"
43          + "  {\n"
44          + "    if (i != 0) s += ', ';\n"
45          + "    s += _arr[i];\n"
46          + "  }\n"
47          + "  s += ']';\n"
48          + "  return s;\n"
49          + "}\n"
50  
51          + "function assertArrEquals(actual, expected) {\n"
52          + "  if (expected == null) {\n"
53          + "    if (actual != null)\n"
54          + "      throw 'Expected >null< got >' + actual + '<';\n"
55          + "    else return;\n"
56          + "  }\n"
57          + "  var expectedStr = arrayToString(expected);\n"
58          + "  var actualStr = arrayToString(actual);\n"
59          + "  if (expectedStr != actualStr)\n"
60          + "    throw 'Expected >' + expectedStr + '< got >' + actualStr + '<';\n"
61          + "}\n"
62  
63          + "assertArrEquals('ab'.match(), ['']);\n"
64          + "assertArrEquals('ab'.match('foo'), null);\n"
65          + "assertArrEquals('ab'.match('a'), ['a']);\n"
66          + "assertArrEquals('abab'.match('a'), ['a']);\n"
67          + "assertArrEquals('abab'.match('.a'), ['ba']);\n"
68          + "assertArrEquals('abab'.match(/.a/), ['ba']);\n"
69          + "assertArrEquals('li'.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i), ['li', undefined, 'li', '']);\n"
70          + "assertArrEquals('abab'.match(new RegExp('.a')), ['ba']);\n"
71          + "var s = '<script>var a = 1;</' + 'script>';\n"
72          + "var re = '(?:<script.*?>)((\\n|\\r|.)*?)(?:<\\/script>)';\n"
73          + "assertArrEquals(s.match(re), [s, 'var a = 1;', ';']);\n";
74  
75      /**
76       * @throws Exception if the test fails
77       */
78      @Test
79      @Alerts("ab21")
80      public void replaceNormalStringReplace() throws Exception {
81          testEvaluate("'121'.replace('1', 'ab')");
82      }
83  
84      /**
85       * @throws Exception if the test fails
86       */
87      @Test
88      @Alerts("xyz121")
89      public void replaceNormalStringEmptySearch() throws Exception {
90          testEvaluate("'121'.replace('', 'xyz')");
91      }
92  
93      /**
94       * @throws Exception if the test fails
95       */
96      @Test
97      @Alerts("1")
98      public void replaceNormalStringEmptyReplace() throws Exception {
99          testEvaluate("'121'.replace('21', '')");
100     }
101 
102     /**
103      * @throws Exception if the test fails
104      */
105     @Test
106     @Alerts("xyz")
107     public void replaceNormalStringEmptyStringEmptySearch() throws Exception {
108         testEvaluate("''.replace('', 'xyz')");
109     }
110 
111     /**
112      * @throws Exception if the test fails
113      */
114     @Test
115     @Alerts("a$c21")
116     public void replaceNormalStringReplaceSingleDollar() throws Exception {
117         testEvaluate("'121'.replace('1', 'a$c')");
118     }
119 
120     /**
121      * @throws Exception if the test fails
122      */
123     @Test
124     @Alerts("a$c21")
125     public void replaceNormalStringReplaceDoubleDollar() throws Exception {
126         testEvaluate("'121'.replace('1', 'a$$c')");
127     }
128 
129     /**
130      * @throws Exception if the test fails
131      */
132     @Test
133     @Alerts("a121")
134     public void replaceNormalStringReplaceSubstring() throws Exception {
135         testEvaluate("'121'.replace('1', 'a$&')");
136     }
137 
138     /**
139      * @throws Exception if the test fails
140      */
141     @Test
142     @Alerts("abaabe")
143     public void replaceNormalStringReplacePreceeding() throws Exception {
144         testEvaluate("'abcde'.replace('cd', 'a$`')");
145     }
146 
147     /**
148      * @throws Exception if the test fails
149      */
150     @Test
151     @Alerts("a21")
152     public void replaceNormalStringReplacePreceedingEmpty() throws Exception {
153         testEvaluate("'121'.replace('1', 'a$`')");
154     }
155 
156     /**
157      * @throws Exception if the test fails
158      */
159     @Test
160     @Alerts("abaee")
161     public void replaceNormalStringReplaceFollowing() throws Exception {
162         testEvaluate("'abcde'.replace('cd', \"a$'\")");
163     }
164 
165     /**
166      * @throws Exception if the test fails
167      */
168     @Test
169     @Alerts("aba")
170     public void replaceNormalStringReplaceFollowingEmpty() throws Exception {
171         testEvaluate("'abcd'.replace('cd', \"a$'\")");
172     }
173 
174     /**
175      * @throws Exception if the test fails
176      */
177     @Test
178     @Alerts("aba$0")
179     public void replaceNormalStringReplaceGroupZero() throws Exception {
180         testEvaluate("'abcd'.replace('cd', 'a$0')");
181     }
182 
183     /**
184      * @throws Exception if the test fails
185      */
186     @Test
187     @Alerts("aba$1")
188     public void replaceNormalStringReplaceGroupOne() throws Exception {
189         testEvaluate("'abcd'.replace('cd', 'a$1')");
190     }
191 
192     /**
193      * Test for bug http://sourceforge.net/p/htmlunit/bugs/513/.
194      * @throws Exception if the test fails
195      */
196     @Test
197     @Alerts("123456")
198     public void replaceNormalStringWithRegexpChars() throws Exception {
199         testEvaluate("'123456'.replace('/{\\d+}', '')");
200     }
201 
202     /**
203      * @throws Exception if the test fails
204      */
205     @Test
206     @Alerts("123456")
207     public void replaceWithUndefinedPattern() throws Exception {
208         final String html = "<html><head><script>\n"
209             + LOG_TITLE_FUNCTION
210             + "  function test() {\n"
211             + "    var pattern;\n"
212             + "    log('123456'.replace(pattern, ''));\n"
213             + "  }\n"
214             + "</script></head><body onload='test()'>\n"
215             + "</body></html>";
216 
217         loadPageVerifyTitle2(html);
218     }
219 
220     /**
221      * @throws Exception if the test fails
222      */
223     @Test
224     @Alerts("123456")
225     public void replace() throws Exception {
226         final String html = "<html><head><script>\n"
227             + LOG_TITLE_FUNCTION
228             + "  function test() {\n"
229             + "    var pattern = /{\\d+}/g;\n"
230             + "    log('123456'.replace(pattern, ''));\n"
231             + "  }\n"
232             + "</script></head><body onload='test()'>\n"
233             + "</body></html>";
234 
235         loadPageVerifyTitle2(html);
236     }
237 
238     /**
239      * @throws Exception if the test fails
240      */
241     @Test
242     public void match() throws Exception {
243         final String html = "<html><head><script>\n"
244             + LOG_TITLE_FUNCTION
245             + SCRIPT_TEST_MATCH
246             + "</script></head><body>\n"
247             + "</body></html>";
248 
249         loadPageVerifyTitle2(html);
250     }
251 
252     /**
253      * @throws Exception if the test fails
254      */
255     @Test
256     @Alerts("0")
257     public void index() throws Exception {
258         final String html = "<html><head><script>\n"
259             + LOG_TITLE_FUNCTION
260             + "  function test() {\n"
261             + "    var match = '#{tests} tests'.match(/(^|.|\\r|\\n)(#\\{(.*?)\\})/);\n"
262             + "    log(match.index);\n"
263             + "  }\n"
264             + "</script></head><body onload='test()'>\n"
265             + "</body></html>";
266 
267         loadPageVerifyTitle2(html);
268     }
269 
270     /**
271      * @throws Exception if the test fails
272      */
273     @Test
274     @Alerts("ab,a")
275     public void match_NotFirstCharacter() throws Exception {
276         testEvaluate("\"ab\".match(/^(.)[^\\1]$/)");
277     }
278 
279     /**
280      * @throws Exception if the test fails
281      */
282     @Test
283     @Alerts("boo();")
284     public void regExp_exec() throws Exception {
285         final String html = "<html><head><script>\n"
286             + LOG_TITLE_FUNCTION
287             + "  function test() {\n"
288             + "    var re = new RegExp('(?:<s' + 'cript.*?>)(.*)<\\/script>');\n"
289             + "    var t = 'foo <scr' + 'ipt>boo();</' + 'script>bar';\n"
290             + "    var r = re.exec(t);\n"
291             + "    log(r[1]);\n"
292             + "  }\n"
293             + "</script></head><body onload='test()'>\n"
294             + "</body></html>";
295 
296         loadPageVerifyTitle2(html);
297     }
298 
299     /**
300      * @throws Exception if the test fails
301      */
302     @Test
303     @Alerts("<script>boo();</script>")
304     public void flag_global() throws Exception {
305         final String html = "<html><head><script>\n"
306             + LOG_TITLE_FUNCTION
307             + "  function test() {\n"
308             + "    var str = 'foo <script>boo();<'+'/script>bar';\n"
309             + "    var regExp = new RegExp('<script[^>]*>([\\\\S\\\\s]*?)<\\/script>', 'img');\n"
310             + "    log(str.match(regExp));\n"
311             + "  }\n"
312             + "</script></head><body onload='test()'>\n"
313             + "</body></html>";
314 
315         loadPageVerifyTitle2(html);
316     }
317 
318     /**
319      * Minimal case exists in {@link #test_minimal()}.  Once successful, either one to be removed.
320      * @throws Exception if the test fails
321      */
322     @Test
323     @Alerts({"true", "false", "true"})
324     public void prototype() throws Exception {
325         final String html = "<html><head><script>\n"
326             + LOG_TITLE_FUNCTION
327             + "  function test() {\n"
328             + "    var regexp = /^(?:(?:(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|"
329             + "(?:(?:16|[2468][048]|[3579][26])00)))(\\/|-|\\.)(?:0?2\\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\\d)?\\d{2})"
330             + "(\\/|-|\\.)(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|"
331             + "(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$/;\n"
332             + "    var str = '2001-06-16';\n"
333             + "    log(regexp.test(str));\n"
334             + "    log(regexp.test('hello'));\n"
335             + "    log(regexp.exec(str) != null);\n"
336             + "  }\n"
337             + "</script></head><body onload='test()'>\n"
338             + "</body></html>";
339 
340         loadPageVerifyTitle2(html);
341     }
342 
343     /**
344      * @throws Exception if the test fails
345      */
346     @Test
347     public void test() throws Exception {
348         test("/foo/", "hello foo", true, true, false);
349         test("/foo/gi", "hello foo", true, false, false);
350         test("/foo/gi", "hello Foo", true, false, false);
351     }
352 
353     private void test(final String regexp, final String testString, final boolean... expectedResults) throws Exception {
354         final String html = "<html><head><script>\n"
355             + LOG_TITLE_FUNCTION
356             + "  function test() {\n"
357             + "    var regexp = " + regexp + ";\n"
358             + "    var str = '" + testString + "';\n"
359             + "    log(regexp.test(str));\n"
360             + "    log(regexp.exec(str) != null);\n"
361             + "    log(regexp.test('blabla'));\n"
362             + "  }\n"
363             + "</script></head><body onload='test()'>\n"
364             + "</body></html>";
365 
366         setExpectedAlerts(String.valueOf(expectedResults[0]), String.valueOf(expectedResults[1]),
367             String.valueOf(expectedResults[2]));
368 
369         loadPageVerifyTitle2(html);
370     }
371 
372     /**
373      * Minimized version of {@link #test()}.  Once successful, either one to be removed.
374      * @throws Exception if the test fails
375      */
376     @Test
377     @Alerts("true")
378     public void test_minimal() throws Exception {
379         final String html = "<html><head><script>\n"
380             + LOG_TITLE_FUNCTION
381             + "  function test() {\n"
382             + "    var regexp = /((?:2001)-)/;\n"
383             + "    var str = '2001-';\n"
384             + "    log(regexp.test(str));\n"
385             + "  }\n"
386             + "</script></head><body onload='test()'>\n"
387             + "</body></html>";
388 
389         loadPageVerifyTitle2(html);
390     }
391 
392     /**
393      * Tests usage of regex with non escaped curly braces, such as is used by dhtmlGrid.
394      * @throws Exception if the test fails
395      */
396     @Test
397     @Alerts("{#abcd},{,abcd,}")
398     public void regexWithNonEscapedCurlyBraces() throws Exception {
399         final String html = "<html><head><script>\n"
400             + LOG_TITLE_FUNCTION
401             + "  function test() {\n"
402             + "    var regexp = /(^|{)#([^}]+)(}|$)/;\n"
403             + "    var str = '|{#abcd}|';\n"
404             + "    log(str.match(regexp));\n"
405             + "  }\n"
406             + "</script></head><body onload='test()'>\n"
407             + "</body></html>";
408 
409         loadPageVerifyTitle2(html);
410     }
411 
412     /**
413      * Tests usage of regex with non escaped curly braces, such as is used by dhtmlGrid.
414      * @throws Exception if the test fails
415      */
416     @Test
417     @Alerts("aa-b-b-")
418     public void backSpace() throws Exception {
419         final String html = "<html><head><script>\n"
420             + LOG_TITLE_FUNCTION
421             + "  function test() {\n"
422             + "    var regexp = /[p\\bz]/g;\n"
423             + "    var str = 'aapbzb' + String.fromCharCode(8);\n"
424             + "    log(str.replace(regexp, '-'));\n"
425             + "  }\n"
426             + "</script></head><body onload='test()'>\n"
427             + "</body></html>";
428 
429         loadPageVerifyTitle2(html);
430     }
431 
432     /**
433      * @throws Exception if the test fails
434      */
435     @Test
436     @Alerts("")
437     public void dollarSignAndCurlyBracket() throws Exception {
438         testEvaluate("''.replace(/\\${/g, '')");
439     }
440 
441     /**
442      * @throws Exception if the test fails
443      */
444     @Test
445     @Alerts({"null", "["})
446     public void openingSquareBracketInCharacterClass() throws Exception {
447         final String html = "<html><head><script>\n"
448             + LOG_TITLE_FUNCTION
449             + "function test() {\n"
450             + "  var re = /[[]/;\n"
451             + "  log('div'.match(re));\n"
452             + "  log('['.match(re));\n"
453             + "  }\n"
454             + "</script></head><body onload='test()'>\n"
455             + "</body></html>";
456 
457         loadPageVerifyTitle2(html);
458     }
459 
460     /**
461      * RegExp used in JQuery 1.3.1 that wasn't correctly transformed to Java RegExp in HtmlUnit-2.4.
462      * @throws Exception if the test fails
463      */
464     @Test
465     @Alerts("div")
466     public void jquerySizzleChunker() throws Exception {
467         final String html = "<html><head><script>\n"
468             + LOG_TITLE_FUNCTION
469             + " var re = /((?:\\((?:\\([^()]+\\)|[^()]+)+\\)|\\[(?:\\[[^[\\]]*\\]|['\"][^'\"]+['\"]|[^[\\]'\"]+)+\\]"
470             + "|\\\\.|[^ >+~,(\\[]+)+|[>+~])(\\s*,\\s*)?/g\n"
471             + "  function test() {\n"
472             + "    log('div'.match(re));\n"
473             + "  }\n"
474             + "</script></head><body onload='test()'>\n"
475             + "</body></html>";
476 
477         loadPageVerifyTitle2(html);
478     }
479 
480     /**
481      * RegExp used in JQuery 1.3.1 that wasn't correctly transformed to Java RegExp in HtmlUnit-2.4.
482      * @throws Exception if the test fails
483      */
484     @Test
485     @Alerts({":toto,toto,,", "null"})
486     public void jqueryPseudo() throws Exception {
487         final String html = "<html><head><script>\n"
488             + LOG_TITLE_FUNCTION
489             + " var re = /:((?:[\\w\\u00c0-\\uFFFF_-]|\\\\.)+)(?:\\((['\"]*)((?:\\([^\\)]+\\)"
490             + "|[^\\2\\(\\)]*)+)\\2\\))?/;\n"
491             + "  function test() {\n"
492             + "    log(':toto'.match(re));\n"
493             + "    log('foo'.match(re));\n"
494             + "  }\n"
495             + "</script></head><body onload='test()'>\n"
496             + "</body></html>";
497 
498         loadPageVerifyTitle2(html);
499     }
500 
501     /**
502      * Sample from ExtJS.
503      * @throws Exception if the test fails
504      */
505     @Test
506     @Alerts({ "[floating=true],floating,=,,true",
507                 "[floating=\"true\"],floating,=,\",true",
508                 "[floating=\"true'],floating,=,,\"true'",
509                 "[floating=\"true],floating,=,,\"true",
510                 "[floating=true\"],floating,=,,true\""})
511     public void extJs() throws Exception {
512         final String html = "<html><head><script>\n"
513             + LOG_TITLE_FUNCTION
514             + "  var re = /^(?:\\[((?:[@?$])?[\\w\\-]*)\\s*(?:([\\^$*~%!\\/]?=)\\s*(['\\\"])?((?:\\\\\\]|.)*?)\\3)?(?!\\\\)\\])/;\n"
515             + "  function test() {\n"
516             + "    log('[floating=true]'.match(re));\n"
517             + "    log('[floating=\"true\"]'.match(re));\n"
518             + "    log('[floating=\"true\\']'.match(re));\n"
519             + "    log('[floating=\"true]'.match(re));\n"
520             + "    log('[floating=true\"]'.match(re));\n"
521             + "  }\n"
522             + "</script></head><body onload='test()'>\n"
523             + "</body></html>";
524 
525         loadPageVerifyTitle2(html);
526     }
527 
528     /**
529      * Javascript has a different idea about back references to optional groups.
530      * @throws Exception if the test fails
531      */
532     @Test
533     @Alerts({"axxxxa,a", "xxxx,", "xxxx,", "xxxx,"})
534     public void backReferenceToOptionalGroup() throws Exception {
535         final String html = "<html><head><script>\n"
536             + LOG_TITLE_FUNCTION
537             + "function test() {\n"
538             + "  var re = /(a)?x+\\1/;\n"
539             + "  log('axxxxa'.match(re));\n"
540             + "  log('axxxx'.match(re));\n"
541             + "  log('xxxxa'.match(re));\n"
542             + "  log('xxxx'.match(re));\n"
543             + "}\n"
544             + "</script></head><body onload='test()'>\n"
545             + "</body></html>";
546 
547         loadPageVerifyTitle2(html);
548     }
549 
550     /**
551      * Browsers ignore back references pointing to not finished groups.
552      * @throws Exception if the test fails
553      */
554     @Test
555     @Alerts({"abcx\u0004,b,x", "null", "null", "null"})
556     public void backReferenceToNotDefinedGroupsAreHandledAsOctal() throws Exception {
557         final String html = "<html><head>\n<script>\n"
558             + LOG_TEXTAREA_FUNCTION
559             + "function test() {\n"
560             + "  var re = /(?:a)(b)c(x)\\4/;\n"
561             + "  log('abcx\\04'.match(re));\n"
562             + "  log('abcx'.match(re));\n"
563             + "  log('abb'.match(re));\n"
564             + "  log('abbxx'.match(re));\n"
565             + "}\n"
566             + "</script></head><body onload='test()'>\n"
567             + LOG_TEXTAREA
568             + "</body></html>";
569 
570         loadPageVerifyTextArea2(html);
571     }
572 
573     /**
574      * Browsers ignore back references pointing to not finished groups.
575      * @throws Exception if the test fails
576      */
577     @Test
578     @Alerts({"abcx,b,x", "abcx,b,x", "null", "null"})
579     public void ignoreBackReferenceNotFinishedGroups() throws Exception {
580         final String html = "<html><head><script>\n"
581             + LOG_TITLE_FUNCTION
582             + "function test() {\n"
583             + "  var re = /(?:a)(b)c(x\\2)/;\n"
584             + "  log('abcx'.match(re));\n"
585             + "  log('abcx\\02'.match(re));\n"
586             + "  log('abb'.match(re));\n"
587             + "  log('abbxx'.match(re));\n"
588             + "}\n"
589             + "</script></head><body onload='test()'>\n"
590             + "</body></html>";
591 
592         loadPageVerifyTitle2(html);
593     }
594 
595     /**
596      * Browsers ignore back references in character classes.
597      * This is a simplified case of {@link #jqueryPseudo()}
598      * @throws Exception if the test fails
599      */
600     @Test
601     @Alerts({"null", "abb,a,b", "abd,a,b"})
602     public void ignoreBackReferenceInCharacterClass() throws Exception {
603         final String html = "<html><head><script>\n"
604             + LOG_TITLE_FUNCTION
605             + "function test() {\n"
606             + "  var re = /(a)(b)[^\\2c]/;\n"
607             + "  log('abc'.match(re));\n"
608             + "  log('abb'.match(re));\n"
609             + "  log('abd'.match(re));\n"
610             + "}\n"
611             + "</script></head><body onload='test()'>\n"
612             + "</body></html>";
613 
614         loadPageVerifyTitle2(html);
615     }
616 
617     /**
618      * Tests basic usage of back references in a <tt>string.replace(...)</tt> invocation.
619      * @throws Exception if an error occurs
620      */
621     @Test
622     @Alerts("afood$0$7b")
623     public void replace_backReferences() throws Exception {
624         testEvaluate("'afoob'.replace(/(foo)/g, '$1d$0$7')");
625     }
626 
627     /**
628      * Use back reference in replacement when it exists.
629      * @throws Exception if an error occurs
630      */
631     @Test
632     @Alerts("hllo")
633     public void replace_backReference_existing() throws Exception {
634         testEvaluate("'hello'.replace(/(h)e/g, '$1')");
635     }
636 
637     /**
638      * If no back reference is present, use $ as litteral.
639      * @throws Exception if an error occurs
640      */
641     @Test
642     @Alerts("$1llo")
643     public void replace_backReference_notExisting() throws Exception {
644         testEvaluate("'hello'.replace(/he/g, '$1')");
645     }
646 
647     /**
648      * Regression test for bug 2638813 (dollar signs with no index are not back references).
649      * @throws Exception if an error occurs
650      */
651     @Test
652     @Alerts("I$want$these$periods$to$be$$s")
653     public void replace_backReferences_2() throws Exception {
654         testEvaluate("'I.want.these.periods.to.be.$s'.replace(/\\./g, '$')");
655     }
656 
657     /**
658      * @throws Exception if an error occurs
659      */
660     @Test
661     @Alerts("food bar")
662     public void replace_backReference_ampersand() throws Exception {
663         testEvaluate("'foo bar'.replace(/foo/g, '$&d')");
664         testEvaluate("'foo bar'.replace(/foo/, '$&d')");
665     }
666 
667     /**
668      * @throws Exception if an error occurs
669      */
670     @Test
671     @Alerts("foo foo ")
672     public void replace_backReference_backtick()  throws Exception {
673         testEvaluate("'foo bar'.replace(/bar/g, '$`')");
674         testEvaluate("'foo bar'.replace(/bar/, '$`')");
675     }
676 
677     /**
678      * @throws Exception if an error occurs
679      */
680     @Test
681     @Alerts(" bar bar")
682     public void replace_backReference_tick() throws Exception {
683         testEvaluate("'foo bar'.replace(/foo/g, '$\\'')");
684         testEvaluate("'foo bar'.replace(/foo/, '$\\'')");
685     }
686 
687     /**
688      * @throws Exception if an error occurs
689      */
690     @Test
691     @Alerts("$' bar")
692     public void replace_$backReference_tick() throws Exception {
693         testEvaluate("'foo bar'.replace(/foo/g, '$$\\'')");
694         testEvaluate("'foo bar'.replace(/foo/, '$$\\'')");
695     }
696 
697     /**
698      * @throws Exception if an error occurs
699      */
700     @Test
701     @Alerts("ad$0db")
702     public void replace_backReference_$0() throws Exception {
703         testEvaluate("'afoob'.replace(/(foo)/g, 'd$0d')");
704         testEvaluate("'afoob'.replace(/(foo)/, 'd$0d')");
705     }
706 
707     /**
708      * @throws Exception if an error occurs
709      */
710     @Test
711     @Alerts("ad$0db")
712     public void replace_backReference_$0WithMultipleGroups() throws Exception {
713         testEvaluate("'afkxxxkob'.replace(/(f)k(.*)k(o)/g, 'd$0d')");
714         testEvaluate("'afkxxxkob'.replace(/(f)k(.*)k(o)/, 'd$0d')");
715     }
716 
717     /**
718      * @throws Exception if an error occurs
719      */
720     @Test
721     @Alerts("ad$0db")
722     public void replace_backReference_$0WithNoGroups() throws Exception {
723         testEvaluate("'afoob'.replace(/foo/g, 'd$0d')");
724         testEvaluate("'afoob'.replace(/foo/, 'd$0d')");
725     }
726 
727     /**
728      * @throws Exception if an error occurs
729      */
730     @Test
731     @Alerts("ad$0dbfuoc")
732     public void replace_backReference_$0WithMultipleHits() throws Exception {
733         testEvaluate("'afoobfuoc'.replace(/(f.o)/, 'd$0d')");
734     }
735 
736     /**
737      * @throws Exception if an error occurs
738      */
739     @Test
740     @Alerts("ad$0dbd$0dc")
741     public void replace_backReference_$0WithMultipleHitsGlobal() throws Exception {
742         testEvaluate("'afoobfuoc'.replace(/(f.o)/g, 'd$0d')");
743     }
744 
745     /**
746      * @throws Exception if an error occurs
747      */
748     @Test
749     @Alerts("kid\\'s toys")
750     public void escapeQuote() throws Exception {
751         testEvaluate("\"kid's toys\".replace(/'/g, \"\\\\'\")");
752     }
753 
754     private static String buildHtml(final String script) {
755         return "<html><head><script>\n"
756             + LOG_TEXTAREA_FUNCTION
757             + "function test() {\n"
758             + script
759             + "\n}</script>\n"
760             + "</head>\n"
761             + "<body onload='test()'>\n"
762             + LOG_TEXTAREA
763             + "</body></html>";
764     }
765 
766     /**
767      * @throws Exception if an error occurs
768      */
769     @Test
770     @Alerts("INPUT")
771     public void test2() throws Exception {
772         final String html = buildHtml("var description = 'INPUT#BasisRenameInput';\n"
773                 + "if(description.match(/^\\s*([a-z0-9\\_\\-]+)/i)) {\n"
774                 + "  log(RegExp.$1);\n"
775                 + "}");
776         loadPageVerifyTextArea2(html);
777     }
778 
779     /**
780      * @throws Exception if an error occurs
781      */
782     @Test
783     @Alerts("a")
784     public void stackOverflow() throws Exception {
785         final String s = IOUtils.toString(getClass().getResourceAsStream("stackOverflow.txt"),
786                 ISO_8859_1);
787         final String html = buildHtml(
788                   "var s = '" + s + "';\n"
789                 + "s = s.replace(/(\\s*\\S+)*/, 'a');\n"
790                 + "log(s);\n");
791         loadPageVerifyTextArea2(html);
792     }
793 
794     /**
795      * Regression test for bug 2890953.
796      * @throws Exception if an error occurs
797      */
798     @Test
799     @Alerts("$$x$")
800     public void replaceDollarDollar() throws Exception {
801         testEvaluate("'x'.replace(/(x)/g, '$$$$x$$')");
802     }
803 
804     /**
805      * Original test resides in
806      * <a href="http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/test/com/google/gwt/emultest/java/lang/StringTest.java">StringTest</a>.
807      *
808      * @throws Exception if the test fails
809      */
810     @Test
811     @Alerts({"\\*\\[", "\\\\", "+1", "abcdef", "1\\1abc123\\123de1234\\1234f", "\n  \n", "x  x", "x\"\\", "$$x$"})
812     public void replaceAll() throws Exception {
813         final String html = "<html>\n"
814             + "<head>\n"
815             + "  <script>\n"
816             + LOG_TEXTAREA_FUNCTION
817             + "    function test() {\n"
818             + "      var regex, replacement, x1, x2, x3, x4, x5;\n"
819             + "      regex = $replaceAll('*[', "
820             + "'([/\\\\\\\\\\\\.\\\\*\\\\+\\\\?\\\\|\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}])', '\\\\\\\\$1');\n"
821             + "      log(regex);\n"
822             + "      replacement = "
823             + "$replaceAll($replaceAll('\\\\', '\\\\\\\\', '\\\\\\\\\\\\\\\\'), '\\\\$', '\\\\\\\\$');\n"
824             + "      log(replacement);\n"
825             + "      log($replaceAll('*[1', regex, '+'));\n"
826             + "      x1 = 'xxxabcxxdexf';\n"
827             + "      log($replaceAll(x1, 'x*', ''));\n"
828             + "      x2 = '1abc123de1234f';\n"
829             + "      log($replaceAll(x2, '([1234]+)', '$1\\\\\\\\$1'));\n"
830             + "      x3 = 'x  x';\n"
831             + "      log($replaceAll(x3, 'x', '\\n'));\n"
832             + "      x4 = 'x  \\n';\n"
833             + "      log($replaceAll(x4, '\\\\\\n', 'x'));\n"
834             + "      x5 = 'x';\n"
835             + "      log($replaceAll(x5, 'x', '\\\\x\\\\\"\\\\\\\\'));\n"
836             + "      log($replaceAll(x5, '(x)', '\\\\$\\\\$$1\\\\$'));\n"
837             + "    }\n"
838             + "    function $replaceAll(this$static, regex, replace){\n"
839             + "      replace = __translateReplaceString(replace);\n"
840             + "      return this$static.replace(RegExp(regex, 'g'), replace);\n"
841             + "    }\n"
842             + "    function __translateReplaceString(replaceStr){\n"
843             + "      var pos = 0;\n"
844             + "      while (0 <= (pos = replaceStr.indexOf('\\\\', pos))) {\n"
845             + "        if (replaceStr.charCodeAt(pos + 1) == 36) {\n"
846             + "          replaceStr = replaceStr.substr(0, pos - 0) + '$' + $substring(replaceStr, ++pos);\n"
847             + "        }\n"
848             + "        else {\n"
849             + "          replaceStr = replaceStr.substr(0, pos - 0) + $substring(replaceStr, ++pos);\n"
850             + "        }\n"
851             + "      }\n"
852             + "      return replaceStr;\n"
853             + "    }\n"
854             + "    function $substring(this$static, beginIndex){\n"
855             + "      return this$static.substr(beginIndex, this$static.length - beginIndex);\n"
856             + "    }\n"
857             + "  </script>\n"
858             + "</head><body onload='test()'>\n"
859             + LOG_TEXTAREA
860             + "</body></html>";
861 
862         loadPageVerifyTextArea2(html);
863     }
864 
865     /**
866      * Regression test for bug 2890953.
867      * @throws Exception if an error occurs
868      */
869     @Test
870     @Alerts("\\$0")
871     public void replaceBackslashDollar() throws Exception {
872         testEvaluate("'$0'.replace(/\\$/g, '\\\\$')");
873     }
874 
875     private void testEvaluate(final String expression) throws Exception {
876         final String script = "log(" + expression + ");";
877         final String html = buildHtml(script);
878 
879         loadPageVerifyTextArea2(html);
880     }
881 
882     /**
883      * Regression test for bug 2879412.
884      * In RegularExpression, backslash followed by a letter that has no special
885      * signification are ignored.
886      * @throws Exception if an error occurs
887      */
888     @Test
889     @Alerts("null")
890     public void backslash() throws Exception {
891         final String html = buildHtml("var regexp = /(\\https:\\/\\/)/;\n"
892                 + "var url = 'http://localhost/test.html';\n"
893                 + "log(url.match(regexp));");
894         loadPageVerifyTextArea2(html);
895     }
896 
897     /**
898      * Regression test for bug 2906293.
899      * @throws Exception if an error occurs
900      */
901     @Test
902     @Alerts("\\\\$11")
903     public void dollar() throws Exception {
904         testEvaluate("'\\\\$1'.replace(/\\$/g, '\\\\$1')");
905     }
906 
907     /**
908      * @throws Exception if the test fails
909      */
910     @Test
911     @Alerts({"foobar", "$0bar", "$1bar", "\\$1bar", "\\1", "cb", "cb", "a$$b", "a$1b", "a$`b", "a$'b"})
912     public void replaceString() throws Exception {
913         final String html = "<html>\n"
914             + "<head>\n"
915             + "  <script>\n"
916             + LOG_TITLE_FUNCTION
917             + "    function test() {\n"
918             + "      log($replace('bazbar', 'baz', 'foo'));\n"
919             + "      log($replace('foobar', 'foo', '$0'));\n"
920             + "      log($replace('foobar', 'foo', '$1'));\n"
921             + "      log($replace('foobar', 'foo', '\\\\$1'));\n"
922             + "      log($replace('*[)1', '*[)', '\\\\'));\n"
923             + "      log($replace('$ab', '$a', 'c'));\n"
924             + "      log($replace('^ab', '^a', 'c'));\n"
925             + "      log($replace('a[x]b', '[x]', '$$'));\n"
926             + "      log($replace('a[x]b', '[x]', '$1'));\n"
927             + "      log($replace('a[x]b', '[x]', '$`'));\n"
928             + "      log($replace('a[x]b', '[x]', \"$'\"));\n"
929             + "    }\n"
930             + "    function $replace(this$static, from, to){\n"
931             + "      var regex, replacement;\n"
932             + "      regex = $replaceAll(from, "
933             + "'([/\\\\\\\\\\\\.\\\\*\\\\+\\\\?\\\\|\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}$^])', '\\\\\\\\$1');\n"
934             + "      replacement = $replaceAll("
935             + "$replaceAll(to, '\\\\\\\\', '\\\\\\\\\\\\\\\\'), '\\\\$', '\\\\\\\\$');\n"
936             + "      return $replaceAll(this$static, regex, replacement);\n"
937             + "    }\n"
938             + "    function $replaceAll(this$static, regex, replace){\n"
939             + "      replace = __translateReplaceString(replace);\n"
940             + "      return this$static.replace(RegExp(regex, 'g'), replace);\n"
941             + "    }\n"
942             + "    function __translateReplaceString(replaceStr){\n"
943             + "      var pos = 0;\n"
944             + "      while (0 <= (pos = replaceStr.indexOf('\\\\', pos))) {\n"
945             + "        if (replaceStr.charCodeAt(pos + 1) == 36) {\n"
946             + "          replaceStr = replaceStr.substr(0, pos - 0) + '$' + $substring(replaceStr, ++pos);\n"
947             + "        }\n"
948             + "        else {\n"
949             + "          replaceStr = replaceStr.substr(0, pos - 0) + $substring(replaceStr, ++pos);\n"
950             + "        }\n"
951             + "      }\n"
952             + "      return replaceStr;\n"
953             + "    }\n"
954             + "    function $substring(this$static, beginIndex){\n"
955             + "      return this$static.substr(beginIndex, this$static.length - beginIndex);\n"
956             + "    }\n"
957             + "  </script>\n"
958             + "</head><body onload='test()'>\n"
959             + "</body></html>";
960 
961         loadPageVerifyTitle2(html);
962     }
963 
964     /**
965      * Regression test for bug 2949446.
966      * @throws Exception if an error occurs
967      */
968     @Test
969     @Alerts("[k]")
970     public void replace_group2digits() throws Exception {
971         testEvaluate("'abcdefghijkl'.replace(/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)/g, '[\\$11]')");
972     }
973 
974     /**
975      * When replacement reference is two digits but not so much groups exist, one digit replacement index is taken.
976      * @throws Exception if an error occurs
977      */
978     @Test
979     @Alerts("[a8]")
980     public void replace_group2digits_doesntExist() throws Exception {
981         testEvaluate("'abcdefghijkl'.replace(/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)/g, '[\\$18]')");
982     }
983 
984     /**
985      * When replacement reference is two digits but not so much groups exist, one digit replacement index is taken.
986      * @throws Exception if an error occurs
987      */
988     @Test
989     @Alerts(":")
990     public void replaceUnicode() throws Exception {
991         testEvaluate("'\\u003a'.replace(/[\\u00c0-\\u00c1]/, 'cat')");
992     }
993 
994     /**
995      * Regression test for bug 3005485.
996      * @throws Exception if an error occurs
997      */
998     @Test
999     @Alerts("part1:part2")
1000     public void replace_laterFilledGroup() throws Exception {
1001         testEvaluate("'#part1\\:part2'.replace(/^#|\\\\(:)/g, '$1')");
1002     }
1003 
1004     /**
1005      * Test for bug 2969230.
1006      * @throws Exception if an error occurs
1007      */
1008     @Test
1009     @Alerts("\\[ab\\]\\(xy\\)\\{z\\} \\{\\[\\(o\\}\\]\\)")
1010     public void replace_EscapeBrackets() throws Exception {
1011         testEvaluate("'[ab](xy){z} {[(o}])'.replace(/([.*+?^${}()|[\\]\\/\\\\])/g, \"\\\\$1\")");
1012     }
1013 
1014     /**
1015      * Test for bug 3000784.
1016      * @throws Exception if an error occurs
1017      */
1018     @Test
1019     @Alerts("><")
1020     public void replace_T() throws Exception {
1021         testEvaluate("'>' + 'a\\\\.b'.replace(/([\\u00C0-\\uFFFF]|[\\w]|\\\\.)+/, '') + '<'");
1022     }
1023 
1024     /**
1025      * Test from Prototype suite running very fast with Java 6 but taking ages with Java 7 &amp; 8.
1026      * @throws Exception if an error occurs
1027      */
1028     @Test
1029     @Timeout(1)
1030     @Alerts({"2200915", "2000915"})
1031     public void replace_huge() throws Exception {
1032         final String html = "<html><body><script>\n"
1033             + LOG_TITLE_FUNCTION
1034             + "String.prototype.times = function(n) {\n"
1035             + "  var s = '';\n"
1036             + "  for (var i =0; i < n; i++) {\n"
1037             + "    s += this;\n"
1038             + "  }\n"
1039             + "  return s;\n"
1040             + "}\n"
1041             + "var size = 100;\n"
1042             + "var longString = '\"' + '123456789\\\\\"'.times(size * 10) + '\"';\n"
1043             + "var object = '{' + longString + ': ' + longString + '},';\n"
1044             + "var huge = '[' + object.times(size) + '{\"test\": 123}]';\n"
1045             + "log(huge.length);\n"
1046             + "log(huge.replace(/\\\\./g, '@').length);\n"
1047             + "</script>\n"
1048             + "</body></html>";
1049 
1050         loadPageVerifyTitle2(html);
1051     }
1052 
1053     /**
1054      * @throws Exception if the test fails
1055      */
1056     @Test
1057     @Alerts({"true", "true", "true", "true", "true", "true", "true"})
1058     public void nullCharacter() throws Exception {
1059         final String html = "<html><head><script>\n"
1060             + LOG_TITLE_FUNCTION
1061             + "  function test() {\n"
1062             + "    var regex = new RegExp('[\\0-\\x08]');\n"
1063             + "    log(regex.test('\\0'));\n"
1064             + "    log('\\0'.match(regex) != null);\n"
1065             + "    regex = new RegExp('[\\\\0-\\x08]');\n"
1066             + "    log(regex.test('\\0'));\n"
1067             + "    log('\\0'.match(regex) != null);\n"
1068             + "    regex = new RegExp('[\\\\\\0-\\x08]');\n"
1069             + "    log(regex.test('\\0'));\n"
1070             + "    log('\\0'.match(regex) != null);\n"
1071             + "    log('\\0'.match(/^\\0/) != null);\n"
1072             + "  }\n"
1073             + "</script></head><body onload='test()'>\n"
1074             + "</body></html>";
1075 
1076         loadPageVerifyTitle2(html);
1077     }
1078 
1079     /**
1080      * @throws Exception if the test fails
1081      */
1082     @Test
1083     @Alerts("abc")
1084     public void specialBrackets1() throws Exception {
1085         // [] matches no character in JS
1086         testEvaluate("'abc'.replace(/[]/, 'x')");
1087     }
1088 
1089     /**
1090      * @throws Exception if the test fails
1091      */
1092     @Test
1093     @Alerts("")
1094     public void specialBrackets2() throws Exception {
1095         // [] matches no character in JS
1096         testEvaluate("'abc'.match(/[]*/)[0]");
1097     }
1098 
1099     /**
1100      * @throws Exception if the test fails
1101      */
1102     @Test
1103     @Alerts("xaxbxcx")
1104     public void specialBrackets3() throws Exception {
1105         // [] matches no character in JS
1106         testEvaluate("'abc'.replace(/[]*/g, 'x')");
1107     }
1108 
1109     /**
1110      * @throws Exception if the test fails
1111      */
1112     @Test
1113     @Alerts("xaxbxxcx*xdx")
1114     public void specialBrackets4() throws Exception {
1115         // [] matches no character in JS
1116         testEvaluate("'ab]c*d'.replace(/[]*]*/g, 'x')");
1117     }
1118 
1119     /**
1120      * @throws Exception if the test fails
1121      */
1122     @Test
1123     @Alerts("xxx")
1124     public void specialBrackets5() throws Exception {
1125         // [^] matches any character in JS
1126         testEvaluate("'abc'.replace(/[^]/g, 'x')");
1127     }
1128 
1129     /**
1130      * @throws Exception if the test fails
1131      */
1132     @Test
1133     @Alerts("ab\nc")
1134     public void specialBrackets6() throws Exception {
1135         // [^] matches any character in JS
1136         testEvaluate("'ab\\nc'.match(/[^]*/)[0]");
1137     }
1138 
1139     /**
1140      * @throws Exception if the test fails
1141      */
1142     @Test
1143     @Alerts("axcd")
1144     public void specialBrackets7() throws Exception {
1145         // [^] matches any character in JS
1146         testEvaluate("'ab]cd'.replace(/[^]]/g, 'x')");
1147     }
1148 
1149     /**
1150      * @throws Exception if the test fails
1151      */
1152     @Test
1153     @Alerts("xcd")
1154     public void specialBrackets8() throws Exception {
1155         // [^] matches any character in JS
1156         testEvaluate("'abdcd'.replace(/a[^]d/g, 'x')");
1157     }
1158 
1159     /**
1160      * @throws Exception if the test fails
1161      */
1162     @Test
1163     @Alerts("x abdu")
1164     public void specialBrackets9() throws Exception {
1165         // [^] matches any character in JS
1166         testEvaluate("'abdo abdu'.replace(/a[^]d[o]/g, 'x')");
1167     }
1168 
1169     /**
1170      * @throws Exception if the test fails
1171      */
1172     @Test
1173     @Alerts("axbxc d efg 1 23")
1174     public void regExMinusInRangeBorderCase1() throws Exception {
1175         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-]+/g, 'x')");
1176     }
1177 
1178     /**
1179      * @throws Exception if the test fails
1180      */
1181     @Test
1182     @Alerts("axbxcxdxefgx1x23")
1183     public void regExMinusInRangeBorderCase2() throws Exception {
1184         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\s]+/g, 'x')");
1185     }
1186 
1187     /**
1188      * @throws Exception if the test fails
1189      */
1190     @Test
1191     @Alerts("x x x x x")
1192     public void regExMinusInRangeBorderCase3() throws Exception {
1193         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\S]+/g, 'x')");
1194     }
1195 
1196     /**
1197      * @throws Exception if the test fails
1198      */
1199     @Test
1200     @Alerts("x x x x x")
1201     public void regExMinusInRangeBorderCase4() throws Exception {
1202         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\w]+/g, 'x')");
1203     }
1204 
1205     /**
1206      * @throws Exception if the test fails
1207      */
1208     @Test
1209     @Alerts("axbxcxdxefgx1x23")
1210     public void regExMinusInRangeBorderCase5() throws Exception {
1211         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\W]+/g, 'x')");
1212     }
1213 
1214     /**
1215      * @throws Exception if the test fails
1216      */
1217     @Test
1218     @Alerts("axbxc d efg x x")
1219     public void regExMinusInRangeBorderCase6() throws Exception {
1220         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\d]+/g, 'x')");
1221     }
1222 
1223     /**
1224      * @throws Exception if the test fails
1225      */
1226     @Test
1227     @Alerts("x1x23")
1228     public void regExMinusInRangeBorderCase7() throws Exception {
1229         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\D]+/g, 'x')");
1230     }
1231 
1232     /**
1233      * @throws Exception if the test fails
1234      */
1235     @Test
1236     @Alerts("x-bxc d efg 1 23")
1237     public void regExMinusInRangeBorderCase8() throws Exception {
1238         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\a]+/g, 'x')");
1239     }
1240 
1241     /**
1242      * @throws Exception if the test fails
1243      */
1244     @Test
1245     @Alerts("https://www.youtube.com/watch?v=1234567,https,1234567")
1246     public void realWorld1() throws Exception {
1247         testEvaluate("'https://www.youtube.com/watch?v=1234567'.match(/^(?:(https?):\\/\\/)?(?:(?:www|m)\\.)?youtube\\.com\\/watch.*v=([a-zA-Z0-9_-]+)/)");
1248     }
1249 
1250     /**
1251      * @throws Exception if the test fails
1252      */
1253     @Test
1254     @Alerts("http://m.youtu.be/abcd,http,abcd")
1255     public void realWorld2() throws Exception {
1256         testEvaluate("'http://m.youtu.be/abcd'.match(/^(?:(https?):\\/\\/)?(?:(?:www|m)\\.)?youtu\\.be\\/([a-zA-Z0-9_-]+)/)");
1257     }
1258 }