├── generators └── JavaGenerator │ ├── .gitignore │ ├── bld │ └── GarglJavaGenerator.jar │ ├── src │ └── main │ │ └── java │ │ └── gargl │ │ ├── typedefinitions │ │ ├── GarglModule.java │ │ └── Function.java │ │ ├── utilities │ │ ├── JCommanderParser.java │ │ ├── JsonUtils.java │ │ └── Parameter.java │ │ ├── generators │ │ ├── GeneratorFactory.java │ │ ├── Generator.java │ │ ├── PowerShellModuleGenerator.java │ │ ├── JavaClassGenerator.java │ │ ├── JavascriptModuleGenerator.java │ │ └── CSharpClassGenerator.java │ │ └── main │ │ ├── Gargl.java │ │ └── InputParser.java │ ├── README.md │ └── pom.xml ├── recorders └── chrome │ ├── icon.png │ ├── icon16.png │ ├── icon48.png │ ├── devtools.html │ ├── devtools.js │ ├── Panel │ ├── gargl.css │ ├── gargl.html │ └── gargl.js │ ├── manifest.json │ └── README.md ├── .gitignore ├── LICENSE.txt ├── README.md └── templates ├── sample.gtf ├── README.md ├── yahoosearch.gtf └── triviacrack.gtf /generators/JavaGenerator/.gitignore: -------------------------------------------------------------------------------- 1 | target/** 2 | dependency-reduced-pom.xml 3 | -------------------------------------------------------------------------------- /recorders/chrome/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodoglevy/gargl/HEAD/recorders/chrome/icon.png -------------------------------------------------------------------------------- /recorders/chrome/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodoglevy/gargl/HEAD/recorders/chrome/icon16.png -------------------------------------------------------------------------------- /recorders/chrome/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodoglevy/gargl/HEAD/recorders/chrome/icon48.png -------------------------------------------------------------------------------- /recorders/chrome/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /recorders/chrome/devtools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create("Gargl", "icon.png", "Panel/gargl.html", function(panel) { 2 | 3 | }); -------------------------------------------------------------------------------- /generators/JavaGenerator/bld/GarglJavaGenerator.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jodoglevy/gargl/HEAD/generators/JavaGenerator/bld/GarglJavaGenerator.jar -------------------------------------------------------------------------------- /recorders/chrome/Panel/gargl.css: -------------------------------------------------------------------------------- 1 | #garglTable { 2 | width: 100%; 3 | } 4 | 5 | tr,th,td{ 6 | text-align: left; 7 | border: 1px solid black; 8 | } 9 | 10 | #editFormFunctionRequestURL, #editFormFunctionDescription, #garglModuleDescription, #editFormFunctionRequestPostDataMimeType, .garglRequestFieldValue { 11 | width:600; 12 | } -------------------------------------------------------------------------------- /recorders/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Gargl", 3 | "version":"0.12", 4 | "description":" Record web requests and turn them into reusable code in any programming language.", 5 | "manifest_version": 2, 6 | "incognito": "spanning", 7 | "icons": { 8 | "16": "icon16.png", 9 | "48": "icon48.png", 10 | "128": "icon.png" 11 | }, 12 | "devtools_page": "devtools.html" 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pydevproject 2 | .project 3 | .metadata 4 | bin/** 5 | tmp/** 6 | tmp/**/* 7 | *.tmp 8 | *.bak 9 | *.swp 10 | *~.nib 11 | local.properties 12 | .classpath 13 | .settings/ 14 | .loadpath 15 | 16 | # External tool builders 17 | .externalToolBuilders/ 18 | 19 | # Locally stored "Eclipse launch configurations" 20 | *.launch 21 | 22 | # CDT-specific 23 | .cproject 24 | 25 | # PDT-specific 26 | .buildpath 27 | 28 | # Java class files 29 | *.class -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/typedefinitions/GarglModule.java: -------------------------------------------------------------------------------- 1 | package gargl.typedefinitions; 2 | 3 | import java.util.List; 4 | 5 | public class GarglModule { 6 | public String name; 7 | public String description; 8 | public List functions; 9 | public String version; 10 | 11 | public GarglModule(String name, String version, String description, List functions) { 12 | this.name = name; 13 | this.description = description; 14 | this.functions = functions; 15 | this.version = version; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/utilities/JCommanderParser.java: -------------------------------------------------------------------------------- 1 | package gargl.utilities; 2 | 3 | import com.beust.jcommander.Parameter; 4 | 5 | public class JCommanderParser { 6 | 7 | @Parameter(names = { "-i", "-input" }, description = "Name of gargl template file to generate a module from") 8 | public String inputFilename; 9 | 10 | @Parameter(names = {"-l", "-lang"}, description = "String identifying which language the generated module should be composed of. Valid values are: java") 11 | public String language; 12 | 13 | @Parameter(names = {"-o", "-outdir"}, description = "Name of output directory for created module") 14 | public String outputDirectory; 15 | 16 | @Parameter(names = {"--help", "/?"}, description = "Print usage") 17 | public boolean help = false; 18 | } 19 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/generators/GeneratorFactory.java: -------------------------------------------------------------------------------- 1 | package gargl.generators; 2 | 3 | import java.util.Hashtable; 4 | import java.util.Set; 5 | 6 | public class GeneratorFactory 7 | { 8 | private static Hashtable generators; 9 | 10 | static 11 | { 12 | generators = new Hashtable(); 13 | generators.put("java", new JavaClassGenerator()); 14 | generators.put("javascript", new JavascriptModuleGenerator()); 15 | generators.put("powershell", new PowerShellModuleGenerator()); 16 | generators.put("csharp", new CSharpClassGenerator()); 17 | } 18 | 19 | public static Generator getGenerator(String generatorType) 20 | { 21 | return generators.get(generatorType.toLowerCase()); 22 | } 23 | 24 | public static Set getValidGeneratorTypes() { 25 | return generators.keySet(); 26 | } 27 | } -------------------------------------------------------------------------------- /recorders/chrome/README.md: -------------------------------------------------------------------------------- 1 | # Gargl Chrome Extension 2 | 3 | ### Overview 4 | 5 | This is a Gargl recorder implemented as a Google Chrome extension to let you record Gargl template files (.gtf) from the Chrome browser. 6 | 7 | ### Installation 8 | 9 | Currently, this Chrome Extension is not available in the Chrome Store and can only be used from its source as an unpacked extension. To add this extension to your Google Chrome browser, open up Google Chrome, then go to Tools -> Extensions. At the top right of the page that opens, check 'Developer Mode.' Click 'load unpacked extension' and then navigate to and select the "chrome" folder of this solution. Lastly, check the "Enabled" check box next to Gargl extension which shows up. 10 | 11 | ### Use 12 | 13 | In Google Chrome, press F12 to bring up the Chrome developer tools. If you've installed the Gargl Chrome recorder, the right most tab in the Chrome dev tools toolbar should say 'Gargl.' Click this tab to get started. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Joe Levy 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 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gargl 2 | 3 | ### Overview 4 | 5 | Gargl - Generic API Recorder and Generator Lite. Pronounced "Gargle." 6 | 7 | Automate any website. Record web requests as they happen and turn them into reusable code in any programming language. 8 | 9 | Read more [here](http://jodoglevy.com/jobloglevy/?p=5). 10 | 11 | ### Folder Hierarchy: 12 | 13 | #### Recorders 14 | The recorders folder contains gargl recorders implemented for different browsers / programs, which let you record Gargl template files (.gtf). 15 | 16 | #### Generators 17 | The generators folder contains gargl generators for different programming languages. These generators let you convert Gargl template files into modules of a chosen programming language. 18 | 19 | #### Templates 20 | The templates folder contains Gargl template files which have already been recorded for various web sites, as well as a sample Gargl template file to show the schema. 21 | 22 | ## Related Work 23 | * [pygargl](https://github.com/KarolTx/pygargl) 24 | * generate python libraries from gargl template files 25 | * dynamically loads gargl template files (no compilation needed) 26 | * supports XPath queries 27 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/generators/Generator.java: -------------------------------------------------------------------------------- 1 | package gargl.generators; 2 | 3 | import gargl.typedefinitions.GarglModule; 4 | 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | 8 | public abstract class Generator { 9 | 10 | protected GarglModule module; 11 | 12 | public Generator(GarglModule module) { 13 | setModule(module); 14 | } 15 | 16 | public Generator() {} 17 | 18 | /** 19 | * This method generates a file at outputLocation using the data contained in Module 20 | * 21 | * @param outputLocation a String specifying the (optional) output location 22 | */ 23 | public abstract void generateClass(String outputLocation); 24 | 25 | public void setModule(GarglModule module) { 26 | this.module = module; 27 | } 28 | 29 | protected void writeFile(String filename, String fileContent) { 30 | // Create file 31 | try { 32 | PrintWriter writer = new PrintWriter(filename, "UTF-8"); 33 | writer.print(fileContent); 34 | writer.close(); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | } 38 | 39 | System.out.println("LOG: File written to " + filename); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /generators/JavaGenerator/README.md: -------------------------------------------------------------------------------- 1 | # Java Gargl Generator 2 | 3 | ### Overview 4 | 5 | This is a Gargl generator implemented in Java. 6 | 7 | ### Use 8 | 9 | The Java Gargl Generator comes as a runnable jar. To run the Java Gargl Generator from the command line: 10 | 11 | java -jar bld\GarglJavaGenerator.jar -outdir some\output\location -input someGarglFile.gtf -lang someLanguage 12 | 13 | Current possible values for lang (language) are: 14 | - java (Java) 15 | - javascript (Browser, Windows 8 app, and Node.js compatible JavaScript) 16 | - powershell (PowerShell) 17 | - csharp (C# .NET) 18 | 19 | ### Building Source 20 | 21 | To build the code, use the `maven` build tool. Start by running the following command: 22 | 23 | mvn compile 24 | 25 | This will make sure that the code builds and will download any dependencies you need. After this, you may start working with the code. 26 | 27 | To create an executable binary, type the following command: 28 | 29 | mvn package 30 | 31 | The resulting jar should be in `target/JavaGenerator-1.0-SNAPSHOT.jar` 32 | 33 | All of these commands must be executed in the `JavaGenerator` directory. A pre-compiled jar of this generator can be found [here](bld). 34 | -------------------------------------------------------------------------------- /generators/JavaGenerator/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | gargl 6 | JavaGenerator 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | JavaGenerator 11 | https://github.com/jodoglevy/gargl 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | com.google.code.gson 20 | gson 21 | 2.2.4 22 | 23 | 24 | com.beust 25 | jcommander 26 | 1.19 27 | 28 | 29 | junit 30 | junit 31 | 3.8.1 32 | test 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-shade-plugin 40 | 2.2 41 | 42 | 43 | package 44 | 45 | shade 46 | 47 | 48 | 49 | 50 | gargl.main.Gargl 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/main/Gargl.java: -------------------------------------------------------------------------------- 1 | package gargl.main; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import gargl.generators.Generator; 5 | import gargl.generators.GeneratorFactory; 6 | import gargl.typedefinitions.GarglModule; 7 | import gargl.utilities.JCommanderParser; 8 | 9 | import java.io.File; 10 | 11 | public class Gargl { 12 | public static void main(String[] args) { 13 | 14 | // Parse command line arguments 15 | 16 | JCommanderParser jct = new JCommanderParser(); 17 | JCommander jcmdr = new JCommander(jct, args); 18 | 19 | if(args == null || args.length == 0 || jct.help){ 20 | jcmdr .usage(); 21 | System.exit(0); 22 | } 23 | 24 | if (jct.inputFilename == null) { 25 | System.out.println("ERROR: Need to specify input filename with -i"); 26 | System.exit(0); 27 | } 28 | 29 | if(jct.language == null){ 30 | System.out.println("ERROR: Need to specify output language with -l"); 31 | System.exit(0); 32 | } 33 | 34 | if(jct.outputDirectory == null){ 35 | // default to current working directory 36 | jct.outputDirectory = ""; 37 | } 38 | else if(jct.outputDirectory.charAt(jct.outputDirectory.length() - 1) != File.separatorChar) { 39 | jct.outputDirectory = jct.outputDirectory + File.separator; 40 | } 41 | 42 | // Read in file and convert to Module containing function name and Requests 43 | InputParser parser = new InputParser(jct.inputFilename); 44 | GarglModule mod = parser.parseAndConvert(); 45 | 46 | System.out.println("LOG: Parsed requests " + jct.inputFilename); 47 | 48 | // Create the necessary generator based on language selected and initialize it with the Module created from file 49 | Generator generator = GeneratorFactory.getGenerator(jct.language); 50 | if(generator == null) { 51 | System.out.println("ERROR: Language '" + jct.language + "' has no associated generator."); 52 | System.out.println("Valid generator languages: " + GeneratorFactory.getValidGeneratorTypes().toString()); 53 | } 54 | else { 55 | generator.setModule(mod); 56 | generator.generateClass(jct.outputDirectory); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/utilities/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package gargl.utilities; 2 | import java.util.LinkedList; 3 | import java.util.Map.Entry; 4 | import java.util.Queue; 5 | import java.util.Set; 6 | 7 | import com.google.gson.JsonArray; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonObject; 10 | 11 | public class JsonUtils { 12 | 13 | /** 14 | * @param element 15 | * The element to search within for a JsonElement named 16 | * elementName 17 | * @param elementName 18 | * The name of the element to find 19 | * @return The element named 'elementName' that is a child of 'element' 20 | */ 21 | public static JsonElement findElement(JsonElement element, 22 | String elementName) { 23 | if(element == null){ 24 | return null; 25 | } 26 | 27 | Queue stack = new LinkedList(); 28 | stack.add(element); 29 | return search(stack, elementName); 30 | } 31 | 32 | private static JsonElement search(Queue queue, String elementName) { 33 | JsonElement ret = null; 34 | while (queue.size() > 0) { 35 | JsonElement element = queue.poll(); 36 | if (element.isJsonObject()) { 37 | JsonObject object = element.getAsJsonObject(); 38 | Set> members = object.entrySet(); 39 | for (Entry member : members) { 40 | if (member.getKey().equals(elementName)) { 41 | return member.getValue(); 42 | } else { 43 | queue.add(member.getValue()); 44 | } 45 | } 46 | 47 | } else if (element.isJsonArray()) { 48 | JsonArray array = element.getAsJsonArray(); 49 | for (JsonElement array_element : array) { 50 | queue.add(array_element); 51 | } 52 | } 53 | } 54 | 55 | return ret; 56 | } 57 | 58 | public static JsonObject asJsonObject(JsonElement element) { 59 | if (element.isJsonObject()) { 60 | return element.getAsJsonObject(); 61 | } else { 62 | return null; 63 | } 64 | } 65 | 66 | public static JsonArray asJsonArray(JsonElement element) { 67 | if (element.isJsonArray()) { 68 | return element.getAsJsonArray(); 69 | } else { 70 | return null; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /templates/sample.gtf: -------------------------------------------------------------------------------- 1 | { 2 | "garglSchemaVersion": "1.0", 3 | "moduleVersion": "1.0", 4 | "moduleName": "Sample-Site", 5 | "moduleDescription": "API for a sample site that doesn't actually exist!", 6 | "functions": [ 7 | { 8 | "functionName": "Function1", 9 | "functionDescription": "This is a sample function", 10 | "request": { 11 | "method": "GET", 12 | "url": "http://www.samplesite.com/@topic@/@subtopic@", 13 | "httpVersion": "HTTP/1.1", 14 | "headers": [ 15 | { 16 | "name": "Accept-Encoding", 17 | "value": "gzip,deflate,sdch" 18 | }, 19 | { 20 | "name": "User-Agent", 21 | "value": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36" 22 | } 23 | ], 24 | "queryString": [ 25 | { 26 | "name": "staticParam", 27 | "value": "value1" 28 | }, 29 | { 30 | "name": "variableParam", 31 | "value": "@variableParamValue@", 32 | "description": "A query string parameter that is user specified" 33 | } 34 | ] 35 | }, 36 | "response": { 37 | "headers": [ 38 | { 39 | "name": "Content-Encoding" 40 | }, 41 | { 42 | "name": "Content-Type" 43 | }, 44 | { 45 | "name": "Content-Length" 46 | }, 47 | { 48 | "name": "Expires" 49 | } 50 | ], 51 | "fields": [ 52 | { 53 | "name": "title", 54 | "cssSelector": "h1" 55 | }, 56 | { 57 | "name": "username", 58 | "cssSelector": "#username" 59 | } 60 | ] 61 | } 62 | }, 63 | { 64 | "functionName": "Function2", 65 | "functionDescription": "This is also a sample function", 66 | "request": { 67 | "method": "POST", 68 | "url": "http://www.samplesite.com/posturl", 69 | "httpVersion": "HTTP/1.1", 70 | "headers": [ 71 | { 72 | "name": "Accept-Encoding", 73 | "value": "gzip,deflate,sdch" 74 | }, 75 | { 76 | "name": "User-Agent", 77 | "value": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36" 78 | }, 79 | { 80 | "name": "Some-Other-Header", 81 | "value": "@otherHeaderValue@", 82 | "description": "A request header that is user specified" 83 | } 84 | ], 85 | "queryString": [], 86 | "postData": [ 87 | { 88 | "name": "staticParam", 89 | "value": "value2" 90 | }, 91 | { 92 | "name": "variableParam", 93 | "value": "@variableParamValue@", 94 | "description": "A post parameter that is user specified" 95 | } 96 | ] 97 | }, 98 | "response": { 99 | "headers": [ 100 | { 101 | "name": "Content-Encoding" 102 | }, 103 | { 104 | "name": "Content-Type" 105 | }, 106 | { 107 | "name": "Content-Length" 108 | }, 109 | { 110 | "name": "Expires" 111 | } 112 | ], 113 | "fields": [ 114 | { 115 | "name": "title", 116 | "cssSelector": "h1" 117 | }, 118 | { 119 | "name": "username", 120 | "cssSelector": "#username" 121 | } 122 | ] 123 | } 124 | } 125 | ] 126 | } 127 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | # Gargl Template Files 2 | 3 | ### Overview 4 | 5 | This folder contains Gargl template files which have already been recorded for various web sites, as well as a sample Gargl template file to show the schema. If you generate a new Gargl template for a website and would like to open source it, this is the proper folder where it should be stored. 6 | 7 | ### Parameterized Values 8 | 9 | Certain fields in the gargl *function object* can contain parameterized values. A parameterized value is a value that is not a static string, but rather defined by a parameter to the function it is a part of. To insert a parameterized value, insert the name of the parameter to be added to the function, surrounded by '@' signs. For example, "@title@" would cause a parameter to be added to the function with the name "title." 10 | 11 | As of Gargl Schema v1.0, the following fields can contain parameterized values: 12 | - The *url* field of a *request object* can contain one or more parameterized values. 13 | - The *value* field of a *request field object* can contain a single parameterized value. 14 | 15 | ### Schema v1.0 16 | 17 | Gargl template files contain a json object composed of the following fields: 18 | 19 | - **garglSchemaVersion**: The string version of the gargl schema. Current latest schema is "1.0". Required. 20 | - **moduleVersion**: The string version of the website this gargl template file was generated against. Required. 21 | - **moduleName**: The string name to be used for the module when it is generated. Cannot contain spaces. Required. 22 | - **moduleDescription**: A string description of the module. Optional. 23 | - **functions**: An array of *function objects* as described below. Required. 24 | 25 | #### Function Object 26 | 27 | A gargl template file contains one or more function objects. A function object is composed of the following fields: 28 | 29 | - **functionName**: The string name to be used for this function when a module is generated. Cannot contain spaces. Required. 30 | - **functionDescription**: A string description of the function. Optional. 31 | - **request**: A *request object* containing the details of the HTTP request for this function. Described below. Required. 32 | - **response**: A *response object* containing the details of the HTTP response for this function. Described below. Optional. 33 | 34 | ##### Request object 35 | 36 | Each function object contains a single request object. A request object is composed of the following fields: 37 | 38 | - **method**: The string HTTP method of the request. Required. 39 | - **url**: The string url of the request. Can contain one or more *parameterized values* or be static. Required. 40 | - **httpVersion**: The string http version of the request. Required. 41 | - **headers**: Any array of *request field objects* to send as request headers. Optional. Request headers should not contain "Cookie" or "Content-Length" headers. 42 | - **queryString**: Any array of *request field objects* to send in the request query string. Optional. 43 | - **postData**: Any array of *request field objects* to send in the request body. Optional. 44 | 45 | ###### Request Field object 46 | 47 | Each request object can contain one or more request field objects. A request field object is composed the following fields: 48 | 49 | - **name**: The string name of the request field object. Required. 50 | - **value**: The string value of the request field object. Can contain a single *parameterized value* or be static. Required. 51 | - **description**: The string description of the request field object. Optional. 52 | 53 | ##### Response object 54 | 55 | Each function object can contain a single response object. A response object is composed of the following fields: 56 | 57 | - **headers**: Any array of *response header objects* as described below. Optional. 58 | - **fields**: Any array of *response field objects* as described below. Optional. 59 | 60 | ###### Response Header object 61 | 62 | Each response object can contain one or more response header objects. A response header object is composed the following fields: 63 | 64 | - **name**: The string name of the response header which the HTTP request returns. Required. 65 | 66 | ###### Response Field object 67 | 68 | Each response object can contain one or more response field objects. A response field object is a value parsed from the returned html in the response body. A response field object is composed the following fields: 69 | 70 | - **name**: The string name of the response field. Required. 71 | - **cssSelector**: The string css selector for the html tag in the response body html whose inner html is the value for this response field object. Required. 72 | 73 | 74 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/main/InputParser.java: -------------------------------------------------------------------------------- 1 | package gargl.main; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import com.google.gson.Gson; 11 | import com.google.gson.JsonArray; 12 | import com.google.gson.JsonElement; 13 | import com.google.gson.JsonObject; 14 | import gargl.typedefinitions.Function; 15 | import gargl.typedefinitions.GarglModule; 16 | import gargl.utilities.JsonUtils; 17 | 18 | 19 | /** 20 | * @author mross 21 | * 22 | */ 23 | public class InputParser { 24 | 25 | private String filename; 26 | 27 | /** 28 | * @param filename 29 | * the input filename for the parser to read 30 | */ 31 | public InputParser(String filename) { 32 | this.filename = filename; 33 | } 34 | 35 | /** 36 | * This function reads in the file supplied in the constructor, parses the 37 | * JSON, and creates a Module with all of the data necessary to generate a 38 | * class 39 | * 40 | * @return a Module containing all of the requests and metadata for the 41 | * given file 42 | */ 43 | public GarglModule parseAndConvert() { 44 | // Initialize JSON parser 45 | Gson gson = new Gson(); 46 | String output = new String(); 47 | try { 48 | output = readFile(filename); 49 | } catch (FileNotFoundException e) { 50 | System.out.println("Error: " + filename 51 | + " does not exist, please specify another file"); 52 | System.exit(0); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | 57 | // Get metadata for the Module 58 | List requestElements = new ArrayList(); 59 | JsonObject moduleJsonObject = gson.fromJson(output, JsonObject.class); 60 | 61 | JsonElement moduleNameElement = moduleJsonObject.get("moduleName"); 62 | JsonElement moduleDescriptionElement = moduleJsonObject.get("moduleDescription"); 63 | JsonElement moduleVersionElement = moduleJsonObject.get("moduleDescription"); 64 | 65 | String moduleName = "default_module_name"; 66 | String moduleDescription = "default_module_description"; 67 | String moduleVersion = "1.0"; 68 | 69 | if (moduleNameElement != null && !moduleNameElement.getAsString().isEmpty()) { 70 | moduleName = moduleNameElement.getAsString(); 71 | } 72 | 73 | if (moduleDescriptionElement != null && !moduleDescriptionElement.getAsString().isEmpty()) { 74 | moduleDescription = moduleDescriptionElement.getAsString(); 75 | } 76 | 77 | if (moduleVersionElement != null && !moduleDescriptionElement.getAsString().isEmpty()) { 78 | moduleDescription = moduleVersionElement.getAsString(); 79 | } 80 | 81 | // Get all 'functions' as JsonObjects from the file and add them as a 82 | // list 83 | JsonElement functions = JsonUtils.findElement(moduleJsonObject, "functions"); 84 | if (functions != null) { 85 | if (functions.isJsonArray()) { 86 | JsonArray requests_array = functions.getAsJsonArray(); 87 | for (JsonElement requestElement : requests_array) { 88 | if (requestElement.isJsonObject()) { 89 | JsonObject request = requestElement.getAsJsonObject(); 90 | requestElements.add(request); 91 | } 92 | } 93 | } 94 | } 95 | else{ 96 | System.out.println("Error: Invalid file format. File does not contain \'functions\' element"); 97 | System.exit(0); 98 | } 99 | 100 | // Parse all JsonObject requests into Requests 101 | List requests = new ArrayList(); 102 | for (JsonElement jsonRequest : requestElements) { 103 | JsonObject request = JsonUtils.asJsonObject(jsonRequest); 104 | if (request != null) { 105 | if (request.get("functionName") != null 106 | && !request.get("functionName").getAsString().isEmpty()) { 107 | // Create Request from JSON Request 108 | requests.add( new Function(request)); 109 | } else { 110 | System.out 111 | .println("WARNING: Function not generated because function is empty!"); 112 | } 113 | } 114 | } 115 | 116 | // return Module containing metadata (e.g. name, description) and 117 | // Requests 118 | return new GarglModule(moduleName, moduleDescription, moduleVersion, requests); 119 | } 120 | 121 | /** 122 | * @param filename 123 | * the name of a file to read 124 | * @return a String with the contents of the file 125 | * @throws IOException 126 | */ 127 | private static String readFile(String filename) throws IOException { 128 | BufferedReader reader = new BufferedReader(new FileReader(filename)); 129 | String line = null; 130 | 131 | StringBuilder sb = new StringBuilder(); 132 | while ((line = reader.readLine()) != null) { 133 | for (char c : line.toCharArray()) { 134 | sb.append(c); 135 | } 136 | sb.append("\n"); 137 | } 138 | 139 | return sb.toString(); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/utilities/Parameter.java: -------------------------------------------------------------------------------- 1 | package gargl.utilities; 2 | 3 | import gargl.typedefinitions.Function; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | import java.util.List; 8 | import java.util.ArrayList; 9 | 10 | public class Parameter { 11 | 12 | private String parameterName; 13 | 14 | private String description; 15 | 16 | //TODO: Add support for parameter description 17 | public Parameter(String parameterName){ 18 | this.parameterName = parameterDecode(parameterName); 19 | System.out.println("Added argument: " + this.parameterName); 20 | } 21 | 22 | public static boolean isParameter(String param){ 23 | return (param.startsWith("@") && param.endsWith("@")); 24 | } 25 | 26 | public static String parameterDecode(String paramName) { 27 | try { 28 | return paramName.substring(1,paramName.length()-1); 29 | } 30 | catch(Exception e) { 31 | return ""; 32 | } 33 | } 34 | 35 | /** 36 | * @param param String that may be a parameter 37 | * @param function Request that param is a member of 38 | * @param parameterFormatter An optional formatter used to return the parameter in a language specific structure 39 | * @return a String appropriately formatted according to whether or not param is a parameter (e.g. is surrounded by quotes or isnt) 40 | */ 41 | public static String processParameter(String param, Function function, String parameterFormatter){ 42 | return processParameterInternal(param, function, parameterFormatter); 43 | } 44 | 45 | /** 46 | * @param param String that may be a parameter 47 | * @param function Request that param is a member of 48 | * @return a String appropriately formatted according to whether or not param is a parameter (e.g. is surrounded by quotes or isnt) 49 | */ 50 | public static String processParameter(String param, Function function){ 51 | return processParameterInternal(param, function, "%1$s"); 52 | } 53 | 54 | private static String processParameterInternal(String param, Function function, String parameterFormatter) { 55 | String decodedParam = parameterDecode(param); 56 | boolean isParam = false; 57 | 58 | for(Parameter p : function.getParameters()) { 59 | if(p.getParameterName().equals(decodedParam)) { 60 | isParam = true; 61 | break; 62 | } 63 | } 64 | 65 | if(isParam) return String.format(parameterFormatter, decodedParam); 66 | else return "\"" + param + "\""; 67 | } 68 | 69 | /** 70 | * @param url URL string that may contain one or more parameters 71 | * @param function Request that url is a member of 72 | * @return a Set of strings representing the different parameter and non-parameter parts of the url, appropriately formatted according to whether or not each item is a parameter (e.g. is surrounded by quotes or isnt). The set items are in order of how they should be concatenated. 73 | */ 74 | public static List processURLParameters(String url, Function function){ 75 | return processURLParametersInternal(url, function, "%1$s"); 76 | } 77 | 78 | /** 79 | * @param url URL string that may contain one or more parameters 80 | * @param function Request that url is a member of 81 | * @param parameterFormatter An optional formatter used to return the parameter in a language specific structure 82 | * @return a List of strings representing the different parameter and non-parameter parts of the url, appropriately formatted according to whether or not each item is a parameter (e.g. is surrounded by quotes or isnt). The items are in order of how they should be concatenated. 83 | */ 84 | public static List processURLParameters(String url, Function function, String parameterFormatter){ 85 | return processURLParametersInternal(url, function, parameterFormatter); 86 | } 87 | 88 | private static List processURLParametersInternal(String url, Function function, String parameterFormatter) { 89 | String[] urlParts = url.split("@"); 90 | List parts = new ArrayList(); 91 | 92 | for(int i = 0; i < urlParts.length; i ++) { 93 | if(i % 2 == 0) { 94 | urlParts[i] = "\"" + urlParts[i] + "\""; 95 | } 96 | else { 97 | urlParts[i] = String.format(parameterFormatter, urlParts[i]); 98 | } 99 | 100 | parts.add(urlParts[i]); 101 | } 102 | 103 | return parts; 104 | } 105 | 106 | public String getDescription() { 107 | return description; 108 | } 109 | 110 | public String getParameterName() { 111 | return parameterName; 112 | } 113 | 114 | public void setDescription(String description) { 115 | this.description = description; 116 | } 117 | 118 | public void setParameterName(String parameterName) { 119 | this.parameterName = parameterName; 120 | } 121 | 122 | @Override 123 | public boolean equals(Object other) { 124 | if(other instanceof Parameter) { 125 | return this.getParameterName().equals(this.getParameterName()); 126 | } 127 | else return false; 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | return this.getParameterName().hashCode(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /recorders/chrome/Panel/gargl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | How would you like to start? 9 |

