/*
 * Created on Jun 1, 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.owasp.webgoat.lessons;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.ecs.Element;
import org.apache.ecs.ElementContainer;
import org.apache.ecs.StringElement;
import org.apache.ecs.html.B;
import org.apache.ecs.html.Input;
import org.apache.ecs.html.P;
import org.apache.ecs.html.PRE;

import org.owasp.webgoat.session.ECSFactory;
import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.util.HtmlEncoder;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * @author rdawes
 * 
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class WsSAXInjection extends LessonAdapter {
	private final static String PASSWORD = "password";

	private String password;

	private static String template1 = "<?xml version='1.0' encoding='UTF-8'?>\n"
			+ "<wsns0:Envelope\n"
			+ "  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
			+ "  xmlns:xsd='http://www.w3.org/2001/XMLSchema'\n"
			+ "  xmlns:wsns0='http://schemas.xmlsoap.org/soap/envelope/'\n"
			+ "  xmlns:wsns1='http://lessons.webgoat.owasp.org'>\n"
			+ "  <wsns0:Body>\n"
			+ "    <wsns1:changePassword>\n"
			+ "      <id xsi:type='xsd:int'>101</id>\n"
			+ "      <password xsi:type='xsd:string'>";

	private static String template2 = "</password>\n"
			+ "    </wsns1:changePassword>\n" + "  </wsns0:Body>\n"
			+ "</wsns0:Envelope>";

	static boolean completed;

	protected Category getDefaultCategory() {
		return AbstractLesson.WEB_SERVICES;
	}

	protected List getHints() {
		List<String> hints = new ArrayList<String>();
		
		hints.add("The backend parses the XML received using a SAX parser.");
		hints.add("SAX parsers often don't care if an element is repeated.");
		hints.add("If there are repeated elements, the last one is the one that is effective");
		hints.add("Try injecting matching 'close' tags, and creating your own XML elements");
		
		return hints;
	}

	private final static Integer DEFAULT_RANKING = new Integer(150);

	protected Integer getDefaultRanking() {
		return DEFAULT_RANKING;
	}

	public String getTitle() {
		return "Web Service SAX Injection";
	}

	protected Element makeInputLine(WebSession s) {
		ElementContainer ec = new ElementContainer();

		ec.addElement(new P().addElement("Please change your password: "));

		Input input = new Input(Input.TEXT, PASSWORD);
		ec.addElement(input);

		Element b = ECSFactory.makeButton("Go!");
		ec.addElement(b);

		return ec;
	}

	protected Element createContent(WebSession s) {
		ElementContainer ec = new ElementContainer();
		try {
			ec.addElement(makeInputLine(s));

			password = s.getParser().getRawParameter(PASSWORD, null);
			
			PRE pre = new PRE();
			String xml = template1;
			xml = xml + (password == null ? "[password]" : password);
			xml = xml + template2;
			pre.addElement(HtmlEncoder.encode(xml));
			ec.addElement(pre);
			
			if (password != null) {
				ec.addElement(checkXML(s, xml));
			}
		} catch (Exception e) {
			s.setMessage("Error generating " + this.getClass().getName());
			e.printStackTrace();
		}
		return (ec);
	}
	
	private Element checkXML(WebSession s, String xml) {
		try {
			XMLReader reader = XMLReaderFactory.createXMLReader();
			PasswordChanger changer = new PasswordChanger();
			reader.setContentHandler(changer);
			reader.parse(new InputSource(new StringReader(xml)));
			if (!"101".equals(changer.getId())) {
				makeSuccess(s);
				return new B(HtmlEncoder.encode("You have changed the passsword for userid " + changer.getId() + " to '" + changer.getPassword() + "'"));
			} else {
				return new StringElement("You changed the password for userid 101. Try again.");
			}
		} catch (SAXException saxe) {
			return new StringElement("The XML was not well formed: " + saxe.getLocalizedMessage());
		} catch (IOException ioe) {
			return new StringElement(ioe.getLocalizedMessage());
		}
	}
	
	private static class PasswordChanger extends DefaultHandler {
		private static String PASSWORD_TAG = "password";
		private static String ID_TAG = "id";
		
		private String id = null;
		private String password = null;
		
		private StringBuffer text = new StringBuffer();
		
		public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
			text.delete(0, text.length());
		}
		
		public void characters(char[] ch, int start, int length) throws SAXException {
			text.append(ch, start, length);
		}

		public void endElement(String uri, String localName, String qName) throws SAXException {
			if (localName.equals(ID_TAG))
				id = text.toString();
			if (localName.equals(PASSWORD_TAG))
				password = text.toString();
			text.delete(0, text.length());
		}

		public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
			text.append(ch, start, length);
		}

		public String getId() {
			return id;
		}

		public String getPassword() {
			return password;
		}

	}
}
