├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── RELEASE-checklist.sh ├── docs ├── JSON-Sanitizer-Arch.graffle ├── JSON-Sanitizer-Arch.png ├── contact.md └── getting_started.md ├── pom.xml └── src ├── main └── java │ └── com │ └── google │ └── json │ ├── EvalMinifier.java │ ├── JsonSanitizer.java │ └── package-info.java ├── site ├── resources │ └── images │ │ └── OWASP_Project_Header.jpg └── site.xml └── test └── java └── com └── google └── json ├── EvalMinifierTest.java ├── FuzzyTest.java └── JsonSanitizerTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .settings 4 | .project 5 | .tern-project 6 | target 7 | 8 | # Intellij 9 | .idea 10 | .iml 11 | 12 | # sonarqube 13 | .sonar_lock 14 | 15 | # Emacs 16 | *~ 17 | \#*\# 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk8 5 | - openjdk11 6 | - openjdk13 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json-sanitizer [](https://travis-ci.org/OWASP/json-sanitizer) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/json-sanitizer.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:json-sanitizer) 2 | 3 | 4 | Given JSON-like content, The JSON Sanitizer converts it to valid JSON. 5 | 6 | [Getting Started](https://github.com/OWASP/json-sanitizer/blob/master/docs/getting_started.md) - [Contact](https://github.com/OWASP/json-sanitizer/blob/master/docs/contact.md) 7 | 8 | This can be attached at either end of a data-pipeline to help satisfy 9 | Postel's principle: 10 | 11 | > be conservative in what you do, be liberal in what you accept from others 12 | 13 | Applied to JSON-like content from others, it will produce well-formed JSON 14 | that should satisfy any parser you use. 15 | 16 | Applied to your output before you send, it will coerce minor mistakes in 17 | encoding and make it easier to embed your JSON in HTML and XML. 18 | 19 | 20 | ## Motivation 21 | 22 | ![Architecture](https://github.com/OWASP/json-sanitizer/blob/master/docs/JSON-Sanitizer-Arch.png) 23 | 24 | Many applications have large amounts of code that uses ad-hoc methods 25 | to generate JSON outputs. 26 | 27 | Frequently these outputs all pass through a small amount of framework 28 | code before being sent over the network. This small amount of 29 | framework code can use this library to make sure that the ad-hoc 30 | outputs are standards compliant and safe to pass to (overly) powerful 31 | deserializers like Javascript's `eval` operator. 32 | 33 | Applications also often have web service APIs that receive JSON from a 34 | variety of sources. When this JSON is created using ad-hoc methods, 35 | this library can massage it into a form that is easy to parse. 36 | 37 | By hooking this library into the code that sends and receives requests 38 | and responses, this library can help software architects ensure 39 | system-wide security and well-formedness guarantees. 40 | 41 | 42 | ## Input 43 | 44 | The sanitizer takes JSON like content, and interprets it as JS eval would. 45 | Specifically, it deals with these non-standard constructs. 46 | 47 | | Construct | Policy | 48 | |---------------|---------------------------------------------------------------| 49 | | `'...'` | Single quoted strings are converted to JSON strings. | 50 | | `\xAB` | Hex escapes are converted to JSON unicode escapes. | 51 | | `\012` | Octal escapes are converted to JSON unicode escapes. | 52 | | `0xAB` | Hex integer literals are converted to JSON decimal numbers. | 53 | | `012` | Octal integer literals are converted to JSON decimal numbers. | 54 | | `+.5` | Decimal numbers are coerced to JSON's stricter format. | 55 | | `[0,,2]` | Elisions in arrays are filled with `null`. | 56 | | `[1,2,3,]` | Trailing commas are removed. | 57 | | `{foo:"bar"}` | Unquoted property names are quoted. | 58 | | `//comments` | JS style line and block comments are removed. | 59 | | `(...)` | Grouping parentheses are removed. | 60 | 61 | The sanitizer fixes missing punctuation, end quotes, and mismatched or 62 | missing close brackets. If an input contains only white-space then 63 | the valid JSON string `null` is substituted. 64 | 65 | 66 | ## Output 67 | 68 | The output is well-formed JSON as defined by 69 | [RFC 4627](http://www.ietf.org/rfc/rfc4627.txt). 70 | The output satisfies these additional properties: 71 | 72 | * The output will not contain the substrings (case-insensitively) `""` and can thus be embedded inside an XML CDATA section without further encoding. 74 | * The output is a valid Javascript expression, so can be parsed by Javascript's `eval` builtin (after being wrapped in parentheses) or by `JSON.parse`. Specifically, the output will not contain any string literals with embedded JS newlines (U+2028 Paragraph separator or U+2029 Line separator). 75 | * The output contains only valid Unicode [scalar values](http://www.unicode.org/glossary/#unicode_scalar_value) (no isolated [UTF-16 surrogates](http://www.unicode.org/glossary/#surrogate_pair)) that are [allowed in XML](http://www.w3.org/TR/xml/#charsets) unescaped. 76 | 77 | 78 | ## Security 79 | 80 | Since the output is well-formed JSON, passing it to `eval` will 81 | have no side-effects and no free variables, so is neither a code-injection 82 | vector, nor a vector for exfiltration of secrets. 83 | 84 | This library only ensures that the JSON string → Javascript object 85 | phase has no side effects and resolves no free variables, and cannot 86 | control how other client side code later interprets the resulting 87 | Javascript object. So if client-side code takes a part of the parsed 88 | data that is controlled by an attacker and passes it back through a 89 | powerful interpreter like `eval` or `innerHTML` then that client-side 90 | code might suffer unintended side-effects. 91 | 92 | ```JavaScript 93 | var myValue = eval(sanitizedJsonString); // safe 94 | var myEmbeddedValue = eval(myValue.foo); // possibly unsafe 95 | ``` 96 | 97 | Additionally, sanitizing JSON cannot protect an application from 98 | [Confused Deputy attacks](http://en.wikipedia.org/wiki/Confused_deputy_problem) 99 | 100 | ```JavaScript 101 | var myValue = JSON.parse(sanitizedJsonString); 102 | addToAdminstratorsGroup(myValue.propertyFromUntrustedSource); 103 | ``` 104 | 105 | # Performance 106 | 107 | The sanitize method will return the input string without allocating a new 108 | buffer when the input is already valid JSON that satisfies the properties 109 | above. Thus, if used on input that is usually well formed, it has minimal 110 | memory overhead. 111 | 112 | The sanitize method takes O(n) time where n is the length of the input 113 | in UTF-16 code-units. 114 | -------------------------------------------------------------------------------- /RELEASE-checklist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo This is not meant to be run automatically. 4 | 5 | exit 6 | 7 | set -e 8 | 9 | 10 | # Make sure the build is ok via 11 | mvn clean verify site javadoc:jar source:jar 12 | 13 | echo 14 | echo Browse to 15 | echo "file://$PWD/target/site" 16 | echo and check the findbugs and jacoco reports. 17 | 18 | echo 19 | echo Check https://central.sonatype.org/pages/apache-maven.html#nexus-staging-maven-plugin-for-deployment-and-release 20 | echo and make sure you have the relevant credentials in your ~/.m2/settings.xml 21 | 22 | echo 23 | echo Check https://search.maven.org/#search%7Cga%7C1%7Cjson-sanitizer 24 | echo and make sure that the current POM release number is max. 25 | 26 | # Pick a release version 27 | export VERSION="$(mvn -q \ 28 | -Dexec.executable="echo" \ 29 | -Dexec.args='${project.version}' \ 30 | --non-recursive \ 31 | org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ 32 | 2> /dev/null)"; 33 | 34 | export NEW_VERSION="$(echo "$VERSION" \ 35 | | perl -pe 's/^(\d+\.\d+)(?:\.(\d+))?.*/$1.".".($2||0)/e')"; 36 | 37 | export NEW_DEV_VERSION="$(echo "$VERSION" \ 38 | | perl -pe 's/^(\d+\.\d+)(?:\.(\d+))?.*/$1.".".($2+1)."-SNAPSHOT"/e')"; 39 | 40 | echo " 41 | VERSION =$VERSION 42 | NEW_VERSION =$NEW_VERSION 43 | NEW_DEV_VERSION=$NEW_DEV_VERSION" 44 | 45 | 46 | cd ~/work 47 | 48 | export RELEASE_CLONE="$PWD/json-san-release" 49 | 50 | rm -rf "$RELEASE_CLONE" 51 | 52 | cd "$(dirname "$RELEASE_CLONE")" 53 | 54 | git clone git@github.com:OWASP/json-sanitizer.git \ 55 | "$(basename "$RELEASE_CLONE")" 56 | 57 | cd "$RELEASE_CLONE" 58 | 59 | 60 | # Update the version 61 | # mvn release:update-versions puts -SNAPSHOT on the end no matter what 62 | # so this is a two step process. 63 | export VERSION_PLACEHOLDER=99999999999999-SNAPSHOT 64 | 65 | mvn release:update-versions \ 66 | -DautoVersionSubmodules=true \ 67 | -DdevelopmentVersion="$VERSION_PLACEHOLDER" 68 | 69 | find . -name pom.xml \ 70 | | xargs perl -i.placeholder -pe "s/$VERSION_PLACEHOLDER/$NEW_VERSION/g" 71 | 72 | 73 | # Make sure there're no snapshots left in any poms. 74 | find . -name pom.xml | xargs grep -- -SNAPSHOT 75 | 76 | 77 | # A dry run. 78 | mvn clean source:jar javadoc:jar verify -DperformRelease=true 79 | 80 | 81 | # Commit and tag 82 | git commit -am "Release candidate $NEW_VERSION" 83 | git tag -m "Release $NEW_VERSION" -s "v$NEW_VERSION" 84 | git push origin "v$NEW_VERSION" 85 | 86 | 87 | # Actually deploy. 88 | mvn clean source:jar javadoc:jar verify deploy:deploy -DperformRelease=true 89 | 90 | 91 | # Bump the development version. 92 | for f in $(find . -name pom.xml.placeholder); do 93 | mv "$f" "$(dirname "$f")"/"$(basename "$f" .placeholder)" 94 | done 95 | 96 | find . -name pom.xml \ 97 | | xargs perl -i -pe "s/$VERSION_PLACEHOLDER/$NEW_DEV_VERSION/" 98 | 99 | git commit -am "Bumped dev version" 100 | 101 | git push origin master 102 | 103 | # Now Release 104 | echo '1. Go to oss.sonatype.org' 105 | echo '2. Look under staging repositories for one named' 106 | echo ' commikesamuel-...' 107 | echo '3. Close it.' 108 | echo '4. Refresh until it is marked "Closed".' 109 | echo '5. Check that its OK.' 110 | echo '6. Release it.' 111 | -------------------------------------------------------------------------------- /docs/JSON-Sanitizer-Arch.graffle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ActiveLayerIndex 6 | 0 7 | ApplicationVersion 8 | 9 | com.omnigroup.OmniGraffle 10 | 139.16.0.171715 11 | 12 | AutoAdjust 13 | 14 | BackgroundGraphic 15 | 16 | Bounds 17 | {{0, 0}, {864, 648}} 18 | Class 19 | SolidGraphic 20 | ID 21 | 2 22 | Style 23 | 24 | shadow 25 | 26 | Draws 27 | NO 28 | 29 | stroke 30 | 31 | Draws 32 | NO 33 | 34 | 35 | 36 | BaseZoom 37 | 0 38 | CanvasOrigin 39 | {0, 0} 40 | CanvasSize 41 | {864, 648} 42 | ColumnAlign 43 | 1 44 | ColumnSpacing 45 | 36 46 | CreationDate 47 | 2012-10-12 13:59:45 +0000 48 | Creator 49 | Mike Samuel 50 | DisplayScale 51 | 1 0/72 in = 1 0/72 in 52 | GraphDocumentVersion 53 | 8 54 | GraphicsList 55 | 56 | 57 | Bounds 58 | {{110.6691752849257, 346.70419379945702}, {43, 38}} 59 | Class 60 | ShapedGraphic 61 | FitText 62 | YES 63 | Flow 64 | Resize 65 | FontInfo 66 | 67 | Color 68 | 69 | w 70 | 0 71 | 72 | Font 73 | Helvetica 74 | Size 75 | 12 76 | 77 | ID 78 | 72 79 | Line 80 | 81 | ID 82 | 57 83 | Position 84 | 0.44718816876411438 85 | RotationType 86 | 0 87 | 88 | Shape 89 | Rectangle 90 | Style 91 | 92 | fill 93 | 94 | Draws 95 | NO 96 | 97 | shadow 98 | 99 | Draws 100 | NO 101 | 102 | stroke 103 | 104 | Draws 105 | NO 106 | 107 | 108 | Text 109 | 110 | Text 111 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 112 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 113 | {\colortbl;\red255\green255\blue255;} 114 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 115 | 116 | \f0\fs24 \cf0 Valid\ 117 | JSON} 118 | 119 | Wrap 120 | NO 121 | 122 | 123 | Bounds 124 | {{126.56122535956533, 219.26202915750127}, {43, 38}} 125 | Class 126 | ShapedGraphic 127 | FitText 128 | YES 129 | Flow 130 | Resize 131 | FontInfo 132 | 133 | Color 134 | 135 | w 136 | 0 137 | 138 | Font 139 | Helvetica 140 | Size 141 | 12 142 | 143 | ID 144 | 71 145 | Line 146 | 147 | ID 148 | 48 149 | Position 150 | 0.44382554292678833 151 | RotationType 152 | 0 153 | 154 | Shape 155 | Rectangle 156 | Style 157 | 158 | fill 159 | 160 | Draws 161 | NO 162 | 163 | shadow 164 | 165 | Draws 166 | NO 167 | 168 | stroke 169 | 170 | Draws 171 | NO 172 | 173 | 174 | Text 175 | 176 | Text 177 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 178 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 179 | {\colortbl;\red255\green255\blue255;} 180 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 181 | 182 | \f0\fs24 \cf0 Valid\ 183 | JSON} 184 | 185 | Wrap 186 | NO 187 | 188 | 189 | Bounds 190 | {{119.91373209717889, 256.82069231550298}, {46, 38}} 191 | Class 192 | ShapedGraphic 193 | FitText 194 | YES 195 | Flow 196 | Resize 197 | FontInfo 198 | 199 | Color 200 | 201 | w 202 | 0 203 | 204 | Font 205 | Helvetica 206 | Size 207 | 12 208 | 209 | ID 210 | 70 211 | Line 212 | 213 | ID 214 | 49 215 | Position 216 | 0.41212114691734314 217 | RotationType 218 | 0 219 | 220 | Shape 221 | Rectangle 222 | Style 223 | 224 | fill 225 | 226 | Draws 227 | NO 228 | 229 | shadow 230 | 231 | Draws 232 | NO 233 | 234 | stroke 235 | 236 | Draws 237 | NO 238 | 239 | 240 | Text 241 | 242 | Text 243 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 244 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 245 | {\colortbl;\red255\green255\blue255;} 246 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 247 | 248 | \f0\fs24 \cf0 JSON-\ 249 | like} 250 | 251 | Wrap 252 | NO 253 | 254 | 255 | Bounds 256 | {{299.32918485652306, 296.61937063525545}, {73.670814514160156, 24}} 257 | Class 258 | ShapedGraphic 259 | FitText 260 | Clip 261 | Flow 262 | Clip 263 | FontInfo 264 | 265 | Color 266 | 267 | w 268 | 0 269 | 270 | Font 271 | Helvetica 272 | Size 273 | 12 274 | 275 | ID 276 | 69 277 | Line 278 | 279 | ID 280 | 50 281 | Position 282 | 0.20949088037014008 283 | RotationType 284 | 0 285 | 286 | Shape 287 | Rectangle 288 | Style 289 | 290 | fill 291 | 292 | Draws 293 | NO 294 | 295 | shadow 296 | 297 | Draws 298 | NO 299 | 300 | stroke 301 | 302 | Draws 303 | NO 304 | 305 | 306 | Text 307 | 308 | Text 309 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 310 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 311 | {\colortbl;\red255\green255\blue255;} 312 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 313 | 314 | \f0\fs24 \cf0 Safe to\ 315 | eval} 316 | 317 | Wrap 318 | NO 319 | 320 | 321 | Bounds 322 | {{299.28687944914219, 363.33215185035237}, {85, 24}} 323 | Class 324 | ShapedGraphic 325 | FitText 326 | YES 327 | Flow 328 | Resize 329 | FontInfo 330 | 331 | Color 332 | 333 | w 334 | 0 335 | 336 | Font 337 | Helvetica 338 | Size 339 | 12 340 | 341 | ID 342 | 68 343 | Line 344 | 345 | ID 346 | 60 347 | Position 348 | 0.49097132682800293 349 | RotationType 350 | 0 351 | 352 | Shape 353 | Rectangle 354 | Style 355 | 356 | fill 357 | 358 | Draws 359 | NO 360 | 361 | shadow 362 | 363 | Draws 364 | NO 365 | 366 | stroke 367 | 368 | Draws 369 | NO 370 | 371 | 372 | Text 373 | 374 | Align 375 | 0 376 | Text 377 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 378 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 379 | {\colortbl;\red255\green255\blue255;} 380 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 381 | 382 | \f0\fs24 \cf0 JSON- like } 383 | 384 | Wrap 385 | NO 386 | 387 | 388 | Bounds 389 | {{100.69519281328974, 386.31311970167781}, {46, 38}} 390 | Class 391 | ShapedGraphic 392 | FitText 393 | YES 394 | Flow 395 | Resize 396 | FontInfo 397 | 398 | Color 399 | 400 | w 401 | 0 402 | 403 | Font 404 | Helvetica 405 | Size 406 | 12 407 | 408 | ID 409 | 67 410 | Line 411 | 412 | ID 413 | 58 414 | Position 415 | 0.67719489336013794 416 | RotationType 417 | 0 418 | 419 | Shape 420 | Rectangle 421 | Style 422 | 423 | fill 424 | 425 | Draws 426 | NO 427 | 428 | shadow 429 | 430 | Draws 431 | NO 432 | 433 | stroke 434 | 435 | Draws 436 | NO 437 | 438 | 439 | Text 440 | 441 | Text 442 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 443 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 444 | {\colortbl;\red255\green255\blue255;} 445 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 446 | 447 | \f0\fs24 \cf0 JSON-\ 448 | like} 449 | 450 | Wrap 451 | NO 452 | 453 | 454 | Bounds 455 | {{299.32918485652306, 460.1308761337495}, {60.172775867962855, 38}} 456 | Class 457 | ShapedGraphic 458 | FitText 459 | Vertical 460 | Flow 461 | Resize 462 | FontInfo 463 | 464 | Color 465 | 466 | w 467 | 0 468 | 469 | Font 470 | Helvetica 471 | Size 472 | 12 473 | 474 | ID 475 | 66 476 | Line 477 | 478 | ID 479 | 61 480 | Position 481 | 0.50753980875015259 482 | RotationType 483 | 0 484 | 485 | Shape 486 | Rectangle 487 | Style 488 | 489 | fill 490 | 491 | Draws 492 | NO 493 | 494 | shadow 495 | 496 | Draws 497 | NO 498 | 499 | stroke 500 | 501 | Draws 502 | NO 503 | 504 | 505 | Text 506 | 507 | Align 508 | 0 509 | Text 510 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 511 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 512 | {\colortbl;\red255\green255\blue255;} 513 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 514 | 515 | \f0\fs24 \cf0 Valid\ 516 | JSON} 517 | 518 | 519 | 520 | Class 521 | LineGraphic 522 | Head 523 | 524 | ID 525 | 35 526 | 527 | ID 528 | 61 529 | Points 530 | 531 | {279.33466367638044, 421} 532 | {335, 431} 533 | {341, 522} 534 | {395.34297067622009, 513.36400867569091} 535 | 536 | Style 537 | 538 | stroke 539 | 540 | HeadArrow 541 | FilledArrow 542 | Legacy 543 | 544 | LineType 545 | 1 546 | TailArrow 547 | 0 548 | 549 | 550 | Tail 551 | 552 | ID 553 | 39 554 | Info 555 | 1 556 | 557 | 558 | 559 | Class 560 | LineGraphic 561 | Head 562 | 563 | ID 564 | 38 565 | 566 | ID 567 | 60 568 | Points 569 | 570 | {395.67906579716447, 399.82659997772811} 571 | {355, 395} 572 | {331, 363} 573 | {279.33466367638044, 355.5} 574 | 575 | Style 576 | 577 | stroke 578 | 579 | HeadArrow 580 | FilledArrow 581 | Legacy 582 | 583 | LineType 584 | 1 585 | TailArrow 586 | 0 587 | 588 | 589 | Tail 590 | 591 | ID 592 | 35 593 | 594 | 595 | 596 | Class 597 | LineGraphic 598 | Head 599 | 600 | ID 601 | 40 602 | Info 603 | 7 604 | 605 | ID 606 | 58 607 | Points 608 | 609 | {81.25817165204711, 347.90160766329228} 610 | {90, 397.09378238687788} 611 | {171.22983870967738, 409.33332091569906} 612 | 613 | Style 614 | 615 | stroke 616 | 617 | HeadArrow 618 | FilledArrow 619 | Legacy 620 | 621 | LineType 622 | 1 623 | TailArrow 624 | 0 625 | 626 | 627 | Tail 628 | 629 | ID 630 | 42 631 | 632 | 633 | 634 | Class 635 | LineGraphic 636 | Head 637 | 638 | ID 639 | 42 640 | 641 | ID 642 | 57 643 | Points 644 | 645 | {171.22983870967738, 367.6666567325592} 646 | {124, 363} 647 | {104.87752123391805, 332.29618632631673} 648 | 649 | Style 650 | 651 | stroke 652 | 653 | HeadArrow 654 | FilledArrow 655 | Legacy 656 | 657 | LineType 658 | 1 659 | TailArrow 660 | 0 661 | 662 | 663 | Tail 664 | 665 | ID 666 | 40 667 | Info 668 | 8 669 | 670 | 671 | 672 | Class 673 | LineGraphic 674 | Head 675 | 676 | ID 677 | 12 678 | 679 | ID 680 | 56 681 | Points 682 | 683 | {452, 143.74999627470964} 684 | {475, 143} 685 | {501.30948195306803, 133.33205783652318} 686 | 687 | Style 688 | 689 | stroke 690 | 691 | HeadArrow 692 | FilledArrow 693 | Legacy 694 | 695 | LineType 696 | 1 697 | TailArrow 698 | 0 699 | 700 | 701 | Tail 702 | 703 | ID 704 | 13 705 | Info 706 | 6 707 | 708 | 709 | 710 | Class 711 | LineGraphic 712 | Head 713 | 714 | ID 715 | 12 716 | 717 | ID 718 | 55 719 | Points 720 | 721 | {452, 81.250000000000057} 722 | {479, 80} 723 | {499.80430919008063, 88.558734793387586} 724 | 725 | Style 726 | 727 | stroke 728 | 729 | HeadArrow 730 | 0 731 | Legacy 732 | 733 | LineType 734 | 1 735 | TailArrow 736 | FilledArrow 737 | 738 | 739 | Tail 740 | 741 | ID 742 | 13 743 | Info 744 | 4 745 | 746 | 747 | 748 | Class 749 | LineGraphic 750 | Head 751 | 752 | ID 753 | 13 754 | Info 755 | 7 756 | 757 | ID 758 | 54 759 | Points 760 | 761 | {491.5, 247.49999100000005} 762 | {475, 207} 763 | {436.74999454617506, 175} 764 | 765 | Style 766 | 767 | stroke 768 | 769 | HeadArrow 770 | FilledArrow 771 | Legacy 772 | 773 | LineType 774 | 1 775 | TailArrow 776 | 0 777 | 778 | 779 | Tail 780 | 781 | ID 782 | 29 783 | Info 784 | 2 785 | 786 | 787 | 788 | Class 789 | LineGraphic 790 | Head 791 | 792 | ID 793 | 13 794 | Info 795 | 9 796 | 797 | ID 798 | 53 799 | Points 800 | 801 | {421.5, 247.49999100000005} 802 | {423, 211} 803 | {406.24999636411673, 175} 804 | 805 | Style 806 | 807 | stroke 808 | 809 | HeadArrow 810 | FilledArrow 811 | Legacy 812 | 813 | LineType 814 | 1 815 | TailArrow 816 | 0 817 | 818 | 819 | Tail 820 | 821 | ID 822 | 28 823 | Info 824 | 2 825 | 826 | 827 | 828 | Class 829 | LineGraphic 830 | Head 831 | 832 | ID 833 | 29 834 | 835 | ID 836 | 52 837 | Points 838 | 839 | {436.74999454617506, 175} 840 | {448.5, 225} 841 | {491.5, 247.49999100000005} 842 | 843 | Style 844 | 845 | stroke 846 | 847 | HeadArrow 848 | FilledArrow 849 | Legacy 850 | 851 | LineType 852 | 1 853 | TailArrow 854 | 0 855 | 856 | 857 | Tail 858 | 859 | ID 860 | 13 861 | Info 862 | 7 863 | 864 | 865 | 866 | Class 867 | LineGraphic 868 | Head 869 | 870 | ID 871 | 28 872 | 873 | ID 874 | 51 875 | Points 876 | 877 | {406.24999636411673, 175} 878 | {391, 220} 879 | {421.5, 247.49999100000005} 880 | 881 | Style 882 | 883 | stroke 884 | 885 | HeadArrow 886 | FilledArrow 887 | Legacy 888 | 889 | LineType 890 | 1 891 | TailArrow 892 | 0 893 | 894 | 895 | Tail 896 | 897 | ID 898 | 13 899 | Info 900 | 9 901 | 902 | 903 | 904 | Class 905 | LineGraphic 906 | Head 907 | 908 | ID 909 | 13 910 | Info 911 | 10 912 | 913 | ID 914 | 50 915 | Points 916 | 917 | {279.33464078819685, 290} 918 | {358, 283.5} 919 | {357, 158} 920 | {391, 143.74997764825827} 921 | 922 | Style 923 | 924 | stroke 925 | 926 | HeadArrow 927 | FilledArrow 928 | Legacy 929 | 930 | LineType 931 | 1 932 | TailArrow 933 | 0 934 | 935 | 936 | Tail 937 | 938 | ID 939 | 37 940 | Info 941 | 1 942 | 943 | 944 | 945 | Class 946 | LineGraphic 947 | Head 948 | 949 | ID 950 | 41 951 | Info 952 | 7 953 | 954 | ID 955 | 49 956 | Points 957 | 958 | {123.08035330888323, 275.09226132221039} 959 | {147, 276} 960 | {171.22983870967738, 277.33332091569901} 961 | 962 | Style 963 | 964 | stroke 965 | 966 | HeadArrow 967 | FilledArrow 968 | Legacy 969 | 970 | LineType 971 | 1 972 | TailArrow 973 | 0 974 | 975 | 976 | Tail 977 | 978 | ID 979 | 42 980 | 981 | 982 | 983 | Class 984 | LineGraphic 985 | Head 986 | 987 | ID 988 | 42 989 | 990 | ID 991 | 48 992 | Points 993 | 994 | {171.22983870967738, 235.66665673255918} 995 | {154, 238} 996 | {129, 239} 997 | {121.23882966806626, 243.32218390622361} 998 | 999 | Style 1000 | 1001 | stroke 1002 | 1003 | HeadArrow 1004 | FilledArrow 1005 | Legacy 1006 | 1007 | LineType 1008 | 1 1009 | TailArrow 1010 | 0 1011 | 1012 | 1013 | Tail 1014 | 1015 | ID 1016 | 41 1017 | Info 1018 | 8 1019 | 1020 | 1021 | 1022 | Class 1023 | LineGraphic 1024 | Head 1025 | 1026 | ID 1027 | 41 1028 | Info 1029 | 3 1030 | 1031 | ID 1032 | 46 1033 | Points 1034 | 1035 | {391, 81.249999999999943} 1036 | {339, 103} 1037 | {333, 234} 1038 | {279.83443326353478, 235.6511544230747} 1039 | 1040 | Style 1041 | 1042 | stroke 1043 | 1044 | HeadArrow 1045 | FilledArrow 1046 | Legacy 1047 | 1048 | LineType 1049 | 1 1050 | TailArrow 1051 | 0 1052 | 1053 | 1054 | Tail 1055 | 1056 | ID 1057 | 13 1058 | Info 1059 | 12 1060 | 1061 | 1062 | 1063 | Bounds 1064 | {{382, 326}, {244, 270}} 1065 | Class 1066 | ShapedGraphic 1067 | ID 1068 | 35 1069 | Magnets 1070 | 1071 | {-1.3176080288316621, -0.20417378593631377} 1072 | {-1.1867144524676341, -0.60785410347750501} 1073 | {-0.94369486381788892, -0.94192233339274922} 1074 | {-0.40796488463418634, -1.2693865791644696} 1075 | {-0.11661624787725521, -1.3282238550200669} 1076 | {0.17899121676342131, -1.3212645466756776} 1077 | {0.69802100751107521, -1.136021280207582} 1078 | {1.0050925327789377, -0.87610885739689082} 1079 | {1.2210278710939164, -0.53560141744507817} 1080 | {1.3209244875934554, 0.1814832469375634} 1081 | {1.1984627150128946, 0.58434993795405255} 1082 | {0.9640054102161133, 0.92112504282870944} 1083 | {0.42862070490838933, 1.2625616431620621} 1084 | {0.14408664482148789, 1.3255250711249706} 1085 | {-0.14335968870538496, 1.3256039241226492} 1086 | {-0.68034392078734385, 1.1466951491462058} 1087 | {-1.0026832406962103, 0.87886528755933724} 1088 | {-1.2224333705174724, 0.53238562189654492} 1089 | 1090 | Shape 1091 | Cloud 1092 | Style 1093 | 1094 | Text 1095 | 1096 | Text 1097 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1098 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1099 | {\colortbl;\red255\green255\blue255;} 1100 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1101 | 1102 | \f0\fs24 \cf0 Uncontrolled Web Services Client} 1103 | VerticalPad 1104 | 0 1105 | 1106 | 1107 | 1108 | Bounds 1109 | {{241.54435606925711, 276.20967741935482}, {47.999999999999993, 27.580645161290317}} 1110 | Class 1111 | ShapedGraphic 1112 | ID 1113 | 37 1114 | Magnets 1115 | 1116 | {0, 1} 1117 | {0, -1} 1118 | {1, 0} 1119 | {-1, 0} 1120 | 1121 | Rotation 1122 | 270 1123 | Shape 1124 | Bezier 1125 | ShapeData 1126 | 1127 | UnitPoints 1128 | 1129 | {-0.5, -0.5} 1130 | {-0.5, -0.5} 1131 | {0.5, -0.5} 1132 | {0.5, -0.5} 1133 | {0.5, -0.5} 1134 | {0.5, 0.49999500000000002} 1135 | {0.5, 0.49999500000000002} 1136 | {0.16666700000000001, 0.49999500000000002} 1137 | {-0.33333400000000002, 0.50000599999999995} 1138 | {-0.5, 0.49999500000000002} 1139 | {-0.49999900000000003, 0.49999500000000002} 1140 | {-0.5, -0.5} 1141 | 1142 | 1143 | Style 1144 | 1145 | fill 1146 | 1147 | Color 1148 | 1149 | b 1150 | 0.509161 1151 | g 1152 | 0.919519 1153 | r 1154 | 0.598275 1155 | 1156 | 1157 | stroke 1158 | 1159 | CornerRadius 1160 | 9 1161 | 1162 | 1163 | Text 1164 | 1165 | Text 1166 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1167 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1168 | {\colortbl;\red255\green255\blue255;} 1169 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1170 | 1171 | \f0\fs18 \cf0 Sanitizer} 1172 | 1173 | 1174 | 1175 | Bounds 1176 | {{241.5443789574407, 341.70967741935482}, {47.999999999999993, 27.580645161290317}} 1177 | Class 1178 | ShapedGraphic 1179 | ID 1180 | 38 1181 | Magnets 1182 | 1183 | {0, 1} 1184 | {0, -1} 1185 | {1, 0} 1186 | {-1, 0} 1187 | 1188 | Rotation 1189 | 270 1190 | Shape 1191 | Bezier 1192 | ShapeData 1193 | 1194 | UnitPoints 1195 | 1196 | {-0.5, -0.5} 1197 | {-0.5, -0.5} 1198 | {0.5, -0.5} 1199 | {0.5, -0.5} 1200 | {0.5, -0.5} 1201 | {0.5, 0.49999500000000002} 1202 | {0.5, 0.49999500000000002} 1203 | {0.16666700000000001, 0.49999500000000002} 1204 | {-0.33333400000000002, 0.50000599999999995} 1205 | {-0.5, 0.49999500000000002} 1206 | {-0.49999900000000003, 0.49999500000000002} 1207 | {-0.5, -0.5} 1208 | 1209 | 1210 | Style 1211 | 1212 | fill 1213 | 1214 | Color 1215 | 1216 | b 1217 | 0.509161 1218 | g 1219 | 0.919519 1220 | r 1221 | 0.598275 1222 | 1223 | 1224 | stroke 1225 | 1226 | CornerRadius 1227 | 9 1228 | 1229 | 1230 | Text 1231 | 1232 | Text 1233 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1234 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1235 | {\colortbl;\red255\green255\blue255;} 1236 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1237 | 1238 | \f0\fs18 \cf0 Sanitizer} 1239 | 1240 | 1241 | 1242 | Bounds 1243 | {{241.5443789574407, 407.20967741935482}, {47.999999999999993, 27.580645161290317}} 1244 | Class 1245 | ShapedGraphic 1246 | ID 1247 | 39 1248 | Magnets 1249 | 1250 | {0, 1} 1251 | {0, -1} 1252 | {1, 0} 1253 | {-1, 0} 1254 | 1255 | Rotation 1256 | 270 1257 | Shape 1258 | Bezier 1259 | ShapeData 1260 | 1261 | UnitPoints 1262 | 1263 | {-0.5, -0.5} 1264 | {-0.5, -0.5} 1265 | {0.5, -0.5} 1266 | {0.5, -0.5} 1267 | {0.5, -0.5} 1268 | {0.5, 0.49999500000000002} 1269 | {0.5, 0.49999500000000002} 1270 | {0.16666700000000001, 0.49999500000000002} 1271 | {-0.33333400000000002, 0.50000599999999995} 1272 | {-0.5, 0.49999500000000002} 1273 | {-0.49999900000000003, 0.49999500000000002} 1274 | {-0.5, -0.5} 1275 | 1276 | 1277 | Style 1278 | 1279 | fill 1280 | 1281 | Color 1282 | 1283 | b 1284 | 0.509161 1285 | g 1286 | 0.919519 1287 | r 1288 | 0.598275 1289 | 1290 | 1291 | stroke 1292 | 1293 | CornerRadius 1294 | 9 1295 | 1296 | 1297 | Text 1298 | 1299 | Text 1300 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1301 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1302 | {\colortbl;\red255\green255\blue255;} 1303 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1304 | 1305 | \f0\fs18 \cf0 Sanitizer} 1306 | 1307 | 1308 | 1309 | Bounds 1310 | {{171.22983870967741, 326}, {108.10483551025391, 125}} 1311 | Class 1312 | ShapedGraphic 1313 | ID 1314 | 40 1315 | Magnets 1316 | 1317 | {-0.42163697454145743, -1.2649110555648793} 1318 | {0.4216371064819644, -1.2649110555648793} 1319 | {1.2649110555648815, -0.42163684260095269} 1320 | {1.2649110555648815, 0.42163714417925147} 1321 | {0.42163699339010319, 1.2649110555648815} 1322 | {-0.42163733062650866, 1.2649109363555919} 1323 | {-1.2649110555648793, 0.42163676720638077} 1324 | {-1.2649109363555897, -0.42163717983737459} 1325 | 1326 | Shape 1327 | Rectangle 1328 | Text 1329 | 1330 | Text 1331 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1332 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1333 | {\colortbl;\red255\green255\blue255;} 1334 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1335 | 1336 | \f0\fs24 \cf0 Web Service Interface} 1337 | 1338 | 1339 | 1340 | Bounds 1341 | {{171.22983870967738, 194}, {108.10483551025391, 125}} 1342 | Class 1343 | ShapedGraphic 1344 | ID 1345 | 41 1346 | Magnets 1347 | 1348 | {-0.42163697454145743, -1.2649110555648804} 1349 | {0.4216371064819644, -1.2649110555648804} 1350 | {1.2649110555648815, -0.4216368426009538} 1351 | {1.2649110555648815, 0.42163714417925036} 1352 | {0.42163699339010319, 1.2649110555648804} 1353 | {-0.42163733062650866, 1.2649109363555908} 1354 | {-1.2649110555648793, 0.42163676720637966} 1355 | {-1.2649109363555897, -0.4216371798373757} 1356 | 1357 | Shape 1358 | Rectangle 1359 | Text 1360 | 1361 | Text 1362 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1363 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1364 | {\colortbl;\red255\green255\blue255;} 1365 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1366 | 1367 | \f0\fs24 \cf0 Servlet\ 1368 | Interface} 1369 | 1370 | 1371 | 1372 | Bounds 1373 | {{6.8951612903226049, 194}, {122.10483551025391, 158}} 1374 | Class 1375 | ShapedGraphic 1376 | ID 1377 | 42 1378 | Shape 1379 | Cloud 1380 | Style 1381 | 1382 | Text 1383 | 1384 | Text 1385 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1386 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1387 | {\colortbl;\red255\green255\blue255;} 1388 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1389 | 1390 | \f0\fs24 \cf0 Lots of Loosely Controlled Application Code} 1391 | VerticalPad 1392 | 0 1393 | 1394 | 1395 | 1396 | Bounds 1397 | {{-2.8421709430404007e-14, 188}, {285.00000000000006, 270}} 1398 | Class 1399 | ShapedGraphic 1400 | ID 1401 | 43 1402 | Shape 1403 | Rectangle 1404 | Style 1405 | 1406 | Text 1407 | 1408 | Text 1409 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1410 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1411 | {\colortbl;\red255\green255\blue255;} 1412 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1413 | 1414 | \f0\fs24 \cf0 Server} 1415 | 1416 | TextPlacement 1417 | 2 1418 | 1419 | 1420 | Bounds 1421 | {{461, 247.5}, {61, 36}} 1422 | Class 1423 | ShapedGraphic 1424 | ID 1425 | 29 1426 | Magnets 1427 | 1428 | {0, 1} 1429 | {0, -1} 1430 | {1, 0} 1431 | {-1, 0} 1432 | 1433 | Shape 1434 | Circle 1435 | Style 1436 | 1437 | Text 1438 | 1439 | Text 1440 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1441 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1442 | {\colortbl;\red255\green255\blue255;} 1443 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1444 | 1445 | \f0\fs24 \cf0 JSON.parse} 1446 | VerticalPad 1447 | 0 1448 | 1449 | 1450 | 1451 | Bounds 1452 | {{394.5, 247.5}, {54, 36}} 1453 | Class 1454 | ShapedGraphic 1455 | ID 1456 | 28 1457 | Magnets 1458 | 1459 | {0, 1} 1460 | {0, -1} 1461 | {1, 0} 1462 | {-1, 0} 1463 | 1464 | Shape 1465 | Circle 1466 | Style 1467 | 1468 | Text 1469 | 1470 | Text 1471 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1472 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1473 | {\colortbl;\red255\green255\blue255;} 1474 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1475 | 1476 | \f0\fs24 \cf0 eval} 1477 | VerticalPad 1478 | 0 1479 | 1480 | 1481 | 1482 | Bounds 1483 | {{391, 50}, {61, 125}} 1484 | Class 1485 | ShapedGraphic 1486 | ID 1487 | 13 1488 | Magnets 1489 | 1490 | {-0.59628479784302491, -1.1925696134567261} 1491 | {1.9868217885725011e-08, -1.3333333730697632} 1492 | {0.59628487781105333, -1.1925696134567261} 1493 | {1.1925696134567283, -0.59628480672836304} 1494 | {1.3333333730697656, 1.5894572413799324e-07} 1495 | {1.1925696134567283, 0.59628473564567486} 1496 | {0.59628465308492407, 1.1925697326660156} 1497 | {2.3684758564530796e-15, 1.3333333730697632} 1498 | {-0.59628494889373729, 1.1925696134567261} 1499 | {-1.1925697326660134, 0.5962844398368361} 1500 | {-1.3333333730697607, -6.3578289655197295e-07} 1501 | {-1.1925696134567239, -0.59628480672836304} 1502 | 1503 | Shape 1504 | Rectangle 1505 | Text 1506 | 1507 | Text 1508 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1509 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1510 | {\colortbl;\red255\green255\blue255;} 1511 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1512 | 1513 | \f0\fs24 \cf0 AJAX\ 1514 | Dispatch & Callback Library} 1515 | 1516 | 1517 | 1518 | Bounds 1519 | {{494, 50}, {128, 125}} 1520 | Class 1521 | ShapedGraphic 1522 | ID 1523 | 12 1524 | Shape 1525 | Cloud 1526 | Style 1527 | 1528 | Text 1529 | 1530 | Text 1531 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1532 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1533 | {\colortbl;\red255\green255\blue255;} 1534 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1535 | 1536 | \f0\fs24 \cf0 Lots of Loosely Controlled Client Code} 1537 | VerticalPad 1538 | 0 1539 | 1540 | 1541 | 1542 | Bounds 1543 | {{382, 44}, {248, 270}} 1544 | Class 1545 | ShapedGraphic 1546 | ID 1547 | 4 1548 | Shape 1549 | Rectangle 1550 | Style 1551 | 1552 | Text 1553 | 1554 | Text 1555 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 1556 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1557 | {\colortbl;\red255\green255\blue255;} 1558 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 1559 | 1560 | \f0\fs24 \cf0 Browser} 1561 | 1562 | TextPlacement 1563 | 2 1564 | 1565 | 1566 | GridInfo 1567 | 1568 | GuidesLocked 1569 | NO 1570 | GuidesVisible 1571 | YES 1572 | HPages 1573 | 2 1574 | ImageCounter 1575 | 1 1576 | KeepToScale 1577 | 1578 | Layers 1579 | 1580 | 1581 | Lock 1582 | NO 1583 | Name 1584 | Layer 1 1585 | Print 1586 | YES 1587 | View 1588 | YES 1589 | 1590 | 1591 | LayoutInfo 1592 | 1593 | Animate 1594 | NO 1595 | circoMinDist 1596 | 18 1597 | circoSeparation 1598 | 0.0 1599 | layoutEngine 1600 | dot 1601 | neatoSeparation 1602 | 0.0 1603 | twopiSeparation 1604 | 0.0 1605 | 1606 | LinksVisible 1607 | NO 1608 | MagnetsVisible 1609 | NO 1610 | MasterSheets 1611 | 1612 | ModificationDate 1613 | 2012-10-12 18:30:45 +0000 1614 | Modifier 1615 | Mike Samuel 1616 | NotesVisible 1617 | NO 1618 | Orientation 1619 | 2 1620 | OriginVisible 1621 | NO 1622 | PageBreaks 1623 | YES 1624 | PrintInfo 1625 | 1626 | NSBottomMargin 1627 | 1628 | float 1629 | 41 1630 | 1631 | NSHorizonalPagination 1632 | 1633 | coded 1634 | BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG 1635 | 1636 | NSLeftMargin 1637 | 1638 | float 1639 | 18 1640 | 1641 | NSPaperSize 1642 | 1643 | size 1644 | {612, 792} 1645 | 1646 | NSPrintReverseOrientation 1647 | 1648 | int 1649 | 0 1650 | 1651 | NSRightMargin 1652 | 1653 | float 1654 | 18 1655 | 1656 | NSTopMargin 1657 | 1658 | float 1659 | 18 1660 | 1661 | 1662 | PrintOnePage 1663 | 1664 | ReadOnly 1665 | NO 1666 | RowAlign 1667 | 1 1668 | RowSpacing 1669 | 36 1670 | SheetTitle 1671 | Canvas 1 1672 | SmartAlignmentGuidesActive 1673 | YES 1674 | SmartDistanceGuidesActive 1675 | YES 1676 | UniqueID 1677 | 1 1678 | UseEntirePage 1679 | 1680 | VPages 1681 | 1 1682 | WindowInfo 1683 | 1684 | CurrentSheet 1685 | 0 1686 | ExpandedCanvases 1687 | 1688 | 1689 | name 1690 | Canvas 1 1691 | 1692 | 1693 | Frame 1694 | {{320, 80}, {987, 943}} 1695 | ListView 1696 | 1697 | OutlineWidth 1698 | 142 1699 | RightSidebar 1700 | 1701 | ShowRuler 1702 | 1703 | Sidebar 1704 | 1705 | SidebarWidth 1706 | 120 1707 | VisibleRegion 1708 | {{-55, -78}, {973, 804}} 1709 | Zoom 1710 | 1 1711 | ZoomValues 1712 | 1713 | 1714 | Canvas 1 1715 | 1 1716 | 2 1717 | 1718 | 1719 | 1720 | 1721 | 1722 | -------------------------------------------------------------------------------- /docs/JSON-Sanitizer-Arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/json-sanitizer/fc612ab374de73d03864d56fb87b6a103b234489/docs/JSON-Sanitizer-Arch.png -------------------------------------------------------------------------------- /docs/contact.md: -------------------------------------------------------------------------------- 1 | # Contact 2 | 3 | If you have questions, please ask at the 4 | [json-sanitizer-support](https://groups.google.com/forum/#!forum/json-sanitizer-support) 5 | Google Group. 6 | 7 | Asking questions on Stack Overflow with the 8 | [*input-sanitization* and *json*](http://stackoverflow.com/questions/tagged/input-sanitization+json) 9 | tags is also a good way to find answers. 10 | -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Importing 4 | 5 | You can fetch the jars from 6 | [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cjson-sanitizer) 7 | or you can let your favorite java package manager handle it for you via 8 | 9 | ```Maven 10 | 11 | com.mikesamuel 12 | json-sanitizer 13 | [1.0,) 14 | 15 | ``` 16 | 17 | Once you've got the JSON sanitizer JAR on your classpath, 18 | 19 | ```Java 20 | import com.google.json.JsonSanitizer; 21 | ``` 22 | 23 | will let you call 24 | 25 | ```Java 26 | String wellFormedJson = JsonSanitizer.sanitize(myJsonLikeString); 27 | ``` 28 | 29 | That's it. Now `wellFormedJson` is a string of well-formed JSON that 30 | is safe to pass to JavaScript's `eval` operator and which can be 31 | easily embedded in XML or HTML. 32 | 33 | If you have further questions, check our 34 | [support list](https://groups.google.com/forum/#!forum/json-sanitizer-support). 35 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | com.mikesamuel 7 | json-sanitizer 8 | jar 9 | 10 | 14 | 1.2.4-SNAPSHOT 15 | json-sanitizer 16 | 17 | Given JSON-like content, converts it to valid JSON. 18 | 19 | This can be attached at either end of a data-pipeline to help satisfy 20 | Postel's principle: 21 | 22 | be conservative in what you do, be liberal in what you accept from 23 | others 24 | 25 | Applied to JSON-like content from others, it will produce well-formed 26 | JSON that should satisfy any parser you use. 27 | 28 | Applied to your output before you send, it will coerce minor mistakes in 29 | encoding and make it easier to embed your JSON in HTML and XML. 30 | 31 | https://github.com/OWASP/json-sanitizer 32 | 33 | 34 | 35 | Apache License, Version 2.0 36 | http://www.apache.org/licenses/LICENSE-2.0.txt 37 | repo 38 | 39 | 40 | 41 | 42 | scm:git:git://github.com/OWASP/json-sanitizer.git 43 | scm:git:git://github.com/OWASP/json-sanitizer.git 44 | https://github.com/OWASP/json-sanitizer 45 | 46 | 47 | 48 | 49 | ossrh 50 | https://oss.sonatype.org/content/repositories/snapshots 51 | 52 | 53 | ossrh 54 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 55 | 56 | 57 | 58 | 59 | GitHub 60 | https://github.com/OWASP/json-sanitizer/issues 61 | 62 | 63 | 64 | OWASP 65 | https://owasp.org 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-project-info-reports-plugin 75 | 3.0.0 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-site-plugin 81 | 3.8.2 82 | 83 | 84 | 85 | org.sonarsource.scanner.maven 86 | sonar-maven-plugin 87 | 3.7.0.1746 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.sonatype.plugins 95 | nexus-staging-maven-plugin 96 | 1.6.8 97 | true 98 | 99 | ossrh 100 | https://oss.sonatype.org/ 101 | true 102 | 108 | 5fd62edf6679f6 109 | 110 | 111 | default-deploy 112 | deploy 113 | 114 | deploy 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-source-plugin 124 | 3.2.1 125 | 126 | 127 | attach-sources 128 | 129 | jar-no-fork 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-javadoc-plugin 138 | 3.1.1 139 | 140 | 141 | attach-javadocs 142 | 143 | jar 144 | 145 | 146 | 147 | 148 | true 149 | public 150 | JSON Sanitizer 151 | 152 | 153 | 154 | 156 | 157 | org.jacoco 158 | jacoco-maven-plugin 159 | 0.8.5 160 | 161 | ${project.basedir}/target/coverage-reports/jacoco-unit.exec 162 | ${project.basedir}/target/coverage-reports/jacoco-unit.exec 163 | 164 | 165 | 166 | jacoco-initialize 167 | 168 | prepare-agent 169 | 170 | 171 | 172 | jacoco-site 173 | package 174 | 175 | report 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | mikesamuel 186 | Mike Samuel 187 | mikesamuel@gmail.com 188 | 189 | 190 | 191 | 192 | 193 | User Support List 194 | json-sanitizer-support+subscribe@googlegroups.com 195 | json-sanitizer-support+unsubscribe@googlegroups.com 196 | https://groups.google.com/forum/#!forum/json-sanitizer-support 197 | 198 | 199 | 200 | 201 | 202 | com.google.code.findbugs 203 | jsr305 204 | 3.0.2 205 | provided 206 | 207 | 208 | 209 | junit 210 | junit 211 | 4.13.1 212 | test 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | com.github.spotbugs 222 | spotbugs-maven-plugin 223 | 3.1.12.2 224 | 225 | 230 | Max 231 | 232 | Low 233 | 234 | 235 | 236 | 237 | 238 | 239 | 1.6 240 | 1.6 241 | 242 | UTF-8 243 | UTF-8 244 | UTF-8 245 | 246 | 247 | 248 | 249 | release-sign-artifact 250 | 251 | 252 | performRelease 253 | true 254 | 255 | 256 | 257 | 258 | 259 | org.apache.maven.plugins 260 | maven-gpg-plugin 261 | 1.6 262 | 263 | 264 | sign-artifacts 265 | verify 266 | 267 | sign 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | jdk12on 277 | 278 | [12,) 279 | 280 | 281 | 1.7 282 | 1.7 283 | 284 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /src/main/java/com/google/json/EvalMinifier.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://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 com.google.json; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Iterator; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import javax.annotation.Nonnull; 26 | import javax.annotation.Nullable; 27 | 28 | /** 29 | * Given a string of valid JSON that is going to be parsed via Javascript's 30 | * {@code eval} builtin, tries to reduce the number of bytes sent over 31 | * the wire by turning it into a Javascript expression that pools constants. 32 | */ 33 | public final class EvalMinifier { 34 | 35 | /** 36 | * Renders JSON-like content safe for use with Javascript {@code eval}. 37 | * 38 | *

