/******************************************************************************* * Copyright (c) 2009 MATERNA Information & Communications. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. For further * project-related information visit http://www.ws4d.org. The most recent * version of the JMEDS framework can be obtained from * http://sourceforge.net/projects/ws4d-javame. ******************************************************************************/ package org.ws4d.java.communication.protocol.http; import java.io.IOException; import java.io.InputStream; import org.ws4d.java.communication.ProtocolException; import org.ws4d.java.constants.Specialchars; import org.ws4d.java.structures.HashMap; /** * HTTP support class. All RFC methods are here! */ public class HTTPUtil { /** * We are shy! */ private HTTPUtil() { } /** * Reads a single element from the input stream. Elements are separated by * space characters. (see RFC2616 5.1) * * @param in input stream to read from. * @return the read element. */ public static String readElement(InputStream in) throws IOException { return HTTPUtil.readElement(in, 0); } /** * Reads a single element from the input stream. Elements are separated by * space characters. (see RFC2616 5.1). Stops after given amount of bytes. * * @param in in input stream to read from. * @param maxlen max length to read from stream. * @return the read element. * @throws IOException */ public static String readElement(InputStream in, int maxlen) throws IOException { int i = -1; int j = -1; StringBuffer buffer = new StringBuffer(); // read until "space" while ((j < maxlen) && ((i = in.read()) != -1) && ((byte) i != Specialchars.SP)) { if (maxlen > 0) j++; buffer.append((char) i); } if (i == -1) { return null; } return buffer.toString(); } /** * Reads a single protocol line from the input stream. HTTP defines the * sequence CR LF as the end-of-line marker. (see RFC2616 2.2) * * @param in input stream to read from. * @return the protocol line. */ public static String readRequestLine(InputStream in) throws IOException { int i; StringBuffer buffer = new StringBuffer(); int j = 0; // read until new line while (((i = in.read()) != -1)) { if ((byte) i == Specialchars.CR) { j = 1; continue; } if ((byte) i == Specialchars.LF && j == 1) { j = 0; return buffer.toString(); } buffer.append((char) i); } throw new IOException(HTTPRequestUtil.FAULT_UNEXPECTED_END); } /** * Reads the HTTP version from the input stream. (see RFC2616 3.1) * * @param in input stream to read from. * @return the read element. */ public static String readRequestVersion(InputStream in) throws IOException, ProtocolException { int i; StringBuffer buffer = new StringBuffer(); /* * "HTTP" "/" 1*DIGIT "." 1*DIGIT */ int j = 0; int k = 0; byte http[] = { 0x48, 0x54, 0x54, 0x50 }; // HTTP; while (((i = in.read()) != -1)) { // check for HTTP if (j < http.length && k == 0) { if ((byte) i != http[j]) { throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_REQUEST); } buffer.append((char) i); j++; continue; } // check for slash after HTTP string if (j == http.length) { if ((byte) i == 0x2F) { // slash buffer.append((char) i); k = 1; j = 0; continue; } throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_REQUEST); } // check for 0-9 and a dot if ((k == 1)) { if ((byte) i >= 0x30 && (byte) i <= 0x39) { buffer.append((char) i); continue; } if ((byte) i == 0x2E) { // dot buffer.append((char) i); k = 2; continue; } throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_REQUEST); } // check for 0-9 and a new line if ((k == 2)) { if ((byte) i >= 0x30 && (byte) i <= 0x39) { buffer.append((char) i); continue; } if ((byte) i == Specialchars.CR) { k = 3; continue; } throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_REQUEST); } // check for new line end if (k == 3) { if ((byte) i == Specialchars.LF) { j = 0; k = 0; // exit! return buffer.toString(); } } } throw new IOException(HTTPRequestUtil.FAULT_UNEXPECTED_END); } /** * Reads a HTTP header fields from the input stream. To learn more about * HTTP header fields, take a look at RFC2616 4.2, 4.5, 5.3, 6.2, 7.1 * * @param in the input stream to read from. * @param headerfields <code>Hashtable</code> to store the fields in. */ public static void readHeaderFields(InputStream in, HashMap headerfields) throws IOException, ProtocolException { String fieldname = null; String fieldvalue = null; int i; StringBuffer buffer = new StringBuffer(); int j = 0; // length of read bytes. int k = 0; // CRLF counter. 2xCRLF = header end. int l = 0; // CRLF detection. 0=nothing, 1=CR, 2=CRLF. // message-header = field-name ":" [ field-value ] // field-name = token // field-value = *( field-content | LWS ) field-content = *TEXT | // *(token, separators, quoted-string) while (((i = in.read()) != -1)) { if (fieldname == null) { // check for new line if ((byte) i == Specialchars.CR) { l = 1; continue; } // check for new line end if ((byte) i == Specialchars.LF && l == 1) { l = 0; return; } // check for colon and create field-name if ((byte) i == Specialchars.COL) { fieldname = buffer.toString().toLowerCase(); buffer = new StringBuffer(); j = 1; continue; } // no CTL (ascii 0-31) allowed for field-name if ((byte) i >= 0x00 && (char) i <= 0x1F) { // throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_HEADERFIELD + " (" + buffer.toString() + ")"); } // no separators allowed for token (see RFC2616 2.2) if ((byte) i == 0x28 || (byte) i == 0x29 || (byte) i == 0x3C || (byte) i == 0x3D || (byte) i == 0x3E || (byte) i == 0x40 || (byte) i == 0x2C || (byte) i == 0x3F || (byte) i == 0x3B || (byte) i == 0x2F || (byte) i == 0x5C || (byte) i == 0x5B || (byte) i == 0x5D || (byte) i == 0x7B || (byte) i == 0x7D || (byte) i == 0x22 || (byte) i == Specialchars.SP || (byte) i == Specialchars.HT) { throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_HEADERFIELD + " (" + buffer.toString() + ")"); } } else { // if field-name set, must read field-value. if (((byte) i == Specialchars.SP || (byte) i == Specialchars.HT)) { buffer.append((char) Specialchars.SP); j++; continue; } // check for new line if ((byte) i == Specialchars.CR) { l = 1; } // check for new line end if ((byte) i == Specialchars.LF && l == 1) { j = 0; k++; l = 2; } if (k > 1) { // add fieldvalue = buffer.toString(); fieldvalue = fieldvalue.trim(); fieldname = fieldname.toLowerCase(); headerfields.put(fieldname, fieldvalue); // double CRLF, header ends here j = 0; k = 0; l = 0; fieldname = null; fieldvalue = null; return; } if (l > 0) { if (l == 2) { l = 0; } continue; } if (j == 0) { // add filed-name and field-value fieldvalue = buffer.toString(); fieldvalue = fieldvalue.trim(); fieldname = fieldname.toLowerCase(); headerfields.put(fieldname, fieldvalue); // reset buffer = new StringBuffer(); fieldname = null; fieldvalue = null; } } buffer.append((char) i); j++; k = 0; l = 0; } throw new IOException(HTTPRequestUtil.FAULT_UNEXPECTED_END + " (" + buffer.toString() + ")"); } /** * Reads the HTTP chunk header from stream. * * @param in Stream from which to read the header. * @return a <code>HTTPChunkHeader</code>. * @throws IOException * @throws ProtocolException */ public static HTTPChunkHeader readChunkHeader(InputStream in) throws IOException, ProtocolException { int chunksize = 0; HashMap chunkextensions = null; HashMap chunktrailer = null; int chunkext = 0; /* * Reads the HTTP chunk size if in chunk mode. (RFC 2616, 3.6.1) */ int i; StringBuffer buffer = new StringBuffer(); while (((i = in.read()) != -1)) { if (((byte) i >= 0x30 && (byte) i <= 0x39) || ((byte) i >= 0x41 && (byte) i <= 0x46) || ((byte) i >= 0x61 && (byte) i <= 0x66)) { buffer.append((char) i); continue; } if ((byte) i == Specialchars.SCOL) { try { int n = Integer.parseInt(buffer.toString(), 16); chunkext = 3; chunksize = n; break; } catch (NumberFormatException e) { throw new IOException(HTTPRequestUtil.FAULT_MALFORMED_CHUNK + " (" + buffer.toString() + ")"); } } if ((byte) i == Specialchars.CR) { chunkext = 1; continue; } if ((byte) i == Specialchars.LF && chunkext == 1) { try { int n = Integer.parseInt(buffer.toString(), 16); chunkext = 2; chunksize = n; break; } catch (NumberFormatException e) { throw new IOException(HTTPRequestUtil.FAULT_MALFORMED_CHUNK + " (" + buffer.toString() + ")"); } } } if (i == -1) { throw new IOException(HTTPRequestUtil.FAULT_UNEXPECTED_END + " (" + buffer.toString() + ")"); } chunkextensions = new HashMap(); if (chunkext == 3) { HTTPUtil.readChunkExtensions(in, chunkextensions); } if (chunksize == 0) { chunktrailer = new HashMap(); // check for trailer readHeaderFields(in, chunktrailer); } if (chunkextensions.size() == 0) { chunkextensions = null; } if (chunktrailer != null && chunktrailer.size() == 0) { chunktrailer = null; } return new HTTPChunkHeader(chunksize, chunkextensions, chunktrailer); } /** * Reads the chunk extension from stream. (RFC 2616, 3.6.1) * * @param in the stream to read from. * @param chunkextensions <code>Map</code> to store the fields in. */ public static void readChunkExtensions(InputStream in, HashMap chunkextensions) throws IOException, ProtocolException { int i; String chunkextname = null; String chunkextvalue = null; int j = 0; StringBuffer buffer = new StringBuffer(); while (((i = in.read()) != -1)) { if (chunkextname == null) { if ((byte) i == Specialchars.EQ) { chunkextname = buffer.toString().toLowerCase(); buffer = new StringBuffer(); continue; } // no CTL (ascii 0-31) allowed for chunk-ext-name if ((byte) i >= 0x00 && (byte) i <= 0x1F) { // throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_CHUNK + " (" + buffer.toString() + ")"); } // no separators allowed for token (see RFC2616 2.2) if ((byte) i == 0x28 || (byte) i == 0x29 || (byte) i == 0x3C || (byte) i == 0x3D || (byte) i == 0x3E || (byte) i == 0x40 || (byte) i == 0x2C || (byte) i == 0x3F || (byte) i == 0x3B || (byte) i == 0x2F || (byte) i == 0x5C || (byte) i == 0x5B || (byte) i == 0x5D || (byte) i == 0x7B || (byte) i == 0x7D || (byte) i == 0x22 || (byte) i == Specialchars.SP || (byte) i == Specialchars.HT) { throw new ProtocolException(HTTPRequestUtil.FAULT_MALFORMED_CHUNK + " (" + buffer.toString() + ")"); } // check for equal and create chunk-ext-name } else { if ((byte) i == Specialchars.CR) { j = 1; continue; } // check for new line end if ((byte) i == Specialchars.LF && j == 1) { j = 0; chunkextvalue = buffer.toString(); chunkextname = chunkextname.trim(); chunkextname = chunkextname.toLowerCase(); chunkextensions.put(chunkextname, chunkextvalue); return; } if ((byte) i == Specialchars.SCOL) { // add filed-name and field-value chunkextvalue = buffer.toString(); chunkextname = chunkextname.trim(); chunkextname = chunkextname.toLowerCase(); chunkextensions.put(chunkextname, chunkextvalue); // reset buffer = new StringBuffer(); chunkextname = null; continue; } } buffer.append((char) i); } throw new IOException(HTTPRequestUtil.FAULT_UNEXPECTED_END + " (" + buffer.toString() + ")"); } public static byte[] camelCase(String s) { byte[] b = s.getBytes(); boolean camel = true; for (int i = 0; i < b.length; i++) { if (b[i] >= 97 && b[i] <= 122 && camel) { b[i] = (byte) (b[i] - 32); camel = false; } if (b[i] == 32 && !camel) { camel = true; } if (b[i] == 45 && !camel) { camel = true; } } return b; } }