package com.crawljax.plugins.jsmodify;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.ast.AstRoot;
import org.owasp.webscarab.httpclient.HTTPClient;
import org.owasp.webscarab.model.Request;
import org.owasp.webscarab.model.Response;
import org.owasp.webscarab.plugin.proxy.ProxyPlugin;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.crawljax.plugins.jsmodify.executionTracer.JSExecutionTracer;
import com.crawljax.util.Helper;
/**
* The JSInstrument proxy plugin used to add instrumentation code to JavaScript files.
*
*/
public class JSModifyProxyPlugin extends ProxyPlugin {
private List<String> excludeFilenamePatterns;
private final JSASTModifier modifier;
/**
* Construct without patterns.
*
* @param modify
* The JSASTModifier to run over all JavaScript.
*/
public JSModifyProxyPlugin(JSASTModifier modify) {
excludeFilenamePatterns = new ArrayList<String>();
modifier = modify;
}
/**
* Constructor with patterns.
*
* @param modify
* The JSASTModifier to run over all JavaScript.
* @param excludes
* List with variable patterns to exclude.
*/
public JSModifyProxyPlugin(JSASTModifier modify, List<String> excludes) {
excludeFilenamePatterns = new ArrayList<String>();
excludeFilenamePatterns = excludes;
modifier = modify;
}
@Override
public String getPluginName() {
return "JSInstrumentPlugin";
}
@Override
public HTTPClient getProxyPlugin(HTTPClient in) {
return new Plugin(in);
}
private boolean shouldModify(String name) {
/* try all patterns and if 1 matches, return false */
for (String pattern : excludeFilenamePatterns) {
if (name.matches(pattern)) {
return false;
}
}
return true;
}
/**
* This method tries to add instrumentation code to the input it receives. The original input is
* returned if we can't parse the input correctly (which might have to do with the fact that the
* input is no JavaScript because the server uses a wrong Content-Type header for JSON data)
*
* @param input
* The JavaScript to be modified
* @param scopename
* Name of the current scope (filename mostly)
* @return The modified JavaScript
*/
private synchronized String modifyJS(String input, String scopename) {
if (!shouldModify(scopename)) {
return input;
}
try {
AstRoot ast = null;
/* initialize JavaScript context */
Context cx = Context.enter();
/* create a new parser */
Parser rhinoParser = new Parser(new CompilerEnvirons(), cx.getErrorReporter());
/* parse some script and save it in AST */
ast = rhinoParser.parse(new String(input), scopename, 0);
modifier.setScopeName(scopename);
modifier.start();
/* recurse through AST */
ast.visit(modifier);
modifier.finish(ast);
/* clean up */
Context.exit();
return ast.toSource();
} catch (RhinoException re) {
System.err.println(re.getMessage()
+ "Unable to instrument. This might be a JSON response sent"
+ " with the wrong Content-Type or a syntax error.");
} catch (IllegalArgumentException iae) {
System.err.println("Invalid operator exception catched. Not instrumenting code.");
}
System.err.println("Here is the corresponding buffer: \n" + input + "\n");
return input;
}
/**
* This method modifies the response to a request.
*
* @param response
* The response.
* @param request
* The request.
* @return The modified response.
*/
private Response createResponse(Response response, Request request) {
String type = response.getHeader("Content-Type");
if (request.getURL().toString().contains("?thisisanexecutiontracingcall")) {
JSExecutionTracer.addPoint(new String(request.getContent()));
return response;
}
if (type != null && type.contains("javascript")) {
/* instrument the code if possible */
response.setContent(modifyJS(new String(response.getContent()),
request.getURL().toString()).getBytes());
} else if (type != null && type.contains("html")) {
try {
Document dom = Helper.getDocument(new String(response.getContent()));
/* find script nodes in the html */
NodeList nodes = dom.getElementsByTagName("script");
for (int i = 0; i < nodes.getLength(); i++) {
Node nType = nodes.item(i).getAttributes().getNamedItem("type");
/* instrument if this is a JavaScript node */
if ((nType != null && nType.getTextContent() != null && nType
.getTextContent().toLowerCase().contains("javascript"))) {
String content = nodes.item(i).getTextContent();
if (content.length() > 0) {
String js = modifyJS(content, request.getURL() + "script" + i);
nodes.item(i).setTextContent(js);
continue;
}
}
/* also check for the less used language="javascript" type tag */
nType = nodes.item(i).getAttributes().getNamedItem("language");
if ((nType != null && nType.getTextContent() != null && nType
.getTextContent().toLowerCase().contains("javascript"))) {
String content = nodes.item(i).getTextContent();
if (content.length() > 0) {
String js = modifyJS(content, request.getURL() + "script" + i);
nodes.item(i).setTextContent(js);
}
}
}
/* only modify content when we did modify anything */
if (nodes.getLength() > 0) {
/* set the new content */
response.setContent(Helper.getDocumentToByteArray(dom));
}
} catch (Exception e) {
e.printStackTrace();
}
}
/* return the response to the webbrowser */
return response;
}
/**
* WebScarab plugin that adds instrumentation code.
*
*/
private class Plugin implements HTTPClient {
private HTTPClient client = null;
/**
* Constructor for this plugin.
*
* @param in
* The HTTPClient connection.
*/
public Plugin(HTTPClient in) {
client = in;
}
@Override
public Response fetchResponse(Request request) throws IOException {
Response response = client.fetchResponse(request);
return createResponse(response, request);
}
}
}