The output is a Javascript expression, not a statement, so if it 39 | * contains an object ({properties}) then it 40 | * still needs to be wrapped in parentheses before being passed to 41 | * {@code eval} as via {@code eval('(' + s + ')')} or {@code eval('0,' + s)}. 42 | * 43 | * @param jsonish a string of JSON-like content as defined by 44 | * {@link JsonSanitizer}. 45 | * @return a valid Javascript expression that has no free variables and whose 46 | * execution will have no side-effects, and which can be embedded safely in 47 | * an HTML {@code } element or inside an XML 48 | * {@code } section. 49 | */ 50 | public static String minify(String jsonish) { 51 | JsonSanitizer s = new JsonSanitizer(jsonish); 52 | s.sanitize(); 53 | return minify(s.toCharSequence()).toString(); 54 | } 55 | 56 | /** 57 | * Same as {@link EvalMinifier#minify(String)}, but allows to set custom maximum nesting depth. 58 | * @param jsonish a string of JSON-like content as defined by 59 | * {@link JsonSanitizer}. 60 | * @param maximumNestingDepth the maximum nesting depth for the {@link JsonSanitizer} 61 | * @return see {@link EvalMinifier#minify(String)} 62 | */ 63 | public static String minify(String jsonish, int maximumNestingDepth) { 64 | JsonSanitizer s = new JsonSanitizer(jsonish, maximumNestingDepth); 65 | s.sanitize(); 66 | return minify(s.toCharSequence()).toString(); 67 | } 68 | 69 | @SuppressWarnings("synthetic-access") 70 | private static CharSequence minify(CharSequence json) { 71 | Map pool = new HashMap(); 72 | int n = json.length(); 73 | for (int i = 0; i < n; ++i) { 74 | char ch = json.charAt(i); 75 | int tokEnd; 76 | if (ch == '"') { 77 | for (tokEnd = i + 1; tokEnd < n; ++tokEnd) { 78 | char tch = json.charAt(tokEnd); 79 | if (tch == '\\') { 80 | ++tokEnd; 81 | } else if (tch == '"') { 82 | ++tokEnd; 83 | break; 84 | } 85 | } 86 | } else if (isLetterOrNumberChar(ch)) { 87 | tokEnd = i + 1; 88 | while (tokEnd < n && isLetterOrNumberChar(json.charAt(tokEnd))) { 89 | ++tokEnd; 90 | } 91 | } else { 92 | continue; 93 | } 94 | 95 | int nextNonWhitespace = tokEnd; 96 | for (; nextNonWhitespace < n; ++nextNonWhitespace) { 97 | char wch = json.charAt(nextNonWhitespace); 98 | if (!(wch == '\t' || wch == '\n' || wch == '\r' || wch == ' ')) { 99 | break; 100 | } 101 | } 102 | 103 | // If the string is followed by a ':' then it is a map key and cannot be 104 | // substituted with an identifier. 105 | // In JavaScript, { a: 1 } is the same as { "a": 1 } regardless of 106 | // what the identifier "a" resolves to. 107 | if (nextNonWhitespace == n || ':' != json.charAt(nextNonWhitespace) 108 | && tokEnd - i >= 4) { 109 | Token tok = new Token(i, tokEnd, json); 110 | @Nullable Token last = pool.put(tok, tok); 111 | if (last != null) { 112 | tok.prev = last; 113 | } 114 | } 115 | 116 | i = nextNonWhitespace - 1; 117 | } 118 | 119 | // Now look at all the token groups that have a next, and then count up the 120 | // savings to see if they meet the cost of the boilerplate. 121 | int potentialSavings = 0; 122 | List dupes = new ArrayList(); 123 | for (Iterator values = pool.values().iterator(); values.hasNext();) { 124 | Token tok = values.next(); 125 | if (tok.prev == null) { 126 | values.remove(); 127 | continue; 128 | } 129 | int chainDepth = 0; 130 | for (Token t = tok; t != null; t = t.prev) { 131 | ++chainDepth; 132 | } 133 | int tokSavings = (chainDepth - 1) * (tok.end - tok.start) 134 | - MARGINAL_VAR_COST; 135 | if (tokSavings > 0) { 136 | potentialSavings += tokSavings; 137 | for (Token t = tok; t != null; t = t.prev) { 138 | dupes.add(t); 139 | } 140 | } 141 | } 142 | if (potentialSavings <= BOILERPLATE_COST + SAVINGS_THRESHOLD) { 143 | return json; 144 | } 145 | 146 | // Dump the tokens into an array and sort them. 147 | Collections.sort(dupes); 148 | 149 | int nTokens = dupes.size(); 150 | 151 | StringBuilder sb = new StringBuilder(n); 152 | sb.append(ENVELOPE_P1); 153 | 154 | { 155 | NameGenerator nameGenerator = new NameGenerator(); 156 | boolean first = true; 157 | for (Token tok : pool.values()) { 158 | String name = nameGenerator.next(); 159 | for (Token t = tok; t != null; t = t.prev) { t.name = name; } 160 | if (first) { first = false; } else { sb.append(','); } 161 | sb.append(name); 162 | } 163 | } 164 | 165 | sb.append(ENVELOPE_P2); 166 | int afterReturn = sb.length(); 167 | int pos = 0, tokIndex = 0; 168 | while (true) { 169 | Token tok = tokIndex < nTokens ? dupes.get(tokIndex++) : null; 170 | int limit = tok != null ? tok.start : n; 171 | boolean inString = false; 172 | for (int i = pos; i < limit; ++i) { 173 | char ch = json.charAt(i); 174 | if (inString) { 175 | if (ch == '"') { 176 | inString = false; 177 | } else if (ch == '\\') { 178 | ++i; 179 | } 180 | } else if (ch == '\t' || ch == '\n' || ch == '\r' || ch == ' ') { 181 | if (pos != i) { 182 | sb.append(json, pos, i); 183 | } 184 | pos = i + 1; 185 | } else if (ch == '"') { 186 | inString = true; 187 | } 188 | } 189 | // There should be no token boundaries inside strings. 190 | assert !inString; 191 | if (pos != limit) { 192 | sb.append(json, pos, limit); 193 | } 194 | if (tok == null) { break; } 195 | sb.append(tok.name); 196 | pos = tok.end; 197 | } 198 | { 199 | // Insert space after return if required. 200 | // This is unlikely to occur in practice. 201 | char ch = sb.charAt(afterReturn); 202 | if (ch != '{' && ch != '[' && ch != '"') { 203 | sb.insert(afterReturn, ' '); 204 | } 205 | } 206 | sb.append(ENVELOPE_P3); 207 | { 208 | boolean first = true; 209 | for (Token tok : pool.values()) { 210 | if (first) { first = false; } else { sb.append(','); } 211 | sb.append(tok.seq, tok.start, tok.end); 212 | } 213 | } 214 | sb.append(ENVELOPE_P4); 215 | 216 | return sb; 217 | } 218 | 219 | private static final String ENVELOPE_P1 = "(function("; 220 | private static final String ENVELOPE_P2 = "){return"; 221 | private static final String ENVELOPE_P3 = "}("; 222 | private static final String ENVELOPE_P4 = "))"; 223 | 224 | private static final int BOILERPLATE_COST = 225 | (ENVELOPE_P1 + ENVELOPE_P2 + ENVELOPE_P3 + ENVELOPE_P4).length(); 226 | private static final int MARGINAL_VAR_COST = ",,".length(); 227 | private static final int SAVINGS_THRESHOLD = 32; 228 | 229 | private static boolean isLetterOrNumberChar(char ch) { 230 | if ('0' <= ch && ch <= '9') { return true; } 231 | char lch = (char) (ch | 32); 232 | if ('a' <= lch && lch <= 'z') { return true; } 233 | return ch == '_' || ch == '$' || ch == '-' || ch == '.'; 234 | } 235 | 236 | private static final class Token implements Comparable { 237 | private final int start, end, hashCode; 238 | private final @Nonnull CharSequence seq; 239 | @Nullable Token prev; 240 | @Nullable String name; 241 | 242 | Token(int start, int end, CharSequence seq) { 243 | this.start = start; 244 | this.end = end; 245 | this.seq = seq; 246 | int hc = 0; 247 | for (int i = start; i < end; ++i) { 248 | char ch = seq.charAt(i); 249 | hc = hc * 31 + ch; 250 | } 251 | this.hashCode = hc; 252 | } 253 | 254 | @Override 255 | public boolean equals(@Nullable Object o) { 256 | if (!(o instanceof Token)) { return false; } 257 | Token that = (Token) o; 258 | if (this.hashCode != that.hashCode) { return false; } 259 | return regionMatches( 260 | this.seq, this.start, this.end, that.seq, that.start, that.end); 261 | } 262 | 263 | @Override 264 | public int hashCode() { 265 | return hashCode; 266 | } 267 | 268 | @Override 269 | public int compareTo(Token t) { 270 | return start - t.start; 271 | } 272 | } 273 | 274 | static boolean regionMatches( 275 | CharSequence a, int as, int ae, CharSequence b, int bs, int be) { 276 | int n = ae - as; 277 | if (be - bs != n) { return false; } 278 | for (int ai = as, bi = bs; ai < ae; ++ai, ++bi) { 279 | if (a.charAt(ai) != b.charAt(bi)) { return false; } 280 | } 281 | return true; 282 | } 283 | 284 | 285 | private static final String[][] RESERVED_KEYWORDS = { 286 | {}, 287 | {}, 288 | {"as", "do", "if", "in", "of"}, 289 | {"for", "get", "let", "new", "set", "try", "var"}, 290 | {"case", "else", "enum", "eval", "from", "null", "this", "true", "void", "with"}, 291 | {"async", "await", "break", "catch", "class", "const", "false", "super", "throw", "while", "yield"}, 292 | {"delete", "export", "import", "public", "return", "switch", "static", "target", "typeof"}, 293 | {"default", "extends", "finally", "package", "private"}, 294 | {"continue", "debugger", "function"}, 295 | {"arguments", "interface", "protected"}, 296 | {"implements", "instanceof"} 297 | }; 298 | 299 | static final class NameGenerator { 300 | 301 | private final StringBuilder sb = new StringBuilder("a"); 302 | 303 | @SuppressWarnings("synthetic-access") 304 | public String next() { 305 | while (true) { 306 | String name = sb.toString(); 307 | 308 | int sbLen = sb.length(); 309 | for (int i = sbLen; --i >= 0;) { 310 | int next = nextIdentChar(sb.charAt(i), i != 0); 311 | if (next < 0) { 312 | sb.setCharAt(i, 'a'); 313 | if (i == 0) { sb.append('a'); } 314 | } else { 315 | sb.setCharAt(i, (char) next); 316 | break; 317 | } 318 | } 319 | 320 | int nameLen = name.length(); 321 | if (nameLen >= RESERVED_KEYWORDS.length 322 | || Arrays.binarySearch(RESERVED_KEYWORDS[nameLen], name) < 0) { 323 | return name; 324 | } 325 | } 326 | } 327 | } 328 | 329 | static int nextIdentChar(char ch, boolean allowDigits) { 330 | if (ch == 'z') { return 'A'; } 331 | if (ch == 'Z') { return '_'; } 332 | if (ch == '_') { return '$'; } 333 | if (ch == '$') { 334 | if (allowDigits) { return '0'; } 335 | return -1; 336 | } 337 | if (ch == '9') { return -1; } 338 | return (char) (ch + 1); 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /src/main/java/com/google/json/JsonSanitizer.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://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 com.google.json; 16 | 17 | import java.math.BigInteger; 18 | 19 | /** 20 | * Given JSON-like content, converts it to valid JSON. 21 | * This can be attached at either end of a data-pipeline to help satisfy 22 | * Postel's principle: 23 | *

24 | * be conservative in what you do, be liberal in what you accept from others 25 | *
26 | *

27 | * Applied to JSON-like content from others, it will produce well-formed JSON 28 | * that should satisfy any parser you use. 29 | *

30 | * Applied to your output before you send, it will coerce minor mistakes in 31 | * encoding and make it easier to embed your JSON in HTML and XML. 32 | * 33 | *

Input

34 | * The sanitizer takes JSON like content, and interprets it as JS eval would. 35 | * Specifically, it deals with these non-standard constructs. 36 | * 49 | * 50 | * The sanitizer fixes missing punctuation, end quotes, and mismatched or 51 | * missing close brackets. If an input contains only white-space then the valid 52 | * JSON string {@code null} is substituted. 53 | * 54 | *

Output

55 | * The output is well-formed JSON as defined by 56 | * RFC 4627. 57 | * The output satisfies three additional properties: 58 | *
    59 | *
  1. The output will not contain the substring (case-insensitively) 60 | * {@code "The output will not contain the substring {@code "]]>"} so can be 63 | * embedded inside an XML CDATA section without further encoding.
  2. 64 | *
  3. The output is a valid Javascript expression, so can be parsed by 65 | * Javascript's eval builtin (after being wrapped in parentheses) 66 | * or by JSON.parse. 67 | * Specifically, the output will not contain any string literals with embedded 68 | * JS newlines (U+2028 Paragraph separator or U+2029 Line separator). 69 | *
  4. The output contains only valid Unicode scalar values 70 | * (no isolated UTF-16 surrogates) that are 71 | * allowed in XML unescaped. 72 | *
73 | * 74 | *

Security

75 | * Since the output is well-formed JSON, passing it to eval will 76 | * have no side-effects and no free variables, so is neither a code-injection 77 | * vector, nor a vector for exfiltration of secrets. 78 | * 79 | *

This library only ensures that the JSON string → Javascript object 80 | * phase has no side effects and resolves no free variables, and cannot control 81 | * how other client side code later interprets the resulting Javascript object. 82 | * So if client-side code takes a part of the parsed data that is controlled by 83 | * an attacker and passes it back through a powerful interpreter like 84 | * {@code eval} or {@code innerHTML} then that client-side code might suffer 85 | * unintended side-effects. 86 | * 87 | *

Efficiency

88 | * The sanitize method will return the input string without allocating a new 89 | * buffer when the input is already valid JSON that satisfies the properties 90 | * above. Thus, if used on input that is usually well formed, it has minimal 91 | * memory overhead. 92 | *

The sanitize method takes O(n) time where n is the length in UTF-16 93 | * code-units. 94 | */ 95 | public final class JsonSanitizer { 96 | 97 | /** The default for the maximumNestingDepth constructor parameter. */ 98 | public static final int DEFAULT_NESTING_DEPTH = 64; 99 | 100 | /** The maximum value for the maximumNestingDepth constructor parameter. */ 101 | public static final int MAXIMUM_NESTING_DEPTH = 4096; 102 | 103 | /** 104 | * Given JSON-like content, produces a string of JSON that is safe to embed, 105 | * safe to pass to JavaScript's {@code eval} operator. 106 | * 107 | * @param jsonish JSON-like content. 108 | * @return embeddable JSON 109 | */ 110 | public static String sanitize(String jsonish) { 111 | return sanitize(jsonish, DEFAULT_NESTING_DEPTH); 112 | } 113 | 114 | /** 115 | * Same as {@link JsonSanitizer#sanitize(String)}, but allows to set a custom 116 | * maximum nesting depth. 117 | * 118 | * @param jsonish JSON-like content. 119 | * @param maximumNestingDepth maximum nesting depth. 120 | * @return embeddable JSON 121 | */ 122 | public static String sanitize(String jsonish, int maximumNestingDepth) { 123 | JsonSanitizer s = new JsonSanitizer(jsonish, maximumNestingDepth); 124 | s.sanitize(); 125 | return s.toString(); 126 | } 127 | 128 | /** 129 | * Describes where we are in a state machine that consists of transitions on 130 | * complete values, colons, commas, and brackets. 131 | */ 132 | private enum State { 133 | /** 134 | * Immediately after '[' and 135 | * {@link #BEFORE_ELEMENT before the first element}. 136 | */ 137 | START_ARRAY, 138 | /** Before a JSON value in an array or at the top level. */ 139 | BEFORE_ELEMENT, 140 | /** 141 | * After a JSON value in an array or at the top level, and before any 142 | * following comma or close bracket. 143 | */ 144 | AFTER_ELEMENT, 145 | /** Immediately after '{' and {@link #BEFORE_KEY before the first key}. */ 146 | START_MAP, 147 | /** Before a key in a key-value map. */ 148 | BEFORE_KEY, 149 | /** After a key in a key-value map but before the required colon. */ 150 | AFTER_KEY, 151 | /** Before a value in a key-value map. */ 152 | BEFORE_VALUE, 153 | /** 154 | * After a value in a key-value map but before any following comma or 155 | * close bracket. 156 | */ 157 | AFTER_VALUE, 158 | ; 159 | } 160 | 161 | /** 162 | * The maximum nesting depth. According to RFC4627 it is implementation-specific. 163 | */ 164 | private final int maximumNestingDepth; 165 | 166 | private final String jsonish; 167 | 168 | /** 169 | * The number of brackets that have been entered and not subsequently exited. 170 | * Also, the length of the used prefix of {@link #isMap}. 171 | */ 172 | private int bracketDepth; 173 | /** 174 | * {@code isMap[i]} when {@code 0 <= i && i < bracketDepth} is true iff 175 | * the i-th open bracket was a '{', not a '['. 176 | */ 177 | private boolean[] isMap; 178 | /** 179 | * If non-null, then contains the sanitized form of 180 | * {@code jsonish.substring(0, cleaned)}. 181 | * If {@code null}, then no unclean constructs have been found in 182 | * {@code jsonish} yet. 183 | */ 184 | private StringBuilder sanitizedJson; 185 | /** 186 | * The length of the prefix of {@link #jsonish} that has been written onto 187 | * {@link #sanitizedJson}. 188 | */ 189 | private int cleaned; 190 | 191 | private static final boolean SUPER_VERBOSE_AND_SLOW_LOGGING = false; 192 | 193 | JsonSanitizer(String jsonish) { 194 | this(jsonish, DEFAULT_NESTING_DEPTH); 195 | } 196 | 197 | JsonSanitizer(String jsonish, int maximumNestingDepth) { 198 | this.maximumNestingDepth = Math.min(Math.max(1, maximumNestingDepth),MAXIMUM_NESTING_DEPTH); 199 | if (SUPER_VERBOSE_AND_SLOW_LOGGING) { 200 | System.err.println("\n" + jsonish + "\n========"); 201 | } 202 | this.jsonish = jsonish != null ? jsonish : "null"; 203 | } 204 | 205 | int getMaximumNestingDepth() { 206 | return this.maximumNestingDepth; 207 | } 208 | 209 | void sanitize() { 210 | // Return to consistent state. 211 | bracketDepth = cleaned = 0; 212 | sanitizedJson = null; 213 | 214 | State state = State.START_ARRAY; 215 | int n = jsonish.length(); 216 | 217 | // Walk over each token and either validate it, by just advancing i and 218 | // computing the next state, or manipulate cleaned&sanitizedJson so that 219 | // sanitizedJson contains the sanitized equivalent of 220 | // jsonish.substring(0, cleaned). 221 | token_loop: 222 | for (int i = 0; i < n; ++i) { 223 | try { 224 | char ch = jsonish.charAt(i); 225 | if (SUPER_VERBOSE_AND_SLOW_LOGGING) { 226 | String sanitizedJsonStr = 227 | (sanitizedJson == null ? "" : sanitizedJson) 228 | + jsonish.substring(cleaned, i); 229 | System.err.println("i=" + i + ", ch=" + ch + ", state=" + state 230 | + ", sanitized=" + sanitizedJsonStr); 231 | } 232 | switch (ch) { 233 | case '\t': case '\n': case '\r': case ' ': 234 | break; 235 | 236 | case '"': case '\'': 237 | state = requireValueState(i, state, true); 238 | int strEnd = endOfQuotedString(jsonish, i); 239 | sanitizeString(i, strEnd); 240 | i = strEnd - 1; 241 | break; 242 | 243 | case '(': case ')': 244 | // Often JSON-like content which is meant for use by eval is 245 | // wrapped in parentheses so that the JS parser treats contained 246 | // curly brackets as part of an object constructor instead of a 247 | // block statement. 248 | // We elide these grouping parentheses to ensure valid JSON. 249 | elide(i, i + 1); 250 | break; 251 | 252 | case '{': case '[': 253 | state = requireValueState(i, state, false); 254 | if (isMap == null) { 255 | isMap = new boolean[maximumNestingDepth]; 256 | } 257 | boolean map = ch == '{'; 258 | isMap[bracketDepth] = map; 259 | ++bracketDepth; 260 | state = map ? State.START_MAP : State.START_ARRAY; 261 | break; 262 | 263 | case '}': case ']': 264 | if (bracketDepth == 0) { 265 | elide(i, jsonish.length()); 266 | break token_loop; 267 | } 268 | 269 | // Strip trailing comma to convert {"a":0,} -> {"a":0} 270 | // and [1,2,3,] -> [1,2,3,] 271 | switch (state) { 272 | case BEFORE_VALUE: 273 | insert(i, "null"); 274 | break; 275 | case BEFORE_ELEMENT: case BEFORE_KEY: 276 | elideTrailingComma(i); 277 | break; 278 | case AFTER_KEY: 279 | insert(i, ":null"); 280 | break; 281 | case START_MAP: case START_ARRAY: 282 | case AFTER_ELEMENT: case AFTER_VALUE: break; 283 | } 284 | 285 | --bracketDepth; 286 | char closeBracket = isMap[bracketDepth] ? '}' : ']'; 287 | if (ch != closeBracket) { 288 | replace(i, i + 1, closeBracket); 289 | } 290 | state = bracketDepth == 0 || !isMap[bracketDepth - 1] 291 | ? State.AFTER_ELEMENT : State.AFTER_VALUE; 292 | break; 293 | case ',': 294 | if (bracketDepth == 0) { throw UNBRACKETED_COMMA; } 295 | // Convert comma elisions like [1,,3] to [1,null,3]. 296 | // [1,,3] in JS is an array that has no element at index 1 297 | // according to the "in" operator so accessing index 1 will 298 | // yield the special value "undefined" which is equivalent to 299 | // JS's "null" value according to "==". 300 | switch (state) { 301 | // Normal 302 | case AFTER_ELEMENT: 303 | state = State.BEFORE_ELEMENT; 304 | break; 305 | case AFTER_VALUE: 306 | state = State.BEFORE_KEY; 307 | break; 308 | // Array elision. 309 | case START_ARRAY: case BEFORE_ELEMENT: 310 | insert(i, "null"); 311 | state = State.BEFORE_ELEMENT; 312 | break; 313 | // Ignore 314 | case START_MAP: case BEFORE_KEY: 315 | case AFTER_KEY: 316 | elide(i, i + 1); 317 | break; 318 | // Supply missing value. 319 | case BEFORE_VALUE: 320 | insert(i, "null"); 321 | state = State.BEFORE_KEY; 322 | break; 323 | } 324 | break; 325 | 326 | case ':': 327 | if (state == State.AFTER_KEY) { 328 | state = State.BEFORE_VALUE; 329 | } else { 330 | elide(i, i + 1); 331 | } 332 | break; 333 | 334 | case '/': 335 | // Skip over JS-style comments since people like inserting them into 336 | // data files and getting huffy with Crockford when he says no to 337 | // versioning JSON to allow ignorable tokens. 338 | int end = i + 1; 339 | if (i + 1 < n) { 340 | switch (jsonish.charAt(i + 1)) { 341 | case '/': 342 | end = n; // Worst case. 343 | for (int j = i + 2; j < n; ++j) { 344 | char cch = jsonish.charAt(j); 345 | if (cch == '\n' || cch == '\r' 346 | || cch == '\u2028' || cch == '\u2029') { 347 | end = j + 1; 348 | break; 349 | } 350 | } 351 | break; 352 | case '*': 353 | end = n; 354 | if (i + 3 < n) { 355 | for (int j = i + 2; 356 | (j = jsonish.indexOf('/', j + 1)) >= 0;) { 357 | if (jsonish.charAt(j - 1) == '*') { 358 | end = j + 1; 359 | break; 360 | } 361 | } 362 | } 363 | break; 364 | } 365 | } 366 | elide(i, end); 367 | i = end - 1; 368 | break; 369 | 370 | default: 371 | // Three kinds of other values can occur. 372 | // 1. Numbers 373 | // 2. Keyword values ("false", "null", "true") 374 | // 3. Unquoted JS property names as in the JS expression 375 | // ({ foo: "bar"}) 376 | // which is equivalent to the JSON 377 | // { "foo": "bar" } 378 | // 4. Cruft tokens like BOMs. 379 | 380 | // Look for a run of '.', [0-9], [a-zA-Z_$], [+-] which subsumes 381 | // all the above without including any JSON special characters 382 | // outside keyword and number. 383 | int runEnd; 384 | for (runEnd = i; runEnd < n; ++runEnd) { 385 | char tch = jsonish.charAt(runEnd); 386 | if (('a' <= tch && tch <= 'z') || ('0' <= tch && tch <= '9') 387 | || tch == '+' || tch == '-' || tch == '.' 388 | || ('A' <= tch && tch <= 'Z') || tch == '_' || tch == '$') { 389 | continue; 390 | } 391 | break; 392 | } 393 | 394 | if (runEnd == i) { 395 | elide(i, i + 1); 396 | break; 397 | } 398 | 399 | state = requireValueState(i, state, true); 400 | 401 | boolean isNumber = ('0' <= ch && ch <= '9') 402 | || ch == '.' || ch == '+' || ch == '-'; 403 | boolean isKeyword = !isNumber && isKeyword(i, runEnd); 404 | 405 | if (!(isNumber || isKeyword)) { 406 | // We're going to have to quote the output. Further expand to 407 | // include more of an unquoted token in a string. 408 | for (; runEnd < n; ++runEnd) { 409 | if (isJsonSpecialChar(runEnd)) { 410 | break; 411 | } 412 | } 413 | if (runEnd < n && jsonish.charAt(runEnd) == '"') { 414 | ++runEnd; 415 | } 416 | } 417 | 418 | if (state == State.AFTER_KEY) { 419 | // We need to quote whatever we have since it is used as a 420 | // property name in a map and only quoted strings can be used that 421 | // way in JSON. 422 | insert(i, '"'); 423 | if (isNumber) { 424 | // By JS rules, 425 | // { .5e-1: "bar" } 426 | // is the same as 427 | // { "0.05": "bar" } 428 | // because a number literal is converted to its string form 429 | // before being used as a property name. 430 | canonicalizeNumber(i, runEnd); 431 | // We intentionally ignore the return value of canonicalize. 432 | // Uncanonicalizable numbers just get put straight through as 433 | // string values. 434 | insert(runEnd, '"'); 435 | } else { 436 | sanitizeString(i, runEnd); 437 | } 438 | } else { 439 | if (isNumber) { 440 | // Convert hex and octal constants to decimal and ensure that 441 | // integer and fraction portions are not empty. 442 | normalizeNumber(i, runEnd); 443 | } else if (!isKeyword) { 444 | // Treat as an unquoted string literal. 445 | insert(i, '"'); 446 | sanitizeString(i, runEnd); 447 | } 448 | } 449 | i = runEnd - 1; 450 | } 451 | } catch (@SuppressWarnings("unused") UnbracketedComma e) { 452 | elide(i, jsonish.length()); 453 | break; 454 | } 455 | } 456 | 457 | if (state == State.START_ARRAY && bracketDepth == 0) { 458 | // No tokens. Only whitespace 459 | insert(n, "null"); 460 | state = State.AFTER_ELEMENT; 461 | } 462 | 463 | if (SUPER_VERBOSE_AND_SLOW_LOGGING) { 464 | System.err.println( 465 | "state=" + state + ", sanitizedJson=" + sanitizedJson 466 | + ", cleaned=" + cleaned + ", bracketDepth=" + bracketDepth); 467 | } 468 | 469 | if ((sanitizedJson != null && sanitizedJson.length() != 0) 470 | || cleaned != 0 || bracketDepth != 0) { 471 | if (sanitizedJson == null) { 472 | sanitizedJson = new StringBuilder(n + bracketDepth); 473 | } 474 | sanitizedJson.append(jsonish, cleaned, n); 475 | cleaned = n; 476 | 477 | switch (state) { 478 | case BEFORE_ELEMENT: case BEFORE_KEY: 479 | elideTrailingComma(n); 480 | break; 481 | case AFTER_KEY: 482 | sanitizedJson.append(":null"); 483 | break; 484 | case BEFORE_VALUE: 485 | sanitizedJson.append("null"); 486 | break; 487 | default: break; 488 | } 489 | 490 | // Insert brackets to close unclosed content. 491 | while (bracketDepth != 0) { 492 | sanitizedJson.append(isMap[--bracketDepth] ? '}' : ']'); 493 | } 494 | } 495 | } 496 | 497 | /** 498 | * Ensures that the output corresponding to {@code jsonish[start:end]} is a 499 | * valid JSON string that has the same meaning when parsed by Javascript 500 | * {@code eval}. 501 | *