/* Reattore HTTP Server
Copyright (C) 2002 Michael Hope <michaelh@juju.net.nz>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
$Id: StartLineParser.java,v 1.6 2003/02/22 04:29:52 michaelh Exp $
*/
package juju.reattore.protocol.http.impl;
import java.util.*;
import java.io.IOException;
import juju.reattore.io.impl.*;
import juju.reattore.io.ByteSource;
import juju.reattore.protocol.http.ParseException;
import juju.reattore.util.CharUtil;
import org.apache.commons.logging.*;
/** Parses a HTTP start (request/response) line.
@todo Minor/Strict: Doesn't detect invalid leading spaces.
*/
public class StartLineParser {
private static Log log = LogFactory.getLog(StartLineParser.class);
private StringBuffer method;
private StringBuffer path;
private StringBuffer query;
private StringBuffer version;
private static final int IN_SP = 0;
private static final int IN_METHOD = 1;
private static final int IN_PATH = 2;
private static final int IN_QUERY = 3;
private static final int IN_ENCODED_1 = 4;
private static final int IN_ENCODED_2 = 5;
private static final int IN_VERSION = 6;
private static final int IN_EOL = 7;
private static final int IN_RESET = 8;
private int state = IN_RESET;
private int nextState;
private Callback callback;
private int encVal;
private StringBuffer encTo;
/** May be used to generate events on the end of parse instead of
being data driven. Register using #setCallback
*/
public interface Callback {
/** Called when a line has been parsed.
@param method The parsed method
@param path The parsed path
@param query The parsed query
@param version The parsed version
*/
void onStartLine(String method, String path, String query,
String version);
};
/** Sets what to call when a line has been parsed.
@param callback The class to call, or null to disable.
*/
public void setCallback(Callback callback) {
this.callback = callback;
}
/** Parse the line.
@param in Source to parse from
@return false means more parsing needed
@throws ParseException if an error occurs while parsing.
@throws IOException on error.
*/
public boolean add(PushbackByteSource in)
throws ParseException, IOException {
int got;
if (state == IN_RESET) {
reset();
}
while ((got = in.get()) != PushbackByteSource.EOF) {
switch (state) {
case IN_SP:
switch (got) {
case ' ':
break;
default:
in.pushback(got);
state = nextState;
break;
}
break;
case IN_METHOD:
switch (got) {
case ' ':
state = IN_SP;
nextState = IN_PATH;
break;
default:
method.append((char)got);
break;
}
break;
case IN_PATH:
switch (got) {
case '\r':
state = IN_EOL;
break;
case '%':
encTo = path;
nextState = state;
state = IN_ENCODED_1;
break;
case ' ':
state = IN_SP;
nextState = IN_VERSION;
break;
case '?':
state = IN_QUERY;
break;
default:
path.append((char)got);
break;
}
break;
case IN_QUERY:
switch (got) {
case '\r':
state = IN_EOL;
break;
case '%':
encTo = query;
nextState = state;
state = IN_ENCODED_1;
break;
case ' ':
state = IN_SP;
nextState = IN_VERSION;
break;
case '+':
query.append(' ');
break;
default:
query.append((char)got);
break;
}
break;
case IN_ENCODED_1:
encVal = CharUtil.fromNibble(got) * 16;
state = IN_ENCODED_2;
break;
case IN_ENCODED_2:
encTo.append((char)(CharUtil.fromNibble(got) + encVal));
state = nextState;
break;
case IN_VERSION:
switch (got) {
case '\r':
state = IN_EOL;
break;
default:
version.append((char)got);
break;
}
break;
case IN_EOL:
switch (got) {
case '\n':
runCallback();
state = IN_RESET;
return true;
default:
throw new ParseException();
}
default:
throw new ParseException();
}
}
return false;
}
private void runCallback() {
if (callback != null) {
callback.onStartLine(method.toString(),
path.toString(),
query.toString(),
version.toString());
}
}
/** Resets back to a clean state.
*/
public void reset() {
method = new StringBuffer();
path = new StringBuffer();
query = new StringBuffer();
version = new StringBuffer();
state = IN_SP;
nextState = IN_METHOD;
}
/** Gets the parsed method.
@return The parsed value, or "" if none.
*/
public String getMethod() {
return method.toString();
}
/** Gets the parsed path.
@return The parsed value, or "" if none.
*/
public String getPath() {
return path.toString();
}
/** Gets the parsed query.
@return The parsed value, or "" if none.
*/
public String getQuery() {
return query.toString();
}
/** Gets the parsed version.
@return The parsed value, or "" if none.
*/
public String getVersion() {
return version.toString();
}
}