├── .classpath ├── .gitignore ├── .project ├── README.md ├── example ├── ValueSetAnalysisExample.apk └── example.json ├── libs ├── android.jar ├── apktool-lib_fat.jar └── apktool_2.3.0.jar └── src ├── edu └── osu │ └── sec │ └── vsa │ ├── backwardslicing │ ├── BackwardContext.java │ ├── BackwardController.java │ ├── CallStackItem.java │ └── ICollecter.java │ ├── base │ ├── ExecTrace.java │ ├── GlobalStatistics.java │ ├── ParameterTransferStmt.java │ └── StmtPoint.java │ ├── forwardexec │ ├── SimulateEngine.java │ └── StmtPath.java │ ├── graph │ ├── CallGraph.java │ ├── CallGraphNode.java │ ├── DGraph.java │ ├── HeapObject.java │ ├── IDGNode.java │ └── ValuePoint.java │ ├── main │ ├── ApkContext.java │ ├── Config.java │ └── Main.java │ └── utility │ ├── BlockGenerator.java │ ├── BlockUtility.java │ ├── ErrorHandler.java │ ├── FileUtility.java │ ├── FunctionUtility.java │ ├── ListUtility.java │ ├── Logger.java │ ├── MethodUtility.java │ └── OtherUtility.java └── org └── json ├── CDL.java ├── Cookie.java ├── CookieList.java ├── HTTP.java ├── HTTPTokener.java ├── JSONArray.java ├── JSONException.java ├── JSONML.java ├── JSONObject.java ├── JSONPointer.java ├── JSONPointerException.java ├── JSONString.java ├── JSONStringer.java ├── JSONTokener.java ├── JSONWriter.java ├── LICENSE ├── Property.java ├── README ├── XML.java └── XMLTokener.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .metadata 2 | bin/ 3 | tmp/ 4 | logs/ 5 | valuesetResult/ 6 | *.tmp 7 | *.bak 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .settings/ 12 | .loadpath 13 | .recommenders 14 | 15 | # External tool builders 16 | .externalToolBuilders/ 17 | 18 | # Locally stored "Eclipse launch configurations" 19 | *.launch 20 | 21 | # PyDev specific (Python IDE for Eclipse) 22 | *.pydevproject 23 | 24 | # CDT-specific (C/C++ Development Tooling) 25 | .cproject 26 | 27 | # CDT- autotools 28 | .autotools 29 | 30 | # Java annotation processor (APT) 31 | .factorypath 32 | 33 | # PDT-specific (PHP Development Tools) 34 | .buildpath 35 | 36 | # sbteclipse plugin 37 | .target 38 | 39 | # Tern plugin 40 | .tern-project 41 | 42 | # TeXlipse plugin 43 | .texlipse 44 | 45 | # STS (Spring Tool Suite) 46 | .springBeans 47 | 48 | # Code Recommenders 49 | .recommenders/ 50 | 51 | # Annotation Processing 52 | .apt_generated/ 53 | 54 | # Scala IDE specific (Scala & Java development for Eclipse) 55 | .cache-main 56 | .scala_dependencies 57 | .worksheet 58 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ValueSetAnalysis_torelease 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeakScope 2 | **LeakScope** is a static analysis tool to automatically detect data leakage vulnerabilities in mobile apps. The key component of **LeakScope** is the **String Value Analysis**, which is designed to apply value-set analysis for revealing the possible values of the keys that are used by the cloud provider to connect the apps with the corresponding back-end instances. This component takes the apk file of the app, the target cloud APIs, and the index of parameters as input and generates the values of the keys that can be solved. 3 | 4 | For more details, please see the following [running example](#jump) and [our paper](http://web.cse.ohio-state.edu/~lin.3021/file/SP19.pdf) (S&P 2019) 5 | 6 | # Dependencies 7 | This is an Eclipse project that depends on Flowdroid: 8 | 9 | - [flowdroid](https://github.com/secure-software-engineering/FlowDroid) 10 | - [soot-infoflow-android](https://github.com/secure-software-engineering/FlowDroid/tree/master/soot-infoflow-android "soot-infoflow-android") 11 | - [soot-infoflow-cmd](https://github.com/secure-software-engineering/FlowDroid/tree/master/soot-infoflow-cmd "soot-infoflow-cmd") 12 | - [soot-infoflow-summaries](https://github.com/secure-software-engineering/FlowDroid/tree/master/soot-infoflow-summaries "soot-infoflow-summaries") 13 | - [soot-infoflow](https://github.com/secure-software-engineering/FlowDroid/tree/master/soot-infoflow "soot-infoflow") 14 | # Running Example 15 | 16 | ### target example code from *example/ValueSetAnalysisExample.apk* 17 | ```java 18 | package com.example.vsa.valuesetanalysisexample; 19 | 20 | import ... 21 | public class VsaTest { 22 | 23 | String keypart1; 24 | String keypart2; 25 | 26 | public void init(Context arg3){ 27 | keypart1 = getHardcodedStr(); 28 | keypart2 = arg3.getResources().getString(R.string.key_part2); 29 | } 30 | 31 | public String getHardcodedStr(){ 32 | return "hardcode"; 33 | } 34 | 35 | public CloudStorageAccount getAccount() throws URISyntaxException, InvalidKeyException { 36 | String key = "part1:"; 37 | key += keypart1; 38 | key += "|part2:"; 39 | key += keypart2; 40 | 41 | //target function 42 | return CloudStorageAccount.parse(key); 43 | } 44 | } 45 | ``` 46 | ### configuration file 47 | ```json 48 | { 49 | "apk":"example/ValueSetAnalysisExample.apk", 50 | "methods": 51 | [ 52 | { 53 | "method":"", 54 | "parmIndexs":[0] 55 | } 56 | ] 57 | } 58 | ``` 59 | ### run 60 | ```sh 61 | $ java -jar ValueSetAnalysis.jar ./libs/android.jar ./example/example.json 62 | May 20, 2019 8:53:14 PM brut.androlib.res.AndrolibResources loadMainPkg 63 | INFO: Loading resource table... 64 | Using './libs/android.jar' as android.jar 65 | com.example.vsa.valuesetanalysisexample[CG time]:10035 66 | com.example.vsa.valuesetanalysisexample[CG time]:10774 67 | ... 68 | com.example.vsa.valuesetanalysisexample===========================875195900=========================== 69 | Class: com.example.vsa.valuesetanalysisexample.VsaTest 70 | Method: 71 | Target: $r3 = staticinvoke ($r2) 72 | Solved: true 73 | Depend: 422751563, 1674086835, 74 | BackwardContexts: 75 | 0 76 | $r1 = new java.lang.StringBuilder 77 | specialinvoke $r1.()>() 78 | virtualinvoke $r1.("part1:") 79 | $r2 = $r0. 80 | virtualinvoke $r1.($r2) 81 | $r2 = virtualinvoke $r1.() 82 | $r1 = new java.lang.StringBuilder 83 | specialinvoke $r1.()>() 84 | virtualinvoke $r1.($r2) 85 | virtualinvoke $r1.("|part2:") 86 | $r2 = virtualinvoke $r1.() 87 | $r1 = new java.lang.StringBuilder 88 | specialinvoke $r1.()>() 89 | virtualinvoke $r1.($r2) 90 | $r2 = $r0. 91 | virtualinvoke $r1.($r2) 92 | $r2 = virtualinvoke $r1.() 93 | $r3 = staticinvoke ($r2) 94 | ValueSet: 95 | |0:part1:hardcode|part2:fromres, 96 | 97 | com.example.vsa.valuesetanalysisexample===========================422751563=========================== 98 | Field: 99 | Solved: true 100 | Depend: 1568161955, 101 | ValueSet: 102 | |-1:fromres, 103 | 104 | com.example.vsa.valuesetanalysisexample===========================1674086835=========================== 105 | Field: 106 | Solved: true 107 | Depend: 38076305, 108 | ValueSet: 109 | |-1:hardcode, 110 | 111 | com.example.vsa.valuesetanalysisexample===========================1568161955=========================== 112 | Class: com.example.vsa.valuesetanalysisexample.VsaTest 113 | Method: 114 | Target: $r0. = $r2 115 | Solved: true 116 | Depend: 117 | BackwardContexts: 118 | 0 119 | $r2 = virtualinvoke $r3.(2131427369) 120 | $r0. = $r2 121 | ValueSet: 122 | |-1:fromres, 123 | 124 | com.example.vsa.valuesetanalysisexample[{"0":["part1:hardcode|part2:fromres"]}] 125 | 126 | ``` 127 | # Citing 128 | 129 | If you create a research work that uses our work, please citing the associated paper: 130 | ``` 131 | @inproceedings{leakscope:sp19, 132 | author = {Chaoshun Zuo and Zhiqiang Lin and Yinqian Zhang} , 133 | title = {Why Does Your Data Leak? Uncovering the Data Leakage in Cloud From Mobile Apps}, 134 | booktitle = {Proceedings of the 2019 IEEE Symposium on Security and Privacy}, 135 | address = {San Francisco, CA}, 136 | month = {May}, 137 | year = 2019, 138 | } 139 | ``` 140 | -------------------------------------------------------------------------------- /example/ValueSetAnalysisExample.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUSecLab/LeakScope/020abf6527458b7aeab63d2580c9576babccfadd/example/ValueSetAnalysisExample.apk -------------------------------------------------------------------------------- /example/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "apk":"example/ValueSetAnalysisExample.apk", 3 | "methods": 4 | [ 5 | { 6 | "method":"", 7 | "parmIndexs":[0] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /libs/android.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUSecLab/LeakScope/020abf6527458b7aeab63d2580c9576babccfadd/libs/android.jar -------------------------------------------------------------------------------- /libs/apktool-lib_fat.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUSecLab/LeakScope/020abf6527458b7aeab63d2580c9576babccfadd/libs/apktool-lib_fat.jar -------------------------------------------------------------------------------- /libs/apktool_2.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUSecLab/LeakScope/020abf6527458b7aeab63d2580c9576babccfadd/libs/apktool_2.3.0.jar -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/backwardslicing/BackwardController.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.backwardslicing; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import edu.osu.sec.vsa.graph.DGraph; 7 | import edu.osu.sec.vsa.graph.ValuePoint; 8 | 9 | public class BackwardController { 10 | static BackwardController sc = new BackwardController(); 11 | 12 | public static BackwardController getInstance() { 13 | return sc; 14 | } 15 | 16 | private BackwardController() { 17 | 18 | } 19 | 20 | public static void main(String[] args) { 21 | // TODO Auto-generated method stub 22 | 23 | } 24 | 25 | public List doBackWard(ValuePoint vp, DGraph dg) { 26 | List bcs = new ArrayList(); 27 | bcs.add(new BackwardContext(vp, dg)); 28 | 29 | BackwardContext bc; 30 | while (true) { 31 | bc = null; 32 | for (BackwardContext tmp : bcs) { 33 | if (!tmp.backWardHasFinished()) { 34 | bc = tmp; 35 | break; 36 | } 37 | } 38 | if (bc == null) { 39 | break; 40 | } 41 | bcs.addAll(bc.oneStepBackWard()); 42 | //bc.oneStepBackWard(); 43 | 44 | } 45 | 46 | bcs.forEach(var -> { 47 | var.printExceTrace(); 48 | }); 49 | 50 | return bcs; 51 | 52 | } 53 | 54 | } 55 | 56 | /* 57 | * if (bcs.get(0).getIntrestedVariable().size() > 0) { 58 | * System.out.println("---------"); 59 | * 60 | * HashSet tt = (HashSet) 61 | * bcs.get(0).getIntrestedVariable().clone(); System.out.println(tt.size() + " " 62 | * + bcs.get(0).getIntrestedVariable().size()); 63 | * System.out.println(tt.toArray()[0]); System.out.println(tt.toArray()[0] == 64 | * bcs.get(0).getIntrestedVariable().toArray()[0]); tt.clear(); 65 | * System.out.println(tt.size() + " " + 66 | * bcs.get(0).getIntrestedVariable().size()); 67 | * 68 | * System.out.println("-"); 69 | * 70 | * List lls = (List) ((ArrayList) 71 | * bcs.get(0).getExecTrace()).clone(); System.out.println(lls.size() + " " + 72 | * bcs.get(0).getExecTrace().size()); System.out.println(lls.toArray()[0]); 73 | * System.out.println(lls.toArray()[0] == 74 | * bcs.get(0).getExecTrace().toArray()[0]); lls.clear(); 75 | * System.out.println(lls.size() + " " + bcs.get(0).getExecTrace().size()); 76 | * 77 | * System.out.println("---------"); } 78 | */ 79 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/backwardslicing/CallStackItem.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.backwardslicing; 2 | 3 | import soot.SootMethod; 4 | import soot.Unit; 5 | import soot.Value; 6 | import soot.toolkits.graph.Block; 7 | 8 | public class CallStackItem { 9 | SootMethod smethd; 10 | Block blcok; 11 | Unit currentInstruction; 12 | Value returnTarget; 13 | 14 | public CallStackItem(SootMethod smethd, Block blcok, Unit currentInstruction, Value returnTarget) { 15 | super(); 16 | this.smethd = smethd; 17 | this.blcok = blcok; 18 | this.currentInstruction = currentInstruction; 19 | this.returnTarget = returnTarget; 20 | } 21 | 22 | public SootMethod getSmethd() { 23 | return smethd; 24 | } 25 | 26 | public void setSmethd(SootMethod smethd) { 27 | this.smethd = smethd; 28 | } 29 | 30 | public Block getBlcok() { 31 | return blcok; 32 | } 33 | 34 | public void setBlcok(Block blcok) { 35 | this.blcok = blcok; 36 | } 37 | 38 | public Unit getCurrentInstruction() { 39 | return currentInstruction; 40 | } 41 | 42 | public void setCurrentInstruction(Unit currentInstruction) { 43 | this.currentInstruction = currentInstruction; 44 | } 45 | 46 | public Value getReturnTarget() { 47 | return returnTarget; 48 | } 49 | 50 | public void setReturnTarget(Value returnTarget) { 51 | this.returnTarget = returnTarget; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/backwardslicing/ICollecter.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.backwardslicing; 2 | 3 | import java.util.List; 4 | 5 | public interface ICollecter { 6 | public void clear(); 7 | 8 | public void put(BackwardContext bc); 9 | 10 | public List retrieve(); 11 | } 12 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/base/ExecTrace.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.base; 2 | 3 | import java.util.ArrayList; 4 | 5 | import soot.jimple.Stmt; 6 | 7 | public class ExecTrace { 8 | ArrayList sts = new ArrayList(); 9 | } 10 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/base/GlobalStatistics.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.base; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class GlobalStatistics { 6 | static GlobalStatistics gs = new GlobalStatistics(); 7 | 8 | private GlobalStatistics() { 9 | } 10 | 11 | public static GlobalStatistics getInstance() { 12 | return gs; 13 | } 14 | 15 | public void countGetString() { 16 | getString++; 17 | } 18 | 19 | public void countAppendString() { 20 | appendString++; 21 | } 22 | 23 | public void countFormatString() { 24 | formatString++; 25 | } 26 | 27 | public void countDiveIntoMethodCall() { 28 | diveIntoMethodCall++; 29 | } 30 | 31 | public void countBackWard2Caller() { 32 | backWard2Caller++; 33 | } 34 | 35 | public void updateMaxCallStack(int i) { 36 | if (i > maxCallStack) 37 | maxCallStack = i; 38 | } 39 | 40 | int getString = 0; 41 | int appendString = 0; 42 | int formatString = 0; 43 | int diveIntoMethodCall = 0; 44 | int backWard2Caller = 0; 45 | int maxCallStack = 0; 46 | 47 | public JSONObject toJson() { 48 | JSONObject result = new JSONObject(); 49 | result.put("getString", getString); 50 | result.put("appendString", appendString); 51 | result.put("formatString", formatString); 52 | result.put("diveIntoMethodCall", diveIntoMethodCall); 53 | result.put("backWard2Caller", backWard2Caller); 54 | result.put("maxCallStack", maxCallStack); 55 | return result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/base/ParameterTransferStmt.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.base; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import soot.Unit; 7 | import soot.UnitBox; 8 | import soot.UnitPrinter; 9 | import soot.Value; 10 | import soot.ValueBox; 11 | import soot.jimple.ArrayRef; 12 | import soot.jimple.AssignStmt; 13 | import soot.jimple.FieldRef; 14 | import soot.jimple.InvokeExpr; 15 | import soot.jimple.internal.VariableBox; 16 | import soot.tagkit.Host; 17 | import soot.tagkit.Tag; 18 | import soot.util.Switch; 19 | 20 | public class ParameterTransferStmt implements AssignStmt { 21 | 22 | private static final long serialVersionUID = 1L; 23 | Value left; 24 | Value right; 25 | 26 | public ParameterTransferStmt(Value left, Value right) { 27 | super(); 28 | this.left = left; 29 | this.right = right; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | // TODO Auto-generated method stub 35 | return String.format("%s = %s", left, right); 36 | } 37 | 38 | @Override 39 | public Value getLeftOp() { 40 | // TODO Auto-generated method stub 41 | return left; 42 | } 43 | 44 | @Override 45 | public Value getRightOp() { 46 | // TODO Auto-generated method stub 47 | return right; 48 | } 49 | 50 | @Override 51 | public ValueBox getLeftOpBox() { 52 | // TODO Auto-generated method stub 53 | return null; 54 | } 55 | 56 | @Override 57 | public ValueBox getRightOpBox() { 58 | // TODO Auto-generated method stub 59 | return null; 60 | } 61 | 62 | @Override 63 | public void toString(UnitPrinter up) { 64 | // TODO Auto-generated method stub 65 | 66 | } 67 | 68 | @Override 69 | public boolean containsInvokeExpr() { 70 | // TODO Auto-generated method stub 71 | return false; 72 | } 73 | 74 | @Override 75 | public InvokeExpr getInvokeExpr() { 76 | // TODO Auto-generated method stub 77 | return null; 78 | } 79 | 80 | @Override 81 | public ValueBox getInvokeExprBox() { 82 | // TODO Auto-generated method stub 83 | return null; 84 | } 85 | 86 | @Override 87 | public boolean containsArrayRef() { 88 | // TODO Auto-generated method stub 89 | return false; 90 | } 91 | 92 | @Override 93 | public ArrayRef getArrayRef() { 94 | // TODO Auto-generated method stub 95 | return null; 96 | } 97 | 98 | @Override 99 | public ValueBox getArrayRefBox() { 100 | // TODO Auto-generated method stub 101 | return null; 102 | } 103 | 104 | @Override 105 | public boolean containsFieldRef() { 106 | // TODO Auto-generated method stub 107 | return false; 108 | } 109 | 110 | @Override 111 | public FieldRef getFieldRef() { 112 | // TODO Auto-generated method stub 113 | return null; 114 | } 115 | 116 | @Override 117 | public ValueBox getFieldRefBox() { 118 | // TODO Auto-generated method stub 119 | return null; 120 | } 121 | 122 | @Override 123 | public List getUseBoxes() { 124 | // TODO Auto-generated method stub 125 | return null; 126 | } 127 | 128 | @Override 129 | public List getDefBoxes() { 130 | // TODO Auto-generated method stub 131 | return null; 132 | } 133 | 134 | @Override 135 | public List getUnitBoxes() { 136 | // TODO Auto-generated method stub 137 | return null; 138 | } 139 | 140 | @Override 141 | public List getBoxesPointingToThis() { 142 | // TODO Auto-generated method stub 143 | return null; 144 | } 145 | 146 | @Override 147 | public void addBoxPointingToThis(UnitBox b) { 148 | // TODO Auto-generated method stub 149 | 150 | } 151 | 152 | @Override 153 | public void removeBoxPointingToThis(UnitBox b) { 154 | // TODO Auto-generated method stub 155 | 156 | } 157 | 158 | @Override 159 | public void clearUnitBoxes() { 160 | // TODO Auto-generated method stub 161 | 162 | } 163 | 164 | @Override 165 | public List getUseAndDefBoxes() { 166 | // JValueBox a = new ValueBox(); 167 | List ret = new ArrayList(); 168 | ret.add(new LinkedVariableBox(left)); 169 | ret.add(new LinkedVariableBox(right)); 170 | return ret; 171 | } 172 | 173 | private static class LinkedVariableBox extends VariableBox { 174 | /** 175 | * 176 | */ 177 | private static final long serialVersionUID = 1L; 178 | 179 | private LinkedVariableBox(Value v) { 180 | super(v); 181 | } 182 | 183 | public boolean canContainValue(Value v) { 184 | 185 | return true; 186 | } 187 | } 188 | 189 | @Override 190 | public boolean fallsThrough() { 191 | // TODO Auto-generated method stub 192 | return false; 193 | } 194 | 195 | @Override 196 | public boolean branches() { 197 | // TODO Auto-generated method stub 198 | return false; 199 | } 200 | 201 | @Override 202 | public void redirectJumpsToThisTo(Unit newLocation) { 203 | // TODO Auto-generated method stub 204 | 205 | } 206 | 207 | @Override 208 | public void apply(Switch sw) { 209 | // TODO Auto-generated method stub 210 | 211 | } 212 | 213 | @Override 214 | public List getTags() { 215 | // TODO Auto-generated method stub 216 | return null; 217 | } 218 | 219 | @Override 220 | public Tag getTag(String aName) { 221 | // TODO Auto-generated method stub 222 | return null; 223 | } 224 | 225 | @Override 226 | public void addTag(Tag t) { 227 | // TODO Auto-generated method stub 228 | 229 | } 230 | 231 | @Override 232 | public void removeTag(String name) { 233 | // TODO Auto-generated method stub 234 | 235 | } 236 | 237 | @Override 238 | public boolean hasTag(String aName) { 239 | // TODO Auto-generated method stub 240 | return false; 241 | } 242 | 243 | @Override 244 | public void removeAllTags() { 245 | // TODO Auto-generated method stub 246 | 247 | } 248 | 249 | @Override 250 | public void addAllTagsOf(Host h) { 251 | // TODO Auto-generated method stub 252 | 253 | } 254 | 255 | @Override 256 | public int getJavaSourceStartLineNumber() { 257 | // TODO Auto-generated method stub 258 | return 0; 259 | } 260 | 261 | @Override 262 | public int getJavaSourceStartColumnNumber() { 263 | // TODO Auto-generated method stub 264 | return 0; 265 | } 266 | 267 | @Override 268 | public void setLeftOp(Value variable) { 269 | // TODO Auto-generated method stub 270 | 271 | } 272 | 273 | @Override 274 | public void setRightOp(Value rvalue) { 275 | // TODO Auto-generated method stub 276 | 277 | } 278 | 279 | public ParameterTransferStmt clone() { 280 | return new ParameterTransferStmt(this.getLeftOp(), this.getRightOp()); 281 | } 282 | 283 | } 284 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/base/StmtPoint.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.base; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | 7 | import edu.osu.sec.vsa.graph.CallGraph; 8 | import edu.osu.sec.vsa.graph.CallGraphNode; 9 | import edu.osu.sec.vsa.main.Config; 10 | import edu.osu.sec.vsa.utility.BlockGenerator; 11 | import edu.osu.sec.vsa.utility.BlockUtility; 12 | import edu.osu.sec.vsa.utility.Logger; 13 | import edu.osu.sec.vsa.utility.MethodUtility; 14 | import soot.PatchingChain; 15 | import soot.Scene; 16 | import soot.SootField; 17 | import soot.SootMethod; 18 | import soot.Unit; 19 | import soot.ValueBox; 20 | import soot.jimple.FieldRef; 21 | import soot.jimple.Stmt; 22 | import soot.toolkits.graph.Block; 23 | import soot.toolkits.graph.CompleteBlockGraph; 24 | 25 | public class StmtPoint { 26 | SootMethod method_location; 27 | Block block_location; 28 | Unit instruction_location; 29 | 30 | public StmtPoint(SootMethod method_location, Block block_location, Unit instruction_location) { 31 | super(); 32 | this.method_location = method_location; 33 | this.block_location = block_location; 34 | this.instruction_location = instruction_location; 35 | } 36 | 37 | public SootMethod getMethod_location() { 38 | return method_location; 39 | } 40 | 41 | public void setMethod_location(SootMethod method_location) { 42 | this.method_location = method_location; 43 | } 44 | 45 | public Block getBlock_location() { 46 | return block_location; 47 | } 48 | 49 | public void setBlock_location(Block block_location) { 50 | this.block_location = block_location; 51 | } 52 | 53 | public Unit getInstruction_location() { 54 | return instruction_location; 55 | } 56 | 57 | public void setInstruction_location(Unit instruction_location) { 58 | this.instruction_location = instruction_location; 59 | } 60 | 61 | public static List findCaller(String signature) { 62 | 63 | SootMethod sm = Scene.v().getMethod(signature); 64 | HashSet ms = new HashSet(); 65 | ms.add(sm); 66 | if (Config.PARSEINTERFACECALL && sm.getName().charAt(0) != '<') { 67 | MethodUtility.findAllPointerOfThisMethod(ms, sm.getSubSignature(), sm.getDeclaringClass()); 68 | } 69 | List sps = new ArrayList(); 70 | CallGraphNode node; 71 | CompleteBlockGraph cbg; 72 | Block block; 73 | for (SootMethod tmpm : ms) { 74 | node = CallGraph.getNode(tmpm.toString()); 75 | for (CallGraphNode bn : node.getCallBy()) { 76 | PatchingChain us = bn.getSmthd().retrieveActiveBody().getUnits(); 77 | for (Unit unit : us) { 78 | if (unit instanceof Stmt) { 79 | if (((Stmt) unit).containsInvokeExpr()) { 80 | if (((Stmt) unit).getInvokeExpr().getMethod() == node.getSmthd()) { 81 | cbg = BlockGenerator.getInstance().generate(bn.getSmthd().retrieveActiveBody()); 82 | block = BlockUtility.findLocatedBlock(cbg, unit); 83 | sps.add(new StmtPoint(bn.getSmthd(), block, unit)); 84 | // Logger.print(block.toString()); 85 | // Logger.print("=================="); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | return sps; 93 | } 94 | 95 | public static List findSetter(SootField sootField) { 96 | List sps = new ArrayList(); 97 | 98 | HashSet mthdes = CallGraph.getSetter(sootField); 99 | CompleteBlockGraph cbg; 100 | Block block; 101 | if (mthdes != null) { 102 | for (SootMethod mthd : mthdes) { 103 | PatchingChain us = mthd.retrieveActiveBody().getUnits(); 104 | for (Unit unit : us) { 105 | if (unit instanceof Stmt) { 106 | for (ValueBox vbox : ((Stmt) unit).getDefBoxes()) { 107 | if (vbox.getValue() instanceof FieldRef && ((FieldRef) vbox.getValue()).getField() == sootField) { 108 | cbg = BlockGenerator.getInstance().generate(mthd.retrieveActiveBody()); 109 | block = BlockUtility.findLocatedBlock(cbg, unit); 110 | sps.add(new StmtPoint(mthd, block, unit)); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } else { 117 | Logger.printW("no Setter " + sootField); 118 | } 119 | 120 | return sps; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/forwardexec/SimulateEngine.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.forwardexec; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | 7 | import edu.osu.sec.vsa.base.GlobalStatistics; 8 | import edu.osu.sec.vsa.base.ParameterTransferStmt; 9 | import edu.osu.sec.vsa.graph.DGraph; 10 | import edu.osu.sec.vsa.graph.HeapObject; 11 | import edu.osu.sec.vsa.main.ApkContext; 12 | import edu.osu.sec.vsa.utility.FunctionUtility; 13 | import edu.osu.sec.vsa.utility.Logger; 14 | import edu.osu.sec.vsa.utility.OtherUtility; 15 | import soot.Local; 16 | import soot.Unit; 17 | import soot.Value; 18 | import soot.jimple.AbstractStmtSwitch; 19 | import soot.jimple.ArrayRef; 20 | import soot.jimple.AssignStmt; 21 | import soot.jimple.FieldRef; 22 | import soot.jimple.IdentityStmt; 23 | import soot.jimple.IntConstant; 24 | import soot.jimple.InvokeExpr; 25 | import soot.jimple.InvokeStmt; 26 | import soot.jimple.NewArrayExpr; 27 | import soot.jimple.NewExpr; 28 | import soot.jimple.ParameterRef; 29 | import soot.jimple.Stmt; 30 | import soot.jimple.StringConstant; 31 | import soot.jimple.VirtualInvokeExpr; 32 | 33 | public class SimulateEngine extends AbstractStmtSwitch { 34 | DGraph dg; 35 | StmtPath spath; 36 | HashMap> currentValues = new HashMap>(); 37 | 38 | public SimulateEngine(DGraph dg, StmtPath spath) { 39 | this.dg = dg; 40 | this.spath = spath; 41 | } 42 | 43 | public StmtPath getSpath() { 44 | return spath; 45 | } 46 | 47 | public HashMap> getCurrentValues() { 48 | return currentValues; 49 | } 50 | 51 | public void setInitValue(Value val, String str, boolean append) { 52 | HashSet tmp; 53 | if (!this.getCurrentValues().containsKey(val)) { 54 | tmp = new HashSet(); 55 | this.getCurrentValues().put(val, tmp); 56 | } else { 57 | tmp = this.getCurrentValues().get(val); 58 | } 59 | if (!append) { 60 | tmp.clear(); 61 | } 62 | tmp.add(str); 63 | } 64 | 65 | @SuppressWarnings("unchecked") 66 | public void transferValues(Value from, Value to) { 67 | HashSet vs = this.getCurrentValues().get(from); 68 | this.getCurrentValues().remove(to); 69 | 70 | if (vs != null) { 71 | this.getCurrentValues().put(to, (HashSet) vs.clone()); 72 | } 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | public void transferValuesAndAppend(Stmt stmt, Value from, Value to, Value arg, boolean apdontheold, boolean delold) { 77 | if (!this.getCurrentValues().containsKey(from)) { 78 | this.getCurrentValues().remove(to); 79 | return; 80 | } 81 | 82 | HashSet apds = null; 83 | if (this.getCurrentValues().containsKey(arg)) { 84 | apds = this.getCurrentValues().get(arg); 85 | } else if (arg instanceof StringConstant) { 86 | apds = new HashSet(); 87 | apds.add(((StringConstant) arg).value); 88 | } else { 89 | Logger.printW(String.format("[%s] [SIMULATE][transferValuesAndAppend arg unknow]: %s (%s)", this.hashCode(), stmt, arg.getClass())); 90 | apds = new HashSet(); 91 | apds.add(String.format("[unknown:%s]", arg.getClass())); 92 | return; 93 | } // 94 | 95 | HashSet vs = this.getCurrentValues().get(from); 96 | HashSet newValues = new HashSet(); 97 | for (String apd : apds) { 98 | for (String str : vs) { 99 | newValues.add(str + apd); 100 | } 101 | } 102 | 103 | if (apdontheold) { 104 | this.getCurrentValues().put(from, newValues); 105 | } 106 | 107 | if (delold) { 108 | this.getCurrentValues().remove(from); 109 | } 110 | 111 | this.getCurrentValues().put(to, (HashSet) newValues.clone()); 112 | } 113 | 114 | public String getPrintableValues() { 115 | StringBuilder sb = new StringBuilder(); 116 | for (Value var : this.getCurrentValues().keySet()) { 117 | sb.append(" "); 118 | sb.append(var); 119 | sb.append('('); 120 | sb.append(var.hashCode()); 121 | sb.append(')'); 122 | sb.append(':'); 123 | for (String content : this.getCurrentValues().get(var)) { 124 | sb.append(content); 125 | sb.append(","); 126 | } 127 | sb.append('\n'); 128 | } 129 | return sb.toString(); 130 | } 131 | 132 | public void simulate() { 133 | Unit lastUnit = getSpath().getStmtPathTail(); 134 | 135 | for (Stmt stmt : getSpath().getStmtPath()) { 136 | if (stmt == lastUnit) { 137 | return; 138 | } 139 | String oldv = getPrintableValues(); 140 | Logger.print("[SIMULATE]" + this.hashCode() + ": " + stmt + " " + stmt.getClass()); 141 | if (stmt instanceof ParameterTransferStmt) { 142 | caseAssignStmt((AssignStmt) stmt); 143 | } else { 144 | stmt.apply(this); 145 | } 146 | String newv = getPrintableValues(); 147 | Logger.print(oldv + "\n====>\n" + newv); 148 | } 149 | } 150 | 151 | @Override 152 | public void caseInvokeStmt(InvokeStmt stmt) { 153 | // TODO Auto-generated method stub 154 | 155 | String msig = stmt.getInvokeExpr().getMethod().toString(); 156 | InvokeExpr vie = stmt.getInvokeExpr(); 157 | if (msig.equals("")) { 158 | transferValuesAndAppend(stmt, ((VirtualInvokeExpr) vie).getBase(), ((VirtualInvokeExpr) vie).getBase(), vie.getArg(0), true, false); 159 | }else{ 160 | super.caseInvokeStmt(stmt); 161 | } 162 | 163 | } 164 | 165 | @SuppressWarnings("unchecked") 166 | @Override 167 | public void caseAssignStmt(AssignStmt stmt) { 168 | // TODO Auto-generated method stub 169 | Value leftop = stmt.getLeftOp(); 170 | Value rightop = stmt.getRightOp(); 171 | if (leftop instanceof Local || leftop instanceof ParameterRef || leftop instanceof ArrayRef) { 172 | if (rightop instanceof InvokeExpr) { 173 | InvokeExpr vie = (InvokeExpr) rightop; 174 | String msig = vie.getMethod().toString(); 175 | if (msig.equals("")) { 176 | GlobalStatistics.getInstance().countAppendString(); 177 | transferValuesAndAppend(stmt, ((VirtualInvokeExpr) vie).getBase(), leftop, vie.getArg(0), true, false); 178 | } else if (msig.equals("") || msig.equals("")) { 179 | GlobalStatistics.getInstance().countGetString(); 180 | if (vie.getArg(0) instanceof IntConstant) { 181 | setInitValue(leftop, ApkContext.getInstance().findResource(((IntConstant) vie.getArg(0)).value), false); 182 | } else if (this.getCurrentValues().get(vie.getArg(0)).size() > 0) { 183 | for (String str : (HashSet) this.getCurrentValues().get(vie.getArg(0)).clone()) { 184 | this.getCurrentValues().remove(leftop); 185 | if (OtherUtility.isInt(str)) { 186 | setInitValue(leftop, ApkContext.getInstance().findResource(Integer.parseInt(str)), true); 187 | } else { 188 | Logger.printW(String.format("[%s] [SIMULATE][arg value not int getString(VirtualInvokeExpr)]: %s (%s)", this.hashCode(), stmt, str)); 189 | } 190 | } 191 | } else { 192 | Logger.printW(String.format("[%s] [SIMULATE][arg not int getString(VirtualInvokeExpr)]: %s (%s)", this.hashCode(), stmt, vie.getArg(0).getClass())); 193 | } 194 | } else if (msig.equals("")) { 195 | transferValues(((VirtualInvokeExpr) vie).getBase(), leftop); 196 | } else if (msig.equals("")) { 197 | transferValues(((VirtualInvokeExpr) vie).getBase(), leftop); 198 | } else if (msig.equals("")) { 199 | setInitValue(leftop, ApkContext.getInstance().getPackageName(), false); 200 | } else if (msig.equals("")) { 201 | this.getCurrentValues().remove(leftop); 202 | for (String p1 : this.getContent(vie.getArg(0))) { 203 | for (String p2 : this.getContent(vie.getArg(1))) { 204 | // for (String p3 : this.getContent(vie.getArg(2))) 205 | // { 206 | setInitValue(leftop, ApkContext.getInstance().getIdentifier(p1, p2, null), true); 207 | // } 208 | } 209 | } 210 | 211 | } else if (msig.equals("")) { 212 | GlobalStatistics.getInstance().countFormatString(); 213 | FunctionUtility.String_format(this, leftop, vie); 214 | } else { 215 | Logger.printW(String.format("[%s] [SIMULATE][right unknown(VirtualInvokeExpr)]: %s (%s)", this.hashCode(), stmt, rightop.getClass())); 216 | } 217 | 218 | } else if (rightop instanceof NewExpr) { 219 | if (rightop.getType().toString().equals("java.lang.StringBuilder")) { 220 | setInitValue(leftop, "", false); 221 | } else { 222 | Logger.printW(String.format("[%s] [SIMULATE][right unknown(NewExpr)]: %s (%s)", this.hashCode(), stmt, rightop.getClass())); 223 | } 224 | } else if (rightop instanceof FieldRef) { 225 | HeapObject ho = HeapObject.getInstance(dg, ((FieldRef) rightop).getField()); 226 | if (ho != null) { 227 | if (ho.inited() && ho.hasSolved()) { 228 | HashSet nv = new HashSet(); 229 | ArrayList>> hoResult = ho.getResult(); 230 | for (HashMap> var : hoResult) { 231 | nv.addAll(var.get(-1)); 232 | } 233 | this.getCurrentValues().put(leftop, nv); 234 | } else { 235 | Logger.printW(String.format("[%s] [SIMULATE][HeapObject not inited or Solved]: %s (%s)", this.hashCode(), stmt, ho.inited())); 236 | } 237 | } else { 238 | Logger.printW(String.format("[%s] [SIMULATE][HeapObject not found]: %s (%s)", this.hashCode(), stmt, rightop.getClass())); 239 | } 240 | } else if (rightop instanceof Local) { 241 | transferValues(stmt.getRightOp(), stmt.getLeftOp()); 242 | } else if (rightop instanceof StringConstant) { 243 | setInitValue(leftop, ((StringConstant) rightop).value, false); 244 | } else if (rightop instanceof IntConstant) { 245 | setInitValue(leftop, ((IntConstant) rightop).value + "", false); 246 | } else if (rightop instanceof NewArrayExpr) { 247 | setInitValue(leftop, ((NewArrayExpr) rightop).getSize() + "", false); 248 | } else { 249 | Logger.printW(String.format("[%s] [SIMULATE][right unknown]: %s (%s)", this.hashCode(), stmt, rightop.getClass())); 250 | } 251 | } else { 252 | Logger.printW(String.format("[%s] [SIMULATE][left unknown]: %s (%s)", this.hashCode(), stmt, leftop.getClass())); 253 | } 254 | } 255 | 256 | @Override 257 | public void caseIdentityStmt(IdentityStmt stmt) { 258 | // TODO Auto-generated method stub 259 | transferValues(stmt.getRightOp(), stmt.getLeftOp()); 260 | } 261 | 262 | @Override 263 | public void defaultCase(Object obj) { 264 | // TODO Auto-generated method stub 265 | Logger.printW(String.format("[%s] [SIMULATE][Can't Handle]: %s (%s)", this.hashCode(), obj, obj.getClass())); 266 | } 267 | 268 | public HashSet getContent(Value valu) { 269 | HashSet vs = new HashSet(); 270 | if (this.getCurrentValues().containsKey(valu)) { 271 | return this.getCurrentValues().get(valu); 272 | } else if (valu instanceof StringConstant) { 273 | vs.add(((StringConstant) valu).value); 274 | } else if (valu instanceof IntConstant) { 275 | vs.add(((IntConstant) valu).value + ""); 276 | } 277 | return vs; 278 | } 279 | 280 | } 281 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/forwardexec/StmtPath.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.forwardexec; 2 | 3 | import java.util.List; 4 | 5 | import soot.Unit; 6 | import soot.jimple.Stmt; 7 | 8 | public interface StmtPath { 9 | 10 | public Unit getStmtPathHeader(); 11 | 12 | public Unit getSuccsinStmtPath(Unit u); 13 | 14 | public Unit getPredsinStmtPath(Unit u); 15 | 16 | public Unit getStmtPathTail(); 17 | 18 | public List getStmtPath(); 19 | } 20 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/graph/CallGraph.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.graph; 2 | 3 | import java.util.HashSet; 4 | import java.util.Hashtable; 5 | 6 | import edu.osu.sec.vsa.utility.ListUtility; 7 | import edu.osu.sec.vsa.utility.Logger; 8 | import soot.Body; 9 | import soot.Scene; 10 | import soot.SootClass; 11 | import soot.SootField; 12 | import soot.SootMethod; 13 | import soot.Unit; 14 | import soot.Value; 15 | import soot.ValueBox; 16 | import soot.jimple.FieldRef; 17 | import soot.jimple.Stmt; 18 | 19 | public class CallGraph { 20 | 21 | static Hashtable nodes = new Hashtable(); 22 | 23 | static Hashtable> fieldSetters = new Hashtable>(); 24 | 25 | public static void init() { 26 | long st = System.currentTimeMillis(); 27 | CallGraphNode tmp; 28 | Value tv; 29 | FieldRef fr; 30 | String str; 31 | for (SootClass sclas : Scene.v().getClasses()) { 32 | for (SootMethod smthd : sclas.getMethods()) { 33 | tmp = new CallGraphNode(smthd); 34 | nodes.put(smthd.toString(), tmp); 35 | if (smthd.isConcrete()) 36 | smthd.retrieveActiveBody(); 37 | } 38 | } 39 | Logger.printI("[CG time]:" + (System.currentTimeMillis() - st)); 40 | for (SootClass sclas : Scene.v().getClasses()) { 41 | for (SootMethod smthd : ListUtility.clone(sclas.getMethods())) { 42 | if (!smthd.isConcrete()) 43 | continue; 44 | Body body = smthd.retrieveActiveBody(); 45 | if (body == null) 46 | continue; 47 | for (Unit unit : body.getUnits()) { 48 | if (unit instanceof Stmt) { 49 | if (((Stmt) unit).containsInvokeExpr()) { 50 | try { 51 | addCall(smthd, ((Stmt) unit).getInvokeExpr().getMethod()); 52 | } catch (Exception e) { 53 | Logger.printW(e.getMessage()); 54 | } 55 | } 56 | for (ValueBox var : ((Stmt) unit).getDefBoxes()) { 57 | tv = var.getValue(); 58 | if (tv instanceof FieldRef) { 59 | fr = (FieldRef) tv; 60 | if (fr.getField().getDeclaringClass().isApplicationClass()) { 61 | str = fr.getField().toString(); 62 | if (!fieldSetters.containsKey(str)) { 63 | fieldSetters.put(str, new HashSet()); 64 | } 65 | fieldSetters.get(str).add(smthd); 66 | // Logger.print("FS:" + smthd + " " + str); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | Logger.printI("[CG time]:" + (System.currentTimeMillis() - st)); 76 | } 77 | 78 | private static void addCall(SootMethod from, SootMethod to) { 79 | CallGraphNode fn, tn; 80 | fn = getNode(from); 81 | tn = getNode(to); 82 | if (fn == null || tn == null) { 83 | // Logger.printW("NULL: " + from + " " + to); 84 | // System.out.println(from); 85 | // System.out.println(to); 86 | return; 87 | } 88 | 89 | fn.addCallTo(tn); 90 | tn.addCallBy(fn); 91 | 92 | } 93 | 94 | public static CallGraphNode getNode(SootMethod from) { 95 | return getNode(from.toString()); 96 | } 97 | 98 | public static CallGraphNode getNode(String from) { 99 | return nodes.get(from); 100 | } 101 | 102 | public static HashSet getSetter(SootField sootField) { 103 | return fieldSetters.get(sootField.toString()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/graph/CallGraphNode.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.graph; 2 | 3 | import java.util.HashSet; 4 | 5 | import soot.SootMethod; 6 | 7 | public class CallGraphNode { 8 | SootMethod smthd; 9 | 10 | HashSet callBy = new HashSet(); 11 | HashSet callTo = new HashSet(); 12 | 13 | public CallGraphNode(SootMethod smthd) { 14 | this.smthd = smthd; 15 | } 16 | 17 | public void addCallBy(CallGraphNode smtd) { 18 | callBy.add(smtd); 19 | } 20 | 21 | public void addCallTo(CallGraphNode smtd) { 22 | callTo.add(smtd); 23 | } 24 | 25 | public HashSet getCallBy() { 26 | return callBy; 27 | } 28 | 29 | public HashSet getCallTo() { 30 | return callTo; 31 | } 32 | 33 | public SootMethod getSmthd() { 34 | return smthd; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/graph/DGraph.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.graph; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | 6 | import org.json.JSONObject; 7 | 8 | import edu.osu.sec.vsa.utility.Logger; 9 | 10 | public class DGraph { 11 | HashSet nodes = new HashSet(); 12 | 13 | public void addNode(IDGNode node) { 14 | nodes.add(node); 15 | } 16 | 17 | public HashSet getNodes() { 18 | return nodes; 19 | } 20 | 21 | public void solve(List vps) { 22 | IDGNode tnode; 23 | while (true) { 24 | initAllIfNeed(); 25 | tnode = getNextSolvableNode(); 26 | 27 | if (hasSolvedAllTarget(vps)) { 28 | Logger.print("[DONE]: Solved All Targets!"); 29 | return; 30 | } 31 | 32 | if (tnode == null) { 33 | Logger.print("[DONE]: No Solvable Node Left!"); 34 | if (try2PartiallySolve()) { 35 | continue; 36 | } else { 37 | Logger.print("[DONE]: No PartiallySolvable Node Left!"); 38 | return; 39 | } 40 | } 41 | tnode.solve(); 42 | 43 | } 44 | } 45 | 46 | private void initAllIfNeed() { 47 | IDGNode whoNeedInit; 48 | while (true) { 49 | whoNeedInit = null; 50 | for (IDGNode tmp : nodes) 51 | if (!tmp.inited()) { 52 | whoNeedInit = tmp; 53 | break; 54 | } 55 | if (whoNeedInit == null) { 56 | return; 57 | } else { 58 | whoNeedInit.initIfHavenot(); 59 | } 60 | } 61 | } 62 | 63 | private IDGNode getNextSolvableNode() { 64 | for (IDGNode tmp : nodes) { 65 | if (tmp.getUnsovledDependentsCount() == 0 && !tmp.hasSolved()) { 66 | return tmp; 67 | } 68 | } 69 | return null; 70 | } 71 | 72 | private boolean try2PartiallySolve() { 73 | for (IDGNode tmp : nodes) { 74 | if (tmp.canBePartiallySolve()) { 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | private boolean hasSolvedAllTarget(List vps) { 82 | for (ValuePoint vp : vps) { 83 | if (!vp.hasSolved()) 84 | return false; 85 | } 86 | return true; 87 | } 88 | 89 | public JSONObject toJson() { 90 | JSONObject result = new JSONObject(); 91 | JSONObject jnodes = new JSONObject(); 92 | JSONObject jedges = new JSONObject(); 93 | for (IDGNode node : nodes) { 94 | jnodes.put(node.hashCode() + "", node.getClass().getSimpleName()); 95 | for (IDGNode subn : node.getDependents()) { 96 | jedges.append(node.hashCode() + "", subn.hashCode() + ""); 97 | } 98 | } 99 | result.put("nodes", jnodes); 100 | result.put("edges", jedges); 101 | return result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/graph/HeapObject.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.graph; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | import edu.osu.sec.vsa.base.StmtPoint; 11 | import edu.osu.sec.vsa.utility.Logger; 12 | import soot.SootField; 13 | 14 | public class HeapObject implements IDGNode { 15 | DGraph dg; 16 | 17 | SootField sootField; 18 | boolean inited = false; 19 | boolean solved = false; 20 | ArrayList vps; 21 | HashSet solvedVps = new HashSet(); 22 | 23 | ArrayList>> result = new ArrayList>>(); 24 | 25 | private HeapObject(DGraph dg, SootField sootField) { 26 | this.dg = dg; 27 | this.sootField = sootField; 28 | } 29 | 30 | @Override 31 | public Set getDependents() { 32 | // TODO Auto-generated method stub 33 | 34 | HashSet dps = new HashSet(); 35 | for (ValuePoint vp : vps) { 36 | dps.add(vp); 37 | } 38 | return dps; 39 | 40 | } 41 | 42 | @Override 43 | public int getUnsovledDependentsCount() { 44 | // TODO Auto-generated method stub 45 | int count = 0; 46 | for (IDGNode vp : getDependents()) { 47 | if (!vp.hasSolved()) { 48 | count++; 49 | } 50 | } 51 | return count; 52 | } 53 | 54 | @Override 55 | public boolean hasSolved() { 56 | // TODO Auto-generated method stub 57 | return solved; 58 | } 59 | 60 | @Override 61 | public void solve() { 62 | // TODO Auto-generated method stub 63 | solved = true; 64 | Logger.print("[HEAP SOLVE]" + sootField); 65 | Logger.print("[SOLVING ME]" + this.hashCode()); 66 | 67 | for (ValuePoint vp : vps) { 68 | ArrayList>> vpResult = vp.getResult(); 69 | for (HashMap> res : vpResult) { 70 | if (res.containsKey(-1)) { 71 | result.add(res); 72 | } 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | public boolean canBePartiallySolve() { 79 | boolean can = false; 80 | for (ValuePoint vp : vps) { 81 | if (!solvedVps.contains(vp) && vp.hasSolved()) { 82 | solvedVps.add(vp); 83 | can = true; 84 | for (HashMap> res : vp.getResult()) { 85 | if (res.containsKey(-1)) { 86 | result.add(res); 87 | } 88 | } 89 | } 90 | } 91 | if (can) { 92 | solved = true; 93 | } 94 | return can; 95 | } 96 | 97 | @Override 98 | public void initIfHavenot() { 99 | // TODO Auto-generated method stub 100 | vps = new ArrayList(); 101 | ValuePoint tmp; 102 | List sps = StmtPoint.findSetter(sootField); 103 | for (StmtPoint sp : sps) { 104 | tmp = new ValuePoint(dg, sp.getMethod_location(), sp.getBlock_location(), sp.getInstruction_location(), Collections.singletonList(-1)); 105 | vps.add(tmp); 106 | } 107 | Logger.print("[HEAP INIT]" + sootField + " " + StmtPoint.findSetter(sootField).size()); 108 | inited = true; 109 | 110 | } 111 | 112 | @Override 113 | public boolean inited() { 114 | // TODO Auto-generated method stub 115 | return inited; 116 | } 117 | 118 | @Override 119 | public ArrayList>> getResult() { 120 | return result; 121 | } 122 | 123 | @Override 124 | public int hashCode() { 125 | final int prime = 31; 126 | int result = 1; 127 | result = prime * result + ((sootField == null) ? 0 : sootField.hashCode()); 128 | return result; 129 | } 130 | 131 | @Override 132 | public boolean equals(Object obj) { 133 | if (this == obj) 134 | return true; 135 | if (obj == null) 136 | return false; 137 | if (getClass() != obj.getClass()) 138 | return false; 139 | HeapObject other = (HeapObject) obj; 140 | if (sootField == null) { 141 | if (other.sootField != null) 142 | return false; 143 | } else if (!sootField.equals(other.sootField)) 144 | return false; 145 | return true; 146 | } 147 | 148 | @Override 149 | public String toString() { 150 | // TODO Auto-generated method stub 151 | if (!inited) 152 | return super.toString(); 153 | StringBuilder sb = new StringBuilder(); 154 | sb.append("==========================="); 155 | sb.append(this.hashCode()); 156 | sb.append("===========================\n"); 157 | sb.append("Field: " + sootField + "\n"); 158 | sb.append("Solved: " + hasSolved() + "\n"); 159 | sb.append("Depend: "); 160 | for (IDGNode var : this.getDependents()) { 161 | sb.append(var.hashCode()); 162 | sb.append(", "); 163 | } 164 | sb.append("\n"); 165 | sb.append("ValueSet: \n"); 166 | for (HashMap> resl : result) { 167 | sb.append(" "); 168 | for (int i : resl.keySet()) { 169 | sb.append(" |" + i + ":"); 170 | for (String str : resl.get(i)) { 171 | sb.append(str + ","); 172 | } 173 | } 174 | sb.append("\n"); 175 | } 176 | 177 | return sb.toString(); 178 | } 179 | 180 | static HashMap hos = new HashMap(); 181 | 182 | public static HeapObject getInstance(DGraph dg, SootField sootField) { 183 | String str = sootField.toString(); 184 | if (!hos.containsKey(str)) { 185 | hos.put(str, new HeapObject(dg, sootField)); 186 | } 187 | return hos.get(str); 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/graph/IDGNode.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.graph; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | public interface IDGNode { 9 | 10 | public Set getDependents(); 11 | 12 | public int getUnsovledDependentsCount(); 13 | 14 | public boolean hasSolved(); 15 | 16 | public void solve(); 17 | 18 | public boolean canBePartiallySolve(); 19 | 20 | public void initIfHavenot(); 21 | 22 | public boolean inited(); 23 | 24 | public ArrayList>> getResult(); 25 | } 26 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/graph/ValuePoint.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.graph; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | import org.json.JSONObject; 10 | 11 | import edu.osu.sec.vsa.backwardslicing.BackwardContext; 12 | import edu.osu.sec.vsa.backwardslicing.BackwardController; 13 | import edu.osu.sec.vsa.base.StmtPoint; 14 | import edu.osu.sec.vsa.forwardexec.SimulateEngine; 15 | import edu.osu.sec.vsa.utility.Logger; 16 | import soot.SootMethod; 17 | import soot.Unit; 18 | import soot.Value; 19 | import soot.jimple.AssignStmt; 20 | import soot.jimple.IntConstant; 21 | import soot.jimple.Stmt; 22 | import soot.jimple.StringConstant; 23 | import soot.toolkits.graph.Block; 24 | 25 | public class ValuePoint implements IDGNode { 26 | 27 | DGraph dg; 28 | 29 | SootMethod method_location; 30 | Block block_location; 31 | Unit instruction_location; 32 | HashSet target_regs = new HashSet(); 33 | List bcs = null; 34 | HashSet solvedBCs = new HashSet(); 35 | 36 | Object appendix = ""; 37 | 38 | ArrayList>> result = new ArrayList>>(); 39 | 40 | boolean inited = false; 41 | boolean solved = false; 42 | 43 | public ValuePoint(DGraph dg, SootMethod method_location, Block block_location, Unit instruction_location, List regIndex) { 44 | this.dg = dg; 45 | this.method_location = method_location; 46 | this.block_location = block_location; 47 | this.instruction_location = instruction_location; 48 | for (int i : regIndex) { 49 | target_regs.add(i); 50 | } 51 | dg.addNode(this); 52 | } 53 | 54 | public DGraph getDg() { 55 | return dg; 56 | } 57 | 58 | public List getBcs() { 59 | return bcs; 60 | } 61 | 62 | public SootMethod getMethod_location() { 63 | return method_location; 64 | } 65 | 66 | public Block getBlock_location() { 67 | return block_location; 68 | } 69 | 70 | public Unit getInstruction_location() { 71 | return instruction_location; 72 | } 73 | 74 | public Set getTargetRgsIndexes() { 75 | return target_regs; 76 | } 77 | 78 | public void setAppendix(Object str) { 79 | appendix = str; 80 | } 81 | 82 | @Override 83 | public Set getDependents() { 84 | // TODO Auto-generated method stub 85 | 86 | HashSet dps = new HashSet(); 87 | for (BackwardContext bc : bcs) { 88 | for (IDGNode node : bc.getDependentHeapObjects()) { 89 | dps.add(node); 90 | } 91 | } 92 | return dps; 93 | } 94 | 95 | @Override 96 | public int getUnsovledDependentsCount() { 97 | // TODO Auto-generated method stub 98 | int count = 0; 99 | for (IDGNode node : getDependents()) { 100 | if (!node.hasSolved()) { 101 | count++; 102 | } 103 | } 104 | Logger.print(this.hashCode() + "[]" + count + " " + bcs.size()); 105 | return count; 106 | } 107 | 108 | @Override 109 | public boolean hasSolved() { 110 | 111 | return solved; 112 | } 113 | 114 | @Override 115 | public boolean canBePartiallySolve() { 116 | boolean can = false; 117 | boolean dsolved; 118 | SimulateEngine tmp; 119 | for (BackwardContext bc : bcs) { 120 | if (!solvedBCs.contains(bc)) { 121 | dsolved = true; 122 | for (HeapObject ho : bc.getDependentHeapObjects()) { 123 | if (!ho.hasSolved()) { 124 | dsolved = false; 125 | break; 126 | } 127 | } 128 | if (dsolved) { 129 | solvedBCs.add(bc); 130 | can = true; 131 | tmp = new SimulateEngine(dg, bc); 132 | tmp.simulate(); 133 | mergeResult(bc, tmp); 134 | } 135 | } 136 | } 137 | if (can) { 138 | solved = true; 139 | } 140 | 141 | return can; 142 | } 143 | 144 | @Override 145 | public void solve() { 146 | solved = true; 147 | Logger.print("[SOLVING ME]" + this.hashCode()); 148 | SimulateEngine tmp; 149 | for (BackwardContext var : this.getBcs()) { 150 | tmp = new SimulateEngine(dg, var); 151 | tmp.simulate(); 152 | mergeResult(var, tmp); 153 | } 154 | 155 | } 156 | 157 | public void mergeResult(BackwardContext var, SimulateEngine tmp) { 158 | HashMap> sval = tmp.getCurrentValues(); 159 | HashMap> resl = new HashMap>(); 160 | Value reg; 161 | for (int i : target_regs) { 162 | if (i == -1) { 163 | reg = ((AssignStmt) var.getStmtPathTail()).getRightOp(); 164 | } else { 165 | reg = ((Stmt) var.getStmtPathTail()).getInvokeExpr().getArg(i); 166 | } 167 | 168 | if (sval.containsKey(reg)) { 169 | resl.put(i, sval.get(reg)); 170 | } else if (reg instanceof StringConstant) { 171 | resl.put(i, new HashSet()); 172 | resl.get(i).add(((StringConstant) reg).value); 173 | } else if (reg instanceof IntConstant) { 174 | resl.put(i, new HashSet()); 175 | resl.get(i).add(((IntConstant) reg).value + ""); 176 | } 177 | } 178 | result.add(resl); 179 | } 180 | 181 | @Override 182 | public boolean inited() { 183 | return inited; 184 | } 185 | 186 | @Override 187 | public void initIfHavenot() { 188 | inited = true; 189 | 190 | bcs = BackwardController.getInstance().doBackWard(this, dg); 191 | 192 | } 193 | 194 | @Override 195 | public ArrayList>> getResult() { 196 | return result; 197 | } 198 | 199 | public static List find(DGraph dg, String signature, List regIndex) { 200 | List vps = new ArrayList(); 201 | 202 | List sps = StmtPoint.findCaller(signature); 203 | ValuePoint tmp; 204 | for (StmtPoint sp : sps) { 205 | // XXXXXXXXXXXXX 206 | // XXXXXXXXXXXXX 207 | // XXXXXXXXXXXXX 208 | // XXXXXXXXXXXXX 209 | // if 210 | // (sp.getMethod_location().toString().equals("")) { 213 | tmp = new ValuePoint(dg, sp.getMethod_location(), sp.getBlock_location(), sp.getInstruction_location(), regIndex); 214 | vps.add(tmp); 215 | // } 216 | } 217 | return vps; 218 | } 219 | 220 | public void print() { 221 | System.out.println("==============================================================="); 222 | System.out.println("Class: " + method_location.getDeclaringClass().toString()); 223 | System.out.println("Method: " + method_location.toString()); 224 | System.out.println("Bolck: "); 225 | block_location.forEach(u -> { 226 | System.out.println(" " + u); 227 | }); 228 | target_regs.forEach(u -> { 229 | System.out.println(" " + u); 230 | }); 231 | 232 | } 233 | 234 | public String toString() { 235 | if (!inited) 236 | return super.toString(); 237 | StringBuilder sb = new StringBuilder(); 238 | sb.append("==========================="); 239 | sb.append(this.hashCode()); 240 | sb.append("===========================\n"); 241 | sb.append("Class: " + method_location.getDeclaringClass().toString() + "\n"); 242 | sb.append("Method: " + method_location.toString() + "\n"); 243 | sb.append("Target: " + instruction_location.toString() + "\n"); 244 | sb.append("Solved: " + hasSolved() + "\n"); 245 | sb.append("Depend: "); 246 | for (IDGNode var : this.getDependents()) { 247 | sb.append(var.hashCode()); 248 | sb.append(", "); 249 | } 250 | sb.append("\n"); 251 | sb.append("BackwardContexts: \n"); 252 | BackwardContext tmp; 253 | for (int i = 0; i < this.bcs.size(); i++) { 254 | tmp = this.bcs.get(i); 255 | sb.append(" " + i + "\n"); 256 | for (Stmt stmt : tmp.getExecTrace()) { 257 | sb.append(" " + stmt + "\n"); 258 | } 259 | // sb.append(" i:"); 260 | // for (Value iv : tmp.getIntrestedVariable()) { 261 | // sb.append(" " + iv + "\n"); 262 | // } 263 | } 264 | sb.append("ValueSet: \n"); 265 | for (HashMap> resl : result) { 266 | sb.append(" "); 267 | for (int i : resl.keySet()) { 268 | sb.append(" |" + i + ":"); 269 | for (String str : resl.get(i)) { 270 | sb.append(str + ","); 271 | } 272 | } 273 | sb.append("\n"); 274 | } 275 | return sb.toString(); 276 | } 277 | 278 | public JSONObject toJson() { 279 | JSONObject js = new JSONObject(); 280 | JSONObject tmp; 281 | for (HashMap> var : this.getResult()) { 282 | tmp = new JSONObject(); 283 | for (int i : var.keySet()) { 284 | for (String str : var.get(i)) { 285 | tmp.append(i + "", str); 286 | } 287 | } 288 | js.append("ValueSet", tmp); 289 | } 290 | if (bcs != null) 291 | for (BackwardContext bc : bcs) { 292 | js.append("BackwardContexts", bc.toJson()); 293 | } 294 | js.put("hashCode", this.hashCode() + ""); 295 | js.put("SootMethod", this.getMethod_location().toString()); 296 | js.put("Block", this.getBlock_location().hashCode()); 297 | js.put("Unit", this.getInstruction_location()); 298 | js.put("UnitHash", this.getInstruction_location().hashCode()); 299 | js.put("appendix", appendix); 300 | 301 | return js; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/main/ApkContext.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.main; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.zip.ZipException; 6 | 7 | import brut.androlib.AndrolibException; 8 | import brut.androlib.res.AndrolibResources; 9 | import brut.androlib.res.data.ResID; 10 | import brut.androlib.res.data.ResPackage; 11 | import brut.androlib.res.data.ResTable; 12 | import brut.androlib.res.data.value.ResStringValue; 13 | import brut.directory.ExtFile; 14 | import soot.jimple.infoflow.android.axml.ApkHandler; 15 | import soot.jimple.infoflow.android.manifest.ProcessManifest; 16 | import soot.jimple.infoflow.android.resources.ARSCFileParser; 17 | 18 | public class ApkContext { 19 | String path = null; 20 | ApkHandler apkh = null; 21 | ProcessManifest mfest = null; 22 | ARSCFileParser afp = null; 23 | ResPackage[] resps = null; 24 | 25 | private ApkContext(String path) { 26 | this.path = path; 27 | } 28 | 29 | public String getAbsolutePath() throws ZipException, IOException, AndrolibException { 30 | if (apkh == null) { 31 | init(); 32 | } 33 | 34 | return apkh.getAbsolutePath(); 35 | } 36 | 37 | public String getPackageName() { 38 | try { 39 | if (apkh == null) { 40 | init(); 41 | } 42 | 43 | if (mfest != null) 44 | return mfest.getPackageName(); 45 | 46 | } catch (Exception e) { 47 | } 48 | 49 | return null; 50 | } 51 | 52 | /* 53 | * public String getIdentifier(String name, String type, String packageName) 54 | * { try { SootClass cls = Scene.v().getSootClass(String.format("%s.R$%s", 55 | * (packageName == null || packageName.trim().length() == 0) ? 56 | * this.getPackageName() : packageName, type)); SootField sfield = 57 | * cls.getFieldByName(name); Tag tag = 58 | * sfield.getTag("IntegerConstantValueTag"); int id = 59 | * ByteBuffer.wrap(tag.getValue()).getInt(); return id + ""; } catch 60 | * (Exception e) { return "-1"; } } 61 | */ 62 | /* 63 | * public String getIdentifier(String name, String type, String packageName) 64 | * { for (ResPackage resPackage : afp.getPackages()) { for (ResType resType 65 | * : resPackage.getDeclaredTypes()) { if (resType.toString().equals(type)) { 66 | * for (AbstractResource res : resType.getAllResources()) { 67 | * Logger.print(res.getResourceName() + " " + res.getResourceID() + " " + 68 | * res); if (res.getResourceName().equals(name)) { return 69 | * res.getResourceID() + ""; } } } } } return "-1"; } 70 | */ 71 | public String getIdentifier(String name, String type, String packageName) { 72 | for (ResPackage resp : resps) { 73 | try { 74 | return resp.getType(type).getResSpec(name).getId().id + ""; 75 | 76 | } catch (AndrolibException e) { 77 | // TODO Auto-generated catch block 78 | e.printStackTrace(); 79 | } 80 | } 81 | return "-1"; 82 | } 83 | 84 | public void init() throws ZipException, IOException, AndrolibException { 85 | apkh = new ApkHandler(path); 86 | 87 | afp = new ARSCFileParser(); 88 | afp.parse(apkh.getInputStream("resources.arsc")); 89 | try { 90 | mfest = new ProcessManifest(apkh.getInputStream("AndroidManifest.xml")); 91 | } catch (Exception e) { 92 | } 93 | ExtFile apkFile = new ExtFile(new File(path)); 94 | 95 | AndrolibResources res = new AndrolibResources(); 96 | ResTable resTab = res.getResTable(apkFile, true); 97 | resps = res.getResPackagesFromApk(apkFile, resTab, true); 98 | 99 | apkh.close(); 100 | } 101 | 102 | public String findResource(int id) { 103 | 104 | String str = String.format("[XML String:%s]", id); 105 | try { 106 | if (apkh == null) { 107 | init(); 108 | } 109 | 110 | // str = afp.findResource(id).toString(); 111 | 112 | for (ResPackage resp : resps) { 113 | if (resp.getResSpec(new ResID(id)) != null) { 114 | str = ((ResStringValue) resp.getResSpec(new ResID(id)).getDefaultResource().getValue()).encodeAsResXmlValue(); 115 | break; 116 | } 117 | } 118 | } catch (Exception e) { 119 | e.printStackTrace(); 120 | } 121 | return str; 122 | } 123 | 124 | static ApkContext apkcontext = null; 125 | 126 | public static ApkContext getInstance(String path) { 127 | apkcontext = new ApkContext(path); 128 | return apkcontext; 129 | } 130 | 131 | public static ApkContext getInstance() { 132 | return apkcontext; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/main/Config.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.main; 2 | 3 | public class Config { 4 | public static String ANDROID_JAR_DIR = "./libs/android.jar"; 5 | 6 | public static String RESULTDIR = "./valuesetResult/"; 7 | public static String LOGDIR = "./logs/"; 8 | public static boolean PARSEINTERFACECALL = true; 9 | 10 | public static int MAXMETHODCHAINLEN = 100; 11 | 12 | public static int TIMEOUT = 60*1000; 13 | 14 | static { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/main/Main.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.main; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.zip.ZipException; 11 | 12 | import org.json.JSONObject; 13 | 14 | import soot.Scene; 15 | import soot.options.Options; 16 | import brut.androlib.AndrolibException; 17 | import edu.osu.sec.vsa.base.GlobalStatistics; 18 | import edu.osu.sec.vsa.graph.CallGraph; 19 | import edu.osu.sec.vsa.graph.DGraph; 20 | import edu.osu.sec.vsa.graph.IDGNode; 21 | import edu.osu.sec.vsa.graph.ValuePoint; 22 | import edu.osu.sec.vsa.utility.ErrorHandler; 23 | import edu.osu.sec.vsa.utility.FileUtility; 24 | import edu.osu.sec.vsa.utility.Logger; 25 | 26 | public class Main { 27 | 28 | static JSONObject targetMethds; 29 | 30 | public static void startWatcher(int sec) { 31 | Thread t = new Thread() { 32 | public void run() { 33 | try { 34 | Thread.sleep(sec * 1000); 35 | } catch (InterruptedException e) { 36 | // TODO Auto-generated catch block 37 | e.printStackTrace(); 38 | } 39 | Logger.printE("TimeOut,exiting..."); 40 | System.exit(0); 41 | } 42 | }; 43 | t.setDaemon(true); 44 | t.start(); 45 | } 46 | 47 | public static void main(String[] args) throws ZipException, IOException, AndrolibException { 48 | initDirs(); 49 | 50 | Config.ANDROID_JAR_DIR = args[0]; 51 | 52 | targetMethds = new JSONObject(new String(Files.readAllBytes(Paths.get(args[1])))); 53 | String apk = targetMethds.getString("apk"); 54 | 55 | Thread.setDefaultUncaughtExceptionHandler(new ErrorHandler(args[0])); 56 | 57 | long stime = System.currentTimeMillis(); 58 | 59 | ApkContext apkcontext = ApkContext.getInstance(apk); 60 | Logger.TAG = apkcontext.getPackageName(); 61 | 62 | soot.G.reset(); 63 | Options.v().set_src_prec(Options.src_prec_apk); 64 | Options.v().set_process_dir(Collections.singletonList(apkcontext.getAbsolutePath())); 65 | //Options.v().set_android_jars(Config.ANDROID_JAR_DIR); 66 | Options.v().set_force_android_jar(Config.ANDROID_JAR_DIR); 67 | Options.v().set_process_multiple_dex(true); 68 | 69 | Options.v().set_whole_program(true); 70 | Options.v().set_allow_phantom_refs(true); 71 | Options.v().set_output_format(Options.output_format_none); 72 | 73 | Options.v().ignore_resolution_errors(); 74 | Scene.v().loadNecessaryClasses(); 75 | 76 | 77 | startWatcher(Config.TIMEOUT); 78 | CallGraph.init(); 79 | startWatcher(20); 80 | long itime = System.currentTimeMillis(); 81 | 82 | DGraph dg = new DGraph(); 83 | 84 | List allvps = new ArrayList(); 85 | List vps; 86 | String tsig; 87 | List regIndex; 88 | JSONObject tmp; 89 | 90 | 91 | for (Object jobj : targetMethds.getJSONArray("methods")) { 92 | 93 | tmp = (JSONObject) jobj; 94 | 95 | tsig = tmp.getString("method"); 96 | regIndex = new ArrayList(); 97 | for(Object tob:tmp.getJSONArray("parmIndexs")){ 98 | regIndex.add((Integer) tob); 99 | } 100 | 101 | vps = ValuePoint.find(dg, tsig, regIndex); 102 | for (ValuePoint vp : vps) { 103 | System.out.println(vp); 104 | 105 | tmp = new JSONObject(); 106 | tmp.put("sigatureInApp", tsig); 107 | //tmp.put("sigatureIndex", targetMethds.getString(tsig)); 108 | vp.setAppendix(tmp); 109 | 110 | vp.print(); 111 | } 112 | allvps.addAll(vps); 113 | } 114 | dg.solve(allvps); 115 | long etime = System.currentTimeMillis(); 116 | 117 | JSONObject result = new JSONObject(); 118 | 119 | for (IDGNode tn : dg.getNodes()) { 120 | Logger.print(tn.toString()); 121 | } 122 | 123 | for (ValuePoint vp : allvps) { 124 | tmp = vp.toJson(); 125 | if (tmp.has("ValueSet")) 126 | Logger.print(tmp.getJSONArray("ValueSet").toString()); 127 | result.append("ValuePoints", vp.toJson()); 128 | } 129 | result.put("pname", ApkContext.getInstance().getPackageName()); 130 | result.put("DGraph", dg.toJson()); 131 | result.put("initTime", (itime - stime)); 132 | result.put("solveTime", (etime - itime)); 133 | result.put("GlobalStatistics", GlobalStatistics.getInstance().toJson()); 134 | 135 | wf(result.toString()); 136 | 137 | } 138 | 139 | public static void wf(String content) { 140 | FileUtility.wf(Config.RESULTDIR + ApkContext.getInstance().getPackageName(), content, false); 141 | } 142 | 143 | public static void initDirs() { 144 | File tmp = new File(Config.RESULTDIR); 145 | if (!tmp.exists()) 146 | tmp.mkdir(); 147 | tmp = new File(Config.LOGDIR); 148 | if (!tmp.exists()) 149 | tmp.mkdir(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/BlockGenerator.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.util.HashSet; 4 | import java.util.Hashtable; 5 | import java.util.List; 6 | 7 | import soot.Body; 8 | import soot.toolkits.graph.Block; 9 | import soot.toolkits.graph.CompleteBlockGraph; 10 | 11 | public class BlockGenerator { 12 | static BlockGenerator bg = new BlockGenerator(); 13 | 14 | public static BlockGenerator getInstance() { 15 | return bg; 16 | } 17 | 18 | private BlockGenerator() { 19 | } 20 | 21 | Hashtable ht = new Hashtable(); 22 | 23 | public CompleteBlockGraph generate(Body b) { 24 | if (!ht.containsKey(b)) { 25 | ht.put(b, new CompleteBlockGraph(b)); 26 | } 27 | return ht.get(b); 28 | } 29 | 30 | public static boolean isCircle(Block b, Block current, CompleteBlockGraph cbg, HashSet history) { 31 | if (history.contains(current)) { 32 | return false; 33 | } 34 | boolean isc = false; 35 | 36 | history.add(current); 37 | for (Block blk : cbg.getPredsOf(current)) { 38 | if (b == blk) 39 | isc = true; 40 | else 41 | isc |= isCircle(b, blk, cbg, history); 42 | if (isc) 43 | return isc; 44 | } 45 | history.remove(current); 46 | return isc; 47 | } 48 | 49 | public static void removeCircleBlocks(List bs, Block current, CompleteBlockGraph cbg) { 50 | HashSet rem = new HashSet(); 51 | 52 | for (Block blk : bs) { 53 | if (isCircle(current, blk, cbg, new HashSet())) { 54 | rem.add(blk); 55 | } 56 | } 57 | for (Block blk : rem) { 58 | bs.remove(blk); 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/BlockUtility.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.util.Iterator; 4 | 5 | import soot.Unit; 6 | import soot.toolkits.graph.Block; 7 | import soot.toolkits.graph.CompleteBlockGraph; 8 | 9 | public class BlockUtility { 10 | 11 | public static Block findLocatedBlock(CompleteBlockGraph cbg, Unit unit) { 12 | // TODO Auto-generated method stub 13 | 14 | for (Block block : cbg.getBlocks()) { 15 | Iterator us = block.iterator(); 16 | while (us.hasNext()) { 17 | if (us.next() == unit) { 18 | return block; 19 | } 20 | } 21 | } 22 | return null; 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.lang.Thread.UncaughtExceptionHandler; 4 | 5 | public class ErrorHandler implements UncaughtExceptionHandler { 6 | String tag; 7 | 8 | public ErrorHandler(String tag) { 9 | this.tag = tag; 10 | } 11 | 12 | @Override 13 | public void uncaughtException(Thread t, Throwable e) { 14 | // TODO Auto-generated method stub 15 | StringBuilder sb = new StringBuilder(); 16 | sb.append("File: " + tag); 17 | sb.append('\n'); 18 | sb.append("Msge: " + e.getMessage()); 19 | sb.append('\n'); 20 | for (StackTraceElement st : e.getStackTrace()) { 21 | sb.append(" " + st.toString()); 22 | sb.append('\n'); 23 | } 24 | Logger.printE(sb.toString()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/FileUtility.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | 8 | public class FileUtility { 9 | 10 | public static void wf(String path, String content, boolean append) { 11 | try { 12 | PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(path, append))); 13 | out.println(content); 14 | out.close(); 15 | } catch (IOException e) { 16 | e.printStackTrace(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/FunctionUtility.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | 6 | import edu.osu.sec.vsa.forwardexec.SimulateEngine; 7 | import soot.Value; 8 | import soot.jimple.ArrayRef; 9 | import soot.jimple.InvokeExpr; 10 | 11 | public class FunctionUtility { 12 | 13 | @SuppressWarnings("unchecked") 14 | public static void String_format(SimulateEngine se, Value leftop, InvokeExpr vie) { 15 | // TODO Auto-generated method stub 16 | se.getCurrentValues().remove(leftop); 17 | HashSet sformat = se.getContent(vie.getArg(0)); 18 | if (sformat.size() <= 0) 19 | return; 20 | 21 | if (!se.getCurrentValues().containsKey(vie.getArg(1))) 22 | return; 23 | 24 | HashSet hs_len = se.getContent(vie.getArg(1)); 25 | if (hs_len.size() <= 0) 26 | return; 27 | int len = Integer.parseInt(hs_len.toArray()[0] + ""); 28 | ArrayRef[] argRefs = new ArrayRef[len]; 29 | int foundCount = 0; 30 | for (Value val : se.getCurrentValues().keySet()) { 31 | if (val instanceof ArrayRef && ((ArrayRef) val).getBase().equivTo(vie.getArg(1))) { 32 | argRefs[Integer.parseInt(((ArrayRef) val).getIndex().toString())] = (ArrayRef) val; 33 | foundCount++; 34 | } 35 | } 36 | if (foundCount != len) { 37 | Logger.printW("Some Args are unknow " + vie); 38 | return; 39 | } 40 | 41 | for (ArrayRef argRef : argRefs) { 42 | se.getContent(argRef).size(); 43 | } 44 | 45 | int[] maxIndex = new int[len]; 46 | List[] vs = new List[len]; 47 | int[] indexes = new int[len]; 48 | HashSet tmp; 49 | for (int i = 0; i < len; i++) { 50 | tmp = se.getContent(argRefs[i]); 51 | vs[i] = ListUtility.Array2List((tmp.toArray(new String[tmp.size()]))); 52 | maxIndex[i] = vs[i].size(); 53 | 54 | indexes[i] = 0; 55 | } 56 | 57 | Object[] obj = new Object[len]; 58 | for (String formt : sformat) { 59 | for (int i = 0; i < len; i++) { 60 | indexes[i] = 0; 61 | } 62 | do { 63 | for (int i = 0; i < len; i++) { 64 | obj[i] = vs[i].get(indexes[i]); 65 | } 66 | se.setInitValue(leftop, String.format(formt, obj), true); 67 | } while (move2Next(maxIndex, indexes)); 68 | } 69 | } 70 | 71 | public static boolean move2Next(int[] maxIndex, int[] indexes) { 72 | indexes[0]++; 73 | for (int i = 0; i < maxIndex.length; i++) { 74 | if (indexes[i] >= maxIndex[i]) { 75 | indexes[i] = 0; 76 | if (i + 1 >= maxIndex.length) { 77 | return false; 78 | } 79 | indexes[i + 1]++; 80 | } 81 | } 82 | return true; 83 | } 84 | 85 | public static void main(String[] arg) { 86 | String a = "%s-%s"; 87 | Object[] obj = new Object[2]; 88 | obj[0] = "aa"; 89 | obj[1] = "bb"; 90 | System.out.println(String.format(a, obj)); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/ListUtility.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import soot.PatchingChain; 7 | import soot.Unit; 8 | 9 | public class ListUtility { 10 | 11 | public static List chain2List(PatchingChain us) { 12 | List ls = new ArrayList(); 13 | for (Unit inst : us) { 14 | ls.add(inst); 15 | } 16 | return ls; 17 | } 18 | 19 | public static List Array2List(T[] ts) { 20 | List ls = new ArrayList(); 21 | for (T inst : ts) { 22 | ls.add(inst); 23 | } 24 | return ls; 25 | } 26 | 27 | public static List clone(List ls) { 28 | List list = new ArrayList(); 29 | for (T t : ls) { 30 | list.add(t); 31 | } 32 | return list; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/Logger.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | public class Logger { 4 | public static String TAG = "Logger"; 5 | 6 | public static void printI(String args) { 7 | System.out.println(TAG + args); 8 | } 9 | 10 | public static void printW(String args) { 11 | String str = TAG + "[W]" + args; 12 | System.out.println(str); 13 | FileUtility.wf("./logs/warnning.txt", str, true); 14 | } 15 | 16 | public static void print(String args) { 17 | System.out.println(TAG + args); 18 | } 19 | 20 | public static void printE(String args) { 21 | args = TAG + args; 22 | FileUtility.wf("./logs/error.txt", args, true); 23 | System.out.println(args); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/MethodUtility.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import java.util.HashSet; 4 | 5 | import soot.Scene; 6 | import soot.SootClass; 7 | import soot.SootMethod; 8 | 9 | public class MethodUtility { 10 | 11 | public static boolean hash(String args) { 12 | try { 13 | return (Scene.v().getMethod(args) != null); 14 | } catch (Exception e) { 15 | } 16 | return false; 17 | } 18 | 19 | public static String dex2Soot_mthd(String sig) { 20 | String[] rest = sig.split("->"); 21 | String cls = dex2Soot_class(rest[0]); 22 | 23 | rest = rest[1].split("\\("); 24 | String mname = rest[0]; 25 | 26 | rest = rest[1].split("\\)"); 27 | String ret = rest[1]; 28 | if (ret.equals("V")) 29 | ret = "void"; 30 | else 31 | ret = dex2Soot_class(ret); 32 | 33 | rest = rest[0].split(";"); 34 | String pas = ""; 35 | for (String str : rest) { 36 | pas += dex2Soot_class(str) + ","; 37 | } 38 | pas = pas.replace(",,", ","); 39 | while (pas.endsWith(",")) 40 | pas = pas.substring(0, pas.length() - 1); 41 | return String.format("<%s: %s %s(%s)>", cls, ret, mname, pas); 42 | } 43 | 44 | public static String dex2Soot_class(String sig) { 45 | sig = sig.trim(); 46 | if (sig.endsWith(";")) { 47 | sig = sig.substring(0, sig.length() - 1); 48 | } 49 | if (sig.startsWith("L")) { 50 | sig = sig.substring(1, sig.length()); 51 | } 52 | 53 | return sig.replace('/', '.'); 54 | } 55 | 56 | public static void findAllPointerOfThisMethod(HashSet ms, String subSig, SootClass sc) { 57 | try { 58 | if (sc.getMethod(subSig) != null) { 59 | ms.add(sc.getMethod(subSig)); 60 | } 61 | } catch (Exception e) { 62 | } 63 | 64 | if (sc.toString().equals("java.lang.Object")) { 65 | return; 66 | } 67 | 68 | if (sc.getSuperclass() != sc) { 69 | findAllPointerOfThisMethod(ms, subSig, sc.getSuperclass()); 70 | } 71 | for (SootClass itf : sc.getInterfaces()) { 72 | findAllPointerOfThisMethod(ms, subSig, itf); 73 | } 74 | 75 | } 76 | 77 | public static void main(String[] args) { 78 | System.out.println(dex2Soot_mthd("Lcom/google/firebase/FirebaseOptions;->(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/edu/osu/sec/vsa/utility/OtherUtility.java: -------------------------------------------------------------------------------- 1 | package edu.osu.sec.vsa.utility; 2 | 3 | import soot.jimple.DoubleConstant; 4 | import soot.jimple.FloatConstant; 5 | import soot.jimple.IntConstant; 6 | import soot.jimple.LongConstant; 7 | import soot.jimple.StringConstant; 8 | 9 | public class OtherUtility { 10 | 11 | public static boolean isStrConstant(Object obj) { 12 | return obj instanceof StringConstant; 13 | } 14 | 15 | public static boolean isNumConstant(Object obj) { 16 | return obj instanceof IntConstant || obj instanceof LongConstant || obj instanceof FloatConstant || obj instanceof DoubleConstant; 17 | } 18 | 19 | public static boolean isInt(String s) { 20 | try { 21 | Integer.parseInt(s); 22 | return true; 23 | } 24 | 25 | catch (NumberFormatException er) { 26 | return false; 27 | } 28 | } 29 | 30 | public static int string2Int(String i) { 31 | return Integer.parseInt(i); 32 | } 33 | 34 | public static long string2Long(String i) { 35 | return Long.parseLong(i); 36 | } 37 | 38 | public static float string2Float(String i) { 39 | return Float.parseFloat(i); 40 | } 41 | 42 | public static double string2Double(String i) { 43 | return Double.parseDouble(i); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/org/json/CDL.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * This provides static methods to convert comma delimited text into a 29 | * JSONArray, and to convert a JSONArray into comma delimited text. Comma 30 | * delimited text is a very popular format for data interchange. It is 31 | * understood by most database, spreadsheet, and organizer programs. 32 | *