10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 | 19 |
20 | 21 |

22 | 23 |
24 | You can specify parameterized values by inserting @some-parameter-name@ within the URL. This will specify to gargl generators that this gargl function takes a parameter called some-parameter-name that will become a part of the url of the request. The URL can have one or more parameterized values defined. 25 |
26 | Example: www.somesite.com/@category@/@topic@/ would create a url with category and topic parameters. 27 |

28 | 29 |

30 | 31 | Function Request Headers: 32 |
33 | You can specify a request header take a parameterized value rather than a static value by specifying @some-parameter-name@ as the value for the request header. This will specify to gargl generators that this gargl function takes a parameter called some-parameter-name that will become the value of this request header. 34 |
35 | Example: If for the 'Host' request header you entered @computerIP@ this would mean the generated function has a parameter called computerIP whose value is set as the value of the Host request header. 36 |
37 |
38 | 39 | Function Request Query String: 40 |
41 | You can specify a query string field take a parameterized value rather than a static value by specifying @some-parameter-name@ as the value for the query string field. This will specify to gargl generators that this gargl function takes a parameter called some-parameter-name that will become the value of this query string field. 42 |
43 | Example: If for a 'user' query string field you entered @username@ this would mean the generated function has a parameter called username whose value is sent as the value of the 'user' query string field. 44 |
45 |
46 | 47 | Function Request Post Data: 48 |
49 | You can specify a post data field take a parameterized value rather than a static value by specifying @some-parameter-name@ as the value for the post data field. This will specify to gargl generators that this gargl function takes a parameter called some-parameter-name that will become the value of this post data field. 50 |
51 | Example: If for a 'user' post data field you entered @username@ this would mean the generated function has a parameter called username whose value is sent as the value of the 'user' post data field. 52 |
53 |
54 | 55 | Function Response Fields: 56 |
57 | 58 |

