|
1 | 1 | package eu.righettod;
|
2 | 2 |
|
| 3 | + |
3 | 4 | import org.apache.commons.csv.CSVFormat;
|
4 | 5 | import org.apache.commons.csv.CSVRecord;
|
5 | 6 | import org.apache.commons.validator.routines.InetAddressValidator;
|
|
30 | 31 |
|
31 | 32 | import javax.crypto.Mac;
|
32 | 33 | import javax.crypto.spec.SecretKeySpec;
|
| 34 | +import javax.json.Json; |
| 35 | +import javax.json.JsonReader; |
33 | 36 | import javax.xml.parsers.DocumentBuilder;
|
34 | 37 | import javax.xml.parsers.DocumentBuilderFactory;
|
35 | 38 | import javax.xml.parsers.ParserConfigurationException;
|
@@ -737,4 +740,113 @@ public static Map<String, Object> ensureSerializedObjectIntegrity(ProcessingMode
|
737 | 740 | }
|
738 | 741 | return results;
|
739 | 742 | }
|
| 743 | + |
| 744 | + /** |
| 745 | + * Apply a collection of validations on a JSON string provided:<br> |
| 746 | + * - Real JSON structure.<br> |
| 747 | + * - Contain less than a specified number of deepness for nested objects or arrays.<br> |
| 748 | + * - Contain less than a specified number of items in any arrays.<br><br> |
| 749 | + * |
| 750 | + * <b>Note:</b> I decided to use a parsing approach using only string processing to prevent any StackOverFlow or OutOfMemory error that can be abused.<br><br> |
| 751 | + * I used the following assumption: |
| 752 | + * <ul> |
| 753 | + * <li>The character <code>{</code> identify the beginning of an object.</li> |
| 754 | + * <li>The character <code>}</code> identify the end of an object.</li> |
| 755 | + * <li>The character <code>[</code> identify the beginning of an array.</li> |
| 756 | + * <li>The character <code>]</code> identify the end of an array.</li> |
| 757 | + * <li>The character <code>"</code> identify the delimiter of a string.</li> |
| 758 | + * <li>The character sequence <code>\"</code> identify the escaping of an double quote.</li> |
| 759 | + * </ul> |
| 760 | + * |
| 761 | + * @param json String containing the JSON data to validate. |
| 762 | + * @param maxItemsByArraysCount Maximum number of items allowed in an array. |
| 763 | + * @param maxDeepnessAllowed Maximum number nested objects or arrays allowed. |
| 764 | + * @return True only if the string pass all validations. |
| 765 | + * @see "https://javaee.github.io/jsonp/" |
| 766 | + * @see "https://community.f5.com/discussions/technicalforum/disable-buffer-overflow-in-json-parameters/124306" |
| 767 | + * @see "https://github.com/InductiveComputerScience/pbJson/issues/2" |
| 768 | + */ |
| 769 | + public static boolean isJSONSafe(String json, int maxItemsByArraysCount, int maxDeepnessAllowed) { |
| 770 | + boolean isSafe = false; |
| 771 | + |
| 772 | + try { |
| 773 | + //Step 1: Analyse the JSON string |
| 774 | + int currentDeepness = 0; |
| 775 | + int currentArrayItemsCount = 0; |
| 776 | + int maxDeepnessReached = 0; |
| 777 | + int maxArrayItemsCountReached = 0; |
| 778 | + boolean currentlyInArray = false; |
| 779 | + boolean currentlyInString = false; |
| 780 | + int currentNestedArrayLevel = 0; |
| 781 | + String jsonEscapedDoubleQuote = "\\\"";//Escaped double quote must not be considered as a string delimiter |
| 782 | + String work = json.replace(jsonEscapedDoubleQuote, "'"); |
| 783 | + for (char c : work.toCharArray()) { |
| 784 | + switch (c) { |
| 785 | + case '{': { |
| 786 | + if (!currentlyInString) { |
| 787 | + currentDeepness++; |
| 788 | + } |
| 789 | + break; |
| 790 | + } |
| 791 | + case '}': { |
| 792 | + if (!currentlyInString) { |
| 793 | + currentDeepness--; |
| 794 | + } |
| 795 | + break; |
| 796 | + } |
| 797 | + case '[': { |
| 798 | + if (!currentlyInString) { |
| 799 | + currentDeepness++; |
| 800 | + if (currentlyInArray) { |
| 801 | + currentNestedArrayLevel++; |
| 802 | + } |
| 803 | + currentlyInArray = true; |
| 804 | + } |
| 805 | + break; |
| 806 | + } |
| 807 | + case ']': { |
| 808 | + if (!currentlyInString) { |
| 809 | + currentDeepness--; |
| 810 | + currentArrayItemsCount = 0; |
| 811 | + if (currentNestedArrayLevel > 0) { |
| 812 | + currentNestedArrayLevel--; |
| 813 | + } |
| 814 | + if (currentNestedArrayLevel == 0) { |
| 815 | + currentlyInArray = false; |
| 816 | + } |
| 817 | + } |
| 818 | + break; |
| 819 | + } |
| 820 | + case '"': { |
| 821 | + currentlyInString = !currentlyInString; |
| 822 | + break; |
| 823 | + } |
| 824 | + case ',': { |
| 825 | + if (!currentlyInString && currentlyInArray) { |
| 826 | + currentArrayItemsCount++; |
| 827 | + } |
| 828 | + break; |
| 829 | + } |
| 830 | + } |
| 831 | + if (currentDeepness > maxDeepnessReached) { |
| 832 | + maxDeepnessReached = currentDeepness; |
| 833 | + } |
| 834 | + if (currentArrayItemsCount > maxArrayItemsCountReached) { |
| 835 | + maxArrayItemsCountReached = currentArrayItemsCount; |
| 836 | + } |
| 837 | + } |
| 838 | + //Step 2: Apply validation against the value specified as limits |
| 839 | + isSafe = ((maxItemsByArraysCount > maxArrayItemsCountReached) && (maxDeepnessAllowed > maxDeepnessReached)); |
| 840 | + |
| 841 | + //Step 3: If the content is safe then ensure that it is valid JSON structure using the "Java API for JSON Processing" (JSR 374) parser reference implementation. |
| 842 | + if (isSafe) { |
| 843 | + JsonReader reader = Json.createReader(new StringReader(json)); |
| 844 | + isSafe = (reader.read() != null); |
| 845 | + } |
| 846 | + |
| 847 | + } catch (Exception e) { |
| 848 | + isSafe = false; |
| 849 | + } |
| 850 | + return isSafe; |
| 851 | + } |
740 | 852 | }
|
0 commit comments