33 | * Each row of text represents a row in a table or a data record. Each row 34 | * ends with a NEWLINE character. Each row contains one or more values. 35 | * Values are separated by commas. A value can contain any character except 36 | * for comma, unless is is wrapped in single quotes or double quotes. 37 | *

38 | * The first row usually contains the names of the columns. 39 | *

40 | * A comma delimited list can be converted into a JSONArray of JSONObjects. 41 | * The names for the elements in the JSONObjects can be taken from the names 42 | * in the first row. 43 | * @author JSON.org 44 | * @version 2016-05-01 45 | */ 46 | public class CDL { 47 | 48 | /** 49 | * Get the next value. The value can be wrapped in quotes. The value can 50 | * be empty. 51 | * @param x A JSONTokener of the source text. 52 | * @return The value string, or null if empty. 53 | * @throws JSONException if the quoted string is badly formed. 54 | */ 55 | private static String getValue(JSONTokener x) throws JSONException { 56 | char c; 57 | char q; 58 | StringBuffer sb; 59 | do { 60 | c = x.next(); 61 | } while (c == ' ' || c == '\t'); 62 | switch (c) { 63 | case 0: 64 | return null; 65 | case '"': 66 | case '\'': 67 | q = c; 68 | sb = new StringBuffer(); 69 | for (;;) { 70 | c = x.next(); 71 | if (c == q) { 72 | //Handle escaped double-quote 73 | if(x.next() != '\"') 74 | { 75 | x.back(); 76 | break; 77 | } 78 | } 79 | if (c == 0 || c == '\n' || c == '\r') { 80 | throw x.syntaxError("Missing close quote '" + q + "'."); 81 | } 82 | sb.append(c); 83 | } 84 | return sb.toString(); 85 | case ',': 86 | x.back(); 87 | return ""; 88 | default: 89 | x.back(); 90 | return x.nextTo(','); 91 | } 92 | } 93 | 94 | /** 95 | * Produce a JSONArray of strings from a row of comma delimited values. 96 | * @param x A JSONTokener of the source text. 97 | * @return A JSONArray of strings. 98 | * @throws JSONException 99 | */ 100 | public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { 101 | JSONArray ja = new JSONArray(); 102 | for (;;) { 103 | String value = getValue(x); 104 | char c = x.next(); 105 | if (value == null || 106 | (ja.length() == 0 && value.length() == 0 && c != ',')) { 107 | return null; 108 | } 109 | ja.put(value); 110 | for (;;) { 111 | if (c == ',') { 112 | break; 113 | } 114 | if (c != ' ') { 115 | if (c == '\n' || c == '\r' || c == 0) { 116 | return ja; 117 | } 118 | throw x.syntaxError("Bad character '" + c + "' (" + 119 | (int)c + ")."); 120 | } 121 | c = x.next(); 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Produce a JSONObject from a row of comma delimited text, using a 128 | * parallel JSONArray of strings to provides the names of the elements. 129 | * @param names A JSONArray of names. This is commonly obtained from the 130 | * first row of a comma delimited text file using the rowToJSONArray 131 | * method. 132 | * @param x A JSONTokener of the source text. 133 | * @return A JSONObject combining the names and values. 134 | * @throws JSONException 135 | */ 136 | public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) 137 | throws JSONException { 138 | JSONArray ja = rowToJSONArray(x); 139 | return ja != null ? ja.toJSONObject(names) : null; 140 | } 141 | 142 | /** 143 | * Produce a comma delimited text row from a JSONArray. Values containing 144 | * the comma character will be quoted. Troublesome characters may be 145 | * removed. 146 | * @param ja A JSONArray of strings. 147 | * @return A string ending in NEWLINE. 148 | */ 149 | public static String rowToString(JSONArray ja) { 150 | StringBuilder sb = new StringBuilder(); 151 | for (int i = 0; i < ja.length(); i += 1) { 152 | if (i > 0) { 153 | sb.append(','); 154 | } 155 | Object object = ja.opt(i); 156 | if (object != null) { 157 | String string = object.toString(); 158 | if (string.length() > 0 && (string.indexOf(',') >= 0 || 159 | string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || 160 | string.indexOf(0) >= 0 || string.charAt(0) == '"')) { 161 | sb.append('"'); 162 | int length = string.length(); 163 | for (int j = 0; j < length; j += 1) { 164 | char c = string.charAt(j); 165 | if (c >= ' ' && c != '"') { 166 | sb.append(c); 167 | } 168 | } 169 | sb.append('"'); 170 | } else { 171 | sb.append(string); 172 | } 173 | } 174 | } 175 | sb.append('\n'); 176 | return sb.toString(); 177 | } 178 | 179 | /** 180 | * Produce a JSONArray of JSONObjects from a comma delimited text string, 181 | * using the first row as a source of names. 182 | * @param string The comma delimited text. 183 | * @return A JSONArray of JSONObjects. 184 | * @throws JSONException 185 | */ 186 | public static JSONArray toJSONArray(String string) throws JSONException { 187 | return toJSONArray(new JSONTokener(string)); 188 | } 189 | 190 | /** 191 | * Produce a JSONArray of JSONObjects from a comma delimited text string, 192 | * using the first row as a source of names. 193 | * @param x The JSONTokener containing the comma delimited text. 194 | * @return A JSONArray of JSONObjects. 195 | * @throws JSONException 196 | */ 197 | public static JSONArray toJSONArray(JSONTokener x) throws JSONException { 198 | return toJSONArray(rowToJSONArray(x), x); 199 | } 200 | 201 | /** 202 | * Produce a JSONArray of JSONObjects from a comma delimited text string 203 | * using a supplied JSONArray as the source of element names. 204 | * @param names A JSONArray of strings. 205 | * @param string The comma delimited text. 206 | * @return A JSONArray of JSONObjects. 207 | * @throws JSONException 208 | */ 209 | public static JSONArray toJSONArray(JSONArray names, String string) 210 | throws JSONException { 211 | return toJSONArray(names, new JSONTokener(string)); 212 | } 213 | 214 | /** 215 | * Produce a JSONArray of JSONObjects from a comma delimited text string 216 | * using a supplied JSONArray as the source of element names. 217 | * @param names A JSONArray of strings. 218 | * @param x A JSONTokener of the source text. 219 | * @return A JSONArray of JSONObjects. 220 | * @throws JSONException 221 | */ 222 | public static JSONArray toJSONArray(JSONArray names, JSONTokener x) 223 | throws JSONException { 224 | if (names == null || names.length() == 0) { 225 | return null; 226 | } 227 | JSONArray ja = new JSONArray(); 228 | for (;;) { 229 | JSONObject jo = rowToJSONObject(names, x); 230 | if (jo == null) { 231 | break; 232 | } 233 | ja.put(jo); 234 | } 235 | if (ja.length() == 0) { 236 | return null; 237 | } 238 | return ja; 239 | } 240 | 241 | 242 | /** 243 | * Produce a comma delimited text from a JSONArray of JSONObjects. The 244 | * first row will be a list of names obtained by inspecting the first 245 | * JSONObject. 246 | * @param ja A JSONArray of JSONObjects. 247 | * @return A comma delimited text. 248 | * @throws JSONException 249 | */ 250 | public static String toString(JSONArray ja) throws JSONException { 251 | JSONObject jo = ja.optJSONObject(0); 252 | if (jo != null) { 253 | JSONArray names = jo.names(); 254 | if (names != null) { 255 | return rowToString(names) + toString(names, ja); 256 | } 257 | } 258 | return null; 259 | } 260 | 261 | /** 262 | * Produce a comma delimited text from a JSONArray of JSONObjects using 263 | * a provided list of names. The list of names is not included in the 264 | * output. 265 | * @param names A JSONArray of strings. 266 | * @param ja A JSONArray of JSONObjects. 267 | * @return A comma delimited text. 268 | * @throws JSONException 269 | */ 270 | public static String toString(JSONArray names, JSONArray ja) 271 | throws JSONException { 272 | if (names == null || names.length() == 0) { 273 | return null; 274 | } 275 | StringBuffer sb = new StringBuffer(); 276 | for (int i = 0; i < ja.length(); i += 1) { 277 | JSONObject jo = ja.optJSONObject(i); 278 | if (jo != null) { 279 | sb.append(rowToString(jo.toJSONArray(names))); 280 | } 281 | } 282 | return sb.toString(); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/org/json/Cookie.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Convert a web browser cookie specification to a JSONObject and back. 29 | * JSON and Cookies are both notations for name/value pairs. 30 | * @author JSON.org 31 | * @version 2015-12-09 32 | */ 33 | public class Cookie { 34 | 35 | /** 36 | * Produce a copy of a string in which the characters '+', '%', '=', ';' 37 | * and control characters are replaced with "%hh". This is a gentle form 38 | * of URL encoding, attempting to cause as little distortion to the 39 | * string as possible. The characters '=' and ';' are meta characters in 40 | * cookies. By convention, they are escaped using the URL-encoding. This is 41 | * only a convention, not a standard. Often, cookies are expected to have 42 | * encoded values. We encode '=' and ';' because we must. We encode '%' and 43 | * '+' because they are meta characters in URL encoding. 44 | * @param string The source string. 45 | * @return The escaped result. 46 | */ 47 | public static String escape(String string) { 48 | char c; 49 | String s = string.trim(); 50 | int length = s.length(); 51 | StringBuilder sb = new StringBuilder(length); 52 | for (int i = 0; i < length; i += 1) { 53 | c = s.charAt(i); 54 | if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { 55 | sb.append('%'); 56 | sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); 57 | sb.append(Character.forDigit((char)(c & 0x0f), 16)); 58 | } else { 59 | sb.append(c); 60 | } 61 | } 62 | return sb.toString(); 63 | } 64 | 65 | 66 | /** 67 | * Convert a cookie specification string into a JSONObject. The string 68 | * will contain a name value pair separated by '='. The name and the value 69 | * will be unescaped, possibly converting '+' and '%' sequences. The 70 | * cookie properties may follow, separated by ';', also represented as 71 | * name=value (except the secure property, which does not have a value). 72 | * The name will be stored under the key "name", and the value will be 73 | * stored under the key "value". This method does not do checking or 74 | * validation of the parameters. It only converts the cookie string into 75 | * a JSONObject. 76 | * @param string The cookie specification string. 77 | * @return A JSONObject containing "name", "value", and possibly other 78 | * members. 79 | * @throws JSONException 80 | */ 81 | public static JSONObject toJSONObject(String string) throws JSONException { 82 | String name; 83 | JSONObject jo = new JSONObject(); 84 | Object value; 85 | JSONTokener x = new JSONTokener(string); 86 | jo.put("name", x.nextTo('=')); 87 | x.next('='); 88 | jo.put("value", x.nextTo(';')); 89 | x.next(); 90 | while (x.more()) { 91 | name = unescape(x.nextTo("=;")); 92 | if (x.next() != '=') { 93 | if (name.equals("secure")) { 94 | value = Boolean.TRUE; 95 | } else { 96 | throw x.syntaxError("Missing '=' in cookie parameter."); 97 | } 98 | } else { 99 | value = unescape(x.nextTo(';')); 100 | x.next(); 101 | } 102 | jo.put(name, value); 103 | } 104 | return jo; 105 | } 106 | 107 | 108 | /** 109 | * Convert a JSONObject into a cookie specification string. The JSONObject 110 | * must contain "name" and "value" members. 111 | * If the JSONObject contains "expires", "domain", "path", or "secure" 112 | * members, they will be appended to the cookie specification string. 113 | * All other members are ignored. 114 | * @param jo A JSONObject 115 | * @return A cookie specification string 116 | * @throws JSONException 117 | */ 118 | public static String toString(JSONObject jo) throws JSONException { 119 | StringBuilder sb = new StringBuilder(); 120 | 121 | sb.append(escape(jo.getString("name"))); 122 | sb.append("="); 123 | sb.append(escape(jo.getString("value"))); 124 | if (jo.has("expires")) { 125 | sb.append(";expires="); 126 | sb.append(jo.getString("expires")); 127 | } 128 | if (jo.has("domain")) { 129 | sb.append(";domain="); 130 | sb.append(escape(jo.getString("domain"))); 131 | } 132 | if (jo.has("path")) { 133 | sb.append(";path="); 134 | sb.append(escape(jo.getString("path"))); 135 | } 136 | if (jo.optBoolean("secure")) { 137 | sb.append(";secure"); 138 | } 139 | return sb.toString(); 140 | } 141 | 142 | /** 143 | * Convert %hh sequences to single characters, and 144 | * convert plus to space. 145 | * @param string A string that may contain 146 | * + (plus) and 147 | * %hh sequences. 148 | * @return The unescaped string. 149 | */ 150 | public static String unescape(String string) { 151 | int length = string.length(); 152 | StringBuilder sb = new StringBuilder(length); 153 | for (int i = 0; i < length; ++i) { 154 | char c = string.charAt(i); 155 | if (c == '+') { 156 | c = ' '; 157 | } else if (c == '%' && i + 2 < length) { 158 | int d = JSONTokener.dehexchar(string.charAt(i + 1)); 159 | int e = JSONTokener.dehexchar(string.charAt(i + 2)); 160 | if (d >= 0 && e >= 0) { 161 | c = (char)(d * 16 + e); 162 | i += 2; 163 | } 164 | } 165 | sb.append(c); 166 | } 167 | return sb.toString(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/org/json/CookieList.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Convert a web browser cookie list string to a JSONObject and back. 31 | * @author JSON.org 32 | * @version 2015-12-09 33 | */ 34 | public class CookieList { 35 | 36 | /** 37 | * Convert a cookie list into a JSONObject. A cookie list is a sequence 38 | * of name/value pairs. The names are separated from the values by '='. 39 | * The pairs are separated by ';'. The names and the values 40 | * will be unescaped, possibly converting '+' and '%' sequences. 41 | * 42 | * To add a cookie to a cooklist, 43 | * cookielistJSONObject.put(cookieJSONObject.getString("name"), 44 | * cookieJSONObject.getString("value")); 45 | * @param string A cookie list string 46 | * @return A JSONObject 47 | * @throws JSONException 48 | */ 49 | public static JSONObject toJSONObject(String string) throws JSONException { 50 | JSONObject jo = new JSONObject(); 51 | JSONTokener x = new JSONTokener(string); 52 | while (x.more()) { 53 | String name = Cookie.unescape(x.nextTo('=')); 54 | x.next('='); 55 | jo.put(name, Cookie.unescape(x.nextTo(';'))); 56 | x.next(); 57 | } 58 | return jo; 59 | } 60 | 61 | /** 62 | * Convert a JSONObject into a cookie list. A cookie list is a sequence 63 | * of name/value pairs. The names are separated from the values by '='. 64 | * The pairs are separated by ';'. The characters '%', '+', '=', and ';' 65 | * in the names and values are replaced by "%hh". 66 | * @param jo A JSONObject 67 | * @return A cookie list string 68 | * @throws JSONException 69 | */ 70 | public static String toString(JSONObject jo) throws JSONException { 71 | boolean b = false; 72 | Iterator keys = jo.keys(); 73 | String string; 74 | StringBuilder sb = new StringBuilder(); 75 | while (keys.hasNext()) { 76 | string = keys.next(); 77 | if (!jo.isNull(string)) { 78 | if (b) { 79 | sb.append(';'); 80 | } 81 | sb.append(Cookie.escape(string)); 82 | sb.append("="); 83 | sb.append(Cookie.escape(jo.getString(string))); 84 | b = true; 85 | } 86 | } 87 | return sb.toString(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/org/json/HTTP.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Convert an HTTP header to a JSONObject and back. 31 | * @author JSON.org 32 | * @version 2015-12-09 33 | */ 34 | public class HTTP { 35 | 36 | /** Carriage return/line feed. */ 37 | public static final String CRLF = "\r\n"; 38 | 39 | /** 40 | * Convert an HTTP header string into a JSONObject. It can be a request 41 | * header or a response header. A request header will contain 42 | *

{
 43 |      *    Method: "POST" (for example),
 44 |      *    "Request-URI": "/" (for example),
 45 |      *    "HTTP-Version": "HTTP/1.1" (for example)
 46 |      * }
47 | * A response header will contain 48 | *
{
 49 |      *    "HTTP-Version": "HTTP/1.1" (for example),
 50 |      *    "Status-Code": "200" (for example),
 51 |      *    "Reason-Phrase": "OK" (for example)
 52 |      * }
53 | * In addition, the other parameters in the header will be captured, using 54 | * the HTTP field names as JSON names, so that
 55 |      *    Date: Sun, 26 May 2002 18:06:04 GMT
 56 |      *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
 57 |      *    Cache-Control: no-cache
58 | * become 59 | *
{...
 60 |      *    Date: "Sun, 26 May 2002 18:06:04 GMT",
 61 |      *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
 62 |      *    "Cache-Control": "no-cache",
 63 |      * ...}
64 | * It does no further checking or conversion. It does not parse dates. 65 | * It does not do '%' transforms on URLs. 66 | * @param string An HTTP header string. 67 | * @return A JSONObject containing the elements and attributes 68 | * of the XML string. 69 | * @throws JSONException 70 | */ 71 | public static JSONObject toJSONObject(String string) throws JSONException { 72 | JSONObject jo = new JSONObject(); 73 | HTTPTokener x = new HTTPTokener(string); 74 | String token; 75 | 76 | token = x.nextToken(); 77 | if (token.toUpperCase().startsWith("HTTP")) { 78 | 79 | // Response 80 | 81 | jo.put("HTTP-Version", token); 82 | jo.put("Status-Code", x.nextToken()); 83 | jo.put("Reason-Phrase", x.nextTo('\0')); 84 | x.next(); 85 | 86 | } else { 87 | 88 | // Request 89 | 90 | jo.put("Method", token); 91 | jo.put("Request-URI", x.nextToken()); 92 | jo.put("HTTP-Version", x.nextToken()); 93 | } 94 | 95 | // Fields 96 | 97 | while (x.more()) { 98 | String name = x.nextTo(':'); 99 | x.next(':'); 100 | jo.put(name, x.nextTo('\0')); 101 | x.next(); 102 | } 103 | return jo; 104 | } 105 | 106 | 107 | /** 108 | * Convert a JSONObject into an HTTP header. A request header must contain 109 | *
{
110 |      *    Method: "POST" (for example),
111 |      *    "Request-URI": "/" (for example),
112 |      *    "HTTP-Version": "HTTP/1.1" (for example)
113 |      * }
114 | * A response header must contain 115 | *
{
116 |      *    "HTTP-Version": "HTTP/1.1" (for example),
117 |      *    "Status-Code": "200" (for example),
118 |      *    "Reason-Phrase": "OK" (for example)
119 |      * }
120 | * Any other members of the JSONObject will be output as HTTP fields. 121 | * The result will end with two CRLF pairs. 122 | * @param jo A JSONObject 123 | * @return An HTTP header string. 124 | * @throws JSONException if the object does not contain enough 125 | * information. 126 | */ 127 | public static String toString(JSONObject jo) throws JSONException { 128 | Iterator keys = jo.keys(); 129 | String string; 130 | StringBuilder sb = new StringBuilder(); 131 | if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { 132 | sb.append(jo.getString("HTTP-Version")); 133 | sb.append(' '); 134 | sb.append(jo.getString("Status-Code")); 135 | sb.append(' '); 136 | sb.append(jo.getString("Reason-Phrase")); 137 | } else if (jo.has("Method") && jo.has("Request-URI")) { 138 | sb.append(jo.getString("Method")); 139 | sb.append(' '); 140 | sb.append('"'); 141 | sb.append(jo.getString("Request-URI")); 142 | sb.append('"'); 143 | sb.append(' '); 144 | sb.append(jo.getString("HTTP-Version")); 145 | } else { 146 | throw new JSONException("Not enough material for an HTTP header."); 147 | } 148 | sb.append(CRLF); 149 | while (keys.hasNext()) { 150 | string = keys.next(); 151 | if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) && 152 | !"Reason-Phrase".equals(string) && !"Method".equals(string) && 153 | !"Request-URI".equals(string) && !jo.isNull(string)) { 154 | sb.append(string); 155 | sb.append(": "); 156 | sb.append(jo.getString(string)); 157 | sb.append(CRLF); 158 | } 159 | } 160 | sb.append(CRLF); 161 | return sb.toString(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/org/json/HTTPTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The HTTPTokener extends the JSONTokener to provide additional methods 29 | * for the parsing of HTTP headers. 30 | * @author JSON.org 31 | * @version 2015-12-09 32 | */ 33 | public class HTTPTokener extends JSONTokener { 34 | 35 | /** 36 | * Construct an HTTPTokener from a string. 37 | * @param string A source string. 38 | */ 39 | public HTTPTokener(String string) { 40 | super(string); 41 | } 42 | 43 | 44 | /** 45 | * Get the next token or string. This is used in parsing HTTP headers. 46 | * @throws JSONException 47 | * @return A String. 48 | */ 49 | public String nextToken() throws JSONException { 50 | char c; 51 | char q; 52 | StringBuilder sb = new StringBuilder(); 53 | do { 54 | c = next(); 55 | } while (Character.isWhitespace(c)); 56 | if (c == '"' || c == '\'') { 57 | q = c; 58 | for (;;) { 59 | c = next(); 60 | if (c < ' ') { 61 | throw syntaxError("Unterminated string."); 62 | } 63 | if (c == q) { 64 | return sb.toString(); 65 | } 66 | sb.append(c); 67 | } 68 | } 69 | for (;;) { 70 | if (c == 0 || Character.isWhitespace(c)) { 71 | return sb.toString(); 72 | } 73 | sb.append(c); 74 | c = next(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/org/json/JSONException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /** 4 | * The JSONException is thrown by the JSON.org classes when things are amiss. 5 | * 6 | * @author JSON.org 7 | * @version 2015-12-09 8 | */ 9 | public class JSONException extends RuntimeException { 10 | /** Serialization ID */ 11 | private static final long serialVersionUID = 0; 12 | 13 | /** 14 | * Constructs a JSONException with an explanatory message. 15 | * 16 | * @param message 17 | * Detail about the reason for the exception. 18 | */ 19 | public JSONException(final String message) { 20 | super(message); 21 | } 22 | 23 | /** 24 | * Constructs a JSONException with an explanatory message and cause. 25 | * 26 | * @param message 27 | * Detail about the reason for the exception. 28 | * @param cause 29 | * The cause. 30 | */ 31 | public JSONException(final String message, final Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | /** 36 | * Constructs a new JSONException with the specified cause. 37 | * 38 | * @param cause 39 | * The cause. 40 | */ 41 | public JSONException(final Throwable cause) { 42 | super(cause.getMessage(), cause); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/org/json/JSONML.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2008 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | 30 | /** 31 | * This provides static methods to convert an XML text into a JSONArray or 32 | * JSONObject, and to covert a JSONArray or JSONObject into an XML text using 33 | * the JsonML transform. 34 | * 35 | * @author JSON.org 36 | * @version 2016-01-30 37 | */ 38 | public class JSONML { 39 | /** 40 | * Parse XML values and store them in a JSONArray. 41 | * @param x The XMLTokener containing the source string. 42 | * @param arrayForm true if array form, false if object form. 43 | * @param ja The JSONArray that is containing the current tag or null 44 | * if we are at the outermost level. 45 | * @param keepStrings Don't type-convert text nodes and attibute values 46 | * @return A JSONArray if the value is the outermost tag, otherwise null. 47 | * @throws JSONException 48 | */ 49 | private static Object parse( 50 | XMLTokener x, 51 | boolean arrayForm, 52 | JSONArray ja, 53 | boolean keepStrings 54 | ) throws JSONException { 55 | String attribute; 56 | char c; 57 | String closeTag = null; 58 | int i; 59 | JSONArray newja = null; 60 | JSONObject newjo = null; 61 | Object token; 62 | String tagName = null; 63 | 64 | // Test for and skip past these forms: 65 | // 66 | // 67 | // 68 | // 69 | 70 | while (true) { 71 | if (!x.more()) { 72 | throw x.syntaxError("Bad XML"); 73 | } 74 | token = x.nextContent(); 75 | if (token == XML.LT) { 76 | token = x.nextToken(); 77 | if (token instanceof Character) { 78 | if (token == XML.SLASH) { 79 | 80 | // Close tag "); 100 | } else { 101 | x.back(); 102 | } 103 | } else if (c == '[') { 104 | token = x.nextToken(); 105 | if (token.equals("CDATA") && x.next() == '[') { 106 | if (ja != null) { 107 | ja.put(x.nextCDATA()); 108 | } 109 | } else { 110 | throw x.syntaxError("Expected 'CDATA['"); 111 | } 112 | } else { 113 | i = 1; 114 | do { 115 | token = x.nextMeta(); 116 | if (token == null) { 117 | throw x.syntaxError("Missing '>' after ' 0); 124 | } 125 | } else if (token == XML.QUEST) { 126 | 127 | // "); 130 | } else { 131 | throw x.syntaxError("Misshaped tag"); 132 | } 133 | 134 | // Open tag < 135 | 136 | } else { 137 | if (!(token instanceof String)) { 138 | throw x.syntaxError("Bad tagName '" + token + "'."); 139 | } 140 | tagName = (String)token; 141 | newja = new JSONArray(); 142 | newjo = new JSONObject(); 143 | if (arrayForm) { 144 | newja.put(tagName); 145 | if (ja != null) { 146 | ja.put(newja); 147 | } 148 | } else { 149 | newjo.put("tagName", tagName); 150 | if (ja != null) { 151 | ja.put(newjo); 152 | } 153 | } 154 | token = null; 155 | for (;;) { 156 | if (token == null) { 157 | token = x.nextToken(); 158 | } 159 | if (token == null) { 160 | throw x.syntaxError("Misshaped tag"); 161 | } 162 | if (!(token instanceof String)) { 163 | break; 164 | } 165 | 166 | // attribute = value 167 | 168 | attribute = (String)token; 169 | if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) { 170 | throw x.syntaxError("Reserved attribute."); 171 | } 172 | token = x.nextToken(); 173 | if (token == XML.EQ) { 174 | token = x.nextToken(); 175 | if (!(token instanceof String)) { 176 | throw x.syntaxError("Missing value"); 177 | } 178 | newjo.accumulate(attribute, keepStrings ? token :JSONObject.stringToValue((String)token)); 179 | token = null; 180 | } else { 181 | newjo.accumulate(attribute, ""); 182 | } 183 | } 184 | if (arrayForm && newjo.length() > 0) { 185 | newja.put(newjo); 186 | } 187 | 188 | // Empty tag <.../> 189 | 190 | if (token == XML.SLASH) { 191 | if (x.nextToken() != XML.GT) { 192 | throw x.syntaxError("Misshaped tag"); 193 | } 194 | if (ja == null) { 195 | if (arrayForm) { 196 | return newja; 197 | } 198 | return newjo; 199 | } 200 | 201 | // Content, between <...> and 202 | 203 | } else { 204 | if (token != XML.GT) { 205 | throw x.syntaxError("Misshaped tag"); 206 | } 207 | closeTag = (String)parse(x, arrayForm, newja, keepStrings); 208 | if (closeTag != null) { 209 | if (!closeTag.equals(tagName)) { 210 | throw x.syntaxError("Mismatched '" + tagName + 211 | "' and '" + closeTag + "'"); 212 | } 213 | tagName = null; 214 | if (!arrayForm && newja.length() > 0) { 215 | newjo.put("childNodes", newja); 216 | } 217 | if (ja == null) { 218 | if (arrayForm) { 219 | return newja; 220 | } 221 | return newjo; 222 | } 223 | } 224 | } 225 | } 226 | } else { 227 | if (ja != null) { 228 | ja.put(token instanceof String 229 | ? keepStrings ? token :JSONObject.stringToValue((String)token) 230 | : token); 231 | } 232 | } 233 | } 234 | } 235 | 236 | 237 | /** 238 | * Convert a well-formed (but not necessarily valid) XML string into a 239 | * JSONArray using the JsonML transform. Each XML tag is represented as 240 | * a JSONArray in which the first element is the tag name. If the tag has 241 | * attributes, then the second element will be JSONObject containing the 242 | * name/value pairs. If the tag contains children, then strings and 243 | * JSONArrays will represent the child tags. 244 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 245 | * @param string The source string. 246 | * @return A JSONArray containing the structured data from the XML string. 247 | * @throws JSONException Thrown on error converting to a JSONArray 248 | */ 249 | public static JSONArray toJSONArray(String string) throws JSONException { 250 | return (JSONArray)parse(new XMLTokener(string), true, null, false); 251 | } 252 | 253 | 254 | /** 255 | * Convert a well-formed (but not necessarily valid) XML string into a 256 | * JSONArray using the JsonML transform. Each XML tag is represented as 257 | * a JSONArray in which the first element is the tag name. If the tag has 258 | * attributes, then the second element will be JSONObject containing the 259 | * name/value pairs. If the tag contains children, then strings and 260 | * JSONArrays will represent the child tags. 261 | * As opposed to toJSONArray this method does not attempt to convert 262 | * any text node or attribute value to any type 263 | * but just leaves it as a string. 264 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 265 | * @param string The source string. 266 | * @param keepStrings If true, then values will not be coerced into boolean 267 | * or numeric values and will instead be left as strings 268 | * @return A JSONArray containing the structured data from the XML string. 269 | * @throws JSONException Thrown on error converting to a JSONArray 270 | */ 271 | public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException { 272 | return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings); 273 | } 274 | 275 | 276 | /** 277 | * Convert a well-formed (but not necessarily valid) XML string into a 278 | * JSONArray using the JsonML transform. Each XML tag is represented as 279 | * a JSONArray in which the first element is the tag name. If the tag has 280 | * attributes, then the second element will be JSONObject containing the 281 | * name/value pairs. If the tag contains children, then strings and 282 | * JSONArrays will represent the child content and tags. 283 | * As opposed to toJSONArray this method does not attempt to convert 284 | * any text node or attribute value to any type 285 | * but just leaves it as a string. 286 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 287 | * @param x An XMLTokener. 288 | * @param keepStrings If true, then values will not be coerced into boolean 289 | * or numeric values and will instead be left as strings 290 | * @return A JSONArray containing the structured data from the XML string. 291 | * @throws JSONException Thrown on error converting to a JSONArray 292 | */ 293 | public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException { 294 | return (JSONArray)parse(x, true, null, keepStrings); 295 | } 296 | 297 | 298 | /** 299 | * Convert a well-formed (but not necessarily valid) XML string into a 300 | * JSONArray using the JsonML transform. Each XML tag is represented as 301 | * a JSONArray in which the first element is the tag name. If the tag has 302 | * attributes, then the second element will be JSONObject containing the 303 | * name/value pairs. If the tag contains children, then strings and 304 | * JSONArrays will represent the child content and tags. 305 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 306 | * @param x An XMLTokener. 307 | * @return A JSONArray containing the structured data from the XML string. 308 | * @throws JSONException Thrown on error converting to a JSONArray 309 | */ 310 | public static JSONArray toJSONArray(XMLTokener x) throws JSONException { 311 | return (JSONArray)parse(x, true, null, false); 312 | } 313 | 314 | 315 | /** 316 | * Convert a well-formed (but not necessarily valid) XML string into a 317 | * JSONObject using the JsonML transform. Each XML tag is represented as 318 | * a JSONObject with a "tagName" property. If the tag has attributes, then 319 | * the attributes will be in the JSONObject as properties. If the tag 320 | * contains children, the object will have a "childNodes" property which 321 | * will be an array of strings and JsonML JSONObjects. 322 | 323 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 324 | * @param string The XML source text. 325 | * @return A JSONObject containing the structured data from the XML string. 326 | * @throws JSONException Thrown on error converting to a JSONObject 327 | */ 328 | public static JSONObject toJSONObject(String string) throws JSONException { 329 | return (JSONObject)parse(new XMLTokener(string), false, null, false); 330 | } 331 | 332 | 333 | /** 334 | * Convert a well-formed (but not necessarily valid) XML string into a 335 | * JSONObject using the JsonML transform. Each XML tag is represented as 336 | * a JSONObject with a "tagName" property. If the tag has attributes, then 337 | * the attributes will be in the JSONObject as properties. If the tag 338 | * contains children, the object will have a "childNodes" property which 339 | * will be an array of strings and JsonML JSONObjects. 340 | 341 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 342 | * @param string The XML source text. 343 | * @param keepStrings If true, then values will not be coerced into boolean 344 | * or numeric values and will instead be left as strings 345 | * @return A JSONObject containing the structured data from the XML string. 346 | * @throws JSONException Thrown on error converting to a JSONObject 347 | */ 348 | public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { 349 | return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings); 350 | } 351 | 352 | 353 | /** 354 | * Convert a well-formed (but not necessarily valid) XML string into a 355 | * JSONObject using the JsonML transform. Each XML tag is represented as 356 | * a JSONObject with a "tagName" property. If the tag has attributes, then 357 | * the attributes will be in the JSONObject as properties. If the tag 358 | * contains children, the object will have a "childNodes" property which 359 | * will be an array of strings and JsonML JSONObjects. 360 | 361 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 362 | * @param x An XMLTokener of the XML source text. 363 | * @return A JSONObject containing the structured data from the XML string. 364 | * @throws JSONException Thrown on error converting to a JSONObject 365 | */ 366 | public static JSONObject toJSONObject(XMLTokener x) throws JSONException { 367 | return (JSONObject)parse(x, false, null, false); 368 | } 369 | 370 | 371 | /** 372 | * Convert a well-formed (but not necessarily valid) XML string into a 373 | * JSONObject using the JsonML transform. Each XML tag is represented as 374 | * a JSONObject with a "tagName" property. If the tag has attributes, then 375 | * the attributes will be in the JSONObject as properties. If the tag 376 | * contains children, the object will have a "childNodes" property which 377 | * will be an array of strings and JsonML JSONObjects. 378 | 379 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored. 380 | * @param x An XMLTokener of the XML source text. 381 | * @param keepStrings If true, then values will not be coerced into boolean 382 | * or numeric values and will instead be left as strings 383 | * @return A JSONObject containing the structured data from the XML string. 384 | * @throws JSONException Thrown on error converting to a JSONObject 385 | */ 386 | public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException { 387 | return (JSONObject)parse(x, false, null, keepStrings); 388 | } 389 | 390 | 391 | /** 392 | * Reverse the JSONML transformation, making an XML text from a JSONArray. 393 | * @param ja A JSONArray. 394 | * @return An XML string. 395 | * @throws JSONException Thrown on error converting to a string 396 | */ 397 | public static String toString(JSONArray ja) throws JSONException { 398 | int i; 399 | JSONObject jo; 400 | String key; 401 | Iterator keys; 402 | int length; 403 | Object object; 404 | StringBuilder sb = new StringBuilder(); 405 | String tagName; 406 | String value; 407 | 408 | // Emit = length) { 445 | sb.append('/'); 446 | sb.append('>'); 447 | } else { 448 | sb.append('>'); 449 | do { 450 | object = ja.get(i); 451 | i += 1; 452 | if (object != null) { 453 | if (object instanceof String) { 454 | sb.append(XML.escape(object.toString())); 455 | } else if (object instanceof JSONObject) { 456 | sb.append(toString((JSONObject)object)); 457 | } else if (object instanceof JSONArray) { 458 | sb.append(toString((JSONArray)object)); 459 | } else { 460 | sb.append(object.toString()); 461 | } 462 | } 463 | } while (i < length); 464 | sb.append('<'); 465 | sb.append('/'); 466 | sb.append(tagName); 467 | sb.append('>'); 468 | } 469 | return sb.toString(); 470 | } 471 | 472 | /** 473 | * Reverse the JSONML transformation, making an XML text from a JSONObject. 474 | * The JSONObject must contain a "tagName" property. If it has children, 475 | * then it must have a "childNodes" property containing an array of objects. 476 | * The other properties are attributes with string values. 477 | * @param jo A JSONObject. 478 | * @return An XML string. 479 | * @throws JSONException Thrown on error converting to a string 480 | */ 481 | public static String toString(JSONObject jo) throws JSONException { 482 | StringBuilder sb = new StringBuilder(); 483 | int i; 484 | JSONArray ja; 485 | String key; 486 | Iterator keys; 487 | int length; 488 | Object object; 489 | String tagName; 490 | String value; 491 | 492 | //Emit '); 528 | } else { 529 | sb.append('>'); 530 | length = ja.length(); 531 | for (i = 0; i < length; i += 1) { 532 | object = ja.get(i); 533 | if (object != null) { 534 | if (object instanceof String) { 535 | sb.append(XML.escape(object.toString())); 536 | } else if (object instanceof JSONObject) { 537 | sb.append(toString((JSONObject)object)); 538 | } else if (object instanceof JSONArray) { 539 | sb.append(toString((JSONArray)object)); 540 | } else { 541 | sb.append(object.toString()); 542 | } 543 | } 544 | } 545 | sb.append('<'); 546 | sb.append('/'); 547 | sb.append(tagName); 548 | sb.append('>'); 549 | } 550 | return sb.toString(); 551 | } 552 | } 553 | -------------------------------------------------------------------------------- /src/org/json/JSONPointer.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | import static java.lang.String.format; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLDecoder; 7 | import java.net.URLEncoder; 8 | import java.util.*; 9 | 10 | /* 11 | Copyright (c) 2002 JSON.org 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | The Software shall be used for Good, not Evil. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | /** 35 | * A JSON Pointer is a simple query language defined for JSON documents by 36 | * RFC 6901. 37 | * 38 | * In a nutshell, JSONPointer allows the user to navigate into a JSON document 39 | * using strings, and retrieve targeted objects, like a simple form of XPATH. 40 | * Path segments are separated by the '/' char, which signifies the root of 41 | * the document when it appears as the first char of the string. Array 42 | * elements are navigated using ordinals, counting from 0. JSONPointer strings 43 | * may be extended to any arbitrary number of segments. If the navigation 44 | * is successful, the matched item is returned. A matched item may be a 45 | * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building 46 | * fails, an appropriate exception is thrown. If the navigation fails to find 47 | * a match, a JSONPointerException is thrown. 48 | * 49 | * @author JSON.org 50 | * @version 2016-05-14 51 | */ 52 | public class JSONPointer { 53 | 54 | // used for URL encoding and decoding 55 | private static final String ENCODING = "utf-8"; 56 | 57 | /** 58 | * This class allows the user to build a JSONPointer in steps, using 59 | * exactly one segment in each step. 60 | */ 61 | public static class Builder { 62 | 63 | // Segments for the eventual JSONPointer string 64 | private final List refTokens = new ArrayList(); 65 | 66 | /** 67 | * Creates a {@code JSONPointer} instance using the tokens previously set using the 68 | * {@link #append(String)} method calls. 69 | */ 70 | public JSONPointer build() { 71 | return new JSONPointer(refTokens); 72 | } 73 | 74 | /** 75 | * Adds an arbitary token to the list of reference tokens. It can be any non-null value. 76 | * 77 | * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the 78 | * argument of this method MUST NOT be escaped. If you want to query the property called 79 | * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no 80 | * need to escape it as {@code "a~0b"}. 81 | * 82 | * @param token the new token to be appended to the list 83 | * @return {@code this} 84 | * @throws NullPointerException if {@code token} is null 85 | */ 86 | public Builder append(String token) { 87 | if (token == null) { 88 | throw new NullPointerException("token cannot be null"); 89 | } 90 | refTokens.add(token); 91 | return this; 92 | } 93 | 94 | /** 95 | * Adds an integer to the reference token list. Although not necessarily, mostly this token will 96 | * denote an array index. 97 | * 98 | * @param arrayIndex the array index to be added to the token list 99 | * @return {@code this} 100 | */ 101 | public Builder append(int arrayIndex) { 102 | refTokens.add(String.valueOf(arrayIndex)); 103 | return this; 104 | } 105 | } 106 | 107 | /** 108 | * Static factory method for {@link Builder}. Example usage: 109 | * 110 | *

111 |      * JSONPointer pointer = JSONPointer.builder()
112 |      *       .append("obj")
113 |      *       .append("other~key").append("another/key")
114 |      *       .append("\"")
115 |      *       .append(0)
116 |      *       .build();
117 |      * 
118 | * 119 | * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained 120 | * {@link Builder#append(String)} calls. 121 | */ 122 | public static Builder builder() { 123 | return new Builder(); 124 | } 125 | 126 | // Segments for the JSONPointer string 127 | private final List refTokens; 128 | 129 | /** 130 | * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to 131 | * evaluate the same JSON Pointer on different JSON documents then it is recommended 132 | * to keep the {@code JSONPointer} instances due to performance considerations. 133 | * 134 | * @param pointer the JSON String or URI Fragment representation of the JSON pointer. 135 | * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer 136 | */ 137 | public JSONPointer(String pointer) { 138 | if (pointer == null) { 139 | throw new NullPointerException("pointer cannot be null"); 140 | } 141 | if (pointer.isEmpty()) { 142 | refTokens = Collections.emptyList(); 143 | return; 144 | } 145 | if (pointer.startsWith("#/")) { 146 | pointer = pointer.substring(2); 147 | try { 148 | pointer = URLDecoder.decode(pointer, ENCODING); 149 | } catch (UnsupportedEncodingException e) { 150 | throw new RuntimeException(e); 151 | } 152 | } else if (pointer.startsWith("/")) { 153 | pointer = pointer.substring(1); 154 | } else { 155 | throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); 156 | } 157 | refTokens = new ArrayList(); 158 | for (String token : pointer.split("/")) { 159 | refTokens.add(unescape(token)); 160 | } 161 | } 162 | 163 | public JSONPointer(List refTokens) { 164 | this.refTokens = new ArrayList(refTokens); 165 | } 166 | 167 | private String unescape(String token) { 168 | return token.replace("~1", "/").replace("~0", "~") 169 | .replace("\\\"", "\"") 170 | .replace("\\\\", "\\"); 171 | } 172 | 173 | /** 174 | * Evaluates this JSON Pointer on the given {@code document}. The {@code document} 175 | * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty 176 | * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the 177 | * returned value will be {@code document} itself. 178 | * 179 | * @param document the JSON document which should be the subject of querying. 180 | * @return the result of the evaluation 181 | * @throws JSONPointerException if an error occurs during evaluation 182 | */ 183 | public Object queryFrom(Object document) { 184 | if (refTokens.isEmpty()) { 185 | return document; 186 | } 187 | Object current = document; 188 | for (String token : refTokens) { 189 | if (current instanceof JSONObject) { 190 | current = ((JSONObject) current).opt(unescape(token)); 191 | } else if (current instanceof JSONArray) { 192 | current = readByIndexToken(current, token); 193 | } else { 194 | throw new JSONPointerException(format( 195 | "value [%s] is not an array or object therefore its key %s cannot be resolved", current, 196 | token)); 197 | } 198 | } 199 | return current; 200 | } 201 | 202 | /** 203 | * Matches a JSONArray element by ordinal position 204 | * @param current the JSONArray to be evaluated 205 | * @param indexToken the array index in string form 206 | * @return the matched object. If no matching item is found a 207 | * JSONPointerException is thrown 208 | */ 209 | private Object readByIndexToken(Object current, String indexToken) { 210 | try { 211 | int index = Integer.parseInt(indexToken); 212 | JSONArray currentArr = (JSONArray) current; 213 | if (index >= currentArr.length()) { 214 | throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, 215 | currentArr.length())); 216 | } 217 | return currentArr.get(index); 218 | } catch (NumberFormatException e) { 219 | throw new JSONPointerException(format("%s is not an array index", indexToken), e); 220 | } 221 | } 222 | 223 | /** 224 | * Returns a string representing the JSONPointer path value using string 225 | * representation 226 | */ 227 | @Override 228 | public String toString() { 229 | StringBuilder rval = new StringBuilder(""); 230 | for (String token: refTokens) { 231 | rval.append('/').append(escape(token)); 232 | } 233 | return rval.toString(); 234 | } 235 | 236 | /** 237 | * Escapes path segment values to an unambiguous form. 238 | * The escape char to be inserted is '~'. The chars to be escaped 239 | * are ~, which maps to ~0, and /, which maps to ~1. Backslashes 240 | * and double quote chars are also escaped. 241 | * @param token the JSONPointer segment value to be escaped 242 | * @return the escaped value for the token 243 | */ 244 | private String escape(String token) { 245 | return token.replace("~", "~0") 246 | .replace("/", "~1") 247 | .replace("\\", "\\\\") 248 | .replace("\"", "\\\""); 249 | } 250 | 251 | /** 252 | * Returns a string representing the JSONPointer path value using URI 253 | * fragment identifier representation 254 | */ 255 | public String toURIFragment() { 256 | try { 257 | StringBuilder rval = new StringBuilder("#"); 258 | for (String token : refTokens) { 259 | rval.append('/').append(URLEncoder.encode(token, ENCODING)); 260 | } 261 | return rval.toString(); 262 | } catch (UnsupportedEncodingException e) { 263 | throw new RuntimeException(e); 264 | } 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/org/json/JSONPointerException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The JSONPointerException is thrown by {@link JSONPointer} if an error occurs 29 | * during evaluating a pointer. 30 | * 31 | * @author JSON.org 32 | * @version 2016-05-13 33 | */ 34 | public class JSONPointerException extends JSONException { 35 | private static final long serialVersionUID = 8872944667561856751L; 36 | 37 | public JSONPointerException(String message) { 38 | super(message); 39 | } 40 | 41 | public JSONPointerException(String message, Throwable cause) { 42 | super(message, cause); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/org/json/JSONString.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | /** 3 | * The JSONString interface allows a toJSONString() 4 | * method so that a class can change the behavior of 5 | * JSONObject.toString(), JSONArray.toString(), 6 | * and JSONWriter.value(Object). The 7 | * toJSONString method will be used instead of the default behavior 8 | * of using the Object's toString() method and quoting the result. 9 | */ 10 | public interface JSONString { 11 | /** 12 | * The toJSONString method allows a class to produce its own JSON 13 | * serialization. 14 | * 15 | * @return A strictly syntactically correct JSON text. 16 | */ 17 | public String toJSONString(); 18 | } 19 | -------------------------------------------------------------------------------- /src/org/json/JSONStringer.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2006 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.io.StringWriter; 28 | 29 | /** 30 | * JSONStringer provides a quick and convenient way of producing JSON text. 31 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 32 | * added, so the results are ready for transmission or storage. Each instance of 33 | * JSONStringer can produce one JSON text. 34 | *

35 | * A JSONStringer instance provides a value method for appending 36 | * values to the 37 | * text, and a key 38 | * method for adding keys before values in objects. There are array 39 | * and endArray methods that make and bound array values, and 40 | * object and endObject methods which make and bound 41 | * object values. All of these methods return the JSONWriter instance, 42 | * permitting cascade style. For example,

43 |  * myString = new JSONStringer()
44 |  *     .object()
45 |  *         .key("JSON")
46 |  *         .value("Hello, World!")
47 |  *     .endObject()
48 |  *     .toString();
which produces the string
49 |  * {"JSON":"Hello, World!"}
50 | *

51 | * The first method called must be array or object. 52 | * There are no methods for adding commas or colons. JSONStringer adds them for 53 | * you. Objects and arrays can be nested up to 20 levels deep. 54 | *

55 | * This can sometimes be easier than using a JSONObject to build a string. 56 | * @author JSON.org 57 | * @version 2015-12-09 58 | */ 59 | public class JSONStringer extends JSONWriter { 60 | /** 61 | * Make a fresh JSONStringer. It can be used to build one JSON text. 62 | */ 63 | public JSONStringer() { 64 | super(new StringWriter()); 65 | } 66 | 67 | /** 68 | * Return the JSON text. This method is used to obtain the product of the 69 | * JSONStringer instance. It will return null if there was a 70 | * problem in the construction of the JSON text (such as the calls to 71 | * array were not properly balanced with calls to 72 | * endArray). 73 | * @return The JSON text. 74 | */ 75 | public String toString() { 76 | return this.mode == 'd' ? this.writer.toString() : null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/org/json/JSONTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.Reader; 8 | import java.io.StringReader; 9 | 10 | /* 11 | Copyright (c) 2002 JSON.org 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | The Software shall be used for Good, not Evil. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | /** 35 | * A JSONTokener takes a source string and extracts characters and tokens from 36 | * it. It is used by the JSONObject and JSONArray constructors to parse 37 | * JSON source strings. 38 | * @author JSON.org 39 | * @version 2014-05-03 40 | */ 41 | public class JSONTokener { 42 | 43 | private long character; 44 | private boolean eof; 45 | private long index; 46 | private long line; 47 | private char previous; 48 | private Reader reader; 49 | private boolean usePrevious; 50 | 51 | 52 | /** 53 | * Construct a JSONTokener from a Reader. 54 | * 55 | * @param reader A reader. 56 | */ 57 | public JSONTokener(Reader reader) { 58 | this.reader = reader.markSupported() 59 | ? reader 60 | : new BufferedReader(reader); 61 | this.eof = false; 62 | this.usePrevious = false; 63 | this.previous = 0; 64 | this.index = 0; 65 | this.character = 1; 66 | this.line = 1; 67 | } 68 | 69 | 70 | /** 71 | * Construct a JSONTokener from an InputStream. 72 | * @param inputStream The source. 73 | */ 74 | public JSONTokener(InputStream inputStream) { 75 | this(new InputStreamReader(inputStream)); 76 | } 77 | 78 | 79 | /** 80 | * Construct a JSONTokener from a string. 81 | * 82 | * @param s A source string. 83 | */ 84 | public JSONTokener(String s) { 85 | this(new StringReader(s)); 86 | } 87 | 88 | 89 | /** 90 | * Back up one character. This provides a sort of lookahead capability, 91 | * so that you can test for a digit or letter before attempting to parse 92 | * the next number or identifier. 93 | * @throws JSONException Thrown if trying to step back more than 1 step 94 | * or if already at the start of the string 95 | */ 96 | public void back() throws JSONException { 97 | if (this.usePrevious || this.index <= 0) { 98 | throw new JSONException("Stepping back two steps is not supported"); 99 | } 100 | this.index -= 1; 101 | this.character -= 1; 102 | this.usePrevious = true; 103 | this.eof = false; 104 | } 105 | 106 | 107 | /** 108 | * Get the hex value of a character (base16). 109 | * @param c A character between '0' and '9' or between 'A' and 'F' or 110 | * between 'a' and 'f'. 111 | * @return An int between 0 and 15, or -1 if c was not a hex digit. 112 | */ 113 | public static int dehexchar(char c) { 114 | if (c >= '0' && c <= '9') { 115 | return c - '0'; 116 | } 117 | if (c >= 'A' && c <= 'F') { 118 | return c - ('A' - 10); 119 | } 120 | if (c >= 'a' && c <= 'f') { 121 | return c - ('a' - 10); 122 | } 123 | return -1; 124 | } 125 | 126 | /** 127 | * @return true if at the end of the file and we didn't step back 128 | */ 129 | public boolean end() { 130 | return this.eof && !this.usePrevious; 131 | } 132 | 133 | 134 | /** 135 | * Determine if the source string still contains characters that next() 136 | * can consume. 137 | * @return true if not yet at the end of the source. 138 | * @throws JSONException thrown if there is an error stepping forward 139 | * or backward while checking for more data. 140 | */ 141 | public boolean more() throws JSONException { 142 | this.next(); 143 | if (this.end()) { 144 | return false; 145 | } 146 | this.back(); 147 | return true; 148 | } 149 | 150 | 151 | /** 152 | * Get the next character in the source string. 153 | * 154 | * @return The next character, or 0 if past the end of the source string. 155 | * @throws JSONException Thrown if there is an error reading the source string. 156 | */ 157 | public char next() throws JSONException { 158 | int c; 159 | if (this.usePrevious) { 160 | this.usePrevious = false; 161 | c = this.previous; 162 | } else { 163 | try { 164 | c = this.reader.read(); 165 | } catch (IOException exception) { 166 | throw new JSONException(exception); 167 | } 168 | 169 | if (c <= 0) { // End of stream 170 | this.eof = true; 171 | c = 0; 172 | } 173 | } 174 | this.index += 1; 175 | if (this.previous == '\r') { 176 | this.line += 1; 177 | this.character = c == '\n' ? 0 : 1; 178 | } else if (c == '\n') { 179 | this.line += 1; 180 | this.character = 0; 181 | } else { 182 | this.character += 1; 183 | } 184 | this.previous = (char) c; 185 | return this.previous; 186 | } 187 | 188 | 189 | /** 190 | * Consume the next character, and check that it matches a specified 191 | * character. 192 | * @param c The character to match. 193 | * @return The character. 194 | * @throws JSONException if the character does not match. 195 | */ 196 | public char next(char c) throws JSONException { 197 | char n = this.next(); 198 | if (n != c) { 199 | throw this.syntaxError("Expected '" + c + "' and instead saw '" + 200 | n + "'"); 201 | } 202 | return n; 203 | } 204 | 205 | 206 | /** 207 | * Get the next n characters. 208 | * 209 | * @param n The number of characters to take. 210 | * @return A string of n characters. 211 | * @throws JSONException 212 | * Substring bounds error if there are not 213 | * n characters remaining in the source string. 214 | */ 215 | public String next(int n) throws JSONException { 216 | if (n == 0) { 217 | return ""; 218 | } 219 | 220 | char[] chars = new char[n]; 221 | int pos = 0; 222 | 223 | while (pos < n) { 224 | chars[pos] = this.next(); 225 | if (this.end()) { 226 | throw this.syntaxError("Substring bounds error"); 227 | } 228 | pos += 1; 229 | } 230 | return new String(chars); 231 | } 232 | 233 | 234 | /** 235 | * Get the next char in the string, skipping whitespace. 236 | * @throws JSONException Thrown if there is an error reading the source string. 237 | * @return A character, or 0 if there are no more characters. 238 | */ 239 | public char nextClean() throws JSONException { 240 | for (;;) { 241 | char c = this.next(); 242 | if (c == 0 || c > ' ') { 243 | return c; 244 | } 245 | } 246 | } 247 | 248 | 249 | /** 250 | * Return the characters up to the next close quote character. 251 | * Backslash processing is done. The formal JSON format does not 252 | * allow strings in single quotes, but an implementation is allowed to 253 | * accept them. 254 | * @param quote The quoting character, either 255 | * " (double quote) or 256 | * ' (single quote). 257 | * @return A String. 258 | * @throws JSONException Unterminated string. 259 | */ 260 | public String nextString(char quote) throws JSONException { 261 | char c; 262 | StringBuilder sb = new StringBuilder(); 263 | for (;;) { 264 | c = this.next(); 265 | switch (c) { 266 | case 0: 267 | case '\n': 268 | case '\r': 269 | throw this.syntaxError("Unterminated string"); 270 | case '\\': 271 | c = this.next(); 272 | switch (c) { 273 | case 'b': 274 | sb.append('\b'); 275 | break; 276 | case 't': 277 | sb.append('\t'); 278 | break; 279 | case 'n': 280 | sb.append('\n'); 281 | break; 282 | case 'f': 283 | sb.append('\f'); 284 | break; 285 | case 'r': 286 | sb.append('\r'); 287 | break; 288 | case 'u': 289 | try { 290 | sb.append((char)Integer.parseInt(this.next(4), 16)); 291 | } catch (NumberFormatException e) { 292 | throw this.syntaxError("Illegal escape.", e); 293 | } 294 | break; 295 | case '"': 296 | case '\'': 297 | case '\\': 298 | case '/': 299 | sb.append(c); 300 | break; 301 | default: 302 | throw this.syntaxError("Illegal escape."); 303 | } 304 | break; 305 | default: 306 | if (c == quote) { 307 | return sb.toString(); 308 | } 309 | sb.append(c); 310 | } 311 | } 312 | } 313 | 314 | 315 | /** 316 | * Get the text up but not including the specified character or the 317 | * end of line, whichever comes first. 318 | * @param delimiter A delimiter character. 319 | * @return A string. 320 | * @throws JSONException Thrown if there is an error while searching 321 | * for the delimiter 322 | */ 323 | public String nextTo(char delimiter) throws JSONException { 324 | StringBuilder sb = new StringBuilder(); 325 | for (;;) { 326 | char c = this.next(); 327 | if (c == delimiter || c == 0 || c == '\n' || c == '\r') { 328 | if (c != 0) { 329 | this.back(); 330 | } 331 | return sb.toString().trim(); 332 | } 333 | sb.append(c); 334 | } 335 | } 336 | 337 | 338 | /** 339 | * Get the text up but not including one of the specified delimiter 340 | * characters or the end of line, whichever comes first. 341 | * @param delimiters A set of delimiter characters. 342 | * @return A string, trimmed. 343 | * @throws JSONException Thrown if there is an error while searching 344 | * for the delimiter 345 | */ 346 | public String nextTo(String delimiters) throws JSONException { 347 | char c; 348 | StringBuilder sb = new StringBuilder(); 349 | for (;;) { 350 | c = this.next(); 351 | if (delimiters.indexOf(c) >= 0 || c == 0 || 352 | c == '\n' || c == '\r') { 353 | if (c != 0) { 354 | this.back(); 355 | } 356 | return sb.toString().trim(); 357 | } 358 | sb.append(c); 359 | } 360 | } 361 | 362 | 363 | /** 364 | * Get the next value. The value can be a Boolean, Double, Integer, 365 | * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. 366 | * @throws JSONException If syntax error. 367 | * 368 | * @return An object. 369 | */ 370 | public Object nextValue() throws JSONException { 371 | char c = this.nextClean(); 372 | String string; 373 | 374 | switch (c) { 375 | case '"': 376 | case '\'': 377 | return this.nextString(c); 378 | case '{': 379 | this.back(); 380 | return new JSONObject(this); 381 | case '[': 382 | this.back(); 383 | return new JSONArray(this); 384 | } 385 | 386 | /* 387 | * Handle unquoted text. This could be the values true, false, or 388 | * null, or it can be a number. An implementation (such as this one) 389 | * is allowed to also accept non-standard forms. 390 | * 391 | * Accumulate characters until we reach the end of the text or a 392 | * formatting character. 393 | */ 394 | 395 | StringBuilder sb = new StringBuilder(); 396 | while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { 397 | sb.append(c); 398 | c = this.next(); 399 | } 400 | this.back(); 401 | 402 | string = sb.toString().trim(); 403 | if ("".equals(string)) { 404 | throw this.syntaxError("Missing value"); 405 | } 406 | return JSONObject.stringToValue(string); 407 | } 408 | 409 | 410 | /** 411 | * Skip characters until the next character is the requested character. 412 | * If the requested character is not found, no characters are skipped. 413 | * @param to A character to skip to. 414 | * @return The requested character, or zero if the requested character 415 | * is not found. 416 | * @throws JSONException Thrown if there is an error while searching 417 | * for the to character 418 | */ 419 | public char skipTo(char to) throws JSONException { 420 | char c; 421 | try { 422 | long startIndex = this.index; 423 | long startCharacter = this.character; 424 | long startLine = this.line; 425 | this.reader.mark(1000000); 426 | do { 427 | c = this.next(); 428 | if (c == 0) { 429 | this.reader.reset(); 430 | this.index = startIndex; 431 | this.character = startCharacter; 432 | this.line = startLine; 433 | return c; 434 | } 435 | } while (c != to); 436 | } catch (IOException exception) { 437 | throw new JSONException(exception); 438 | } 439 | this.back(); 440 | return c; 441 | } 442 | 443 | 444 | /** 445 | * Make a JSONException to signal a syntax error. 446 | * 447 | * @param message The error message. 448 | * @return A JSONException object, suitable for throwing 449 | */ 450 | public JSONException syntaxError(String message) { 451 | return new JSONException(message + this.toString()); 452 | } 453 | 454 | /** 455 | * Make a JSONException to signal a syntax error. 456 | * 457 | * @param message The error message. 458 | * @param causedBy The throwable that caused the error. 459 | * @return A JSONException object, suitable for throwing 460 | */ 461 | public JSONException syntaxError(String message, Throwable causedBy) { 462 | return new JSONException(message + this.toString(), causedBy); 463 | } 464 | 465 | /** 466 | * Make a printable string of this JSONTokener. 467 | * 468 | * @return " at {index} [character {character} line {line}]" 469 | */ 470 | @Override 471 | public String toString() { 472 | return " at " + this.index + " [character " + this.character + " line " + 473 | this.line + "]"; 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/org/json/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | /* 7 | Copyright (c) 2006 JSON.org 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | The Software shall be used for Good, not Evil. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | /** 31 | * JSONWriter provides a quick and convenient way of producing JSON text. 32 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 33 | * added, so the results are ready for transmission or storage. Each instance of 34 | * JSONWriter can produce one JSON text. 35 | *

36 | * A JSONWriter instance provides a value method for appending 37 | * values to the 38 | * text, and a key 39 | * method for adding keys before values in objects. There are array 40 | * and endArray methods that make and bound array values, and 41 | * object and endObject methods which make and bound 42 | * object values. All of these methods return the JSONWriter instance, 43 | * permitting a cascade style. For example,

 44 |  * new JSONWriter(myWriter)
 45 |  *     .object()
 46 |  *         .key("JSON")
 47 |  *         .value("Hello, World!")
 48 |  *     .endObject();
which writes
 49 |  * {"JSON":"Hello, World!"}
50 | *

51 | * The first method called must be array or object. 52 | * There are no methods for adding commas or colons. JSONWriter adds them for 53 | * you. Objects and arrays can be nested up to 20 levels deep. 54 | *

55 | * This can sometimes be easier than using a JSONObject to build a string. 56 | * @author JSON.org 57 | * @version 2015-12-09 58 | */ 59 | public class JSONWriter { 60 | private static final int maxdepth = 200; 61 | 62 | /** 63 | * The comma flag determines if a comma should be output before the next 64 | * value. 65 | */ 66 | private boolean comma; 67 | 68 | /** 69 | * The current mode. Values: 70 | * 'a' (array), 71 | * 'd' (done), 72 | * 'i' (initial), 73 | * 'k' (key), 74 | * 'o' (object). 75 | */ 76 | protected char mode; 77 | 78 | /** 79 | * The object/array stack. 80 | */ 81 | private final JSONObject stack[]; 82 | 83 | /** 84 | * The stack top index. A value of 0 indicates that the stack is empty. 85 | */ 86 | private int top; 87 | 88 | /** 89 | * The writer that will receive the output. 90 | */ 91 | protected Writer writer; 92 | 93 | /** 94 | * Make a fresh JSONWriter. It can be used to build one JSON text. 95 | */ 96 | public JSONWriter(Writer w) { 97 | this.comma = false; 98 | this.mode = 'i'; 99 | this.stack = new JSONObject[maxdepth]; 100 | this.top = 0; 101 | this.writer = w; 102 | } 103 | 104 | /** 105 | * Append a value. 106 | * @param string A string value. 107 | * @return this 108 | * @throws JSONException If the value is out of sequence. 109 | */ 110 | private JSONWriter append(String string) throws JSONException { 111 | if (string == null) { 112 | throw new JSONException("Null pointer"); 113 | } 114 | if (this.mode == 'o' || this.mode == 'a') { 115 | try { 116 | if (this.comma && this.mode == 'a') { 117 | this.writer.write(','); 118 | } 119 | this.writer.write(string); 120 | } catch (IOException e) { 121 | throw new JSONException(e); 122 | } 123 | if (this.mode == 'o') { 124 | this.mode = 'k'; 125 | } 126 | this.comma = true; 127 | return this; 128 | } 129 | throw new JSONException("Value out of sequence."); 130 | } 131 | 132 | /** 133 | * Begin appending a new array. All values until the balancing 134 | * endArray will be appended to this array. The 135 | * endArray method must be called to mark the array's end. 136 | * @return this 137 | * @throws JSONException If the nesting is too deep, or if the object is 138 | * started in the wrong place (for example as a key or after the end of the 139 | * outermost array or object). 140 | */ 141 | public JSONWriter array() throws JSONException { 142 | if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { 143 | this.push(null); 144 | this.append("["); 145 | this.comma = false; 146 | return this; 147 | } 148 | throw new JSONException("Misplaced array."); 149 | } 150 | 151 | /** 152 | * End something. 153 | * @param mode Mode 154 | * @param c Closing character 155 | * @return this 156 | * @throws JSONException If unbalanced. 157 | */ 158 | private JSONWriter end(char mode, char c) throws JSONException { 159 | if (this.mode != mode) { 160 | throw new JSONException(mode == 'a' 161 | ? "Misplaced endArray." 162 | : "Misplaced endObject."); 163 | } 164 | this.pop(mode); 165 | try { 166 | this.writer.write(c); 167 | } catch (IOException e) { 168 | throw new JSONException(e); 169 | } 170 | this.comma = true; 171 | return this; 172 | } 173 | 174 | /** 175 | * End an array. This method most be called to balance calls to 176 | * array. 177 | * @return this 178 | * @throws JSONException If incorrectly nested. 179 | */ 180 | public JSONWriter endArray() throws JSONException { 181 | return this.end('a', ']'); 182 | } 183 | 184 | /** 185 | * End an object. This method most be called to balance calls to 186 | * object. 187 | * @return this 188 | * @throws JSONException If incorrectly nested. 189 | */ 190 | public JSONWriter endObject() throws JSONException { 191 | return this.end('k', '}'); 192 | } 193 | 194 | /** 195 | * Append a key. The key will be associated with the next value. In an 196 | * object, every value must be preceded by a key. 197 | * @param string A key string. 198 | * @return this 199 | * @throws JSONException If the key is out of place. For example, keys 200 | * do not belong in arrays or if the key is null. 201 | */ 202 | public JSONWriter key(String string) throws JSONException { 203 | if (string == null) { 204 | throw new JSONException("Null key."); 205 | } 206 | if (this.mode == 'k') { 207 | try { 208 | this.stack[this.top - 1].putOnce(string, Boolean.TRUE); 209 | if (this.comma) { 210 | this.writer.write(','); 211 | } 212 | this.writer.write(JSONObject.quote(string)); 213 | this.writer.write(':'); 214 | this.comma = false; 215 | this.mode = 'o'; 216 | return this; 217 | } catch (IOException e) { 218 | throw new JSONException(e); 219 | } 220 | } 221 | throw new JSONException("Misplaced key."); 222 | } 223 | 224 | 225 | /** 226 | * Begin appending a new object. All keys and values until the balancing 227 | * endObject will be appended to this object. The 228 | * endObject method must be called to mark the object's end. 229 | * @return this 230 | * @throws JSONException If the nesting is too deep, or if the object is 231 | * started in the wrong place (for example as a key or after the end of the 232 | * outermost array or object). 233 | */ 234 | public JSONWriter object() throws JSONException { 235 | if (this.mode == 'i') { 236 | this.mode = 'o'; 237 | } 238 | if (this.mode == 'o' || this.mode == 'a') { 239 | this.append("{"); 240 | this.push(new JSONObject()); 241 | this.comma = false; 242 | return this; 243 | } 244 | throw new JSONException("Misplaced object."); 245 | 246 | } 247 | 248 | 249 | /** 250 | * Pop an array or object scope. 251 | * @param c The scope to close. 252 | * @throws JSONException If nesting is wrong. 253 | */ 254 | private void pop(char c) throws JSONException { 255 | if (this.top <= 0) { 256 | throw new JSONException("Nesting error."); 257 | } 258 | char m = this.stack[this.top - 1] == null ? 'a' : 'k'; 259 | if (m != c) { 260 | throw new JSONException("Nesting error."); 261 | } 262 | this.top -= 1; 263 | this.mode = this.top == 0 264 | ? 'd' 265 | : this.stack[this.top - 1] == null 266 | ? 'a' 267 | : 'k'; 268 | } 269 | 270 | /** 271 | * Push an array or object scope. 272 | * @param jo The scope to open. 273 | * @throws JSONException If nesting is too deep. 274 | */ 275 | private void push(JSONObject jo) throws JSONException { 276 | if (this.top >= maxdepth) { 277 | throw new JSONException("Nesting too deep."); 278 | } 279 | this.stack[this.top] = jo; 280 | this.mode = jo == null ? 'a' : 'k'; 281 | this.top += 1; 282 | } 283 | 284 | 285 | /** 286 | * Append either the value true or the value 287 | * false. 288 | * @param b A boolean. 289 | * @return this 290 | * @throws JSONException 291 | */ 292 | public JSONWriter value(boolean b) throws JSONException { 293 | return this.append(b ? "true" : "false"); 294 | } 295 | 296 | /** 297 | * Append a double value. 298 | * @param d A double. 299 | * @return this 300 | * @throws JSONException If the number is not finite. 301 | */ 302 | public JSONWriter value(double d) throws JSONException { 303 | return this.value(new Double(d)); 304 | } 305 | 306 | /** 307 | * Append a long value. 308 | * @param l A long. 309 | * @return this 310 | * @throws JSONException 311 | */ 312 | public JSONWriter value(long l) throws JSONException { 313 | return this.append(Long.toString(l)); 314 | } 315 | 316 | 317 | /** 318 | * Append an object value. 319 | * @param object The object to append. It can be null, or a Boolean, Number, 320 | * String, JSONObject, or JSONArray, or an object that implements JSONString. 321 | * @return this 322 | * @throws JSONException If the value is out of sequence. 323 | */ 324 | public JSONWriter value(Object object) throws JSONException { 325 | return this.append(JSONObject.valueToString(object)); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/org/json/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002 JSON.org 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | The Software shall be used for Good, not Evil. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/org/json/Property.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Enumeration; 28 | import java.util.Iterator; 29 | import java.util.Properties; 30 | 31 | /** 32 | * Converts a Property file data into JSONObject and back. 33 | * @author JSON.org 34 | * @version 2015-05-05 35 | */ 36 | public class Property { 37 | /** 38 | * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. 39 | * @param properties java.util.Properties 40 | * @return JSONObject 41 | * @throws JSONException 42 | */ 43 | public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { 44 | JSONObject jo = new JSONObject(); 45 | if (properties != null && !properties.isEmpty()) { 46 | Enumeration enumProperties = properties.propertyNames(); 47 | while(enumProperties.hasMoreElements()) { 48 | String name = (String)enumProperties.nextElement(); 49 | jo.put(name, properties.getProperty(name)); 50 | } 51 | } 52 | return jo; 53 | } 54 | 55 | /** 56 | * Converts the JSONObject into a property file object. 57 | * @param jo JSONObject 58 | * @return java.util.Properties 59 | * @throws JSONException 60 | */ 61 | public static Properties toProperties(JSONObject jo) throws JSONException { 62 | Properties properties = new Properties(); 63 | if (jo != null) { 64 | Iterator keys = jo.keys(); 65 | while (keys.hasNext()) { 66 | String name = keys.next(); 67 | properties.put(name, jo.getString(name)); 68 | } 69 | } 70 | return properties; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/org/json/README: -------------------------------------------------------------------------------- 1 | JSON in Java [package org.json] 2 | 3 | JSON is a light-weight, language independent, data interchange format. 4 | See http://www.JSON.org/ 5 | 6 | The files in this package implement JSON encoders/decoders in Java. 7 | It also includes the capability to convert between JSON and XML, HTTP 8 | headers, Cookies, and CDL. 9 | 10 | This is a reference implementation. There is a large number of JSON packages 11 | in Java. Perhaps someday the Java community will standardize on one. Until 12 | then, choose carefully. 13 | 14 | The license includes this restriction: "The software shall be used for good, 15 | not evil." If your conscience cannot live with that, then choose a different 16 | package. 17 | 18 | The package compiles on Java 1.6-1.8. 19 | 20 | 21 | JSONObject.java: The JSONObject can parse text from a String or a JSONTokener 22 | to produce a map-like object. The object provides methods for manipulating its 23 | contents, and for producing a JSON compliant object serialization. 24 | 25 | JSONArray.java: The JSONObject can parse text from a String or a JSONTokener 26 | to produce a vector-like object. The object provides methods for manipulating 27 | its contents, and for producing a JSON compliant array serialization. 28 | 29 | JSONTokener.java: The JSONTokener breaks a text into a sequence of individual 30 | tokens. It can be constructed from a String, Reader, or InputStream. 31 | 32 | JSONException.java: The JSONException is the standard exception type thrown 33 | by this package. 34 | 35 | JSONPointer.java: Implementation of 36 | [JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901). Supports 37 | JSON Pointers both in the form of string representation and URI fragment 38 | representation. 39 | 40 | JSONString.java: The JSONString interface requires a toJSONString method, 41 | allowing an object to provide its own serialization. 42 | 43 | JSONStringer.java: The JSONStringer provides a convenient facility for 44 | building JSON strings. 45 | 46 | JSONWriter.java: The JSONWriter provides a convenient facility for building 47 | JSON text through a writer. 48 | 49 | 50 | CDL.java: CDL provides support for converting between JSON and comma 51 | delimited lists. 52 | 53 | Cookie.java: Cookie provides support for converting between JSON and cookies. 54 | 55 | CookieList.java: CookieList provides support for converting between JSON and 56 | cookie lists. 57 | 58 | HTTP.java: HTTP provides support for converting between JSON and HTTP headers. 59 | 60 | HTTPTokener.java: HTTPTokener extends JSONTokener for parsing HTTP headers. 61 | 62 | XML.java: XML provides support for converting between JSON and XML. 63 | 64 | JSONML.java: JSONML provides support for converting between JSONML and XML. 65 | 66 | XMLTokener.java: XMLTokener extends JSONTokener for parsing XML text. 67 | 68 | Unit tests are maintained in a separate project. Contributing developers can test 69 | JSON-java pull requests with the code in this project: 70 | https://github.com/stleary/JSON-Java-unit-test 71 | 72 | Numeric types in this package comply with ECMA-404: The JSON Data Interchange Format 73 | (http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) and 74 | RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format 75 | (https://tools.ietf.org/html/rfc7159#section-6). 76 | This package fully supports Integer, Long, and Double Java types. Partial support 77 | for BigInteger and BigDecimal values in JSONObject and JSONArray objects is provided 78 | in the form of get(), opt(), and put() API methods. 79 | 80 | Although 1.6 compatibility is currently supported, it is not a project goal and may be 81 | removed in some future release. 82 | 83 | In compliance with RFC7159 page 10 section 9, the parser is more lax with what is valid 84 | JSON than the Generator. For Example, the tab character (U+0009) is allowed when reading 85 | JSON Text strings, but when output by the Generator, tab is properly converted to \t in 86 | the string. Other instances may occur where reading invalid JSON text does not cause an 87 | error to be generated. Malformed JSON Texts such as missing end " (quote) on strings or 88 | invalid number formats (1.2e6.3) will cause errors as such documents can not be read 89 | reliably. 90 | 91 | Release history: 92 | 93 | 20160810 Revert code that was breaking opt*() methods. 94 | 95 | 20160807 This release contains a bug in the JSONObject.opt*() and JSONArray.opt*() methods, 96 | it is not recommended for use. 97 | Java 1.6 compatability fixed, JSONArray.toList() and JSONObject.toMap(), 98 | RFC4180 compatibility, JSONPointer, some exception fixes, optional XML type conversion. 99 | Contains the latest code as of 7 Aug, 2016 100 | 101 | 20160212 Java 1.6 compatibility, OSGi bundle. Contains the latest code as of 12 Feb, 2016. 102 | 103 | 20151123 JSONObject and JSONArray initialization with generics. Contains the 104 | latest code as of 23 Nov, 2015. 105 | 106 | 20150729 Checkpoint for Maven central repository release. Contains the latest code 107 | as of 29 July, 2015. 108 | 109 | JSON-java releases can be found by searching the Maven repository for groupId "org.json" 110 | and artifactId "json". For example: 111 | https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.json%22%20AND%20a%3A%22json%22 112 | -------------------------------------------------------------------------------- /src/org/json/XML.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2015 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * This provides static methods to convert an XML text into a JSONObject, and to 31 | * covert a JSONObject into an XML text. 32 | * 33 | * @author JSON.org 34 | * @version 2016-01-30 35 | */ 36 | @SuppressWarnings("boxing") 37 | public class XML { 38 | 39 | /** The Character '&'. */ 40 | public static final Character AMP = '&'; 41 | 42 | /** The Character '''. */ 43 | public static final Character APOS = '\''; 44 | 45 | /** The Character '!'. */ 46 | public static final Character BANG = '!'; 47 | 48 | /** The Character '='. */ 49 | public static final Character EQ = '='; 50 | 51 | /** The Character '>'. */ 52 | public static final Character GT = '>'; 53 | 54 | /** The Character '<'. */ 55 | public static final Character LT = '<'; 56 | 57 | /** The Character '?'. */ 58 | public static final Character QUEST = '?'; 59 | 60 | /** The Character '"'. */ 61 | public static final Character QUOT = '"'; 62 | 63 | /** The Character '/'. */ 64 | public static final Character SLASH = '/'; 65 | 66 | /** 67 | * Replace special characters with XML escapes: 68 | * 69 | *

 70 |      * & (ampersand) is replaced by &amp;
 71 |      * < (less than) is replaced by &lt;
 72 |      * > (greater than) is replaced by &gt;
 73 |      * " (double quote) is replaced by &quot;
 74 |      * 
75 | * 76 | * @param string 77 | * The string to be escaped. 78 | * @return The escaped string. 79 | */ 80 | public static String escape(String string) { 81 | StringBuilder sb = new StringBuilder(string.length()); 82 | for (int i = 0, length = string.length(); i < length; i++) { 83 | char c = string.charAt(i); 84 | switch (c) { 85 | case '&': 86 | sb.append("&"); 87 | break; 88 | case '<': 89 | sb.append("<"); 90 | break; 91 | case '>': 92 | sb.append(">"); 93 | break; 94 | case '"': 95 | sb.append("""); 96 | break; 97 | case '\'': 98 | sb.append("'"); 99 | break; 100 | default: 101 | sb.append(c); 102 | } 103 | } 104 | return sb.toString(); 105 | } 106 | 107 | /** 108 | * Throw an exception if the string contains whitespace. Whitespace is not 109 | * allowed in tagNames and attributes. 110 | * 111 | * @param string 112 | * A string. 113 | * @throws JSONException Thrown if the string contains whitespace or is empty. 114 | */ 115 | public static void noSpace(String string) throws JSONException { 116 | int i, length = string.length(); 117 | if (length == 0) { 118 | throw new JSONException("Empty string."); 119 | } 120 | for (i = 0; i < length; i += 1) { 121 | if (Character.isWhitespace(string.charAt(i))) { 122 | throw new JSONException("'" + string 123 | + "' contains a space character."); 124 | } 125 | } 126 | } 127 | 128 | /** 129 | * Scan the content following the named tag, attaching it to the context. 130 | * 131 | * @param x 132 | * The XMLTokener containing the source string. 133 | * @param context 134 | * The JSONObject that will include the new material. 135 | * @param name 136 | * The tag name. 137 | * @return true if the close tag is processed. 138 | * @throws JSONException 139 | */ 140 | private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) 141 | throws JSONException { 142 | char c; 143 | int i; 144 | JSONObject jsonobject = null; 145 | String string; 146 | String tagName; 147 | Object token; 148 | 149 | // Test for and skip past these forms: 150 | // 151 | // 152 | // 153 | // 154 | // Report errors for these forms: 155 | // <> 156 | // <= 157 | // << 158 | 159 | token = x.nextToken(); 160 | 161 | // "); 168 | return false; 169 | } 170 | x.back(); 171 | } else if (c == '[') { 172 | token = x.nextToken(); 173 | if ("CDATA".equals(token)) { 174 | if (x.next() == '[') { 175 | string = x.nextCDATA(); 176 | if (string.length() > 0) { 177 | context.accumulate("content", string); 178 | } 179 | return false; 180 | } 181 | } 182 | throw x.syntaxError("Expected 'CDATA['"); 183 | } 184 | i = 1; 185 | do { 186 | token = x.nextMeta(); 187 | if (token == null) { 188 | throw x.syntaxError("Missing '>' after ' 0); 195 | return false; 196 | } else if (token == QUEST) { 197 | 198 | // "); 200 | return false; 201 | } else if (token == SLASH) { 202 | 203 | // Close tag 250 | if (x.nextToken() != GT) { 251 | throw x.syntaxError("Misshaped tag"); 252 | } 253 | if (jsonobject.length() > 0) { 254 | context.accumulate(tagName, jsonobject); 255 | } else { 256 | context.accumulate(tagName, ""); 257 | } 258 | return false; 259 | 260 | } else if (token == GT) { 261 | // Content, between <...> and 262 | for (;;) { 263 | token = x.nextContent(); 264 | if (token == null) { 265 | if (tagName != null) { 266 | throw x.syntaxError("Unclosed tag " + tagName); 267 | } 268 | return false; 269 | } else if (token instanceof String) { 270 | string = (String) token; 271 | if (string.length() > 0) { 272 | jsonobject.accumulate("content", 273 | keepStrings ? token : JSONObject.stringToValue(string)); 274 | } 275 | 276 | } else if (token == LT) { 277 | // Nested element 278 | if (parse(x, jsonobject, tagName,keepStrings)) { 279 | if (jsonobject.length() == 0) { 280 | context.accumulate(tagName, ""); 281 | } else if (jsonobject.length() == 1 282 | && jsonobject.opt("content") != null) { 283 | context.accumulate(tagName, 284 | jsonobject.opt("content")); 285 | } else { 286 | context.accumulate(tagName, jsonobject); 287 | } 288 | return false; 289 | } 290 | } 291 | } 292 | } else { 293 | throw x.syntaxError("Misshaped tag"); 294 | } 295 | } 296 | } 297 | } 298 | 299 | /** 300 | * This method has been deprecated in favor of the 301 | * {@link JSONObject.stringToValue(String)} method. Use it instead. 302 | * 303 | * @deprecated Use {@link JSONObject#stringToValue(String)} instead. 304 | * @param string String to convert 305 | * @return JSON value of this string or the string 306 | */ 307 | @Deprecated 308 | public static Object stringToValue(String string) { 309 | return JSONObject.stringToValue(string); 310 | } 311 | 312 | /** 313 | * Convert a well-formed (but not necessarily valid) XML string into a 314 | * JSONObject. Some information may be lost in this transformation because 315 | * JSON is a data format and XML is a document format. XML uses elements, 316 | * attributes, and content text, while JSON uses unordered collections of 317 | * name/value pairs and arrays of values. JSON does not does not like to 318 | * distinguish between elements and attributes. Sequences of similar 319 | * elements are represented as JSONArrays. Content text may be placed in a 320 | * "content" member. Comments, prologs, DTDs, and <[ [ ]]> 321 | * are ignored. 322 | * 323 | * @param string 324 | * The source string. 325 | * @return A JSONObject containing the structured data from the XML string. 326 | * @throws JSONException Thrown if there is an errors while parsing the string 327 | */ 328 | public static JSONObject toJSONObject(String string) throws JSONException { 329 | return toJSONObject(string, false); 330 | } 331 | 332 | 333 | /** 334 | * Convert a well-formed (but not necessarily valid) XML string into a 335 | * JSONObject. Some information may be lost in this transformation because 336 | * JSON is a data format and XML is a document format. XML uses elements, 337 | * attributes, and content text, while JSON uses unordered collections of 338 | * name/value pairs and arrays of values. JSON does not does not like to 339 | * distinguish between elements and attributes. Sequences of similar 340 | * elements are represented as JSONArrays. Content text may be placed in a 341 | * "content" member. Comments, prologs, DTDs, and <[ [ ]]> 342 | * are ignored. 343 | * 344 | * All values are converted as strings, for 1, 01, 29.0 will not be coerced to 345 | * numbers but will instead be the exact value as seen in the XML document. 346 | * 347 | * @param string 348 | * The source string. 349 | * @param keepStrings If true, then values will not be coerced into boolean 350 | * or numeric values and will instead be left as strings 351 | * @return A JSONObject containing the structured data from the XML string. 352 | * @throws JSONException Thrown if there is an errors while parsing the string 353 | */ 354 | public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { 355 | JSONObject jo = new JSONObject(); 356 | XMLTokener x = new XMLTokener(string); 357 | while (x.more() && x.skipPast("<")) { 358 | parse(x, jo, null, keepStrings); 359 | } 360 | return jo; 361 | } 362 | /** 363 | * Convert a JSONObject into a well-formed, element-normal XML string. 364 | * 365 | * @param object 366 | * A JSONObject. 367 | * @return A string. 368 | * @throws JSONException Thrown if there is an error parsing the string 369 | */ 370 | public static String toString(Object object) throws JSONException { 371 | return toString(object, null); 372 | } 373 | 374 | /** 375 | * Convert a JSONObject into a well-formed, element-normal XML string. 376 | * 377 | * @param object 378 | * A JSONObject. 379 | * @param tagName 380 | * The optional name of the enclosing tag. 381 | * @return A string. 382 | * @throws JSONException Thrown if there is an error parsing the string 383 | */ 384 | public static String toString(Object object, String tagName) 385 | throws JSONException { 386 | StringBuilder sb = new StringBuilder(); 387 | JSONArray ja; 388 | JSONObject jo; 389 | String key; 390 | Iterator keys; 391 | String string; 392 | Object value; 393 | 394 | if (object instanceof JSONObject) { 395 | 396 | // Emit 397 | if (tagName != null) { 398 | sb.append('<'); 399 | sb.append(tagName); 400 | sb.append('>'); 401 | } 402 | 403 | // Loop thru the keys. 404 | jo = (JSONObject) object; 405 | keys = jo.keys(); 406 | while (keys.hasNext()) { 407 | key = keys.next(); 408 | value = jo.opt(key); 409 | if (value == null) { 410 | value = ""; 411 | } else if (value.getClass().isArray()) { 412 | value = new JSONArray(value); 413 | } 414 | string = value instanceof String ? (String) value : null; 415 | 416 | // Emit content in body 417 | if ("content".equals(key)) { 418 | if (value instanceof JSONArray) { 419 | ja = (JSONArray) value; 420 | int i = 0; 421 | for (Object val : ja) { 422 | if (i > 0) { 423 | sb.append('\n'); 424 | } 425 | sb.append(escape(val.toString())); 426 | i++; 427 | } 428 | } else { 429 | sb.append(escape(value.toString())); 430 | } 431 | 432 | // Emit an array of similar keys 433 | 434 | } else if (value instanceof JSONArray) { 435 | ja = (JSONArray) value; 436 | for (Object val : ja) { 437 | if (val instanceof JSONArray) { 438 | sb.append('<'); 439 | sb.append(key); 440 | sb.append('>'); 441 | sb.append(toString(val)); 442 | sb.append("'); 445 | } else { 446 | sb.append(toString(val, key)); 447 | } 448 | } 449 | } else if ("".equals(value)) { 450 | sb.append('<'); 451 | sb.append(key); 452 | sb.append("/>"); 453 | 454 | // Emit a new tag 455 | 456 | } else { 457 | sb.append(toString(value, key)); 458 | } 459 | } 460 | if (tagName != null) { 461 | 462 | // Emit the close tag 463 | sb.append("'); 466 | } 467 | return sb.toString(); 468 | 469 | } 470 | 471 | if (object != null) { 472 | if (object.getClass().isArray()) { 473 | object = new JSONArray(object); 474 | } 475 | 476 | if (object instanceof JSONArray) { 477 | ja = (JSONArray) object; 478 | for (Object val : ja) { 479 | // XML does not have good support for arrays. If an array 480 | // appears in a place where XML is lacking, synthesize an 481 | // element. 482 | sb.append(toString(val, tagName == null ? "array" : tagName)); 483 | } 484 | return sb.toString(); 485 | } 486 | } 487 | 488 | string = (object == null) ? "null" : escape(object.toString()); 489 | return (tagName == null) ? "\"" + string + "\"" 490 | : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName 491 | + ">" + string + ""; 492 | 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /src/org/json/XMLTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The XMLTokener extends the JSONTokener to provide additional methods 29 | * for the parsing of XML texts. 30 | * @author JSON.org 31 | * @version 2015-12-09 32 | */ 33 | public class XMLTokener extends JSONTokener { 34 | 35 | 36 | /** The table of entity values. It initially contains Character values for 37 | * amp, apos, gt, lt, quot. 38 | */ 39 | public static final java.util.HashMap entity; 40 | 41 | static { 42 | entity = new java.util.HashMap(8); 43 | entity.put("amp", XML.AMP); 44 | entity.put("apos", XML.APOS); 45 | entity.put("gt", XML.GT); 46 | entity.put("lt", XML.LT); 47 | entity.put("quot", XML.QUOT); 48 | } 49 | 50 | /** 51 | * Construct an XMLTokener from a string. 52 | * @param s A source string. 53 | */ 54 | public XMLTokener(String s) { 55 | super(s); 56 | } 57 | 58 | /** 59 | * Get the text in the CDATA block. 60 | * @return The string up to the ]]>. 61 | * @throws JSONException If the ]]> is not found. 62 | */ 63 | public String nextCDATA() throws JSONException { 64 | char c; 65 | int i; 66 | StringBuilder sb = new StringBuilder(); 67 | for (;;) { 68 | c = next(); 69 | if (end()) { 70 | throw syntaxError("Unclosed CDATA"); 71 | } 72 | sb.append(c); 73 | i = sb.length() - 3; 74 | if (i >= 0 && sb.charAt(i) == ']' && 75 | sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { 76 | sb.setLength(i); 77 | return sb.toString(); 78 | } 79 | } 80 | } 81 | 82 | 83 | /** 84 | * Get the next XML outer token, trimming whitespace. There are two kinds 85 | * of tokens: the '<' character which begins a markup tag, and the content 86 | * text between markup tags. 87 | * 88 | * @return A string, or a '<' Character, or null if there is no more 89 | * source text. 90 | * @throws JSONException 91 | */ 92 | public Object nextContent() throws JSONException { 93 | char c; 94 | StringBuilder sb; 95 | do { 96 | c = next(); 97 | } while (Character.isWhitespace(c)); 98 | if (c == 0) { 99 | return null; 100 | } 101 | if (c == '<') { 102 | return XML.LT; 103 | } 104 | sb = new StringBuilder(); 105 | for (;;) { 106 | if (c == '<' || c == 0) { 107 | back(); 108 | return sb.toString().trim(); 109 | } 110 | if (c == '&') { 111 | sb.append(nextEntity(c)); 112 | } else { 113 | sb.append(c); 114 | } 115 | c = next(); 116 | } 117 | } 118 | 119 | 120 | /** 121 | * Return the next entity. These entities are translated to Characters: 122 | * & ' > < ". 123 | * @param ampersand An ampersand character. 124 | * @return A Character or an entity String if the entity is not recognized. 125 | * @throws JSONException If missing ';' in XML entity. 126 | */ 127 | public Object nextEntity(char ampersand) throws JSONException { 128 | StringBuilder sb = new StringBuilder(); 129 | for (;;) { 130 | char c = next(); 131 | if (Character.isLetterOrDigit(c) || c == '#') { 132 | sb.append(Character.toLowerCase(c)); 133 | } else if (c == ';') { 134 | break; 135 | } else { 136 | throw syntaxError("Missing ';' in XML entity: &" + sb); 137 | } 138 | } 139 | String string = sb.toString(); 140 | Object object = entity.get(string); 141 | return object != null ? object : ampersand + string + ";"; 142 | } 143 | 144 | 145 | /** 146 | * Returns the next XML meta token. This is used for skipping over 147 | * and structures. 148 | * @return Syntax characters (< > / = ! ?) are returned as 149 | * Character, and strings and names are returned as Boolean. We don't care 150 | * what the values actually are. 151 | * @throws JSONException If a string is not properly closed or if the XML 152 | * is badly structured. 153 | */ 154 | public Object nextMeta() throws JSONException { 155 | char c; 156 | char q; 157 | do { 158 | c = next(); 159 | } while (Character.isWhitespace(c)); 160 | switch (c) { 161 | case 0: 162 | throw syntaxError("Misshaped meta tag"); 163 | case '<': 164 | return XML.LT; 165 | case '>': 166 | return XML.GT; 167 | case '/': 168 | return XML.SLASH; 169 | case '=': 170 | return XML.EQ; 171 | case '!': 172 | return XML.BANG; 173 | case '?': 174 | return XML.QUEST; 175 | case '"': 176 | case '\'': 177 | q = c; 178 | for (;;) { 179 | c = next(); 180 | if (c == 0) { 181 | throw syntaxError("Unterminated string"); 182 | } 183 | if (c == q) { 184 | return Boolean.TRUE; 185 | } 186 | } 187 | default: 188 | for (;;) { 189 | c = next(); 190 | if (Character.isWhitespace(c)) { 191 | return Boolean.TRUE; 192 | } 193 | switch (c) { 194 | case 0: 195 | case '<': 196 | case '>': 197 | case '/': 198 | case '=': 199 | case '!': 200 | case '?': 201 | case '"': 202 | case '\'': 203 | back(); 204 | return Boolean.TRUE; 205 | } 206 | } 207 | } 208 | } 209 | 210 | 211 | /** 212 | * Get the next XML Token. These tokens are found inside of angle 213 | * brackets. It may be one of these characters: / > = ! ? or it 214 | * may be a string wrapped in single quotes or double quotes, or it may be a 215 | * name. 216 | * @return a String or a Character. 217 | * @throws JSONException If the XML is not well formed. 218 | */ 219 | public Object nextToken() throws JSONException { 220 | char c; 221 | char q; 222 | StringBuilder sb; 223 | do { 224 | c = next(); 225 | } while (Character.isWhitespace(c)); 226 | switch (c) { 227 | case 0: 228 | throw syntaxError("Misshaped element"); 229 | case '<': 230 | throw syntaxError("Misplaced '<'"); 231 | case '>': 232 | return XML.GT; 233 | case '/': 234 | return XML.SLASH; 235 | case '=': 236 | return XML.EQ; 237 | case '!': 238 | return XML.BANG; 239 | case '?': 240 | return XML.QUEST; 241 | 242 | // Quoted string 243 | 244 | case '"': 245 | case '\'': 246 | q = c; 247 | sb = new StringBuilder(); 248 | for (;;) { 249 | c = next(); 250 | if (c == 0) { 251 | throw syntaxError("Unterminated string"); 252 | } 253 | if (c == q) { 254 | return sb.toString(); 255 | } 256 | if (c == '&') { 257 | sb.append(nextEntity(c)); 258 | } else { 259 | sb.append(c); 260 | } 261 | } 262 | default: 263 | 264 | // Name 265 | 266 | sb = new StringBuilder(); 267 | for (;;) { 268 | sb.append(c); 269 | c = next(); 270 | if (Character.isWhitespace(c)) { 271 | return sb.toString(); 272 | } 273 | switch (c) { 274 | case 0: 275 | return sb.toString(); 276 | case '>': 277 | case '/': 278 | case '=': 279 | case '!': 280 | case '?': 281 | case '[': 282 | case ']': 283 | back(); 284 | return sb.toString(); 285 | case '<': 286 | case '"': 287 | case '\'': 288 | throw syntaxError("Bad character in a name"); 289 | } 290 | } 291 | } 292 | } 293 | 294 | 295 | /** 296 | * Skip characters until past the requested string. 297 | * If it is not found, we are left at the end of the source with a result of false. 298 | * @param to A string to skip past. 299 | * @throws JSONException 300 | */ 301 | public boolean skipPast(String to) throws JSONException { 302 | boolean b; 303 | char c; 304 | int i; 305 | int j; 306 | int offset = 0; 307 | int length = to.length(); 308 | char[] circle = new char[length]; 309 | 310 | /* 311 | * First fill the circle buffer with as many characters as are in the 312 | * to string. If we reach an early end, bail. 313 | */ 314 | 315 | for (i = 0; i < length; i += 1) { 316 | c = next(); 317 | if (c == 0) { 318 | return false; 319 | } 320 | circle[i] = c; 321 | } 322 | 323 | /* We will loop, possibly for all of the remaining characters. */ 324 | 325 | for (;;) { 326 | j = offset; 327 | b = true; 328 | 329 | /* Compare the circle buffer with the to string. */ 330 | 331 | for (i = 0; i < length; i += 1) { 332 | if (circle[j] != to.charAt(i)) { 333 | b = false; 334 | break; 335 | } 336 | j += 1; 337 | if (j >= length) { 338 | j -= length; 339 | } 340 | } 341 | 342 | /* If we exit the loop with b intact, then victory is ours. */ 343 | 344 | if (b) { 345 | return true; 346 | } 347 | 348 | /* Get the next character. If there isn't one, then defeat is ours. */ 349 | 350 | c = next(); 351 | if (c == 0) { 352 | return false; 353 | } 354 | /* 355 | * Shove the character in the circle buffer and advance the 356 | * circle offset. The offset is mod n. 357 | */ 358 | circle[offset] = c; 359 | offset += 1; 360 | if (offset >= length) { 361 | offset -= length; 362 | } 363 | } 364 | } 365 | } 366 | --------------------------------------------------------------------------------