James has posted 2 posts at DZone. View Full User Profile

Obfuscating Javascript with Maven and Google Closure

04.18.2011
| 28863 views |
  • submit to reddit

Original blog post: http://eggsylife.co.uk/2011/04/18/obfuscating-javascript-with-maven-and-google-closure/

GitHub Repository – with Maven project example

At the time of writing I couldn’t find an easy way to automate Javascript obfuscation when building a Mavenproject. Using the Googles Closure compiler I decided to work on my own concept. This guide shows how you can incorporate the same process into your maven projects.

EDIT: wro4j is a plugin that makes use of the Closure compiler however it only allows for SIMPLE_CUSTOMIZATIONS level obfuscation.

Firstly, you need to include the Exec-Maven. This plugin allows you to execute Java applications during your build process. Include this plugin in your maven pom.xml like so:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>uk.co.eggsylife.JavascriptObfuscator</mainClass>
<arguments>
<argument>${basedir}/src/main/webapp/scripts/raw.js</argument> <!-- Input file -->
<argument>${basedir}/src/main/webapp/scripts/raw.min.js</argument> <!-- Output file -->
</arguments>
</configuration>
</plugin>

Notice the configuration of the plugin. Firstly we are targeting the goal ‘java’. I shall discuss this later. Secondly, we define a main class. This is the Java application the plugin will run. Finally we have the arguments, these are the arguments that will be passed to the ‘public static void main(String[] args)’ method. In the example above we pass the input file and the location/name of the output file.

Now for the ‘JavascriptObfuscator’ class. This class communicates with the Closure Compiler API passing in the raw Javascript from the input file – asking for the obfuscated version from the API and then saving the output to the specified output file.

package uk.co.eggsylife;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
 
public class JavascriptObfuscator {
 
/**
* @param args
*/
public static void main(String[] args) {
 
System.out.println("Obfuscating Javascript");
 
if( args.length >= 2 ) {
 
int argumentCount = args.length;
String jsFile = args[0];
 
String outputFile = args[1];
 
String compilationLevel = "ADVANCED_OPTIMIZATIONS";
if(argumentCount == 3 ) {
compilationLevel = args[2];
}
 
 
String outputFormat = "text";
if(argumentCount == 4 ) {
outputFormat = args[3];
}
 
System.out.println("Processing ( " + jsFile + " )" );
System.out.println("Compilation Level ( " + compilationLevel + " )");
System.out.println("Output Format ( " + outputFormat + " )");
System.out.println("Saving to ( " + outputFile + " )");
 
StringBuffer contents = new StringBuffer();
BufferedReader reader = null;
 
try {
File file = new File(jsFile);
reader = new BufferedReader(new FileReader(file));
String text = null;
 
// Read the Javascript
while ((text = reader.readLine()) != null) {
contents.append(text).append(System.getProperty("line.separator"));
}
 
// Javascript Pre-Obfuscation
String jsCode = contents.toString();
 
ClosureCompiler compiler = new ClosureCompiler(outputFile, jsCode, compilationLevel, outputFormat);
compiler.processCompilation();
 
System.out.println("Obfuscation Finished");
 
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
if (reader != null) {
reader.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
 
}
else {
System.err.println("No (not enough) arguments passed to Obfuscation - requires JS file and Output directory");
}
}
}

The class *must* have at least two arguments – the input and output file – however you can pass further arguments which allow you to customise the Closure compiler obfuscation.

  • args[0] = Input file (required)
  • args[1] = Output file (required)
  • args[2] = Compilation level (default ADVANCED_OPTIMIZATIONS)
  • args[3] = Output format (default test)

You can of course customise this class to make it perform as you like

Next notice the use of the ‘ClosureCompiler’ class, this class communicates with the Closure API

package uk.co.eggsylife;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
 
public class ClosureCompiler {
 
private String outputFile = null;
private String jsCode = null;
private String compilationLevel = null;
private String outputFormat = null;
 
public ClosureCompiler(String outputFile, String jsCode, String compilationlevel, String outputFormat) {
this.outputFile = outputFile;
this.jsCode = jsCode;
this.compilationLevel = compilationlevel;
this.outputFormat = outputFormat;
}
 
public void processCompilation() {
try {
String obfuscatedCode = sendForCompilation();
saveObfuscatedCodeToFile(outputFile, obfuscatedCode);
}
catch (Exception e) {
System.err.println("Exception when obfuscating code: " + e.getMessage());
}
}
 
private String sendForCompilation() throws Exception {
URL url = new URL("http://closure-compiler.appspot.com/compile");
 
String data = URLEncoder.encode("js_code", "UTF-8") + "=" + URLEncoder.encode(jsCode, "UTF-8");
data += "&" + URLEncoder.encode("compilation_level", "UTF-8") + "=" + URLEncoder.encode(compilationLevel, "UTF-8");
data += "&" + URLEncoder.encode("output_format", "UTF-8") + "=" + URLEncoder.encode(outputFormat, "UTF-8");
data += "&" + URLEncoder.encode("output_info", "UTF-8") + "=" + URLEncoder.encode("compiled_code", "UTF-8");
 
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
 
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String codeLine;
 
StringBuffer obfuscatedCodeLine = new StringBuffer();
while ((codeLine = rd.readLine()) != null) {
obfuscatedCodeLine.append(codeLine).append(System.getProperty("line.separator"));
}
wr.close();
rd.close();
 
return obfuscatedCodeLine.toString();
}
 
private void saveObfuscatedCodeToFile(String outputdirectory, String javascriptCode) throws Exception{
FileWriter fstream = new FileWriter(outputdirectory);
BufferedWriter out = new BufferedWriter(fstream);
out.write(javascriptCode);
//Close the output stream
out.close();
}
 
}

It fetches the obfuscated result and saves it to the specified output file

That just about completes the blog, the last thing is to show how your would invoke this. Thats a simple as:

mvn exec:java

You can of course customise the goal of the plugin so that it obfuscates as and when you need it to.

I would also welcome any comments or further customisations on this

Published at DZone with permission of its author, James Heggs.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)