59 | 60 |

61 |
62 | 63 | 64 |
65 | 66 |
67 | 68 | 69 | 70 | 71 | 72 |

73 | 74 |
75 | 76 |

77 | 78 |
79 | 80 | Requests: 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
Function NameURLMethodDetailsEditRemove
91 |
92 | 93 | -------------------------------------------------------------------------------- /templates/yahoosearch.gtf: -------------------------------------------------------------------------------- 1 | { 2 | "garglSchemaVersion": "1.0", 3 | "moduleVersion": "1.0", 4 | "moduleName": "YahooSearch", 5 | "moduleDescription": "Gargl module for Yahoo Search", 6 | "functions": [ 7 | { 8 | "request": { 9 | "method": "GET", 10 | "url": "http://sugg.search.yahoo.com/gossip-us-fp/", 11 | "httpVersion": "HTTP/1.1", 12 | "headers": [ 13 | { 14 | "description": "", 15 | "name": "Accept-Encoding", 16 | "value": "gzip,deflate,sdch" 17 | }, 18 | { 19 | "description": "", 20 | "name": "Host", 21 | "value": "sugg.search.yahoo.com" 22 | }, 23 | { 24 | "description": "", 25 | "name": "Accept-Language", 26 | "value": "en-US,en;q=0.8" 27 | }, 28 | { 29 | "description": "", 30 | "name": "User-Agent", 31 | "value": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36" 32 | }, 33 | { 34 | "description": "", 35 | "name": "Accept", 36 | "value": "*/*" 37 | }, 38 | { 39 | "description": "", 40 | "name": "Referer", 41 | "value": "http://www.yahoo.com/" 42 | }, 43 | { 44 | "description": "", 45 | "name": "Connection", 46 | "value": "keep-alive" 47 | } 48 | ], 49 | "queryString": [ 50 | { 51 | "description": "", 52 | "name": "nresults", 53 | "value": "10" 54 | }, 55 | { 56 | "description": "", 57 | "name": "queryfirst", 58 | "value": "2" 59 | }, 60 | { 61 | "description": "", 62 | "name": "output", 63 | "value": "yjsonp" 64 | }, 65 | { 66 | "description": "", 67 | "name": "version", 68 | "value": "" 69 | }, 70 | { 71 | "description": "", 72 | "name": "command", 73 | "value": "@term@" 74 | } 75 | ] 76 | }, 77 | "response": { 78 | "headers": [ 79 | { 80 | "name": "Date" 81 | }, 82 | { 83 | "name": "Content-Encoding" 84 | }, 85 | { 86 | "name": "Server" 87 | }, 88 | { 89 | "name": "Age" 90 | }, 91 | { 92 | "name": "P3P" 93 | }, 94 | { 95 | "name": "Vary" 96 | }, 97 | { 98 | "name": "Content-Type" 99 | }, 100 | { 101 | "name": "Cache-Control" 102 | }, 103 | { 104 | "name": "Transfer-Encoding" 105 | }, 106 | { 107 | "name": "Connection" 108 | }, 109 | { 110 | "name": "Expires" 111 | } 112 | ], 113 | "fields": [] 114 | }, 115 | "functionName": "Autocomplete", 116 | "functionDescription": "Yahoo autocomplete" 117 | }, 118 | { 119 | "request": { 120 | "method": "GET", 121 | "url": "http://search.yahoo.com/search", 122 | "httpVersion": "HTTP/1.1", 123 | "headers": [ 124 | { 125 | "description": "", 126 | "name": "Accept-Encoding", 127 | "value": "gzip,deflate,sdch" 128 | }, 129 | { 130 | "description": "", 131 | "name": "Host", 132 | "value": "search.yahoo.com" 133 | }, 134 | { 135 | "description": "", 136 | "name": "Accept-Language", 137 | "value": "en-US,en;q=0.8" 138 | }, 139 | { 140 | "description": "", 141 | "name": "User-Agent", 142 | "value": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36" 143 | }, 144 | { 145 | "description": "", 146 | "name": "Accept", 147 | "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" 148 | }, 149 | { 150 | "description": "", 151 | "name": "Referer", 152 | "value": "http://www.yahoo.com/" 153 | }, 154 | { 155 | "description": "", 156 | "name": "Connection", 157 | "value": "keep-alive" 158 | } 159 | ], 160 | "queryString": [ 161 | { 162 | "description": "", 163 | "name": "p", 164 | "value": "@query@" 165 | }, 166 | { 167 | "description": "", 168 | "name": "toggle", 169 | "value": "1" 170 | }, 171 | { 172 | "description": "", 173 | "name": "cop", 174 | "value": "mss" 175 | }, 176 | { 177 | "description": "", 178 | "name": "ei", 179 | "value": "UTF-8" 180 | }, 181 | { 182 | "description": "", 183 | "name": "fr", 184 | "value": "yfp-t-900" 185 | } 186 | ] 187 | }, 188 | "response": { 189 | "headers": [ 190 | { 191 | "name": "Date" 192 | }, 193 | { 194 | "name": "Content-Encoding" 195 | }, 196 | { 197 | "name": "Server" 198 | }, 199 | { 200 | "name": "Age" 201 | }, 202 | { 203 | "name": "P3P" 204 | }, 205 | { 206 | "name": "Vary" 207 | }, 208 | { 209 | "name": "Connection" 210 | }, 211 | { 212 | "name": "Content-Type" 213 | }, 214 | { 215 | "name": "Cache-Control" 216 | }, 217 | { 218 | "name": "Transfer-Encoding" 219 | }, 220 | { 221 | "name": "Set-Cookie" 222 | } 223 | ], 224 | "fields": [ 225 | { 226 | "name": "searchResultTitles", 227 | "cssSelector": "h3 a" 228 | } 229 | ] 230 | }, 231 | "functionName": "Search", 232 | "functionDescription": "search yahoo" 233 | } 234 | ] 235 | } -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/generators/PowerShellModuleGenerator.java: -------------------------------------------------------------------------------- 1 | package gargl.generators; 2 | 3 | import gargl.typedefinitions.Function; 4 | import gargl.utilities.Parameter; 5 | 6 | import java.util.Set; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class PowerShellModuleGenerator extends Generator { 11 | 12 | private static String POWERSHELL_MAKE_REQUEST_FORMAT = "\tif($WebSession) {\n" + 13 | "\t\tInvoke-WebRequest -Uri $url -Headers $headers -Body $body -Method $method -WebSession $WebSession\n" + 14 | "\t}\n" + 15 | "\telse {\n" + 16 | "\t\tInvoke-WebRequest -Uri $url -Headers $headers -Body $body -Method $method -SessionVariable $SessionVariable\n" + 17 | "\t\tInvoke-Expression \"`$global:$SessionVariable = `$$SessionVariable\"\n" + 18 | "\t}"; 19 | 20 | private static String POWERSHELL_KEYVALUE_FORMAT = "\t\t\"%1$s\" = %2$s;\n"; 21 | 22 | private static String POWERSHELL_REQUEST_STRING_CONCAT_FORMAT = " + \"&%1$s=\" + [System.Web.HttpUtility]::UrlEncode(%2$s)"; 23 | 24 | private static String POWERSHELL_VARIABLE_FORMAT = "$%1$s"; 25 | 26 | private static String POWERSHELL_STRING_VARIABLE_DECLARATION_FORMAT = "$%1$s = \"%2$s\"\n\n"; 27 | private static String POWERSHELL_CODE_VARIABLE_DECLARATION_FORMAT = "\t$%1$s = %2$s\n\n"; 28 | 29 | private static String POWERSHELL_MODULE_FORMAT = "Add-Type -AssemblyName System.Web\n\n" + 30 | "%1$s\n" + 31 | "Export-ModuleMember %2$s"; 32 | 33 | private static String POWERSHELL_FUNCTION_FORMAT = "function %1$s {\n" + 34 | "\t%2$s\n\n" + 35 | "\tif(!$SessionVariable) { $SessionVariable = \"_session\" }\n\n" + 36 | "\t%3$s\n" + 37 | "}\n\n"; 38 | 39 | private static String POWERSHELL_FUNCTION_NAME_FORMAT = "Invoke-%1$s%2$s"; 40 | 41 | private static String POWERSHELL_PARAMETER_FORMAT = "\n\t\t[Parameter(Mandatory=$%3$s)][%1$s] $%2$s"; 42 | 43 | public String generateFunction(String moduleName, Function function) { 44 | Set parameters = function.getParameters(); 45 | StringBuilder parametersSB = new StringBuilder("param("); 46 | for(Parameter parameter : parameters) { 47 | parametersSB.append(String.format(POWERSHELL_PARAMETER_FORMAT, "string", parameter.getParameterName(), "true")); 48 | parametersSB.append(","); 49 | } 50 | parametersSB.append(String.format(POWERSHELL_PARAMETER_FORMAT, "string", "SessionVariable", "false") + ","); 51 | parametersSB.append(String.format(POWERSHELL_PARAMETER_FORMAT, "Microsoft.PowerShell.Commands.WebRequestSession", "WebSession", "false")); 52 | parametersSB.append("\n\t)"); 53 | 54 | StringBuilder functionBodySB = new StringBuilder(""); 55 | functionBodySB.append(String.format(POWERSHELL_STRING_VARIABLE_DECLARATION_FORMAT, "method", function.getMethod())); 56 | 57 | Map headers = function.getHeaders(); 58 | StringBuilder headersStringSB = new StringBuilder("@{\n"); 59 | for(String headerKey : headers.keySet()) { 60 | if(headerKey.equalsIgnoreCase("Connection")) continue; 61 | headersStringSB.append(String.format(POWERSHELL_KEYVALUE_FORMAT, headerKey, Parameter.processParameter(headers.get(headerKey).replace("\"","`\""), function, POWERSHELL_VARIABLE_FORMAT))); 62 | } 63 | headersStringSB.append("\t}"); 64 | functionBodySB.append(String.format(POWERSHELL_CODE_VARIABLE_DECLARATION_FORMAT, "headers", headersStringSB.toString())); 65 | 66 | Map queryString = function.getQueryString(); 67 | StringBuilder queryStringSB = new StringBuilder("\"?\""); 68 | for(String queryStringKey : queryString.keySet()) { 69 | queryStringSB.append(String.format(POWERSHELL_REQUEST_STRING_CONCAT_FORMAT, queryStringKey, Parameter.processParameter(queryString.get(queryStringKey).replace("\"","`\""), function, POWERSHELL_VARIABLE_FORMAT))); 70 | } 71 | functionBodySB.append(String.format(POWERSHELL_CODE_VARIABLE_DECLARATION_FORMAT, "queryString", queryStringSB.toString())); 72 | 73 | Map postData = function.getPostData(); 74 | StringBuilder postDataSB = new StringBuilder("@{\n"); 75 | for(String postDataKey : postData.keySet()) { 76 | postDataSB.append(String.format(POWERSHELL_KEYVALUE_FORMAT, postDataKey, Parameter.processParameter(postData.get(postDataKey).replace("\"","`\""), function, POWERSHELL_VARIABLE_FORMAT))); 77 | } 78 | postDataSB.append("\t}"); 79 | functionBodySB.append(String.format(POWERSHELL_CODE_VARIABLE_DECLARATION_FORMAT, "body", postDataSB.toString())); 80 | 81 | List urlParts = Parameter.processURLParameters(function.getUrl(), function, POWERSHELL_VARIABLE_FORMAT); 82 | StringBuilder urlStringSB = new StringBuilder(); 83 | for(String urlPart : urlParts) { 84 | urlStringSB.append(urlPart); 85 | urlStringSB.append(" + "); 86 | } 87 | urlStringSB.append("$queryString"); 88 | functionBodySB.append(String.format(POWERSHELL_CODE_VARIABLE_DECLARATION_FORMAT, "url", urlStringSB.toString())); 89 | 90 | functionBodySB.append(String.format(POWERSHELL_MAKE_REQUEST_FORMAT, function.getMethod())); 91 | 92 | String functionName = String.format(POWERSHELL_FUNCTION_NAME_FORMAT, moduleName, function.getFunctionName()); 93 | return String.format(POWERSHELL_FUNCTION_FORMAT, functionName, parametersSB.toString(), functionBodySB.toString()); 94 | } 95 | 96 | @Override 97 | public void generateClass(String outputLocation) { 98 | String moduleName = this.module.name; 99 | if(this.module.name == null || this.module.name.isEmpty()){ 100 | moduleName = "gargl_output"; 101 | System.out.println("WARNING: No module name given, module name defaulting to " + moduleName); 102 | } 103 | 104 | StringBuilder functionsSB = new StringBuilder(); 105 | for (Function function : this.module.functions) { 106 | functionsSB.append(generateFunction(moduleName, function)); 107 | System.out.println("LOG: Function " + function.getFunctionName() + " created"); 108 | } 109 | 110 | String moduleMembersString = generateModuleMembersDeclaration(moduleName, this.module.functions); 111 | 112 | // File name is currently forced to be the same as module name 113 | writeFile(outputLocation + moduleName + ".psm1", 114 | String.format(POWERSHELL_MODULE_FORMAT, functionsSB.toString(), moduleMembersString) 115 | ); 116 | } 117 | 118 | private String generateModuleMembersDeclaration(String moduleName, List functions) { 119 | StringBuilder moduleMembersDeclarationSB = new StringBuilder(); 120 | for (int i = 0; i < functions.size(); i ++) { 121 | moduleMembersDeclarationSB.append(String.format(POWERSHELL_FUNCTION_NAME_FORMAT, moduleName, functions.get(i).getFunctionName())); 122 | 123 | if(i != functions.size() - 1) moduleMembersDeclarationSB.append(", "); 124 | } 125 | 126 | return moduleMembersDeclarationSB.toString(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/typedefinitions/Function.java: -------------------------------------------------------------------------------- 1 | package gargl.typedefinitions; 2 | 3 | import java.util.HashSet; 4 | import java.util.HashMap; 5 | import java.util.Set; 6 | import java.util.Map; 7 | 8 | import com.google.gson.JsonArray; 9 | import com.google.gson.JsonElement; 10 | import com.google.gson.JsonObject; 11 | import gargl.utilities.JsonUtils; 12 | import gargl.utilities.Parameter; 13 | 14 | public class Function { 15 | 16 | private String functionName; 17 | private Set parameters; 18 | private Map headers; 19 | private String url; 20 | private String httpVersion; 21 | private Map queryString; 22 | private String method; 23 | private Map postData; 24 | private Map responseFields; 25 | 26 | public Function(JsonObject jsonRequest) { 27 | parameters = new HashSet(); 28 | headers = new HashMap(); 29 | queryString = new HashMap(); 30 | postData = new HashMap(); 31 | responseFields = new HashMap(); 32 | 33 | // Set up Request object with all properties from JSON request 34 | this.setFunctionName(jsonRequest); 35 | this.setRequestMethod(jsonRequest); 36 | this.setHeaders(JsonUtils.findElement(jsonRequest, "headers")); 37 | this.setUrl(JsonUtils.findElement(jsonRequest, "url")); 38 | this.setQueryString(JsonUtils.findElement(jsonRequest, "queryString")); 39 | this.setPostData(JsonUtils.findElement(jsonRequest, "postData")); 40 | this.setResponseFields(JsonUtils.findElement(jsonRequest, "response")); 41 | } 42 | 43 | private void setResponseFields(JsonElement jsonResponse) { 44 | JsonElement responseFields = null; 45 | if(jsonResponse != null) responseFields = JsonUtils.findElement(jsonResponse, "fields"); 46 | 47 | if (responseFields != null) { 48 | for (JsonElement responseField : JsonUtils.asJsonArray(responseFields)) { 49 | JsonObject response = JsonUtils.asJsonObject(responseField); 50 | String name = response.get("name").getAsString(); 51 | String cssSelector = response.get("cssSelector").getAsString(); 52 | this.addResponseField(name, cssSelector); 53 | } 54 | } 55 | } 56 | 57 | private void setFunctionName(JsonObject jsonRequest) { 58 | JsonElement functionName = jsonRequest.get("functionName"); 59 | if (functionName != null) { 60 | this.functionName = functionName.getAsString(); 61 | } else { 62 | System.out.println("ERROR: Request object did not contain function name"); 63 | } 64 | } 65 | 66 | private void setHeaders(JsonElement headers) { 67 | JsonArray array_headers = JsonUtils.asJsonArray(headers); 68 | if (array_headers != null) { 69 | for (JsonElement jHeader : array_headers) { 70 | JsonObject header = JsonUtils.asJsonObject(jHeader); 71 | String name = header.get("name").getAsString(); 72 | String value = header.get("value").getAsString(); 73 | this.addHeader(name, value); 74 | if (Parameter.isParameter(value)) { 75 | this.addParameter(new Parameter(value)); 76 | } 77 | } 78 | } 79 | } 80 | 81 | private void setPostData(JsonElement jsonPostData) { 82 | JsonArray array_postDataParams = null; 83 | if(jsonPostData != null) array_postDataParams = JsonUtils.asJsonArray(jsonPostData); 84 | 85 | if (array_postDataParams != null) { 86 | for (JsonElement postParam : array_postDataParams) { 87 | JsonObject param = JsonUtils.asJsonObject(postParam); 88 | String name = param.get("name").getAsString(); 89 | String value = param.get("value").getAsString(); 90 | this.addPostData(name, value); 91 | if (Parameter.isParameter(value)) { 92 | this.addParameter(new Parameter(value)); 93 | } 94 | } 95 | } else { 96 | // Log if postData is empty for POST request (it shouldn't be) 97 | if ("POST".equals(this.getMethod())) { 98 | System.out.println("WARNING: POST request to " + this.getUrl() 99 | + " contains no postData"); 100 | } 101 | } 102 | } 103 | 104 | private void setQueryString(JsonElement queryStringParams) { 105 | JsonArray array_params = JsonUtils.asJsonArray(queryStringParams); 106 | if (array_params != null) { 107 | for (JsonElement jQueryParam : array_params) { 108 | JsonObject queryParam = JsonUtils.asJsonObject(jQueryParam); 109 | String name = queryParam.get("name").getAsString(); 110 | String value = queryParam.get("value").getAsString(); 111 | this.addQueryStringParam(name, value); 112 | if (Parameter.isParameter(value)) { 113 | this.addParameter(new Parameter(value)); 114 | } 115 | } 116 | } 117 | } 118 | 119 | private void setRequestMethod(JsonObject jsonRequest) { 120 | String method = JsonUtils.findElement(jsonRequest, "method").getAsString(); 121 | if (method != null) { 122 | this.method = method; 123 | } else { 124 | System.out.println("WARNING: Request did not contain method"); 125 | } 126 | } 127 | 128 | private void setUrl(JsonElement jsonRequest) { 129 | if (jsonRequest != null) { 130 | String url = jsonRequest.getAsString(); 131 | String[] urlParts = url.split("@"); 132 | 133 | for(int i = 0; i < urlParts.length; i ++) { 134 | if(i % 2 == 1) this.addParameter( new Parameter("@" + urlParts[i] + "@")); 135 | } 136 | 137 | this.url = url; 138 | } 139 | } 140 | 141 | private void addParameter(Parameter param) { 142 | parameters.add(param); 143 | } 144 | 145 | private void addResponseField(String name, String cssSelector) { 146 | responseFields.put(name, cssSelector); 147 | } 148 | 149 | private void addHeader(String header, String value) { 150 | headers.put(header, value); 151 | } 152 | 153 | private void addPostData(String name, String value) { 154 | this.postData.put(name, value); 155 | } 156 | 157 | private void addQueryStringParam(String name, String value) { 158 | this.queryString.put(name, value); 159 | } 160 | 161 | public Set getParameters() { 162 | return parameters; 163 | } 164 | 165 | public Map getResponseFields() { 166 | return responseFields; 167 | } 168 | 169 | public String getFunctionName() { 170 | return functionName; 171 | } 172 | 173 | public Map getHeaders() { 174 | return headers; 175 | } 176 | 177 | public String getHttpVersion() { 178 | return httpVersion; 179 | } 180 | 181 | public String getMethod() { 182 | return method; 183 | } 184 | 185 | public Map getPostData() { 186 | return postData; 187 | } 188 | 189 | public Map getQueryString() { 190 | return queryString; 191 | } 192 | 193 | public String getUrl() { 194 | return url; 195 | } 196 | 197 | public void printHeaders() { 198 | System.out.println(headers); 199 | } 200 | 201 | private void removeHeader(String header) { 202 | headers.remove(header); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/generators/JavaClassGenerator.java: -------------------------------------------------------------------------------- 1 | package gargl.generators; 2 | 3 | import gargl.typedefinitions.Function; 4 | import gargl.utilities.Parameter; 5 | 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | 9 | public class JavaClassGenerator extends Generator { 10 | 11 | private static String JAVA_CLASS_FORMAT = "import java.io.BufferedReader;\n" + 12 | "import java.io.DataOutputStream;\n" + 13 | "import java.io.InputStreamReader;\n" + 14 | "import java.net.HttpURLConnection;\n" + 15 | "import java.io.InputStream;\n" + 16 | "import java.util.zip.GZIPInputStream;\n" + 17 | "import java.net.URL;\n\n" + 18 | "public class %1$s \n{\n %2$s \n}"; 19 | private static String JAVA_METHOD_FORMAT = "public String %1$s throws Exception \n{\n %2$s \n}\n\n"; 20 | private static String JAVA_METHOD_SIGNATURE = "%1$s (%2$s)"; 21 | private static String JAVA_HEADER_FORMAT = "con.setRequestProperty(%1$s);\n"; 22 | private static String JAVA_HTTP_CONNECTION_FORMAT = "String url = %1$s;\n" 23 | + "URL obj = new URL(url);\n" 24 | + "HttpURLConnection con = (HttpURLConnection) obj.openConnection();\n\n" 25 | 26 | + "%2$s\n" + 27 | 28 | "int responseCode = con.getResponseCode();\n" 29 | + "System.out.println(\"Sending 'GET' request to URL : \" + url);\n" 30 | + "System.out.println(\"Response Code : \" + responseCode);\n\n" + 31 | "InputStream input = con.getInputStream()\n;" + 32 | "if (\"gzip\".equals(con.getContentEncoding())) {\n" + 33 | "input = new GZIPInputStream(input);\n" + 34 | "}\n\n"+ 35 | 36 | "BufferedReader in = new BufferedReader(new InputStreamReader(input));\n" 37 | + "String inputLine;\n" + "StringBuffer response = new StringBuffer();\n\n" + 38 | 39 | "while ((inputLine = in.readLine()) != null) {" + " response.append(inputLine);\n" 40 | + "}\n\n" + 41 | 42 | "in.close();\n" + "System.out.println(response.toString());\n" 43 | + "return response.toString();"; 44 | 45 | private static String JAVA_POST_REQUEST_FORMAT = "String postData = \"%1$s\";\n" 46 | + "con.setDoOutput(true);\n\n" 47 | + "DataOutputStream wr = new DataOutputStream(con.getOutputStream());\n" 48 | + "wr.writeBytes(postData);\n" + "wr.flush();\n" + "wr.close();\n"; 49 | 50 | public String generateFunction(Function function) { 51 | // Generate method signature using all necessary parameters 52 | String methodSignature = generateMethodSignature(function); 53 | 54 | // Generate strings for setting request method and headers 55 | String methodAndHeaders = generateMethodAndHeaders(function); 56 | 57 | // Generate querystring by appending url params and applying 58 | // formatting 59 | String queryString = generateQueryString(function); 60 | 61 | // Surround request.getUrl() with quotes if it is a parameter, and 62 | // do nothing if it is a parameter name 63 | String url = Parameter.processParameter(function.getUrl(), function) + " + \"?" + queryString 64 | + "\""; 65 | 66 | // Handle specifics for each type of request 67 | if ("POST".equals(function.getMethod())) { 68 | String postDataString = generatePostDataString(function.getPostData()); 69 | methodAndHeaders += String.format(JAVA_POST_REQUEST_FORMAT, postDataString); 70 | } else if ("GET".equals(function.getMethod())) { 71 | // Currently there is no special behavior for GET requests 72 | } 73 | 74 | // Insert connection specific properties into connection format 75 | // string 76 | String fullMethodBody = String.format(JAVA_HTTP_CONNECTION_FORMAT, url, 77 | methodAndHeaders); 78 | 79 | // Insert full method body in method format string 80 | return String.format(JAVA_METHOD_FORMAT, methodSignature, fullMethodBody); 81 | } 82 | 83 | @Override 84 | public void generateClass(String outputLocation) { 85 | StringBuilder methodsSB = new StringBuilder(); 86 | 87 | for (Function request : this.module.functions) { 88 | methodsSB.append(generateFunction(request)); 89 | System.out.println("LOG: Function " + request.getFunctionName() + " created"); 90 | } 91 | 92 | String filename = this.module.name; 93 | if(this.module.name == null || this.module.name.isEmpty()){ 94 | filename = "gargl_output"; 95 | System.out.println("WARNING: No module name given, filename defaulting to " + filename); 96 | } 97 | 98 | // File name is currently forced to be the same as module name 99 | writeFile(outputLocation + filename + ".java", 100 | String.format(JAVA_CLASS_FORMAT, filename, methodsSB.toString())); 101 | } 102 | 103 | private String generateMethodAndHeaders(Function function) { 104 | StringBuilder sb = new StringBuilder(); 105 | 106 | // Set request method 107 | sb.append("con.setRequestMethod(\"" + function.getMethod() + "\");\n\n"); 108 | 109 | // Set request headers 110 | sb.append(generateHeaders(function) + "\n"); 111 | 112 | return sb.toString(); 113 | } 114 | 115 | private String generateHeaders(Function function) { 116 | StringBuilder sb = new StringBuilder(); 117 | for (Entry header : function.getHeaders().entrySet()) { 118 | StringBuilder headerSB = new StringBuilder("\"" + header.getKey() + "\", "); 119 | headerSB.append(Parameter.processParameter(header.getValue(), function)); 120 | 121 | // Adding 'con.setRequestProperty(header, value) to overall 122 | // StringBuilder 123 | sb.append(String.format(JAVA_HEADER_FORMAT, headerSB.toString())); 124 | } 125 | 126 | return sb.toString(); 127 | } 128 | 129 | String generateMethodSignature(Function function) { 130 | StringBuilder sb = new StringBuilder(); 131 | if (function.getParameters().size() > 0) { 132 | for (Parameter param : function.getParameters()) { 133 | sb.append("String " + param.getParameterName() + ","); 134 | } 135 | sb.deleteCharAt(sb.length() - 1); 136 | } 137 | 138 | return String.format(JAVA_METHOD_SIGNATURE, function.getFunctionName(), sb.toString()); 139 | } 140 | 141 | private String generatePostDataString(Map postData) { 142 | StringBuilder sb = new StringBuilder(); 143 | if (postData != null) { 144 | for (Entry param : postData.entrySet()) { 145 | if (Parameter.isParameter(param.getValue())) { 146 | String paramName = Parameter.parameterDecode(param.getValue()); 147 | sb.append(param.getKey() + "=\" + " + paramName + " + \"&"); 148 | } else { 149 | sb.append(param.getKey() + "=" + param.getValue() + "&"); 150 | } 151 | } 152 | 153 | if (sb.length() > 0) { 154 | sb.deleteCharAt(sb.length() - 1); 155 | } 156 | 157 | } else { 158 | System.out.println("POST request generated without postData"); 159 | } 160 | 161 | return sb.toString(); 162 | } 163 | 164 | private String generateQueryString(Function function) { 165 | StringBuilder sb = new StringBuilder(); 166 | for (Entry param : function.getQueryString().entrySet()) { 167 | if (Parameter.isParameter(param.getValue())) { 168 | String paramName = Parameter.parameterDecode(param.getValue()); 169 | sb.append(param.getKey() + "=\" + " + paramName + " + \"&"); 170 | } else { 171 | sb.append(param.getKey() + "=" + param.getValue() + "&"); 172 | } 173 | } 174 | 175 | if (sb.length() > 0) { 176 | sb.deleteCharAt(sb.length() - 1); 177 | } 178 | 179 | return sb.toString(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/generators/JavascriptModuleGenerator.java: -------------------------------------------------------------------------------- 1 | package gargl.generators; 2 | 3 | import gargl.typedefinitions.Function; 4 | import gargl.utilities.Parameter; 5 | 6 | import java.util.Set; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class JavascriptModuleGenerator extends Generator { 11 | 12 | private static final String JAVASCRIPT_REQUEST_STRING_CONCAT_FORMAT = " + \"%1$s\" + \"=\" + encodeURIComponent(%2$s) + \"&\""; 13 | 14 | private static String JAVASCRIPT_STRING_VARIABLE_DECLARATION_FORMAT = "\tvar %1$s = '%2$s';\n\n"; 15 | private static String JAVASCRIPT_CODE_VARIABLE_DECLARATION_FORMAT = "\tvar %1$s = %2$s;\n\n"; 16 | 17 | private static String JAVASCRIPT_KEYVALUE_FORMAT = "\t\t\"%1$s\": %2$s,\n"; 18 | private static String JAVASCRIPT_KEYVALUE_FORMAT2 = "\n\t\t\t%1$s: %2$s,"; 19 | 20 | private static String JAVASCRIPT_MODULE_FORMAT = 21 | "// This module requires jQuery. In Node.JS, jsdom and xmlhttprequest are also required.\n\n" + 22 | "try {\n" + 23 | "\t// Enable module to work with jQuery in Node.JS\n" + 24 | "\tvar jsdom = require('jsdom');\n" + 25 | "\tvar window = jsdom.jsdom().createWindow();\n" + 26 | "\tvar XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;\n\n" + 27 | "\tvar $ = require('jquery')(window);\n" + 28 | "\t$.support.cors = true;\n" + 29 | "\t$.ajaxSettings.xhr = function() {\n" + 30 | "\t\treturn new XMLHttpRequest;\n" + 31 | "\t}\n" + 32 | "}\n" + 33 | "catch(e) {\n" + 34 | "\tconsole.log(e);\n" + 35 | "}\n\n" + 36 | "var %1$s = {};\n\n" + 37 | "%2$s\n"; 38 | 39 | private static String JAVASCRIPT_FUNCTION_FORMAT = "\n%1$s.%2$s = function %3$s {\n%4$s" + 40 | "\n};\n" + 41 | "if(typeof(exports) != \"undefined\") exports.%2$s = %1$s.%2$s; // For nodeJS\n"; 42 | 43 | private static String JAVASCRIPT_GET_RESPONSE_FIELD = "\t\t\tvar _%1$s = $html.find('%2$s');\n"; 44 | 45 | private static String JAVASCRIPT_XHR_FORMAT = "\t$.ajax({\n" + 46 | "\t\ttype: type,\n" + 47 | "\t\turl: url,\n" + 48 | "\t\theaders: headers,\n" + 49 | "\t\tdata: data,\n" + 50 | "\t\tbeforeSend: function(xmlHttpRequest) {\n" + 51 | "\t\t\t// Requires node-XMLHttpRequest version 1.5.1 or later to set some headers in Node.js\n" + 52 | "\t\t\tif(xmlHttpRequest.setDisableHeaderCheck) xmlHttpRequest.setDisableHeaderCheck(true);\n" + 53 | "\t\t\treturn true;\n" + 54 | "\t\t}\n" + 55 | "\t})\n" + 56 | "\t.always(\n" + 57 | "\t\tfunction (response, error) {\n" + 58 | "\t\t\tresponse = response || '';\n\n" + 59 | "\t\t\tif (!response.responseText) {\n" + 60 | "\t\t\t\ttry {\n" + 61 | "\t\t\t\t\tvar $html = $(toStaticHTML(response));\n" + 62 | "\t\t\t\t}\n" + 63 | "\t\t\t\tcatch(e) {\n" + 64 | "\t\t\t\t\tvar $html = $(response);\n" + 65 | "\t\t\t\t}\n" + 66 | "\t\t\t}\n" + 67 | "\t\t\telse response = response.responseText;\n" + 68 | "%1$s\n" + 69 | "\t\t\tvar fullResponse = {\n" + 70 | "\t\t\t\tresponse: response," + 71 | "%2$s\n" + 72 | "\t\t\t};\n\n" + 73 | "\t\t\tcallback(null, fullResponse);\n\n" + 74 | "\t\t}\n" + 75 | "\t);"; 76 | 77 | public String generateFunction(String moduleName, Function function) { 78 | StringBuilder parametersSB = new StringBuilder("("); 79 | for(Parameter parameter : function.getParameters()) { 80 | parametersSB.append(parameter.getParameterName()); 81 | parametersSB.append(", "); 82 | } 83 | parametersSB.append("callback)"); 84 | 85 | StringBuilder functionBodySB = new StringBuilder(""); 86 | functionBodySB.append(String.format(JAVASCRIPT_STRING_VARIABLE_DECLARATION_FORMAT, "type", function.getMethod())); 87 | 88 | Map headers = function.getHeaders(); 89 | StringBuilder headersStringSB = new StringBuilder("{\n"); 90 | for(String headerKey : headers.keySet()) { 91 | headersStringSB.append(String.format(JAVASCRIPT_KEYVALUE_FORMAT, headerKey, Parameter.processParameter(headers.get(headerKey).replace("\"","\\\""), function))); 92 | } 93 | headersStringSB.append("\t}"); 94 | functionBodySB.append(String.format(JAVASCRIPT_CODE_VARIABLE_DECLARATION_FORMAT, "headers", headersStringSB.toString())); 95 | 96 | Map queryString = function.getQueryString(); 97 | StringBuilder queryStringSB = new StringBuilder("\"?\""); 98 | for(String queryStringKey : queryString.keySet()) { 99 | queryStringSB.append(String.format(JAVASCRIPT_REQUEST_STRING_CONCAT_FORMAT, queryStringKey, Parameter.processParameter(queryString.get(queryStringKey).replace("\"","\\\""), function))); 100 | } 101 | functionBodySB.append(String.format(JAVASCRIPT_CODE_VARIABLE_DECLARATION_FORMAT, "queryString", queryStringSB.toString())); 102 | 103 | Map postData = function.getPostData(); 104 | StringBuilder postDataSB = new StringBuilder("\"\""); 105 | for(String postDataKey : postData.keySet()) { 106 | postDataSB.append(String.format(JAVASCRIPT_REQUEST_STRING_CONCAT_FORMAT, postDataKey, Parameter.processParameter(postData.get(postDataKey).replace("\"","\\\""), function))); 107 | } 108 | functionBodySB.append(String.format(JAVASCRIPT_CODE_VARIABLE_DECLARATION_FORMAT, "data", postDataSB.toString())); 109 | 110 | List urlParts = Parameter.processURLParameters(function.getUrl(), function); 111 | StringBuilder urlStringSB = new StringBuilder(); 112 | for(String urlPart : urlParts) { 113 | urlStringSB.append(urlPart); 114 | urlStringSB.append(" + "); 115 | } 116 | urlStringSB.append("queryString"); 117 | functionBodySB.append(String.format(JAVASCRIPT_CODE_VARIABLE_DECLARATION_FORMAT, "url", urlStringSB.toString())); 118 | 119 | StringBuilder responseFieldsGrabSB = new StringBuilder(); 120 | StringBuilder responseFieldsSB = new StringBuilder(); 121 | Map responseFields = function.getResponseFields(); 122 | for(String responseName : responseFields.keySet()) { 123 | responseFieldsGrabSB.append(String.format(JAVASCRIPT_GET_RESPONSE_FIELD, responseName, responseFields.get(responseName))); 124 | responseFieldsSB.append(String.format(JAVASCRIPT_KEYVALUE_FORMAT2, "\t" + responseName, "_" + responseName)); 125 | } 126 | 127 | functionBodySB.append(String.format(JAVASCRIPT_XHR_FORMAT, responseFieldsGrabSB.toString(), responseFieldsSB.toString())); 128 | 129 | return String.format(JAVASCRIPT_FUNCTION_FORMAT, moduleName, function.getFunctionName(), parametersSB.toString(), functionBodySB.toString()); 130 | } 131 | 132 | @Override 133 | public void generateClass(String outputLocation) { 134 | String moduleName = this.module.name; 135 | if(this.module.name == null || this.module.name.isEmpty()){ 136 | moduleName = "gargl_output"; 137 | System.out.println("WARNING: No module name given, module name defaulting to " + moduleName); 138 | } 139 | 140 | StringBuilder functionsSB = new StringBuilder(); 141 | 142 | for (Function function : this.module.functions) { 143 | functionsSB.append(generateFunction(moduleName, function)); 144 | System.out.println("LOG: Function " + function.getFunctionName() + " created"); 145 | } 146 | 147 | // File name is currently forced to be the same as module name 148 | writeFile(outputLocation + moduleName + ".js", 149 | String.format(JAVASCRIPT_MODULE_FORMAT, moduleName, functionsSB.toString())); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /generators/JavaGenerator/src/main/java/gargl/generators/CSharpClassGenerator.java: -------------------------------------------------------------------------------- 1 | package gargl.generators; 2 | 3 | import gargl.typedefinitions.Function; 4 | import gargl.utilities.Parameter; 5 | 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.List; 9 | import java.util.Arrays; 10 | 11 | public class CSharpClassGenerator extends Generator { 12 | 13 | private static String CSHARP_CLASS_FORMAT = "using System;\n" 14 | + "using System.Net;\n" 15 | + "using System.IO;\n\n" 16 | + "public class %1$s \n" 17 | + "{\n" 18 | + "%2$s\n" 19 | + "}"; 20 | private static String CSHARP_METHOD_FORMAT = "public string %1$s \n" 21 | + "{\n" 22 | + "%2$s \n" 23 | + "}\n\n"; 24 | private static String CSHARP_METHOD_SIGNATURE = "%1$s (%2$s)"; 25 | private static String CSHARP_HEADER_FORMAT = "req.Headers.Add(%1$s);\n"; 26 | private static String CSHARP_HEADER_FORMAT_FIX = "req.%1$s = \"%2$s\";\n"; 27 | private static String CSHARP_HTTP_CONNECTION_FORMAT = "string url = %1$s;\n" 28 | + "HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);\n" 29 | + "req.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;\n\n" 30 | 31 | + "%2$s\n" 32 | 33 | + "HttpWebResponse resp = (HttpWebResponse)req.GetResponse();\n" 34 | + "int responseCode = (int)resp.StatusCode;\n" 35 | + "Console.WriteLine(\"Sending 'GET' request to URL : \" + url);\n" 36 | + "Console.WriteLine(\"Response Code : \" + responseCode);\n\n" 37 | 38 | + "string response;\n" 39 | + "using(StreamReader input = new StreamReader(resp.GetResponseStream()))\n" 40 | + "{\n" 41 | + "response = input.ReadToEnd();\n" 42 | + "}\n\n" 43 | 44 | +"Console.WriteLine(response.ToString());\n\n" 45 | + "return response.ToString();"; 46 | 47 | private static String CSHARP_POST_REQUEST_FORMAT = "string postData = \"%1$s\";\n" 48 | + "using(StreamWriter writer = new StreamWriter(req.GetRequestStream()))\n" 49 | + "{\n" 50 | + "writer.Write(postData);\n" 51 | + "writer.Flush();\n" 52 | + "}\n"; 53 | 54 | private static List CSHARP_RESTRICTED_HEADERS = Arrays.asList( 55 | "Accept", 56 | "Connection", 57 | "Content-Length", 58 | "Content-Type", 59 | "Date", 60 | "Expect", 61 | "Host", 62 | "If-Modified-Since", 63 | "Keep-Alive", 64 | "Proxy-Connection", 65 | "Range", 66 | "Referer", 67 | "Transfer-Encoding", 68 | "User-Agent" 69 | ); 70 | 71 | public String generateFunction(Function function) { 72 | // Generate method signature using all necessary parameters 73 | String methodSignature = generateMethodSignature(function); 74 | 75 | // Generate strings for setting request method and headers 76 | String methodAndHeaders = generateMethodAndHeaders(function); 77 | 78 | // Generate querystring by appending url params and applying 79 | // formatting 80 | String queryString = generateQueryString(function); 81 | 82 | // Surround request.getUrl() with quotes if it is a parameter, and 83 | // do nothing if it is a parameter name 84 | String url = Parameter.processParameter(function.getUrl(), function) + " + \"?" + queryString 85 | + "\""; 86 | 87 | // Handle specifics for each type of request 88 | if ("POST".equals(function.getMethod())) { 89 | String postDataString = generatePostDataString(function.getPostData()); 90 | methodAndHeaders += String.format(CSHARP_POST_REQUEST_FORMAT, postDataString); 91 | } else if ("GET".equals(function.getMethod())) { 92 | // Currently there is no special behavior for GET requests 93 | } 94 | 95 | // Insert connection specific properties into connection format 96 | // string 97 | String fullMethodBody = String.format(CSHARP_HTTP_CONNECTION_FORMAT, url, 98 | methodAndHeaders); 99 | 100 | // Insert full method body in method format string 101 | return String.format(CSHARP_METHOD_FORMAT, methodSignature, fullMethodBody); 102 | } 103 | 104 | @Override 105 | public void generateClass(String outputLocation) { 106 | StringBuilder methodsSB = new StringBuilder(); 107 | 108 | for (Function request : this.module.functions) { 109 | methodsSB.append(generateFunction(request)); 110 | System.out.println("LOG: Function " + request.getFunctionName() + " created"); 111 | } 112 | 113 | String filename = this.module.name; 114 | if(this.module.name == null || this.module.name.isEmpty()){ 115 | filename = "gargl_output"; 116 | System.out.println("WARNING: No module name given, filename defaulting to " + filename); 117 | } 118 | 119 | // File name is currently forced to be the same as module name 120 | writeFile(outputLocation + filename + ".cs", 121 | String.format(CSHARP_CLASS_FORMAT, filename, methodsSB.toString())); 122 | } 123 | 124 | private String generateMethodAndHeaders(Function function) { 125 | StringBuilder sb = new StringBuilder(); 126 | 127 | // Set request method 128 | sb.append("req.Method = \"" + function.getMethod() + "\";\n\n"); 129 | 130 | // Set request headers 131 | sb.append(generateHeaders(function) + "\n"); 132 | 133 | return sb.toString(); 134 | } 135 | 136 | private String generateHeaders(Function function) { 137 | StringBuilder sb = new StringBuilder(); 138 | for (Entry header : function.getHeaders().entrySet()) { 139 | StringBuilder headerSB = new StringBuilder("\"" + header.getKey() + "\", "); 140 | headerSB.append(Parameter.processParameter(header.getValue(), function)); 141 | 142 | // derived from http://stackoverflow.com/a/23070923 143 | // sets headers differently if they're restricted (See CSHARP_RESTRICTED_HEADERS) 144 | if(CSHARP_RESTRICTED_HEADERS.contains(header.getKey())) 145 | { 146 | // of course, connection is another special header... 147 | if(header.getKey().equals("Connection") && header.getValue().equals("keep-alive")) 148 | { 149 | sb.append("req.KeepAlive = true;\n"); 150 | } 151 | else 152 | { 153 | sb.append(String.format(CSHARP_HEADER_FORMAT_FIX, header.getKey().replace("-", ""), header.getValue())); 154 | } 155 | } 156 | else 157 | { 158 | sb.append(String.format(CSHARP_HEADER_FORMAT, headerSB.toString())); 159 | } 160 | } 161 | 162 | return sb.toString(); 163 | } 164 | 165 | String generateMethodSignature(Function function) { 166 | StringBuilder sb = new StringBuilder(); 167 | if (function.getParameters().size() > 0) { 168 | for (Parameter param : function.getParameters()) { 169 | sb.append("string " + param.getParameterName() + ","); 170 | } 171 | sb.deleteCharAt(sb.length() - 1); 172 | } 173 | 174 | return String.format(CSHARP_METHOD_SIGNATURE, function.getFunctionName(), sb.toString()); 175 | } 176 | 177 | private String generatePostDataString(Map postData) { 178 | StringBuilder sb = new StringBuilder(); 179 | if (postData != null) { 180 | for (Entry param : postData.entrySet()) { 181 | if (Parameter.isParameter(param.getValue())) { 182 | String paramName = Parameter.parameterDecode(param.getValue()); 183 | sb.append(param.getKey() + "=\" + " + paramName + " + \"&"); 184 | } else { 185 | sb.append(param.getKey() + "=" + param.getValue() + "&"); 186 | } 187 | } 188 | 189 | if (sb.length() > 0) { 190 | sb.deleteCharAt(sb.length() - 1); 191 | } 192 | 193 | } else { 194 | System.out.println("POST request generated without postData"); 195 | } 196 | 197 | return sb.toString(); 198 | } 199 | 200 | private String generateQueryString(Function function) { 201 | StringBuilder sb = new StringBuilder(); 202 | for (Entry param : function.getQueryString().entrySet()) { 203 | if (Parameter.isParameter(param.getValue())) { 204 | String paramName = Parameter.parameterDecode(param.getValue()); 205 | sb.append(param.getKey() + "=\" + " + paramName + " + \"&"); 206 | } else { 207 | sb.append(param.getKey() + "=" + param.getValue() + "&"); 208 | } 209 | } 210 | 211 | if (sb.length() > 0) { 212 | sb.deleteCharAt(sb.length() - 1); 213 | } 214 | 215 | return sb.toString(); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /templates/triviacrack.gtf: -------------------------------------------------------------------------------- 1 | { 2 | "garglSchemaVersion": "1.0", 3 | "moduleVersion": "1.0", 4 | "moduleName": "TriviaCrack", 5 | "moduleDescription": "Integrate into the Trivia Crack game (https://apps.facebook.com/triviacrack/)", 6 | "functions": [ 7 | { 8 | "request": { 9 | "method": "GET", 10 | "url": "https://api.preguntados.com/api/users/@UserID@", 11 | "httpVersion": "HTTP/1.1", 12 | "headers": [ 13 | { 14 | "description": "", 15 | "name": "Eter-Agent", 16 | "value": "1|Web-FB|Chrome 39.0.2171.65|0|Windows|0|1.1||en||1" 17 | }, 18 | { 19 | "description": "", 20 | "name": "Origin", 21 | "value": "https://preguntados.com" 22 | }, 23 | { 24 | "description": "", 25 | "name": "Accept-Encoding", 26 | "value": "gzip, deflate, sdch" 27 | }, 28 | { 29 | "description": "", 30 | "name": "etergames-referer", 31 | "value": "true" 32 | }, 33 | { 34 | "description": "", 35 | "name": "Host", 36 | "value": "api.preguntados.com" 37 | }, 38 | { 39 | "description": "", 40 | "name": "Accept-Language", 41 | "value": "en-US,en;q=0.8" 42 | }, 43 | { 44 | "description": "", 45 | "name": "User-Agent", 46 | "value": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36" 47 | }, 48 | { 49 | "description": "", 50 | "name": "Content-Type", 51 | "value": "application/json; charset=utf-8" 52 | }, 53 | { 54 | "description": "", 55 | "name": "Accept", 56 | "value": "application/json, text/javascript, */*; q=0.01" 57 | }, 58 | { 59 | "description": "", 60 | "name": "Referer", 61 | "value": "https://preguntados.com/game/" 62 | }, 63 | { 64 | "description": "", 65 | "name": "Connection", 66 | "value": "keep-alive" 67 | }, 68 | { 69 | "description": "", 70 | "name": "Eter-Session", 71 | "value": "ap_session=undefined" 72 | } 73 | ], 74 | "queryString": [ 75 | { 76 | "description": "Integer value representing the number of milliseconds since 1 January 1970 00:00:00 UTC (Unix Epoch).", 77 | "name": "_", 78 | "value": "@TimestampInMilliseconds@" 79 | } 80 | ] 81 | }, 82 | "response": { 83 | "headers": [ 84 | { 85 | "name": "Date" 86 | }, 87 | { 88 | "name": "Server" 89 | }, 90 | { 91 | "name": "Vary" 92 | }, 93 | { 94 | "name": "Access-Control-Allow-Methods" 95 | }, 96 | { 97 | "name": "Content-Type" 98 | }, 99 | { 100 | "name": "Access-Control-Allow-Origin" 101 | }, 102 | { 103 | "name": "Access-Control-Allow-Credentials" 104 | }, 105 | { 106 | "name": "Connection" 107 | }, 108 | { 109 | "name": "Accept-Ranges" 110 | }, 111 | { 112 | "name": "Access-Control-Allow-Headers" 113 | }, 114 | { 115 | "name": "Content-Length" 116 | } 117 | ], 118 | "fields": [] 119 | }, 120 | "connection": "745790", 121 | "functionName": "GetUserData", 122 | "functionDescription": "Get data about a trivia crack user" 123 | }, 124 | { 125 | "request": { 126 | "method": "GET", 127 | "url": "https://api.preguntados.com/api/products", 128 | "httpVersion": "HTTP/1.1", 129 | "headers": [ 130 | { 131 | "description": "", 132 | "name": "Eter-Agent", 133 | "value": "1|Web-FB|Chrome 39.0.2171.65|0|Windows|0|1.1|en|en||1" 134 | }, 135 | { 136 | "description": "", 137 | "name": "Origin", 138 | "value": "https://preguntados.com" 139 | }, 140 | { 141 | "description": "", 142 | "name": "Accept-Encoding", 143 | "value": "gzip, deflate, sdch" 144 | }, 145 | { 146 | "description": "", 147 | "name": "etergames-referer", 148 | "value": "true" 149 | }, 150 | { 151 | "description": "", 152 | "name": "Host", 153 | "value": "api.preguntados.com" 154 | }, 155 | { 156 | "description": "", 157 | "name": "Accept-Language", 158 | "value": "en-US,en;q=0.8" 159 | }, 160 | { 161 | "description": "", 162 | "name": "User-Agent", 163 | "value": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36" 164 | }, 165 | { 166 | "description": "", 167 | "name": "Content-Type", 168 | "value": "application/json; charset=utf-8" 169 | }, 170 | { 171 | "description": "", 172 | "name": "Accept", 173 | "value": "application/json, text/javascript, */*; q=0.01" 174 | }, 175 | { 176 | "description": "", 177 | "name": "Referer", 178 | "value": "https://preguntados.com/game/" 179 | }, 180 | { 181 | "description": "", 182 | "name": "Connection", 183 | "value": "keep-alive" 184 | }, 185 | { 186 | "description": "", 187 | "name": "Eter-Session", 188 | "value": "ap_session=undefined" 189 | } 190 | ], 191 | "queryString": [ 192 | { 193 | "description": "", 194 | "name": "market_type", 195 | "value": "FACEBOOK" 196 | }, 197 | { 198 | "description": "Integer value representing the number of milliseconds since 1 January 1970 00:00:00 UTC (Unix Epoch).", 199 | "name": "_", 200 | "value": "@TimestampInMilliseconds@" 201 | } 202 | ] 203 | }, 204 | "response": { 205 | "headers": [ 206 | { 207 | "name": "Date" 208 | }, 209 | { 210 | "name": "Server" 211 | }, 212 | { 213 | "name": "Vary" 214 | }, 215 | { 216 | "name": "Access-Control-Allow-Methods" 217 | }, 218 | { 219 | "name": "Content-Type" 220 | }, 221 | { 222 | "name": "Access-Control-Allow-Origin" 223 | }, 224 | { 225 | "name": "Access-Control-Allow-Credentials" 226 | }, 227 | { 228 | "name": "Connection" 229 | }, 230 | { 231 | "name": "Accept-Ranges" 232 | }, 233 | { 234 | "name": "Access-Control-Allow-Headers" 235 | }, 236 | { 237 | "name": "Content-Length" 238 | } 239 | ], 240 | "fields": [] 241 | }, 242 | "connection": "745790", 243 | "functionName": "GetProducts", 244 | "functionDescription": "Get Trivia Crack products" 245 | }, 246 | { 247 | "request": { 248 | "method": "GET", 249 | "url": "https://api.preguntados.com/api/users/@UserID@/dashboard", 250 | "httpVersion": "HTTP/1.1", 251 | "headers": [ 252 | { 253 | "description": "", 254 | "name": "Eter-Agent", 255 | "value": "1|Web-FB|Chrome 39.0.2171.65|0|Windows|0|1.1|en|en||1" 256 | }, 257 | { 258 | "description": "", 259 | "name": "Origin", 260 | "value": "https://preguntados.com" 261 | }, 262 | { 263 | "description": "", 264 | "name": "Accept-Encoding", 265 | "value": "gzip, deflate, sdch" 266 | }, 267 | { 268 | "description": "", 269 | "name": "etergames-referer", 270 | "value": "true" 271 | }, 272 | { 273 | "description": "", 274 | "name": "Host", 275 | "value": "api.preguntados.com" 276 | }, 277 | { 278 | "description": "", 279 | "name": "Accept-Language", 280 | "value": "en-US,en;q=0.8" 281 | }, 282 | { 283 | "description": "", 284 | "name": "User-Agent", 285 | "value": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36" 286 | }, 287 | { 288 | "description": "", 289 | "name": "Content-Type", 290 | "value": "application/json; charset=utf-8" 291 | }, 292 | { 293 | "description": "", 294 | "name": "Accept", 295 | "value": "application/json, text/javascript, */*; q=0.01" 296 | }, 297 | { 298 | "description": "", 299 | "name": "Referer", 300 | "value": "https://preguntados.com/game/" 301 | }, 302 | { 303 | "description": "", 304 | "name": "Connection", 305 | "value": "keep-alive" 306 | }, 307 | { 308 | "description": "", 309 | "name": "Eter-Session", 310 | "value": "ap_session=undefined" 311 | } 312 | ], 313 | "queryString": [ 314 | { 315 | "description": "", 316 | "name": "app_config_version", 317 | "value": "1418500689" 318 | }, 319 | { 320 | "description": "Integer value representing the number of milliseconds since 1 January 1970 00:00:00 UTC (Unix Epoch).", 321 | "name": "_", 322 | "value": "@TimestampInMilliseconds@" 323 | } 324 | ] 325 | }, 326 | "response": { 327 | "headers": [ 328 | { 329 | "name": "Date" 330 | }, 331 | { 332 | "name": "Server" 333 | }, 334 | { 335 | "name": "transfer-encoding" 336 | }, 337 | { 338 | "name": "Vary" 339 | }, 340 | { 341 | "name": "Access-Control-Allow-Methods" 342 | }, 343 | { 344 | "name": "Content-Type" 345 | }, 346 | { 347 | "name": "Access-Control-Allow-Origin" 348 | }, 349 | { 350 | "name": "Access-Control-Allow-Credentials" 351 | }, 352 | { 353 | "name": "Connection" 354 | }, 355 | { 356 | "name": "Accept-Ranges" 357 | }, 358 | { 359 | "name": "Access-Control-Allow-Headers" 360 | } 361 | ], 362 | "fields": [] 363 | }, 364 | "connection": "745790", 365 | "functionName": "GetDashboard", 366 | "functionDescription": "Get Trivia Crack Dashboard" 367 | }, 368 | { 369 | "request": { 370 | "method": "GET", 371 | "url": "https://api.preguntados.com/api/users/@UserID@/games/@GameID@", 372 | "httpVersion": "HTTP/1.1", 373 | "headers": [ 374 | { 375 | "description": "", 376 | "name": "Eter-Agent", 377 | "value": "1|Web-FB|Chrome 39.0.2171.65|0|Windows|0|1.1|en|en||1" 378 | }, 379 | { 380 | "description": "", 381 | "name": "Origin", 382 | "value": "https://preguntados.com" 383 | }, 384 | { 385 | "description": "", 386 | "name": "Accept-Encoding", 387 | "value": "gzip, deflate, sdch" 388 | }, 389 | { 390 | "description": "", 391 | "name": "etergames-referer", 392 | "value": "true" 393 | }, 394 | { 395 | "description": "", 396 | "name": "Host", 397 | "value": "api.preguntados.com" 398 | }, 399 | { 400 | "description": "", 401 | "name": "Accept-Language", 402 | "value": "en-US,en;q=0.8" 403 | }, 404 | { 405 | "description": "", 406 | "name": "User-Agent", 407 | "value": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36" 408 | }, 409 | { 410 | "description": "", 411 | "name": "Content-Type", 412 | "value": "application/json; charset=utf-8" 413 | }, 414 | { 415 | "description": "", 416 | "name": "Accept", 417 | "value": "application/json, text/javascript, */*; q=0.01" 418 | }, 419 | { 420 | "description": "", 421 | "name": "Referer", 422 | "value": "https://preguntados.com/game/" 423 | }, 424 | { 425 | "description": "", 426 | "name": "Connection", 427 | "value": "keep-alive" 428 | }, 429 | { 430 | "description": "", 431 | "name": "Eter-Session", 432 | "value": "ap_session=undefined" 433 | } 434 | ], 435 | "queryString": [ 436 | { 437 | "description": "Integer value representing the number of milliseconds since 1 January 1970 00:00:00 UTC (Unix Epoch).", 438 | "name": "_", 439 | "value": "@TimestampInMilliseconds@" 440 | } 441 | ] 442 | }, 443 | "response": { 444 | "headers": [ 445 | { 446 | "name": "Date" 447 | }, 448 | { 449 | "name": "Server" 450 | }, 451 | { 452 | "name": "Vary" 453 | }, 454 | { 455 | "name": "Access-Control-Allow-Methods" 456 | }, 457 | { 458 | "name": "Content-Type" 459 | }, 460 | { 461 | "name": "Access-Control-Allow-Origin" 462 | }, 463 | { 464 | "name": "Access-Control-Allow-Credentials" 465 | }, 466 | { 467 | "name": "Connection" 468 | }, 469 | { 470 | "name": "Accept-Ranges" 471 | }, 472 | { 473 | "name": "Access-Control-Allow-Headers" 474 | }, 475 | { 476 | "name": "Content-Length" 477 | } 478 | ], 479 | "fields": [] 480 | }, 481 | "connection": "747627", 482 | "functionName": "GetGameData", 483 | "functionDescription": "Get Trivia Crack game data" 484 | } 485 | ] 486 | } -------------------------------------------------------------------------------- /recorders/chrome/Panel/gargl.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var shouldRecord = false; 3 | var garglItems = []; 4 | var nextId = 0; 5 | 6 | const maxUrlLength = 90; 7 | const garglSchemaVersion = "1.0"; 8 | 9 | const removeButtonHtml = " 0) string += (alternateSeperatorBetweenKeyAndValue || "=") + param.value; 90 | } 91 | 92 | return string; 93 | } 94 | 95 | function addRowToGarglItemsTable(idNumber, garglItem) { 96 | var removeButtonId = "removeButton" + idNumber; 97 | var editButtonId = "editButton" + idNumber; 98 | var detailsButtonId = "detailsButton" + idNumber; 99 | 100 | var funcNameInputId = "funcNameInput" + idNumber; 101 | var funcUrlId = "funcUrlId" + idNumber; 102 | var funcMethodId = "funcMethodId" + idNumber; 103 | 104 | var url = garglItem.request.url; 105 | var method = garglItem.request.method; 106 | 107 | if(url.length > maxUrlLength) url = url.substr(0,maxUrlLength) + "..." 108 | 109 | var tr = document.createElement("tr"); 110 | tr.setAttribute("class","garglTableEntry"); 111 | tr.innerHTML = "" + functionNameInputHtml + " id='" + funcNameInputId + "' value='" + (garglItem.functionName || "") + "' />"; 112 | tr.innerHTML += "" + url + ""; 113 | tr.innerHTML += "" + method + ""; 114 | tr.innerHTML += "" + detailsButtonHtml + " id='" + detailsButtonId + "' />"; 115 | tr.innerHTML += "" + editButtonHtml + " id='" + editButtonId + "' />"; 116 | tr.innerHTML += "" + removeButtonHtml + " id='" + removeButtonId + "' />"; 117 | 118 | document.querySelector(garglTableSelector).appendChild(tr); 119 | 120 | var removeButton = document.querySelector("#" + removeButtonId); 121 | removeButton.addEventListener('click', function() { 122 | removeGarglItem(idNumber, removeButton); 123 | }); 124 | 125 | var editButton = document.querySelector("#" + editButtonId); 126 | editButton.addEventListener('click', function() { 127 | showEditForm(idNumber); 128 | }); 129 | 130 | var detailsButton = document.querySelector("#" + detailsButtonId); 131 | detailsButton.addEventListener('click', function() { 132 | showDetails(idNumber); 133 | }); 134 | } 135 | 136 | function updateRowInGarglItemsTable(idNumber, garglItem) { 137 | var funcNameInputId = "funcNameInput" + idNumber; 138 | var funcUrlId = "funcUrlId" + idNumber; 139 | var funcMethodId = "funcMethodId" + idNumber; 140 | 141 | var url = garglItem.request.url; 142 | if(url.length > maxUrlLength) url = url.substr(0,maxUrlLength) + "..." 143 | 144 | document.querySelector("#"+funcNameInputId).value = garglItem.functionName; 145 | document.querySelector("#"+funcUrlId).innerHTML = url; 146 | document.querySelector("#"+funcMethodId).innerHTML = garglItem.request.method; 147 | } 148 | 149 | function showEditForm(idNumber) { 150 | var garglItem = garglItems[idNumber]; 151 | garglItem.functionName = getFunctionName(idNumber); 152 | 153 | document.querySelector(garglEditFormFunctionIdNumberSelector).value = idNumber; 154 | document.querySelector(garglEditFormFunctionNameSelector).value = garglItem.functionName; 155 | document.querySelector(garglEditFormFunctionDescriptionSelector).value = garglItem.functionDescription || ""; 156 | document.querySelector(garglEditFormFunctionRequestURLSelector).value = garglItem.request.url; 157 | document.querySelector(garglEditFormFunctionRequestMethodSelector).value = garglItem.request.method; 158 | 159 | createRequestFieldForm(garglEditFormFunctionRequestHeadersSelector, garglItem.request.headers); 160 | createRequestFieldForm(garglEditFormFunctionRequestQueryStringSelector, garglItem.request.queryString); 161 | 162 | if(garglItem.request.postData) { 163 | createRequestFieldForm(garglEditFormFunctionRequestPostDataSelector, garglItem.request.postData); 164 | } 165 | else createRequestFieldForm(garglEditFormFunctionRequestPostDataSelector, null); 166 | 167 | if(garglItem.response.fields) { 168 | garglItem.response.fields.forEach(function(field) { 169 | addResponseField(null, field.name, field.cssSelector); 170 | }) 171 | }; 172 | 173 | document.querySelector(garglRecordAreaSelector).style.display = "none"; 174 | document.querySelector(garglStartFormSelector).style.display = "none"; 175 | document.querySelector(garglOpenFormSelector).style.display = "none"; 176 | document.querySelector(garglEditFormSelector).style.display = "block"; 177 | } 178 | 179 | function createRequestFieldForm(formAreaSelector, requestFieldArray) { 180 | var formAreaElement = document.querySelector(formAreaSelector); 181 | 182 | if(!requestFieldArray || requestFieldArray.length == 0) formAreaElement.style.display = "none"; 183 | else { 184 | var lastRequestFieldElement = null; 185 | formAreaElement.style.display = "block"; 186 | 187 | requestFieldArray.forEach(function(requestField) { 188 | var requestFieldId = "requestField" + requestField.name; 189 | 190 | var span = document.createElement("span"); 191 | span.setAttribute("class", garglRequestFieldElementClass); 192 | 193 | span.innerHTML = "
"; 194 | span.innerHTML += ""; 195 | 196 | formAreaElement.appendChild(span); 197 | lastRequestFieldElement = span; 198 | }); 199 | 200 | var br1 = document.createElement("br"); 201 | var br2 = document.createElement("br"); 202 | lastRequestFieldElement.appendChild(br1); 203 | lastRequestFieldElement.appendChild(br2); 204 | } 205 | } 206 | 207 | function grabRequestFieldFormData(formAreaSelector) { 208 | var fieldData = []; 209 | var formAreaElement = document.querySelector(formAreaSelector); 210 | 211 | if(formAreaElement.style.display != "none") { 212 | 213 | var fieldElements = formAreaElement.querySelectorAll("." + garglRequestFieldElementClass); 214 | 215 | for(var i = 0; i < fieldElements.length; i ++) { 216 | var field = { 217 | description: "", 218 | name: fieldElements[i].querySelector("label").innerHTML.replace(": ",""), 219 | value: fieldElements[i].querySelector("input").value, 220 | }; 221 | 222 | fieldData.push(field); 223 | } 224 | } 225 | 226 | return fieldData; 227 | } 228 | 229 | function cancelEditGarglItem() { 230 | var requestFieldElements = document.querySelectorAll("." + garglRequestFieldElementClass); 231 | var responseFieldElements = document.querySelectorAll("." + garglResponseFieldElementClass); 232 | 233 | if(requestFieldElements) { 234 | for(var i = 0; i < requestFieldElements.length; i ++) { 235 | requestFieldElements[i].parentNode.removeChild(requestFieldElements[i]); 236 | }; 237 | } 238 | 239 | if(responseFieldElements) { 240 | for(var i = 0; i < responseFieldElements.length; i ++) { 241 | responseFieldElements[i].parentNode.removeChild(responseFieldElements[i]); 242 | }; 243 | } 244 | 245 | startGargl(); 246 | } 247 | 248 | function saveEditGarglItem() { 249 | var idNumber = document.querySelector(garglEditFormFunctionIdNumberSelector).value; 250 | 251 | var garglItem = garglItems[idNumber]; 252 | 253 | garglItem.functionName = document.querySelector(garglEditFormFunctionNameSelector).value; 254 | garglItem.functionDescription = document.querySelector(garglEditFormFunctionDescriptionSelector).value; 255 | garglItem.request.url = document.querySelector(garglEditFormFunctionRequestURLSelector).value; 256 | garglItem.request.method = document.querySelector(garglEditFormFunctionRequestMethodSelector).value; 257 | 258 | garglItem.request.headers = grabRequestFieldFormData(garglEditFormFunctionRequestHeadersSelector); 259 | garglItem.request.queryString = grabRequestFieldFormData(garglEditFormFunctionRequestQueryStringSelector); 260 | 261 | if(garglItem.request.postData) { 262 | garglItem.request.postData = grabRequestFieldFormData(garglEditFormFunctionRequestPostDataSelector); 263 | } 264 | 265 | garglItem.response.fields = grabResponseFieldFormData(); 266 | 267 | updateRowInGarglItemsTable(idNumber, garglItem); 268 | cancelEditGarglItem(); 269 | } 270 | 271 | function removeGarglItem(idNumber, removeButton) { 272 | garglItems[idNumber] = null; 273 | 274 | var tr = removeButton.parentNode.parentNode; 275 | tr.parentNode.removeChild(tr); 276 | } 277 | 278 | function showDetails(idNumber) { 279 | var garglItem = garglItems[idNumber]; 280 | var garglRequest = garglItem.request 281 | 282 | garglItem.functionName = getFunctionName(idNumber); 283 | 284 | var detailsString = "Function Name: " + garglItem.functionName || ""; 285 | detailsString += "\nFunction Description: " + garglItem.functionDescription || ""; 286 | 287 | detailsString += "\n\nFunction Request URL: " + garglRequest.url; 288 | detailsString += "\nFunction Request Method: " + garglRequest.method; 289 | 290 | if(garglRequest.queryString && garglRequest.queryString.length > 0) { 291 | detailsString += "\n\nFunction Request Query String:\n" + convertRequestFieldArrayToString(garglRequest.queryString, "\n", ": "); 292 | } 293 | 294 | if(garglRequest.postData && garglRequest.postData.length > 0) { 295 | detailsString += "\n\nFunction Request Post Data:\n" + convertRequestFieldArrayToString(garglRequest.postData, "\n", ": "); 296 | } 297 | 298 | alert(detailsString); 299 | } 300 | 301 | function getFunctionName(id) { 302 | return document.querySelector("#funcNameInput" + id).value.replace(/ /g, "-"); 303 | } 304 | 305 | function trackRequest(request) { 306 | var urlWithoutQueryString = removeQueryStringFromUrl(request.request.url); 307 | var domain = getDomainOfUrl(urlWithoutQueryString); 308 | 309 | if(shouldRecord && !urlWithoutQueryString.match(/.gif$|.jpeg$|.jpg$|.png$|.js$|.css$|.swf$/)) { 310 | var domainMustContain = document.querySelector(garglDomainSearchSelector).value; 311 | if(domainMustContain.length > 0 && domain.indexOf(domainMustContain) === -1) return; 312 | 313 | addGarglMetadataToItem(request); 314 | removeUnneededMetadataFromItem(request); 315 | 316 | garglItems.push(request); 317 | 318 | addRowToGarglItemsTable(nextId, request); 319 | nextId ++; 320 | } 321 | } 322 | 323 | function cleanUpDownloadLink(a) { 324 | a.textContent = 'Downloaded'; 325 | a.dataset.disabled = true; 326 | 327 | // Need a small delay for the revokeObjectURL to work properly. 328 | setTimeout(function() { 329 | window.URL.revokeObjectURL(a.href); 330 | a.parentNode.removeChild(a); 331 | }, 1500); 332 | } 333 | 334 | function createDownloadLink() { 335 | window.URL = window.webkitURL || window.URL; 336 | var prevLink = document.querySelector('a'); 337 | var moduleName = document.querySelector(garglModuleNameSelector).value.replace(/ /g, "-"); 338 | var moduleDescription = document.querySelector(garglModuleDescriptionSelector).value; 339 | 340 | if (prevLink) window.URL.revokeObjectURL(prevLink.href); 341 | 342 | var garglItemsWithoutEmptys = []; 343 | garglItems.forEach(function(item, itemIndex) { 344 | if(item) { 345 | item.functionName = getFunctionName(itemIndex); 346 | garglItemsWithoutEmptys.push(item); 347 | } 348 | }); 349 | 350 | var garglFormattedItems = { 351 | garglSchemaVersion: garglSchemaVersion, 352 | moduleVersion: "1.0", 353 | moduleName: moduleName, 354 | moduleDescription: moduleDescription, 355 | functions: garglItemsWithoutEmptys 356 | }; 357 | 358 | var fileContents = JSON.stringify(garglFormattedItems, null, "\t"); 359 | var bb = new Blob([fileContents], {type: 'text/plain'}); 360 | 361 | var a = prevLink || document.createElement('a'); 362 | a.download = ((moduleName.length > 0 ? moduleName.toLowerCase() : "gargl") + ".gtf"); 363 | a.href = window.URL.createObjectURL(bb); 364 | a.textContent = 'Click to download'; 365 | 366 | a.dataset.downloadurl = ['text/plain', a.download, a.href].join(':'); 367 | 368 | document.querySelector(garglSaveHolderSelector).appendChild(a); 369 | 370 | a.onclick = function(e) { 371 | if ('disabled' in this.dataset) return false; 372 | cleanUpDownloadLink(this); 373 | }; 374 | } 375 | 376 | function removeUnneededMetadataFromItem(item) { 377 | delete(item.startedDateTime); 378 | delete(item.time); 379 | delete(item.cache); 380 | delete(item.timings); 381 | delete(item.pageref); 382 | 383 | delete(item.request.headersSize); 384 | delete(item.request.bodySize); 385 | delete(item.request.cookies); 386 | 387 | if(item.request) { 388 | if(item.request.postData && item.request.postData.params) { 389 | item.request.postData = item.request.postData.params; 390 | } 391 | item.request.headers = removeUnneededHeaders(item.request.headers, /Cookie|Content-Length/i, false); 392 | } 393 | 394 | if(item.response) { 395 | var responseFields = item.response.fields || []; 396 | item.response = { 397 | headers: removeUnneededHeaders(item.response.headers, null, true), 398 | fields: responseFields 399 | }; 400 | } 401 | } 402 | 403 | function addGarglMetadataToItem(item) { 404 | if(item.request) { 405 | item.request.url = removeQueryStringFromUrl(item.request.url); 406 | 407 | if(item.request.queryString) { 408 | item.request.queryString.forEach(function(queryArg) { 409 | queryArg.description = ""; 410 | queryArg.name = decodeURIComponent(queryArg.name); 411 | queryArg.value = decodeURIComponent(queryArg.value); 412 | }); 413 | } 414 | 415 | if(item.request.postData) { 416 | var postData = item.request.postData; 417 | var params = postData.params || postData; 418 | params.forEach(function(postArg) { 419 | postArg.description = ""; 420 | postArg.name = decodeURIComponent(postArg.name); 421 | postArg.value = decodeURIComponent(postArg.value); 422 | }); 423 | } 424 | } 425 | } 426 | 427 | function removeUnneededHeaders(headersArray, regexForUnneeded, removeHeaderValuesFromAll) { 428 | var headersToKeep = []; 429 | headersArray.forEach(function (header) { 430 | if(removeHeaderValuesFromAll) delete(header.value); 431 | 432 | if(!header.name.match(regexForUnneeded)) headersToKeep.push(header) 433 | }); 434 | 435 | return headersToKeep; 436 | } 437 | 438 | function startGargl() { 439 | document.querySelector(garglRecordAreaSelector).style.display = "block"; 440 | document.querySelector(garglStartFormSelector).style.display = "none"; 441 | document.querySelector(garglOpenFormSelector).style.display = "none"; 442 | document.querySelector(garglEditFormSelector).style.display = "none"; 443 | } 444 | 445 | function showOpenForm() { 446 | document.querySelector(garglRecordAreaSelector).style.display = "none"; 447 | document.querySelector(garglStartFormSelector).style.display = "none"; 448 | document.querySelector(garglOpenFormSelector).style.display = "block"; 449 | document.querySelector(garglEditFormSelector).style.display = "none"; 450 | } 451 | 452 | function processFile() { 453 | var file = document.querySelector(garglOpenSelector).files[0]; 454 | 455 | var extensionIndex = file.name.indexOf(".gtf"); 456 | if(extensionIndex == -1 || extensionIndex != (file.name.length - 4)) { 457 | alert("File must be a gargle template file (.gtf)"); 458 | } 459 | else { 460 | var reader = new FileReader(); 461 | 462 | reader.addEventListener("load", function(event) { 463 | var text = event.target.result; 464 | var garglModule = JSON.parse(text); 465 | 466 | document.querySelector(garglModuleNameSelector).value = garglModule.moduleName; 467 | document.querySelector(garglModuleDescriptionSelector).value = garglModule.moduleDescription; 468 | 469 | shouldRecord = true; 470 | garglModule.functions.forEach(function(item) { trackRequest(item); }); 471 | shouldRecord = false; 472 | 473 | startGargl(); 474 | }); 475 | 476 | reader.readAsText(file); 477 | } 478 | } 479 | 480 | function addResponseField(clickEvent, responseName, responseSelector) { 481 | responseName = responseName || ""; 482 | responseSelector = responseSelector || ""; 483 | 484 | var responseFieldsArea = document.querySelector(editFormFunctionResponseFieldsSelector); 485 | 486 | var nextResponseId = responseFieldsArea.querySelectorAll("."+garglResponseFieldElementClass).length; 487 | var fieldNameId = "editFormFunctionResponseFieldName" + nextResponseId; 488 | var fieldSelectorId = "editFormFunctionResponseFieldSelector" + nextResponseId; 489 | var fieldSelectorTestId = "editFormFunctionResponseFieldTest" + nextResponseId; 490 | 491 | var span = document.createElement("span"); 492 | span.setAttribute("class", garglResponseFieldElementClass); 493 | 494 | span.innerHTML = ''; 495 | span.innerHTML += ''; 496 | span.innerHTML += '
'; 497 | span.innerHTML += ''; 498 | span.innerHTML += ''; 499 | span.innerHTML += '' 500 | span.innerHTML += '

'; 501 | 502 | responseFieldsArea.appendChild(span); 503 | 504 | document.querySelector("#"+fieldSelectorTestId).onclick = function() { 505 | var idNumber = document.querySelector(garglEditFormFunctionIdNumberSelector).value; 506 | var selectorString = document.querySelector("#"+fieldSelectorId).value; 507 | 508 | var garglItem = garglItems[idNumber]; 509 | 510 | if(garglItem.getContent) { 511 | garglItem.getContent(function(content, encoding) { 512 | try { 513 | content = content.replace(/\n|\t/g,"") 514 | 515 | var holder = document.createElement("span"); 516 | holder.innerHTML = content; 517 | 518 | var matches = holder.querySelectorAll(selectorString); 519 | 520 | if(matches.length > 0) { 521 | var matchString = "Inner contents of matching elements:\n\n" 522 | 523 | for(var i = 0; i < matches.length; i ++) { 524 | var contents = null; 525 | 526 | if(matches[i].nodeName.match(/input|textarea/i)) { 527 | contents = matches[i].value; 528 | } 529 | else contents = matches[i].innerHTML; 530 | 531 | matchString += (contents + "\n\n"); 532 | } 533 | 534 | alert(matchString); 535 | } 536 | else alert("No matching elements found"); 537 | } 538 | catch(e) { 539 | alert(e); 540 | } 541 | }); 542 | } 543 | else { 544 | alert(garglFileGetResponseError); 545 | } 546 | }; 547 | 548 | document.querySelector("#"+fieldNameId).focus(); 549 | } 550 | 551 | function viewResponse() { 552 | var idNumber = document.querySelector(garglEditFormFunctionIdNumberSelector).value; 553 | var garglItem = garglItems[idNumber]; 554 | 555 | if(garglItem.getContent) { 556 | garglItem.getContent(function(content, encoding) { 557 | window.URL = window.webkitURL || window.URL; 558 | 559 | var responseTextarea = document.getElementById("responseTextarea"); 560 | var prevLink = document.querySelector('a'); 561 | if (prevLink) window.URL.revokeObjectURL(prevLink.href); 562 | 563 | var fileContents = content; 564 | var bb = new Blob([fileContents], {type: 'text/plain'}); 565 | 566 | 567 | if (responseTextarea) { //if we already added a textare, just change value of it 568 | responseTextarea.value = fileContents; 569 | } else { //if we didn't added a textarea yet, add one 570 | responseTextarea = document.createElement('input'); 571 | responseTextarea.type = "textarea"; 572 | responseTextarea.id = "responseTextarea"; 573 | responseTextarea.value = fileContents; 574 | responseTextarea.style.position = "fixed"; 575 | responseTextarea.style.zIndex = "-1"; 576 | responseTextarea.style.top = "-1000px"; 577 | 578 | var garglCopyResponseBtn = document.createElement('input'); 579 | garglCopyResponseBtn.type = "button" 580 | garglCopyResponseBtn.id = "garglCopyResponseBtn"; 581 | garglCopyResponseBtn.value = "Copy Response"; 582 | 583 | document.querySelector(garglViewResponseHolderSelector).appendChild(garglCopyResponseBtn); 584 | document.querySelector(garglViewResponseHolderSelector).appendChild(responseTextarea); 585 | 586 | garglCopyResponseBtn.addEventListener('click', function(e) { 587 | responseTextarea.select(); 588 | 589 | try { 590 | var successful = document.execCommand('copy'); 591 | if(!successful) 592 | alert('Unable to copy!'); 593 | } catch (err) { 594 | alert('Unsupported Browser!'); 595 | } 596 | }); 597 | } 598 | 599 | var a = prevLink || document.createElement('a'); 600 | a.download = "response.txt"; 601 | a.href = window.URL.createObjectURL(bb); 602 | a.textContent = 'Click to download'; 603 | 604 | a.dataset.downloadurl = ['text/plain', a.download, a.href].join(':'); 605 | 606 | document.querySelector(garglViewResponseHolderSelector).appendChild(a); 607 | 608 | a.onclick = function(e) { 609 | if ('disabled' in this.dataset) return false; 610 | cleanUpDownloadLink(this); 611 | }; 612 | }); 613 | } 614 | else { 615 | alert(garglFileGetResponseError); 616 | } 617 | } 618 | 619 | function grabResponseFieldFormData() { 620 | var fieldData = []; 621 | var fieldElements = document.querySelectorAll("." + garglResponseFieldElementClass); 622 | 623 | for(var i = 0; i < fieldElements.length; i ++) { 624 | var fieldInputs = fieldElements[i].querySelectorAll("input"); 625 | 626 | var field = { 627 | name: fieldInputs[0].value.replace(/ /g,"-"), 628 | cssSelector: fieldInputs[1].value 629 | }; 630 | 631 | if(field.name.length > 0 && field.cssSelector.length > 0) fieldData.push(field); 632 | } 633 | 634 | return fieldData; 635 | } 636 | 637 | window.addEventListener('load', function() { 638 | document.querySelector(garglRecordSelector).onclick = toggleRecord; 639 | document.querySelector(garglClearSelector).onclick = clearGarglItemsTable; 640 | document.querySelector(garglSaveSelector).onclick = createDownloadLink; 641 | document.querySelector(garglNewSelector).onclick = startGargl; 642 | document.querySelector(garglExistingSelector).onclick = showOpenForm; 643 | document.querySelector(garglEditSaveSelector).onclick = saveEditGarglItem; 644 | document.querySelector(garglEditCancelSelector).onclick = cancelEditGarglItem; 645 | document.querySelector(garglNewResponseFieldSelector).onclick = addResponseField; 646 | document.querySelector(garglViewResponseSelector).onclick = viewResponse; 647 | 648 | document.querySelector(garglOpenSelector).onchange = processFile; 649 | 650 | document.querySelector(garglRecordAreaSelector).style.display = "none"; 651 | document.querySelector(garglOpenFormSelector).style.display = "none"; 652 | document.querySelector(garglEditFormSelector).style.display = "none"; 653 | }); 654 | 655 | chrome.devtools.network.onRequestFinished.addListener(trackRequest); 656 | })(); 657 | --------------------------------------------------------------------------------