|
30 | 30 | import java.time.Duration;
|
31 | 31 | import java.util.ArrayList;
|
32 | 32 | import java.util.Arrays;
|
| 33 | +import java.util.Collections; |
33 | 34 | import java.util.EnumSet;
|
34 | 35 | import java.util.Enumeration;
|
35 | 36 | import java.util.List;
|
|
88 | 89 | import org.junit.jupiter.api.Test;
|
89 | 90 | import org.junit.jupiter.api.extension.ExtendWith;
|
90 | 91 | import org.junit.jupiter.params.ParameterizedTest;
|
| 92 | +import org.junit.jupiter.params.provider.Arguments; |
| 93 | +import org.junit.jupiter.params.provider.MethodSource; |
91 | 94 | import org.junit.jupiter.params.provider.ValueSource;
|
92 | 95 | import org.slf4j.Logger;
|
93 | 96 | import org.slf4j.LoggerFactory;
|
94 | 97 |
|
95 | 98 | import static org.hamcrest.MatcherAssert.assertThat;
|
96 | 99 | import static org.hamcrest.Matchers.containsString;
|
| 100 | +import static org.hamcrest.Matchers.hasItem; |
97 | 101 | import static org.hamcrest.Matchers.is;
|
98 | 102 | import static org.hamcrest.Matchers.not;
|
99 | 103 | import static org.hamcrest.Matchers.nullValue;
|
@@ -667,6 +671,149 @@ public void testBadUtf8ParamExtraction() throws Exception
|
667 | 671 | assertThat(responses, startsWith("HTTP/1.1 200"));
|
668 | 672 | }
|
669 | 673 |
|
| 674 | + public static Stream<Arguments> queryBehaviors() |
| 675 | + { |
| 676 | + List<Arguments> cases = new ArrayList<>(); |
| 677 | + |
| 678 | + // Normal cases |
| 679 | + cases.add(Arguments.of("param=aaa", 200, "param", "aaa")); |
| 680 | + cases.add(Arguments.of("param=aaa&other=foo", 200, "param", "aaa")); |
| 681 | + cases.add(Arguments.of("param=", 200, "param", "")); |
| 682 | + cases.add(Arguments.of("param=&other=foo", 200, "param", "")); |
| 683 | + cases.add(Arguments.of("param=%E2%9C%94", 200, "param", "✔")); |
| 684 | + cases.add(Arguments.of("param=%E2%9C%94&other=foo", 200, "param", "✔")); |
| 685 | + |
| 686 | + // Truncated / Insufficient Hex cases |
| 687 | + cases.add(Arguments.of("param=%E2%9C%9", 400, "param", "")); |
| 688 | + cases.add(Arguments.of("param=%E2%9C%9&other=foo", 400, "param", "")); |
| 689 | + cases.add(Arguments.of("param=%E2%9C%", 400, "param", "")); |
| 690 | + cases.add(Arguments.of("param=%E2%9C%&other=foo", 400, "param", "")); |
| 691 | + cases.add(Arguments.of("param=%E2%9C", 200, "param", "�")); |
| 692 | + cases.add(Arguments.of("param=%E2%9C&other=foo", 200, "param", "�")); |
| 693 | + cases.add(Arguments.of("param=%E2%9", 400, "param", "")); |
| 694 | + cases.add(Arguments.of("param=%E2%9&other=foo", 400, "param", "")); |
| 695 | + cases.add(Arguments.of("param=%E2%", 400, "param", "")); |
| 696 | + cases.add(Arguments.of("param=%E2%&other=foo", 400, "param", "")); |
| 697 | + cases.add(Arguments.of("param=%E2", 200, "param", "�")); |
| 698 | + cases.add(Arguments.of("param=%E2&other=foo", 200, "param", "�")); |
| 699 | + |
| 700 | + // Tokenized cases |
| 701 | + cases.add(Arguments.of("param=%%TOK%%", 400, "param", "")); |
| 702 | + cases.add(Arguments.of("param=%%TOK%%&other=foo", 400, "param", "")); |
| 703 | + |
| 704 | + // Bad Hex |
| 705 | + cases.add(Arguments.of("param=%xx", 400, "param", "")); |
| 706 | + cases.add(Arguments.of("param=%xx&other=foo", 400, "param", "")); |
| 707 | + |
| 708 | + // Overlong UTF-8 Encoding |
| 709 | + cases.add(Arguments.of("param=%C0%AF", 400, "param", "")); |
| 710 | + cases.add(Arguments.of("param=%C0%AF&other=foo", 400, "param", "")); |
| 711 | + |
| 712 | + // Out of range |
| 713 | + cases.add(Arguments.of("param=%F4%90%80%80", 400, "param", "")); |
| 714 | + cases.add(Arguments.of("param=%F4%90%80%80&other=foo", 400, "param", "")); |
| 715 | + |
| 716 | + // Long surrogate |
| 717 | + cases.add(Arguments.of("param=%ED%A0%80", 400, "param", "")); |
| 718 | + cases.add(Arguments.of("param=%ED%A0%80&other=foo", 400, "param", "")); |
| 719 | + |
| 720 | + // Standalone continuations |
| 721 | + cases.add(Arguments.of("param=%80", 400, "param", "")); |
| 722 | + cases.add(Arguments.of("param=%80&other=foo", 400, "param", "")); |
| 723 | + |
| 724 | + // Truncated sequence |
| 725 | + cases.add(Arguments.of("param=%E2%82", 200, "param", "�")); |
| 726 | + cases.add(Arguments.of("param=%E2%82&other=foo", 200, "param", "�")); |
| 727 | + |
| 728 | + // C1 never starts UTF-8 |
| 729 | + cases.add(Arguments.of("param=%C1%BF", 400, "param", "")); |
| 730 | + cases.add(Arguments.of("param=%C1%BF&other=foo", 400, "param", "")); |
| 731 | + |
| 732 | + // E0 must be followed by A0-BF |
| 733 | + cases.add(Arguments.of("param=%E0%9F%80", 400, "param", "")); |
| 734 | + cases.add(Arguments.of("param=%E0%9F%80&other=foo", 400, "param", "")); |
| 735 | + |
| 736 | + // Community Examples |
| 737 | + cases.add(Arguments.of("param=f_%e0%b8", 200, "param", "f_�")); |
| 738 | + cases.add(Arguments.of("param=f_%e0%b8&other=foo", 200, "param", "f_�")); |
| 739 | + cases.add(Arguments.of("param=%£", 400, "param", "")); |
| 740 | + cases.add(Arguments.of("param=%£&other=foo", 400, "param", "")); |
| 741 | + |
| 742 | + // Extra ampersands |
| 743 | + cases.add(Arguments.of("param=aaa&&&", 200, "param", "aaa")); |
| 744 | + cases.add(Arguments.of("&&¶m=aaa", 200, "param", "aaa")); |
| 745 | + cases.add(Arguments.of("&¶m=aaa&&other=foo", 200, "param", "aaa")); |
| 746 | + cases.add(Arguments.of("param=aaa&&other=foo&&", 200, "param", "aaa")); |
| 747 | + |
| 748 | + // Encoded ampersands |
| 749 | + cases.add(Arguments.of("param=aaa%26&other=foo", 200, "param", "aaa&")); |
| 750 | + cases.add(Arguments.of("param=aaa&%26other=foo", 200, "&other", "foo")); |
| 751 | + |
| 752 | + // pct-encoded parameter names ("帽子" means "hat" in japanese) |
| 753 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD%90=Beret", 200, "帽子", "Beret")); |
| 754 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD%90=Beret&other=foo", 200, "帽子", "Beret")); |
| 755 | + cases.add(Arguments.of("other=foo&%E5%B8%BD%E5%AD%90=Beret", 200, "帽子", "Beret")); |
| 756 | + |
| 757 | + // truncated pct-encoded parameter names |
| 758 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD%9=Beret", 200, "帽孝Beret", "")); |
| 759 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD%=Beret", 400, "帽子", "")); |
| 760 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD=Beret", 200, "帽�", "Beret")); |
| 761 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD%9=Beret&other=foo", 200, "帽孝Beret", "")); |
| 762 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD%=Beret&other=foo", 400, "帽子", "")); |
| 763 | + cases.add(Arguments.of("%E5%B8%BD%E5%AD=Beret&other=foo", 200, "帽�", "Beret")); |
| 764 | + |
| 765 | + // raw unicode parameter names (strange replacement logic here) |
| 766 | + cases.add(Arguments.of("€=currency", 200, "?", "currency")); |
| 767 | + cases.add(Arguments.of("帽子=Beret", 200, "??", "Beret")); |
| 768 | + |
| 769 | + // invalid pct-encoded parameter name |
| 770 | + cases.add(Arguments.of("foo%xx=abc", 400, "foo�", "")); |
| 771 | + cases.add(Arguments.of("foo%x=abc", 400, "foo�", "")); |
| 772 | + cases.add(Arguments.of("foo%=abc", 400, "foo�", "")); |
| 773 | + |
| 774 | + return cases.stream(); |
| 775 | + } |
| 776 | + |
| 777 | + @ParameterizedTest |
| 778 | + @MethodSource("queryBehaviors") |
| 779 | + public void testQueryExtractionBehavior(String inputQuery, int expectedStatus, String expectedKey, String expectedValue) throws Exception |
| 780 | + { |
| 781 | + _handler._checker = (request, response) -> |
| 782 | + { |
| 783 | + switch (expectedStatus) |
| 784 | + { |
| 785 | + case 200: |
| 786 | + { |
| 787 | + // Verify that parameter exists |
| 788 | + List<String> paramNames = Collections.list(request.getParameterNames()); |
| 789 | + assertThat(paramNames, hasItem(expectedKey)); |
| 790 | + |
| 791 | + // Get value |
| 792 | + String value = request.getParameter(expectedKey); |
| 793 | + assertThat(value, is(expectedValue)); |
| 794 | + response.setStatus(200); |
| 795 | + return true; |
| 796 | + } |
| 797 | + default: |
| 798 | + { |
| 799 | + // We are expecting an exception, rethrow it. |
| 800 | + throw assertThrows(RuntimeException.class, () -> request.getParameter(expectedKey)); |
| 801 | + } |
| 802 | + } |
| 803 | + }; |
| 804 | + |
| 805 | + //Send a request with query string with illegal hex code to cause |
| 806 | + //an exception parsing the params |
| 807 | + String rawRequest = String.format("GET /?%s HTTP/1.1\r\n" + |
| 808 | + "Host: whatever\r\n" + |
| 809 | + "Connection: close\n" + |
| 810 | + "\n", inputQuery); |
| 811 | + |
| 812 | + String rawResponse = _connector.getResponse(rawRequest); |
| 813 | + HttpTester.Response response = HttpTester.parseResponse(rawResponse); |
| 814 | + assertThat(response.getStatus(), is(expectedStatus)); |
| 815 | + } |
| 816 | + |
670 | 817 | @Test
|
671 | 818 | public void testEncodedParamExtraction() throws Exception
|
672 | 819 | {
|
@@ -2412,3 +2559,4 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
|
2412 | 2559 | }
|
2413 | 2560 | }
|
2414 | 2561 | }
|
| 2562 | + |
0 commit comments