From 87191d8259963c34643aab5c5db041920adf8ac8 Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Tue, 21 Dec 2010 13:01:24 +0100 Subject: [PATCH 1/2] cleaning house --- src/de/ioexception/www/Http.java | 71 --------- src/de/ioexception/www/http/HttpMessage.java | 34 ----- src/de/ioexception/www/http/HttpMethod.java | 44 ------ src/de/ioexception/www/http/HttpRequest.java | 28 ---- src/de/ioexception/www/http/HttpResponse.java | 21 --- .../ioexception/www/http/HttpStatusCode.java | 109 ------------- src/de/ioexception/www/http/HttpVersion.java | 73 --------- .../www/http/impl/BasicHttpMessage.java | 52 ------- .../www/http/impl/BasicHttpRequest.java | 42 ----- .../www/http/impl/BasicHttpResponse.java | 22 --- src/de/ioexception/www/server/HttpServer.java | 43 ------ src/de/ioexception/www/server/HttpWorker.java | 143 ------------------ 12 files changed, 682 deletions(-) delete mode 100644 src/de/ioexception/www/Http.java delete mode 100644 src/de/ioexception/www/http/HttpMessage.java delete mode 100644 src/de/ioexception/www/http/HttpMethod.java delete mode 100644 src/de/ioexception/www/http/HttpRequest.java delete mode 100644 src/de/ioexception/www/http/HttpResponse.java delete mode 100644 src/de/ioexception/www/http/HttpStatusCode.java delete mode 100644 src/de/ioexception/www/http/HttpVersion.java delete mode 100644 src/de/ioexception/www/http/impl/BasicHttpMessage.java delete mode 100644 src/de/ioexception/www/http/impl/BasicHttpRequest.java delete mode 100644 src/de/ioexception/www/http/impl/BasicHttpResponse.java delete mode 100644 src/de/ioexception/www/server/HttpServer.java delete mode 100644 src/de/ioexception/www/server/HttpWorker.java diff --git a/src/de/ioexception/www/Http.java b/src/de/ioexception/www/Http.java deleted file mode 100644 index 51ea5bc..0000000 --- a/src/de/ioexception/www/Http.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.ioexception.www; - -/** - * Class for general HTTP-related constants. - * - * @author Benjamin Erb - * - */ -public final class Http -{ - public static final String CRLF = "\r\n"; - public static final String HTTP = "HTTP"; - public static final String PROTOCOL_DELIMITER = "/"; - - public static final String ACCEPT_RANGES = "Accept-Ranges"; - public static final String AGE = "Age"; - public static final String ALLOW = "Allow"; - public static final String CACHE_CONTROL = "Cache-Control"; - public static final String CONTENT_ENCODING = "Content-Encoding"; - public static final String CONTENT_LANGUAGE = "Content-Language"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_LOCATION = "Content-Location"; - public static final String CONTENT_DISPOSITION = "Content-Disposition"; - public static final String CONTENT_MD5 = "Content-MD5"; - public static final String CONTENT_RANGE = "Content-Range"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String DATE = "Date"; - public static final String ETAG = "ETag"; - public static final String EXPIRES = "Expires"; - public static final String LAST_MODIFIED = "Last-Modified"; - public static final String LOCATION = "Location"; - public static final String PRAGMA = "Pragma"; - public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; - public static final String REFRESH = "Refresh"; - public static final String RETRY_AFTER = "Retry-After"; - public static final String SERVER = "Server"; - public static final String SET_COOKIE = "Set-Cookie"; - public static final String TRAILER = "Trailer"; - public static final String TRANSFER_ENCODING = "Transfer-Encoding"; - public static final String VARY = "Vary"; - public static final String VIA = "Via"; - public static final String WARNING = "Warning"; - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; - public static final String ACCEPT = "Accept"; - public static final String ACCEPT_CHARSET = "Accept-Charset"; - public static final String ACCEPT_ENCODING = "Accept-Encoding"; - public static final String ACCEPT_LANGUAGE = "Accept-Language"; - public static final String AUTHORIZATION = "Authorization"; - public static final String CONNECTION = "Connection"; - public static final String COOKIE = "Cookie"; - public static final String EXPECT = "Expect"; - public static final String FROM = "From"; - public static final String HOST = "Host"; - public static final String IF_MATCH = "If-Match"; - public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; - public static final String IF_NONE_MATCH = "If-None-Match"; - public static final String IF_RANGE = "If-Range"; - public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; - public static final String MAX_FORWARDS = "Max-Forwards"; - public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; - public static final String RANGE = "Range"; - public static final String REFERER = "Referer"; - public static final String TE = "TE"; - public static final String UPGRADE = "Upgrade"; - public static final String USER_AGENT = "User-Agent"; - - private Http() - { - // no instances... - } -} diff --git a/src/de/ioexception/www/http/HttpMessage.java b/src/de/ioexception/www/http/HttpMessage.java deleted file mode 100644 index c521e30..0000000 --- a/src/de/ioexception/www/http/HttpMessage.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.ioexception.www.http; - -import java.util.Map; - -/** - * An interface for basic HTTP message operations. - * - * @author Benjamin Erb - * - */ -public interface HttpMessage -{ - /** - * Returns the used protocol version of this message. - * - * @return - */ - HttpVersion getHttpVersion(); - - /** - * Returns a list of key-value header fields. - * - * @return - */ - Map getHeaders(); - - /** - * Returns the entity as a byte array or null, if no entity is available. - * - * @return - */ - byte[] getEntity(); - -} diff --git a/src/de/ioexception/www/http/HttpMethod.java b/src/de/ioexception/www/http/HttpMethod.java deleted file mode 100644 index 1df978d..0000000 --- a/src/de/ioexception/www/http/HttpMethod.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.ioexception.www.http; - -/** - * An enum of available HTTP methods. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1 - * - * @author Benjamin Erb - * - */ -public enum HttpMethod -{ - HEAD, - GET, - POST, - PUT, - DELETE; - - @Override - public String toString() - { - return this.name(); - } - - /** - * Extracts the HTTP method from the header line. - * - * @param headerLine HTTP request header line - * @return the HTTP method - * @throws IllegalArgumentException - */ - public static HttpMethod extractMethod(String headerLine) throws IllegalArgumentException - { - String method = headerLine.split(" ")[0]; - if (method != null) - { - return HttpMethod.valueOf(method); - } - else - { - throw new IllegalArgumentException(); - } - } -} diff --git a/src/de/ioexception/www/http/HttpRequest.java b/src/de/ioexception/www/http/HttpRequest.java deleted file mode 100644 index 2e49c50..0000000 --- a/src/de/ioexception/www/http/HttpRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.ioexception.www.http; - -/** - * An interface for HTTP requests. - * - * @author Benjamin Erb - * - */ -public interface HttpRequest extends HttpMessage -{ - /** - * Returns the HTTP method of this request. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1.1 - * - * @return method - */ - HttpMethod getHttpMethod(); - - /** - * Returns the request URI of this request. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1.2 - * - * @return the request URI as string - */ - String getRequestUri(); -} diff --git a/src/de/ioexception/www/http/HttpResponse.java b/src/de/ioexception/www/http/HttpResponse.java deleted file mode 100644 index 05ac7c3..0000000 --- a/src/de/ioexception/www/http/HttpResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.ioexception.www.http; - -/** - * An interface for HTTP responses. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-6 - * - * @author Benjamin Erb - * - */ -public interface HttpResponse extends HttpMessage -{ - /** - * Returns the HTTP Status Code of this response. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-6.1.1 - * - * @return - */ - HttpStatusCode getStatusCode(); -} diff --git a/src/de/ioexception/www/http/HttpStatusCode.java b/src/de/ioexception/www/http/HttpStatusCode.java deleted file mode 100644 index a1759a2..0000000 --- a/src/de/ioexception/www/http/HttpStatusCode.java +++ /dev/null @@ -1,109 +0,0 @@ -package de.ioexception.www.http; - -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; - -/** - * An enum of available HTTP status codes. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-6.1.1 - * - * @author Benjamin Erb - * - */ -public enum HttpStatusCode -{ - CONTINUE(100, "Continue"), - SWITCHING_PROTOCOLS(101, "Switching Protocols"), - OK(200, "OK"), - CREATED(201, "Created"), - ACCEPTED(202, "Accepted"), - NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), - NO_CONTENT(204, "No Content"), - RESET_CONTENT(205, "Reset Content"), - PARTIAL_CONTENT(206, "Partial Content"), - MULTIPLE_CHOICES(300, "Multiple Choices"), - MOVED_PERMANENTLY(301, "Moved Permanently"), - FOUND(302, "Found"), - SEE_OTHER(303, "See Other"), - NOT_MODIFIED(304, "Not Modified"), - USE_PROXY(305, "Use Proxy"), - SWITCH_PROXY(306, "Switch Proxy"), - TEMPORARY_REDIRECT(307, "Temporary Redirect "), - BAD_REQUEST(400, "Bad Request"), - UNAUTHORIZED(401, "Unauthorized"), - PAYMENT_REQUIRED(402, "Payment Required"), - FORBIDDEN(403, "Forbidden"), - NOT_FOUND(404, "Not Found"), - METHOD_NOT_ALLOWED(405, "Method Not Allowed"), - NOT_ACCEPTABLE(406, "Not Acceptable"), - PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), - REQUEST_TIMEOUT(408, "Request Timeout"), - CONFLICT(409, "Conflict"), - GONE(410, "Gone"), - LENGTH_REQUIRED(411, "Length Required"), - PRECONDITION_FAILED(412, "Precondition Failed"), - REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), - REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"), - UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), - REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), - EXPECTATION_FAILED(417, "Expectation Failed"), - INTERNAL_SERVER_ERROR(500, "Internal Server Error"), - NOT_IMPLEMENTED(501, "Not Implemented"), - BAD_GATEWAY(502, "Bad Gateway"), - SERVICE_UNAVAILABLE(503, "Service Unavailable"), - GATEWAY_TIMEOUT(504, "Gateway Timeout"), - HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"); - - private final int code; - private final String reasonPhrase; - - private static final Map codeLookupTable = new HashMap(); - - static - { - for (HttpStatusCode s : EnumSet.allOf(HttpStatusCode.class)) - { - codeLookupTable.put(s.getCode(), s); - } - } - - private HttpStatusCode(int code, String reasonPhrase) - { - this.code = code; - this.reasonPhrase = reasonPhrase; - } - - /** - * Returns the numerical code. - * - * @return Code - */ - public int getCode() - { - return code; - } - - /** - * Returns the verbatim code. - * - * @return reasons phrase - */ - public String getReasonPhrase() - { - return reasonPhrase; - } - - /** - * Gets the {@link HttpStatusCode} type by the code number. - * - * @param code - * numerical code representation (i.e. 200) - * @return assosciated status code - */ - public static HttpStatusCode getStatusCode(int code) - { - return codeLookupTable.get(code); - } -} diff --git a/src/de/ioexception/www/http/HttpVersion.java b/src/de/ioexception/www/http/HttpVersion.java deleted file mode 100644 index 8f70636..0000000 --- a/src/de/ioexception/www/http/HttpVersion.java +++ /dev/null @@ -1,73 +0,0 @@ -package de.ioexception.www.http; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.ioexception.www.Http; - -/** - * An enum of available HTTP versions. - * - * @see also http://tools.ietf.org/html/rfc2616.html#section-3.1 - * - * @author Benjamin Erb - * - */ -public enum HttpVersion -{ - /** - * HTTP/1.0 - */ - VERSION_1_0(1, 0), - - /** - * HTTP/1.1 - */ - VERSION_1_1(1, 1); - - private final int major; - private final int minor; - - private HttpVersion(int major, int minor) - { - this.major = major; - this.minor = minor; - } - - @Override - public String toString() - { - return Http.HTTP + Http.PROTOCOL_DELIMITER + major + "." + minor; - } - - /** - * Extracts the HTTP version from the header line. - * - * @param headerLine HTTP request header line - * @return - * @throws IllegalArgumentException - */ - public static HttpVersion extractVersion(String headerLine) throws IllegalArgumentException - { - Matcher m = Pattern.compile(Http.HTTP + "/(\\d+)\\.(\\d+)").matcher(headerLine); - if (m.find()) - { - if ((Integer.parseInt(m.group(1)) == 1) && (Integer.parseInt(m.group(2)) == 1)) - { - return VERSION_1_1; - } - else if ((Integer.parseInt(m.group(1)) == 1) && (Integer.parseInt(m.group(2)) == 0)) - { - return VERSION_1_0; - } - else - { - throw new IllegalArgumentException("Unknown HTTP Version"); - } - } - else - { - throw new IllegalArgumentException("Unknown HTTP Version"); - } - } -} diff --git a/src/de/ioexception/www/http/impl/BasicHttpMessage.java b/src/de/ioexception/www/http/impl/BasicHttpMessage.java deleted file mode 100644 index 528c129..0000000 --- a/src/de/ioexception/www/http/impl/BasicHttpMessage.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.ioexception.www.http.impl; - -import java.util.Map; - -import de.ioexception.www.http.HttpMessage; -import de.ioexception.www.http.HttpVersion; - -public class BasicHttpMessage implements HttpMessage -{ - private HttpVersion version; - private byte[] entity = null; - private Map headers; - - @Override - public HttpVersion getHttpVersion() - { - return version; - } - - @Override - public Map getHeaders() - { - return headers; - } - - @Override - public byte[] getEntity() - { - return entity; - } - - public HttpVersion getVersion() - { - return version; - } - - public void setVersion(HttpVersion version) - { - this.version = version; - } - - public void setEntity(byte[] entity) - { - this.entity = entity; - } - - public void setHeaders(Map headers) - { - this.headers = headers; - } - -} diff --git a/src/de/ioexception/www/http/impl/BasicHttpRequest.java b/src/de/ioexception/www/http/impl/BasicHttpRequest.java deleted file mode 100644 index 869b9fb..0000000 --- a/src/de/ioexception/www/http/impl/BasicHttpRequest.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.ioexception.www.http.impl; - -import de.ioexception.www.http.HttpMethod; -import de.ioexception.www.http.HttpRequest; - -public class BasicHttpRequest extends BasicHttpMessage implements HttpRequest -{ - HttpMethod method; - String requestUri; - - @Override - public HttpMethod getHttpMethod() - { - return method; - } - - @Override - public String getRequestUri() - { - return requestUri; - } - - public HttpMethod getMethod() - { - return method; - } - - public void setMethod(HttpMethod method) - { - this.method = method; - } - - public void setRequestUri(String requestUri) - { - this.requestUri = requestUri; - } - - - - - -} diff --git a/src/de/ioexception/www/http/impl/BasicHttpResponse.java b/src/de/ioexception/www/http/impl/BasicHttpResponse.java deleted file mode 100644 index 00526d0..0000000 --- a/src/de/ioexception/www/http/impl/BasicHttpResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.ioexception.www.http.impl; - -import de.ioexception.www.http.HttpResponse; -import de.ioexception.www.http.HttpStatusCode; - -public class BasicHttpResponse extends BasicHttpMessage implements HttpResponse -{ - HttpStatusCode statusCode; - - @Override - public HttpStatusCode getStatusCode() - { - return statusCode; - } - - public void setStatusCode(HttpStatusCode statusCode) - { - this.statusCode = statusCode; - } - - -} diff --git a/src/de/ioexception/www/server/HttpServer.java b/src/de/ioexception/www/server/HttpServer.java deleted file mode 100644 index b6913e1..0000000 --- a/src/de/ioexception/www/server/HttpServer.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.ioexception.www.server; - -import java.net.Socket; - -import de.ioexception.www.server.router.RequestRouter; - -/** - * A basic HTTP server interface. - * - * @author Benjamin Erb - * - */ -public interface HttpServer -{ - /** - * Starts the configured webserver. - */ - public void start(); - - /** - * Shuts down the webserver. - */ - public void stop(); - - /** - * Dispatches an incoming socket connection to an appropriate handler. - * - * @param socket - */ - public void dispatchRequest(Socket socket); - - /** - * Returns the signature of the webserver. - * - * @return - */ - public String getServerSignature(); - - public RequestRouter getRequestRouter(); - - public void setRequestRouter(RequestRouter requestRouter); - -} diff --git a/src/de/ioexception/www/server/HttpWorker.java b/src/de/ioexception/www/server/HttpWorker.java deleted file mode 100644 index bbc7f2c..0000000 --- a/src/de/ioexception/www/server/HttpWorker.java +++ /dev/null @@ -1,143 +0,0 @@ -package de.ioexception.www.server; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.util.concurrent.Callable; - -import de.ioexception.www.http.HttpRequest; -import de.ioexception.www.http.HttpResponse; - -/** - * An abstract worker for processing incoming HTTP requests. - * - * Note that {@link Callable} is used, because it is able to bubble up - * {@link Exception}s, as opposed to {@link Runnable}. - * - * @author Benjamin Erb - * - */ -public abstract class HttpWorker implements Callable -{ - protected final Socket socket; - protected final HttpServer server; - - /** - * Creates a new worker that handles the incoming request. - * - * @param socket - * The socket this request is sent over. - * @param server - * A reference to the core server instance. - */ - public HttpWorker(Socket socket, HttpServer server) - { - this.socket = socket; - this.server = server; - } - - @Override - public Void call() throws Exception - { - // Parse request from InputStream - HttpRequest request = parseRequest(socket.getInputStream()); - - // Create appropriate response - HttpResponse response = handleRequest(request); - - // Send response and close connection, if necessary - if (keepAlive(request, response)) - { - sendResponse(response, socket.getOutputStream()); - server.dispatchRequest(socket); - } - else - { - response.getHeaders().put("Connection", "close"); - sendResponse(response, socket.getOutputStream()); - socket.close(); - } - - // We do not return anything here. - return null; - } - - /** - * A helper method that reads an InputStream until it reads a CRLF (\r\n\). - * Everything in front of the linefeed occured is returned as String. - * - * @param inputStream - * The stream to read from. - * @return The character sequence in front of the linefeed. - * @throws IOException - */ - protected String readLine(InputStream inputStream) throws IOException - { - StringBuffer result = new StringBuffer(); - boolean crRead = false; - int n; - while ((n = inputStream.read()) != -1) - { - if (n == '\r') - { - crRead = true; - continue; - } - else if (n == '\n' && crRead) - { - return result.toString(); - } - else - { - result.append((char) n); - } - } - return result.toString(); - } - - /** - * Parses an incoming request. Reads the {@link InputStream} and creates an - * corresponding {@link HttpRequest} object, which will be returned. - * - * @param inputStream - * The stream to read from. - * @return - * @throws IOException - */ - abstract protected HttpRequest parseRequest(InputStream inputStream) throws IOException; - - /** - * Creates an appropriate {@link HttpResponse} to the given - * {@link HttpRequest}. Note however, that this method is not yet sending - * the response. - * - * @param request - * The {@link HttpRequest} that must be handled. - * @return - */ - abstract protected HttpResponse handleRequest(HttpRequest request); - - /** - * Sends a given {@link HttpResponse} over the given {@link OutputStream}. - * - * @param response - * @param outputStream - * @throws IOException - */ - abstract protected void sendResponse(HttpResponse response, OutputStream outputStream) throws IOException; - - /** - * Determines whether a connection should be kept alive or not on - * server-side. This decision is made based upon the given ( - * {@link HttpRequest}, {@link HttpResponse}) couple, respectively their - * header values. - * - * @param request - * @param response - * @return true, if the server should keep open the connection, otherwise - * false. - */ - abstract protected boolean keepAlive(HttpRequest request, HttpResponse response); - -} From cd9feaccc0caa558dcdc012d01663ff680788b72 Mon Sep 17 00:00:00 2001 From: Benjamin Erb Date: Tue, 21 Dec 2010 13:08:06 +0100 Subject: [PATCH 2/2] added skeleton --- src/de/ioexception/www/Http.java | 71 +++ src/de/ioexception/www/http/HttpMessage.java | 34 ++ src/de/ioexception/www/http/HttpMethod.java | 44 ++ src/de/ioexception/www/http/HttpRequest.java | 28 + src/de/ioexception/www/http/HttpResponse.java | 21 + .../ioexception/www/http/HttpStatusCode.java | 109 ++++ src/de/ioexception/www/http/HttpVersion.java | 73 +++ .../www/http/impl/BasicHttpMessage.java | 55 ++ .../www/http/impl/BasicHttpRequest.java | 42 ++ .../www/http/impl/BasicHttpResponse.java | 22 + src/de/ioexception/www/server/HttpServer.java | 37 ++ src/de/ioexception/www/server/HttpWorker.java | 143 +++++ src/de/ioexception/www/server/Main.java | 12 + .../www/server/impl/BasicHttpServer.java | 126 ++++ .../www/server/impl/BasicHttpWorker.java | 203 +++++++ src/util/Base64.java | 575 ++++++++++++++++++ webroot/.gitignore | 5 + 17 files changed, 1600 insertions(+) create mode 100644 src/de/ioexception/www/Http.java create mode 100644 src/de/ioexception/www/http/HttpMessage.java create mode 100644 src/de/ioexception/www/http/HttpMethod.java create mode 100644 src/de/ioexception/www/http/HttpRequest.java create mode 100644 src/de/ioexception/www/http/HttpResponse.java create mode 100644 src/de/ioexception/www/http/HttpStatusCode.java create mode 100644 src/de/ioexception/www/http/HttpVersion.java create mode 100644 src/de/ioexception/www/http/impl/BasicHttpMessage.java create mode 100644 src/de/ioexception/www/http/impl/BasicHttpRequest.java create mode 100644 src/de/ioexception/www/http/impl/BasicHttpResponse.java create mode 100644 src/de/ioexception/www/server/HttpServer.java create mode 100644 src/de/ioexception/www/server/HttpWorker.java create mode 100644 src/de/ioexception/www/server/Main.java create mode 100644 src/de/ioexception/www/server/impl/BasicHttpServer.java create mode 100644 src/de/ioexception/www/server/impl/BasicHttpWorker.java create mode 100644 src/util/Base64.java create mode 100644 webroot/.gitignore diff --git a/src/de/ioexception/www/Http.java b/src/de/ioexception/www/Http.java new file mode 100644 index 0000000..51ea5bc --- /dev/null +++ b/src/de/ioexception/www/Http.java @@ -0,0 +1,71 @@ +package de.ioexception.www; + +/** + * Class for general HTTP-related constants. + * + * @author Benjamin Erb + * + */ +public final class Http +{ + public static final String CRLF = "\r\n"; + public static final String HTTP = "HTTP"; + public static final String PROTOCOL_DELIMITER = "/"; + + public static final String ACCEPT_RANGES = "Accept-Ranges"; + public static final String AGE = "Age"; + public static final String ALLOW = "Allow"; + public static final String CACHE_CONTROL = "Cache-Control"; + public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final String CONTENT_LANGUAGE = "Content-Language"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_LOCATION = "Content-Location"; + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + public static final String CONTENT_MD5 = "Content-MD5"; + public static final String CONTENT_RANGE = "Content-Range"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String DATE = "Date"; + public static final String ETAG = "ETag"; + public static final String EXPIRES = "Expires"; + public static final String LAST_MODIFIED = "Last-Modified"; + public static final String LOCATION = "Location"; + public static final String PRAGMA = "Pragma"; + public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + public static final String REFRESH = "Refresh"; + public static final String RETRY_AFTER = "Retry-After"; + public static final String SERVER = "Server"; + public static final String SET_COOKIE = "Set-Cookie"; + public static final String TRAILER = "Trailer"; + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + public static final String VARY = "Vary"; + public static final String VIA = "Via"; + public static final String WARNING = "Warning"; + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final String ACCEPT = "Accept"; + public static final String ACCEPT_CHARSET = "Accept-Charset"; + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + public static final String AUTHORIZATION = "Authorization"; + public static final String CONNECTION = "Connection"; + public static final String COOKIE = "Cookie"; + public static final String EXPECT = "Expect"; + public static final String FROM = "From"; + public static final String HOST = "Host"; + public static final String IF_MATCH = "If-Match"; + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + public static final String IF_NONE_MATCH = "If-None-Match"; + public static final String IF_RANGE = "If-Range"; + public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + public static final String MAX_FORWARDS = "Max-Forwards"; + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + public static final String RANGE = "Range"; + public static final String REFERER = "Referer"; + public static final String TE = "TE"; + public static final String UPGRADE = "Upgrade"; + public static final String USER_AGENT = "User-Agent"; + + private Http() + { + // no instances... + } +} diff --git a/src/de/ioexception/www/http/HttpMessage.java b/src/de/ioexception/www/http/HttpMessage.java new file mode 100644 index 0000000..c521e30 --- /dev/null +++ b/src/de/ioexception/www/http/HttpMessage.java @@ -0,0 +1,34 @@ +package de.ioexception.www.http; + +import java.util.Map; + +/** + * An interface for basic HTTP message operations. + * + * @author Benjamin Erb + * + */ +public interface HttpMessage +{ + /** + * Returns the used protocol version of this message. + * + * @return + */ + HttpVersion getHttpVersion(); + + /** + * Returns a list of key-value header fields. + * + * @return + */ + Map getHeaders(); + + /** + * Returns the entity as a byte array or null, if no entity is available. + * + * @return + */ + byte[] getEntity(); + +} diff --git a/src/de/ioexception/www/http/HttpMethod.java b/src/de/ioexception/www/http/HttpMethod.java new file mode 100644 index 0000000..1df978d --- /dev/null +++ b/src/de/ioexception/www/http/HttpMethod.java @@ -0,0 +1,44 @@ +package de.ioexception.www.http; + +/** + * An enum of available HTTP methods. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1 + * + * @author Benjamin Erb + * + */ +public enum HttpMethod +{ + HEAD, + GET, + POST, + PUT, + DELETE; + + @Override + public String toString() + { + return this.name(); + } + + /** + * Extracts the HTTP method from the header line. + * + * @param headerLine HTTP request header line + * @return the HTTP method + * @throws IllegalArgumentException + */ + public static HttpMethod extractMethod(String headerLine) throws IllegalArgumentException + { + String method = headerLine.split(" ")[0]; + if (method != null) + { + return HttpMethod.valueOf(method); + } + else + { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/de/ioexception/www/http/HttpRequest.java b/src/de/ioexception/www/http/HttpRequest.java new file mode 100644 index 0000000..2e49c50 --- /dev/null +++ b/src/de/ioexception/www/http/HttpRequest.java @@ -0,0 +1,28 @@ +package de.ioexception.www.http; + +/** + * An interface for HTTP requests. + * + * @author Benjamin Erb + * + */ +public interface HttpRequest extends HttpMessage +{ + /** + * Returns the HTTP method of this request. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1.1 + * + * @return method + */ + HttpMethod getHttpMethod(); + + /** + * Returns the request URI of this request. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1.2 + * + * @return the request URI as string + */ + String getRequestUri(); +} diff --git a/src/de/ioexception/www/http/HttpResponse.java b/src/de/ioexception/www/http/HttpResponse.java new file mode 100644 index 0000000..05ac7c3 --- /dev/null +++ b/src/de/ioexception/www/http/HttpResponse.java @@ -0,0 +1,21 @@ +package de.ioexception.www.http; + +/** + * An interface for HTTP responses. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-6 + * + * @author Benjamin Erb + * + */ +public interface HttpResponse extends HttpMessage +{ + /** + * Returns the HTTP Status Code of this response. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-6.1.1 + * + * @return + */ + HttpStatusCode getStatusCode(); +} diff --git a/src/de/ioexception/www/http/HttpStatusCode.java b/src/de/ioexception/www/http/HttpStatusCode.java new file mode 100644 index 0000000..a1759a2 --- /dev/null +++ b/src/de/ioexception/www/http/HttpStatusCode.java @@ -0,0 +1,109 @@ +package de.ioexception.www.http; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +/** + * An enum of available HTTP status codes. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-6.1.1 + * + * @author Benjamin Erb + * + */ +public enum HttpStatusCode +{ + CONTINUE(100, "Continue"), + SWITCHING_PROTOCOLS(101, "Switching Protocols"), + OK(200, "OK"), + CREATED(201, "Created"), + ACCEPTED(202, "Accepted"), + NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), + NO_CONTENT(204, "No Content"), + RESET_CONTENT(205, "Reset Content"), + PARTIAL_CONTENT(206, "Partial Content"), + MULTIPLE_CHOICES(300, "Multiple Choices"), + MOVED_PERMANENTLY(301, "Moved Permanently"), + FOUND(302, "Found"), + SEE_OTHER(303, "See Other"), + NOT_MODIFIED(304, "Not Modified"), + USE_PROXY(305, "Use Proxy"), + SWITCH_PROXY(306, "Switch Proxy"), + TEMPORARY_REDIRECT(307, "Temporary Redirect "), + BAD_REQUEST(400, "Bad Request"), + UNAUTHORIZED(401, "Unauthorized"), + PAYMENT_REQUIRED(402, "Payment Required"), + FORBIDDEN(403, "Forbidden"), + NOT_FOUND(404, "Not Found"), + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + NOT_ACCEPTABLE(406, "Not Acceptable"), + PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), + REQUEST_TIMEOUT(408, "Request Timeout"), + CONFLICT(409, "Conflict"), + GONE(410, "Gone"), + LENGTH_REQUIRED(411, "Length Required"), + PRECONDITION_FAILED(412, "Precondition Failed"), + REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), + REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"), + UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), + REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), + EXPECTATION_FAILED(417, "Expectation Failed"), + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + NOT_IMPLEMENTED(501, "Not Implemented"), + BAD_GATEWAY(502, "Bad Gateway"), + SERVICE_UNAVAILABLE(503, "Service Unavailable"), + GATEWAY_TIMEOUT(504, "Gateway Timeout"), + HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"); + + private final int code; + private final String reasonPhrase; + + private static final Map codeLookupTable = new HashMap(); + + static + { + for (HttpStatusCode s : EnumSet.allOf(HttpStatusCode.class)) + { + codeLookupTable.put(s.getCode(), s); + } + } + + private HttpStatusCode(int code, String reasonPhrase) + { + this.code = code; + this.reasonPhrase = reasonPhrase; + } + + /** + * Returns the numerical code. + * + * @return Code + */ + public int getCode() + { + return code; + } + + /** + * Returns the verbatim code. + * + * @return reasons phrase + */ + public String getReasonPhrase() + { + return reasonPhrase; + } + + /** + * Gets the {@link HttpStatusCode} type by the code number. + * + * @param code + * numerical code representation (i.e. 200) + * @return assosciated status code + */ + public static HttpStatusCode getStatusCode(int code) + { + return codeLookupTable.get(code); + } +} diff --git a/src/de/ioexception/www/http/HttpVersion.java b/src/de/ioexception/www/http/HttpVersion.java new file mode 100644 index 0000000..8f70636 --- /dev/null +++ b/src/de/ioexception/www/http/HttpVersion.java @@ -0,0 +1,73 @@ +package de.ioexception.www.http; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.ioexception.www.Http; + +/** + * An enum of available HTTP versions. + * + * @see also http://tools.ietf.org/html/rfc2616.html#section-3.1 + * + * @author Benjamin Erb + * + */ +public enum HttpVersion +{ + /** + * HTTP/1.0 + */ + VERSION_1_0(1, 0), + + /** + * HTTP/1.1 + */ + VERSION_1_1(1, 1); + + private final int major; + private final int minor; + + private HttpVersion(int major, int minor) + { + this.major = major; + this.minor = minor; + } + + @Override + public String toString() + { + return Http.HTTP + Http.PROTOCOL_DELIMITER + major + "." + minor; + } + + /** + * Extracts the HTTP version from the header line. + * + * @param headerLine HTTP request header line + * @return + * @throws IllegalArgumentException + */ + public static HttpVersion extractVersion(String headerLine) throws IllegalArgumentException + { + Matcher m = Pattern.compile(Http.HTTP + "/(\\d+)\\.(\\d+)").matcher(headerLine); + if (m.find()) + { + if ((Integer.parseInt(m.group(1)) == 1) && (Integer.parseInt(m.group(2)) == 1)) + { + return VERSION_1_1; + } + else if ((Integer.parseInt(m.group(1)) == 1) && (Integer.parseInt(m.group(2)) == 0)) + { + return VERSION_1_0; + } + else + { + throw new IllegalArgumentException("Unknown HTTP Version"); + } + } + else + { + throw new IllegalArgumentException("Unknown HTTP Version"); + } + } +} diff --git a/src/de/ioexception/www/http/impl/BasicHttpMessage.java b/src/de/ioexception/www/http/impl/BasicHttpMessage.java new file mode 100644 index 0000000..7bb1b37 --- /dev/null +++ b/src/de/ioexception/www/http/impl/BasicHttpMessage.java @@ -0,0 +1,55 @@ +package de.ioexception.www.http.impl; + +import java.util.Map; + +import de.ioexception.www.http.HttpMessage; +import de.ioexception.www.http.HttpVersion; + +public class BasicHttpMessage implements HttpMessage +{ + private HttpVersion version; + private byte[] entity = null; + private Map headers; + + + @Override + public HttpVersion getHttpVersion() + { + return version; + } + + @Override + public Map getHeaders() + { + return headers; + } + + @Override + public byte[] getEntity() + { + return entity; + } + + public HttpVersion getVersion() + { + return version; + } + + public void setVersion(HttpVersion version) + { + this.version = version; + } + + public void setEntity(byte[] entity) + { + this.entity = entity; + } + + public void setHeaders(Map headers) + { + this.headers = headers; + } + + + +} diff --git a/src/de/ioexception/www/http/impl/BasicHttpRequest.java b/src/de/ioexception/www/http/impl/BasicHttpRequest.java new file mode 100644 index 0000000..869b9fb --- /dev/null +++ b/src/de/ioexception/www/http/impl/BasicHttpRequest.java @@ -0,0 +1,42 @@ +package de.ioexception.www.http.impl; + +import de.ioexception.www.http.HttpMethod; +import de.ioexception.www.http.HttpRequest; + +public class BasicHttpRequest extends BasicHttpMessage implements HttpRequest +{ + HttpMethod method; + String requestUri; + + @Override + public HttpMethod getHttpMethod() + { + return method; + } + + @Override + public String getRequestUri() + { + return requestUri; + } + + public HttpMethod getMethod() + { + return method; + } + + public void setMethod(HttpMethod method) + { + this.method = method; + } + + public void setRequestUri(String requestUri) + { + this.requestUri = requestUri; + } + + + + + +} diff --git a/src/de/ioexception/www/http/impl/BasicHttpResponse.java b/src/de/ioexception/www/http/impl/BasicHttpResponse.java new file mode 100644 index 0000000..00526d0 --- /dev/null +++ b/src/de/ioexception/www/http/impl/BasicHttpResponse.java @@ -0,0 +1,22 @@ +package de.ioexception.www.http.impl; + +import de.ioexception.www.http.HttpResponse; +import de.ioexception.www.http.HttpStatusCode; + +public class BasicHttpResponse extends BasicHttpMessage implements HttpResponse +{ + HttpStatusCode statusCode; + + @Override + public HttpStatusCode getStatusCode() + { + return statusCode; + } + + public void setStatusCode(HttpStatusCode statusCode) + { + this.statusCode = statusCode; + } + + +} diff --git a/src/de/ioexception/www/server/HttpServer.java b/src/de/ioexception/www/server/HttpServer.java new file mode 100644 index 0000000..55e1fda --- /dev/null +++ b/src/de/ioexception/www/server/HttpServer.java @@ -0,0 +1,37 @@ +package de.ioexception.www.server; + +import java.net.Socket; + +/** + * A basic HTTP server interface. + * + * @author Benjamin Erb + * + */ +public interface HttpServer +{ + /** + * Starts the configured webserver. + */ + public void start(); + + /** + * Shuts down the webserver. + */ + public void stop(); + + /** + * Dispatches an incoming socket connection to an appropriate handler. + * + * @param socket + */ + public void dispatchRequest(Socket socket); + + /** + * Returns the signature of the webserver. + * + * @return + */ + public String getServerSignature(); + +} diff --git a/src/de/ioexception/www/server/HttpWorker.java b/src/de/ioexception/www/server/HttpWorker.java new file mode 100644 index 0000000..bbc7f2c --- /dev/null +++ b/src/de/ioexception/www/server/HttpWorker.java @@ -0,0 +1,143 @@ +package de.ioexception.www.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.concurrent.Callable; + +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; + +/** + * An abstract worker for processing incoming HTTP requests. + * + * Note that {@link Callable} is used, because it is able to bubble up + * {@link Exception}s, as opposed to {@link Runnable}. + * + * @author Benjamin Erb + * + */ +public abstract class HttpWorker implements Callable +{ + protected final Socket socket; + protected final HttpServer server; + + /** + * Creates a new worker that handles the incoming request. + * + * @param socket + * The socket this request is sent over. + * @param server + * A reference to the core server instance. + */ + public HttpWorker(Socket socket, HttpServer server) + { + this.socket = socket; + this.server = server; + } + + @Override + public Void call() throws Exception + { + // Parse request from InputStream + HttpRequest request = parseRequest(socket.getInputStream()); + + // Create appropriate response + HttpResponse response = handleRequest(request); + + // Send response and close connection, if necessary + if (keepAlive(request, response)) + { + sendResponse(response, socket.getOutputStream()); + server.dispatchRequest(socket); + } + else + { + response.getHeaders().put("Connection", "close"); + sendResponse(response, socket.getOutputStream()); + socket.close(); + } + + // We do not return anything here. + return null; + } + + /** + * A helper method that reads an InputStream until it reads a CRLF (\r\n\). + * Everything in front of the linefeed occured is returned as String. + * + * @param inputStream + * The stream to read from. + * @return The character sequence in front of the linefeed. + * @throws IOException + */ + protected String readLine(InputStream inputStream) throws IOException + { + StringBuffer result = new StringBuffer(); + boolean crRead = false; + int n; + while ((n = inputStream.read()) != -1) + { + if (n == '\r') + { + crRead = true; + continue; + } + else if (n == '\n' && crRead) + { + return result.toString(); + } + else + { + result.append((char) n); + } + } + return result.toString(); + } + + /** + * Parses an incoming request. Reads the {@link InputStream} and creates an + * corresponding {@link HttpRequest} object, which will be returned. + * + * @param inputStream + * The stream to read from. + * @return + * @throws IOException + */ + abstract protected HttpRequest parseRequest(InputStream inputStream) throws IOException; + + /** + * Creates an appropriate {@link HttpResponse} to the given + * {@link HttpRequest}. Note however, that this method is not yet sending + * the response. + * + * @param request + * The {@link HttpRequest} that must be handled. + * @return + */ + abstract protected HttpResponse handleRequest(HttpRequest request); + + /** + * Sends a given {@link HttpResponse} over the given {@link OutputStream}. + * + * @param response + * @param outputStream + * @throws IOException + */ + abstract protected void sendResponse(HttpResponse response, OutputStream outputStream) throws IOException; + + /** + * Determines whether a connection should be kept alive or not on + * server-side. This decision is made based upon the given ( + * {@link HttpRequest}, {@link HttpResponse}) couple, respectively their + * header values. + * + * @param request + * @param response + * @return true, if the server should keep open the connection, otherwise + * false. + */ + abstract protected boolean keepAlive(HttpRequest request, HttpResponse response); + +} diff --git a/src/de/ioexception/www/server/Main.java b/src/de/ioexception/www/server/Main.java new file mode 100644 index 0000000..3a76ca1 --- /dev/null +++ b/src/de/ioexception/www/server/Main.java @@ -0,0 +1,12 @@ +package de.ioexception.www.server; + +import de.ioexception.www.server.impl.BasicHttpServer; + +public class Main +{ + public static void main(String[] args) + { + HttpServer server = new BasicHttpServer(8080); + server.start(); + } +} diff --git a/src/de/ioexception/www/server/impl/BasicHttpServer.java b/src/de/ioexception/www/server/impl/BasicHttpServer.java new file mode 100644 index 0000000..ddca550 --- /dev/null +++ b/src/de/ioexception/www/server/impl/BasicHttpServer.java @@ -0,0 +1,126 @@ +package de.ioexception.www.server.impl; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import de.ioexception.www.server.HttpServer; + +/** + * A simple HTTP server implementation. + * + * @author Benjamin Erb + * + */ +public class BasicHttpServer implements HttpServer +{ + public static final String SERVER_NAME = "MyServer"; + public static final String SERVER_VERSION = "0.1"; + public static final int DEFAULT_PORT = 8080; + public static final String SERVER_SIGNATURE = SERVER_NAME + "/" + SERVER_VERSION; + + private volatile boolean running = false; + + private final ExecutorService workerPool; + private final ExecutorService dispatcherService; + private final ServerSocket serverSocket; + + + /** + * Creates a new HTTP server. + */ + public BasicHttpServer() + { + this(BasicHttpServer.DEFAULT_PORT); + } + + /** + * Creates a new HTTP server bound to the given port. + * + * @param port + * listening port + * @throws IOException + */ + public BasicHttpServer(int port) + { + try + { + serverSocket = new ServerSocket(port); + workerPool = Executors.newFixedThreadPool(16); + dispatcherService = Executors.newSingleThreadExecutor(); + } + catch (IOException e) + { + throw new RuntimeException("Error while starting server", e); + } + + } + + @Override + public void dispatchRequest(Socket socket) + { + workerPool.submit(new BasicHttpWorker(socket, this)); + } + + @Override + public void start() + { + running = true; + // Initiate the main server loop accepting incoming connections. + dispatcherService.submit(new Runnable() + { + @Override + public void run() + { + while (running) + { + try + { + Socket socket = serverSocket.accept(); + dispatchRequest(socket); + } + catch (SocketException e) + { + // ignore due to close signaling + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + }); + System.err.println("Webserver started on port " + serverSocket.getLocalPort() + "..."); + + } + + @Override + public void stop() + { + try + { + running = false; + dispatcherService.shutdown(); + workerPool.shutdown(); + serverSocket.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + System.err.println("Webserver stopped."); + } + } + + @Override + public String getServerSignature() + { + return BasicHttpServer.SERVER_SIGNATURE; + } + +} diff --git a/src/de/ioexception/www/server/impl/BasicHttpWorker.java b/src/de/ioexception/www/server/impl/BasicHttpWorker.java new file mode 100644 index 0000000..519e03b --- /dev/null +++ b/src/de/ioexception/www/server/impl/BasicHttpWorker.java @@ -0,0 +1,203 @@ +package de.ioexception.www.server.impl; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; + +import de.ioexception.www.Http; +import de.ioexception.www.http.HttpMethod; +import de.ioexception.www.http.HttpRequest; +import de.ioexception.www.http.HttpResponse; +import de.ioexception.www.http.HttpStatusCode; +import de.ioexception.www.http.HttpVersion; +import de.ioexception.www.http.impl.BasicHttpRequest; +import de.ioexception.www.http.impl.BasicHttpResponse; +import de.ioexception.www.server.HttpWorker; + +/** + * @author Benjamin Erb + * + */ +public class BasicHttpWorker extends HttpWorker +{ + public BasicHttpWorker(Socket socket, BasicHttpServer server) + { + super(socket, server); + } + + @Override + protected HttpRequest parseRequest(InputStream inputStream) throws IOException + { + String firstLine = readLine(inputStream); + + BasicHttpRequest request = new BasicHttpRequest(); + + request.setVersion(HttpVersion.extractVersion(firstLine)); + request.setRequestUri(firstLine.split(" ", 3)[1]); + request.setMethod(HttpMethod.extractMethod(firstLine)); + + Map headers = new HashMap(); + + String nextLine = ""; + while (!(nextLine = readLine(inputStream)).equals("")) + { + String values[] = nextLine.split(":", 2); + headers.put(values[0], values[1].trim()); + } + request.setHeaders(headers); + + if (headers.containsKey(Http.CONTENT_LENGTH)) + { + int size = Integer.parseInt(headers.get(Http.CONTENT_LENGTH)); + byte[] data = new byte[size]; + int n; + for (int i = 0; i < size && (n = inputStream.read()) != -1; i++) + { + data[i] = (byte) n; + } + request.setEntity(data); + } + else + { + request.setEntity(null); + } + + return request; + } + + @Override + protected HttpResponse handleRequest(HttpRequest request) + { + + + BasicHttpResponse response = new BasicHttpResponse(); + response.setHeaders(new HashMap()); + response.getHeaders().put(Http.SERVER, server.getServerSignature()); + response.setVersion(request.getHttpVersion()); + + String requestUri = request.getRequestUri(); + if (requestUri.equals("/")) + { + requestUri = "/index.html"; + } + File f = new File("webroot/" + requestUri); + + File rootDir = new File("webroot/"); + try + { + if (!f.getCanonicalPath().startsWith(rootDir.getCanonicalPath())) + { + response.setStatusCode(HttpStatusCode.FORBIDDEN); + return response; + } + } + catch (IOException e1) + { + response.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR); + return response; + } + + if (f.exists()) + { + response.setStatusCode(HttpStatusCode.OK); + InputStream inputStream; + try + { + inputStream = new FileInputStream(f); + byte fileContent[] = new byte[(int) f.length()]; + inputStream.read(fileContent); + inputStream.close(); + response.setEntity(fileContent); + + // guess and set the content type + response.getHeaders().put(Http.CONTENT_TYPE, URLConnection.guessContentTypeFromName(f.getAbsolutePath())); + } + catch (FileNotFoundException e) + { + response.setStatusCode(HttpStatusCode.NOT_FOUND); + } + catch (IOException e) + { + response.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR); + } + + } + else + { + response.setStatusCode(HttpStatusCode.NOT_FOUND); + } + + return response; + } + + @Override + protected void sendResponse(HttpResponse response, OutputStream outputStream) throws IOException + { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); + + writer.write(response.getHttpVersion().toString()); + writer.write(' '); + writer.write("" + response.getStatusCode().getCode()); + writer.write(' '); + writer.write(response.getStatusCode().getReasonPhrase()); + writer.write(Http.CRLF); + + if (response.getEntity() != null && response.getEntity().length > 0) + { + response.getHeaders().put(Http.CONTENT_LENGTH, "" + response.getEntity().length); + } + else + { + response.getHeaders().put(Http.CONTENT_LENGTH, "" + 0); + } + + if (response.getHeaders() != null) + { + for (String key : response.getHeaders().keySet()) + { + writer.write(key + ": " + response.getHeaders().get(key) + Http.CRLF); + } + } + writer.write(Http.CRLF); + writer.flush(); + + if (response.getEntity() != null && response.getEntity().length > 0) + { + outputStream.write(response.getEntity()); + } + outputStream.flush(); + + } + + @Override + protected boolean keepAlive(HttpRequest request, HttpResponse response) + { + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html + if (response.getHeaders().containsKey(Http.CONNECTION) && response.getHeaders().get(Http.CONNECTION).equalsIgnoreCase("close")) + { + return false; + } + if (request.getHttpVersion().equals(HttpVersion.VERSION_1_1)) + { + if (request.getHeaders().containsKey(Http.CONNECTION) && request.getHeaders().get(Http.CONNECTION).equalsIgnoreCase("close")) + { + return false; + } + else + { + return true; + } + } + return false; + } + +} diff --git a/src/util/Base64.java b/src/util/Base64.java new file mode 100644 index 0000000..7c00f79 --- /dev/null +++ b/src/util/Base64.java @@ -0,0 +1,575 @@ +package util; + +import java.util.Arrays; + +/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance + * with RFC 2045.

+ * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster + * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes) + * compared to sun.misc.Encoder()/Decoder().

+ * + * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and + * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small + * arrays (< 30 bytes). If source/destination is a String this + * version is about three times as fast due to the fact that the Commons Codec result has to be recoded + * to a String from byte[], which is very expensive.

+ * + * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only + * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice + * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown + * whether Sun's sun.misc.Encoder()/Decoder() produce temporary arrays but since performance + * is quite low it probably does.

+ * + * The encoder produces the same output as the Sun one except that the Sun's encoder appends + * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the + * length and is probably a side effect. Both are in conformance with RFC 2045 though.
+ * Commons codec seem to always att a trailing line separator.

+ * + * Note! + * The encode/decode method pairs (types) come in three versions with the exact same algorithm and + * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different + * format types. The methods not used can simply be commented out.

+ * + * There is also a "fast" version of all decode methods that works the same way as the normal ones, but + * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if + * the input is known and it hasn't bee tampered with.

+ * + * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com. + * + * Licence (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) + * 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 the name of the MiG InfoCom AB 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 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 OWNER OR 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. + * + * @version 2.2 + * @author Mikael Grev + * Date: 2004-aug-02 + * Time: 11:31:11 + */ + +public class Base64 +{ + private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final int[] IA = new int[256]; + static { + Arrays.fill(IA, -1); + for (int i = 0, iS = CA.length; i < iS; i++) + IA[CA[i]] = i; + IA['='] = 0; + } + + // **************************************************************************************** + // * char[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 char[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static char[] encodeToChar(byte[] sArr, boolean lineSep) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new char[0]; + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + char[] dArr = new char[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = CA[(i >>> 18) & 0x3f]; + dArr[d++] = CA[(i >>> 12) & 0x3f]; + dArr[d++] = CA[(i >>> 6) & 0x3f]; + dArr[d++] = CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = CA[i >> 12]; + dArr[dLen - 3] = CA[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(char[] sArr) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new byte[0]; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[sArr[i]] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) + if (sArr[i] == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++]]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + return dArr; + } + + /** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(char[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(char[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx]] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx]] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[sArr[sIx++]] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } + + // **************************************************************************************** + // * byte[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static byte[] encodeToByte(byte[] sArr, boolean lineSep) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new byte[0]; + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + byte[] dArr = new byte[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = (byte) CA[(i >>> 18) & 0x3f]; + dArr[d++] = (byte) CA[(i >>> 12) & 0x3f]; + dArr[d++] = (byte) CA[(i >>> 6) & 0x3f]; + dArr[d++] = (byte) CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't an even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = (byte) CA[i >> 12]; + dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[sArr[i] & 0xff] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;) + if (sArr[i] == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++] & 0xff]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + + return dArr; + } + + + /** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[sArr[sIx++]] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } + + // **************************************************************************************** + // * String version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static String encodeToString(byte[] sArr, boolean lineSep) + { + // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. + return new String(encodeToChar(sArr, lineSep)); + } + + /** Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with + * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That + * will create a temporary array though. This version will use str.charAt(i) to iterate the string. + * @param str The source string. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(String str) + { + // Check special case + int sLen = str != null ? str.length() : 0; + if (sLen == 0) + return new byte[0]; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[str.charAt(i)] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + // Count '=' at end + int pad = 0; + for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) + if (str.charAt(i) == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[str.charAt(s++)]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + return dArr; + } + + /** Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(String)}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param s The source string. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(String s) + { + // Check special case + int sLen = s.length(); + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[s.charAt(sIx++)] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } +} \ No newline at end of file diff --git a/webroot/.gitignore b/webroot/.gitignore new file mode 100644 index 0000000..4b573b8 --- /dev/null +++ b/webroot/.gitignore @@ -0,0 +1,5 @@ +/test.php +/rfc +/index.html +/images +/images.html