view src/luan/webserver/RequestParser.java @ 1146:2dda3c92a473

webserver - implement cookies
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 01 Feb 2018 03:08:21 -0700
parents 12ececf30597
children 30d87b7d1d62
line wrap: on
line source

package luan.webserver;

import java.util.List;
import java.util.ArrayList;
import luan.lib.parser.Parser;
import luan.lib.parser.ParseException;


final class RequestParser {
	private final Request request;
	private Parser parser;

	RequestParser(Request request) {
		this.request = request;
	}

	void parseUrlencoded() throws ParseException {
		this.parser = new Parser(request.body);
		parseQuery();
		require( parser.endOfInput() );
	}

	void parseHead() throws ParseException {
		this.parser = new Parser(request.rawHead);
		parseRequestLine();
		while( !parser.match("\r\n") ) {
			parserHeaderField();
		}
		parseCookies();
	}

	private void parseRequestLine() throws ParseException {
		parseMethod();
		require( parser.match(' ') );
		parseRawPath();
		require( parser.match(' ') );
		parseProtocol();
		require( parser.match("\r\n") );
	}

	private void parseMethod() throws ParseException {
		int start = parser.currentIndex();
		if( !methodChar() )
			throw new ParseException(parser,"no method");
		while( methodChar() );
		request.method = parser.textFrom(start);
	}

	private boolean methodChar() {
		return parser.inCharRange('A','Z');
	}

	private void parseRawPath() throws ParseException {
		int start = parser.currentIndex();
		parsePath();
		if( parser.match('?') )
			parseQuery();
		request.rawPath = parser.textFrom(start);
	}

	private void parsePath() throws ParseException {
		int start = parser.currentIndex();
		if( !parser.match('/') )
			throw new ParseException(parser,"bad path");
		while( safePathChar() || parser.anyOf("&=") );
		request.path = Util.urlDecode( parser.textFrom(start) );
	}

	private void parseQuery() throws ParseException {
		while(true) {
			while( parser.match('&') );
			int start = parser.currentIndex();
			if( !queryChar() )
				return;
			while( queryChar() );
			String name = Util.urlDecode( parser.textFrom(start) );
			String value;
			if( parser.match('=') ) {
				start = parser.currentIndex();
				while( queryChar() );
				value = Util.urlDecode( parser.textFrom(start) );
			} else {
				value = "";
			}
			Util.add(request.parameters,name,value);
		}
	}

	private boolean queryChar() {
		return safePathChar() || parser.anyOf("?");
	}

	// where did I get this?
	private boolean safePathChar() {
		return parser.inCharRange('A','Z')
			|| parser.inCharRange('a','z')
			|| parser.inCharRange('0','9')
			|| parser.anyOf("-._~:/[]@!$'()*+,;`.%")
		;
	}

	private void parseProtocol() throws ParseException {
		int start = parser.currentIndex();
		if( !(
			parser.match("HTTP/")
			&& parser.inCharRange('0','9')
			&& parser.match('.')
			&& parser.inCharRange('0','9')
		) )
			throw new ParseException(parser,"bad protocol");
		request.protocol = parser.textFrom(start);
	}


	private void parserHeaderField() throws ParseException {
		String name = parseName();
		require( parser.match(':') );
		while( parser.anyOf(" \t") );
		String value = parseValue();
		while( parser.anyOf(" \t") );
		require( parser.match("\r\n") );
		Util.add(request.headers,name,value);
	}

	private String parseName() throws ParseException {
		StringBuilder buf = new StringBuilder();
		boolean cap = true;
		require( tokenChar() );
		do {
			char c = parser.lastChar();
			if( c == '-' ) {
				cap = true;
			} else if( cap ) {
				c = Character.toUpperCase(c);
				cap = false;
			} else {
				c = Character.toLowerCase(c);
			}
			buf.append(c);
		} while( tokenChar() );
		return buf.toString();
	}

	private String parseValue() {
		int start = parser.currentIndex();
		while( !testEndOfValue() )
			parser.anyChar();
		return parser.textFrom(start);
	}

	private boolean testEndOfValue() {
		parser.begin();
		while( parser.anyOf(" \t") );
		boolean b = parser.endOfInput() || parser.anyOf("\r\n");
		parser.failure();  // rollback
		return b;
	}

	private void require(boolean b) throws ParseException {
		if( !b )
			throw new ParseException(parser,"failed");
	}

	boolean tokenChar() {
		if( parser.endOfInput() )
			return false;
		char c = parser.currentChar();
		if( 32 <= c && c <= 126 && "()<>@,;:\\\"/[]?={} \t\r\n".indexOf(c) == -1 ) {
			parser.anyChar();
			return true;
		} else {
			return false;
		}
	}


	private void parseCookies() throws ParseException {
		String text = (String)request.headers.get("Cookie");
		if( text == null )
			return;
		this.parser = new Parser(text);
		while(true) {
			int start = parser.currentIndex();
			while( parser.noneOf("=;") );
			String name = Util.urlDecode( parser.textFrom(start) );
			if( parser.match('=') ) {
				start = parser.currentIndex();
				while( parser.noneOf(";") );
				String value = Util.urlDecode( parser.textFrom(start) );
				request.cookies.put(name,value);
			}
			if( parser.endOfInput() )
				return;
			require( parser.match("; ") );
		}
	}

}