/*
 * Java Payloads.
 * 
 * Copyright (c) 2010, Michael 'mihi' Schierl
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *   
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *   
 * - Neither name of the copyright holders nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package javapayload.builder;

import java.io.FileOutputStream;
import java.io.InputStream;

import javapayload.stager.Stager;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class ClassBuilder extends Stager {

	protected static void buildClass(final String classname, final String stager, Class loaderClass, final String embeddedArgs) throws Exception {

		final ClassWriter cw = new ClassWriter(0);

		class MyMethodVisitor extends MethodAdapter {
			private final String newClassName;

			public MyMethodVisitor(MethodVisitor mv, String newClassName) {
				super(mv);
				this.newClassName = newClassName;
			}

			private String cleanType(String type) {
				if (type.startsWith("javapayload/")) {
					type = newClassName;
				}
				return type;
			}

			public void visitFieldInsn(int opcode, String owner, String name, String desc) {
				super.visitFieldInsn(opcode, cleanType(owner), name, desc);
			}

			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				super.visitMethodInsn(opcode, cleanType(owner), name, desc);
			}

			public void visitTypeInsn(int opcode, String type) {
				super.visitTypeInsn(opcode, cleanType(type));
			}
		}
		final ClassVisitor stagerVisitor = new ClassAdapter(cw) {

			public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
				super.visit(version, access, classname, signature, "java/lang/ClassLoader", interfaces);
			}

			public void visitEnd() {
				// not the end!
			}

			public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
				// strip constructors
				if (name.equals("<init>")) {
					return null;
				}
				return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions), classname);
			}
		};
		visitClass(Class.forName("javapayload.stager." + stager), stagerVisitor, cw);
		final ClassVisitor stagerBaseVisitor = new ClassAdapter(cw) {

			public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
				// not the beginning!
			}

			public void visitEnd() {
				// not the end!
			}

			public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
				// strip abstract bootstrap method
				if (name.equals("bootstrap") && (access & Opcodes.ACC_ABSTRACT) != 0) {
					return null;
				}
				return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions), classname);
			}
		};
		visitClass(Stager.class, stagerBaseVisitor, cw);
		final ClassVisitor loaderVisitor = new ClassAdapter(cw) {
			public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
				// not the beginning!
			}

			public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
				if (embeddedArgs != null && name.equals("EMBEDDED_ARGS")) {
					// create EMBEDDED_ARGS field
					return super.visitField(access, name, desc, signature, embeddedArgs);
				}
				// do not copy other fields
				return null;
			}

			public void visitInnerClass(String name, String outerName, String innerName, int access) {
				// do not copy inner classes
			}

			public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
				if (name.equals("mainToEmbed")) {
					return new MyMethodVisitor(super.visitMethod(access, "main", desc, signature, exceptions), classname);
				} else {
					return null;
				}
			}

			public void visitOuterClass(String owner, String name, String desc) {
				// do not copy outer classes
			}
		};
		visitClass(loaderClass, loaderVisitor, cw);
		final byte[] newBytecode = cw.toByteArray();
		final FileOutputStream fos = new FileOutputStream(classname + ".class");
		fos.write(newBytecode);
		fos.close();
	}

	public static void main(String[] args) throws Exception {
		if (args.length != 1 && args.length != 2) {
			System.out.println("Usage: java javapayload.builder.ClassBuilder <stager> [classname]");
			return;
		}
		final String stager = args[0];
		String classname = stager + "Class";
		if (args.length == 2) {
			classname = args[1];
		}

		buildClass(classname, stager, ClassBuilder.class, null);
	}

	public static void mainToEmbed(String[] args) throws Exception {
		new ClassBuilder().bootstrap(args);
	}

	private static void visitClass(Class clazz, ClassVisitor stagerVisitor, ClassWriter cw) throws Exception {
		final InputStream is = ClassBuilder.class.getResourceAsStream("/" + clazz.getName().replace('.', '/') + ".class");
		final ClassReader cr = new ClassReader(is);
		cr.accept(stagerVisitor, ClassReader.SKIP_DEBUG);
		is.close();
	}

	public void bootstrap(String[] parameters) throws Exception {
		throw new Exception("Never used!");
	}
}
