├── webroot └── .gitignore ├── src ├── de │ └── ioexception │ │ └── www │ │ ├── server │ │ ├── Main.java │ │ ├── HttpServer.java │ │ ├── impl │ │ │ ├── BasicHttpServer.java │ │ │ └── BasicHttpWorker.java │ │ └── HttpWorker.java │ │ ├── http │ │ ├── HttpResponse.java │ │ ├── impl │ │ │ ├── BasicHttpResponse.java │ │ │ ├── BasicHttpRequest.java │ │ │ └── BasicHttpMessage.java │ │ ├── HttpRequest.java │ │ ├── HttpMessage.java │ │ ├── HttpMethod.java │ │ ├── HttpVersion.java │ │ └── HttpStatusCode.java │ │ └── Http.java └── util │ └── Base64.java ├── LICENSE └── README.md /webroot/.gitignore: -------------------------------------------------------------------------------- 1 | /test.php 2 | /rfc 3 | /index.html 4 | /images 5 | /images.html 6 | -------------------------------------------------------------------------------- /src/de/ioexception/www/server/Main.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.server; 2 | 3 | import de.ioexception.www.server.impl.BasicHttpServer; 4 | 5 | public class Main 6 | { 7 | public static void main(String[] args) 8 | { 9 | HttpServer server = new BasicHttpServer(8080); 10 | server.start(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http; 2 | 3 | /** 4 | * An interface for HTTP responses. 5 | * 6 | * @see also http://tools.ietf.org/html/rfc2616.html#section-6 7 | * 8 | * @author Benjamin Erb 9 | * 10 | */ 11 | public interface HttpResponse extends HttpMessage 12 | { 13 | /** 14 | * Returns the HTTP Status Code of this response. 15 | * 16 | * @see also http://tools.ietf.org/html/rfc2616.html#section-6.1.1 17 | * 18 | * @return 19 | */ 20 | HttpStatusCode getStatusCode(); 21 | } 22 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/impl/BasicHttpResponse.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http.impl; 2 | 3 | import de.ioexception.www.http.HttpResponse; 4 | import de.ioexception.www.http.HttpStatusCode; 5 | 6 | public class BasicHttpResponse extends BasicHttpMessage implements HttpResponse 7 | { 8 | HttpStatusCode statusCode; 9 | 10 | @Override 11 | public HttpStatusCode getStatusCode() 12 | { 13 | return statusCode; 14 | } 15 | 16 | public void setStatusCode(HttpStatusCode statusCode) 17 | { 18 | this.statusCode = statusCode; 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http; 2 | 3 | /** 4 | * An interface for HTTP requests. 5 | * 6 | * @author Benjamin Erb 7 | * 8 | */ 9 | public interface HttpRequest extends HttpMessage 10 | { 11 | /** 12 | * Returns the HTTP method of this request. 13 | * 14 | * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1.1 15 | * 16 | * @return method 17 | */ 18 | HttpMethod getHttpMethod(); 19 | 20 | /** 21 | * Returns the request URI of this request. 22 | * 23 | * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1.2 24 | * 25 | * @return the request URI as string 26 | */ 27 | String getRequestUri(); 28 | } 29 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/HttpMessage.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * An interface for basic HTTP message operations. 7 | * 8 | * @author Benjamin Erb 9 | * 10 | */ 11 | public interface HttpMessage 12 | { 13 | /** 14 | * Returns the used protocol version of this message. 15 | * 16 | * @return 17 | */ 18 | HttpVersion getHttpVersion(); 19 | 20 | /** 21 | * Returns a list of key-value header fields. 22 | * 23 | * @return 24 | */ 25 | Map getHeaders(); 26 | 27 | /** 28 | * Returns the entity as a byte array or null, if no entity is available. 29 | * 30 | * @return 31 | */ 32 | byte[] getEntity(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/de/ioexception/www/server/HttpServer.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.server; 2 | 3 | import java.net.Socket; 4 | 5 | /** 6 | * A basic HTTP server interface. 7 | * 8 | * @author Benjamin Erb 9 | * 10 | */ 11 | public interface HttpServer 12 | { 13 | /** 14 | * Starts the configured webserver. 15 | */ 16 | public void start(); 17 | 18 | /** 19 | * Shuts down the webserver. 20 | */ 21 | public void stop(); 22 | 23 | /** 24 | * Dispatches an incoming socket connection to an appropriate handler. 25 | * 26 | * @param socket 27 | */ 28 | public void dispatchRequest(Socket socket); 29 | 30 | /** 31 | * Returns the signature of the webserver. 32 | * 33 | * @return 34 | */ 35 | public String getServerSignature(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/impl/BasicHttpRequest.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http.impl; 2 | 3 | import de.ioexception.www.http.HttpMethod; 4 | import de.ioexception.www.http.HttpRequest; 5 | 6 | public class BasicHttpRequest extends BasicHttpMessage implements HttpRequest 7 | { 8 | HttpMethod method; 9 | String requestUri; 10 | 11 | @Override 12 | public HttpMethod getHttpMethod() 13 | { 14 | return method; 15 | } 16 | 17 | @Override 18 | public String getRequestUri() 19 | { 20 | return requestUri; 21 | } 22 | 23 | public HttpMethod getMethod() 24 | { 25 | return method; 26 | } 27 | 28 | public void setMethod(HttpMethod method) 29 | { 30 | this.method = method; 31 | } 32 | 33 | public void setRequestUri(String requestUri) 34 | { 35 | this.requestUri = requestUri; 36 | } 37 | 38 | 39 | 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/HttpMethod.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http; 2 | 3 | /** 4 | * An enum of available HTTP methods. 5 | * 6 | * @see also http://tools.ietf.org/html/rfc2616.html#section-5.1 7 | * 8 | * @author Benjamin Erb 9 | * 10 | */ 11 | public enum HttpMethod 12 | { 13 | HEAD, 14 | GET, 15 | POST, 16 | PUT, 17 | DELETE; 18 | 19 | @Override 20 | public String toString() 21 | { 22 | return this.name(); 23 | } 24 | 25 | /** 26 | * Extracts the HTTP method from the header line. 27 | * 28 | * @param headerLine HTTP request header line 29 | * @return the HTTP method 30 | * @throws IllegalArgumentException 31 | */ 32 | public static HttpMethod extractMethod(String headerLine) throws IllegalArgumentException 33 | { 34 | String method = headerLine.split(" ")[0]; 35 | if (method != null) 36 | { 37 | return HttpMethod.valueOf(method); 38 | } 39 | else 40 | { 41 | throw new IllegalArgumentException(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/impl/BasicHttpMessage.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http.impl; 2 | 3 | import java.util.Map; 4 | 5 | import de.ioexception.www.http.HttpMessage; 6 | import de.ioexception.www.http.HttpVersion; 7 | 8 | public class BasicHttpMessage implements HttpMessage 9 | { 10 | private HttpVersion version; 11 | private byte[] entity = null; 12 | private Map headers; 13 | 14 | 15 | @Override 16 | public HttpVersion getHttpVersion() 17 | { 18 | return version; 19 | } 20 | 21 | @Override 22 | public Map getHeaders() 23 | { 24 | return headers; 25 | } 26 | 27 | @Override 28 | public byte[] getEntity() 29 | { 30 | return entity; 31 | } 32 | 33 | public HttpVersion getVersion() 34 | { 35 | return version; 36 | } 37 | 38 | public void setVersion(HttpVersion version) 39 | { 40 | this.version = version; 41 | } 42 | 43 | public void setEntity(byte[] entity) 44 | { 45 | this.entity = entity; 46 | } 47 | 48 | public void setHeaders(Map headers) 49 | { 50 | this.headers = headers; 51 | } 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Benjamin Erb 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-web-server 2 | 3 | A simple multi-threaded HTTP server written in Java for educational or experimental purposes 4 | 5 | ## License 6 | 7 | 8 | Copyright (c) 2010 Benjamin Erb 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/HttpVersion.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | import de.ioexception.www.Http; 7 | 8 | /** 9 | * An enum of available HTTP versions. 10 | * 11 | * @see also http://tools.ietf.org/html/rfc2616.html#section-3.1 12 | * 13 | * @author Benjamin Erb 14 | * 15 | */ 16 | public enum HttpVersion 17 | { 18 | /** 19 | * HTTP/1.0 20 | */ 21 | VERSION_1_0(1, 0), 22 | 23 | /** 24 | * HTTP/1.1 25 | */ 26 | VERSION_1_1(1, 1); 27 | 28 | private final int major; 29 | private final int minor; 30 | 31 | private HttpVersion(int major, int minor) 32 | { 33 | this.major = major; 34 | this.minor = minor; 35 | } 36 | 37 | @Override 38 | public String toString() 39 | { 40 | return Http.HTTP + Http.PROTOCOL_DELIMITER + major + "." + minor; 41 | } 42 | 43 | /** 44 | * Extracts the HTTP version from the header line. 45 | * 46 | * @param headerLine HTTP request header line 47 | * @return 48 | * @throws IllegalArgumentException 49 | */ 50 | public static HttpVersion extractVersion(String headerLine) throws IllegalArgumentException 51 | { 52 | Matcher m = Pattern.compile(Http.HTTP + "/(\\d+)\\.(\\d+)").matcher(headerLine); 53 | if (m.find()) 54 | { 55 | if ((Integer.parseInt(m.group(1)) == 1) && (Integer.parseInt(m.group(2)) == 1)) 56 | { 57 | return VERSION_1_1; 58 | } 59 | else if ((Integer.parseInt(m.group(1)) == 1) && (Integer.parseInt(m.group(2)) == 0)) 60 | { 61 | return VERSION_1_0; 62 | } 63 | else 64 | { 65 | throw new IllegalArgumentException("Unknown HTTP Version"); 66 | } 67 | } 68 | else 69 | { 70 | throw new IllegalArgumentException("Unknown HTTP Version"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/de/ioexception/www/server/impl/BasicHttpServer.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.server.impl; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import java.net.Socket; 6 | import java.net.SocketException; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | 10 | import de.ioexception.www.server.HttpServer; 11 | 12 | /** 13 | * A simple HTTP server implementation. 14 | * 15 | * @author Benjamin Erb 16 | * 17 | */ 18 | public class BasicHttpServer implements HttpServer 19 | { 20 | public static final String SERVER_NAME = "MyServer"; 21 | public static final String SERVER_VERSION = "0.1"; 22 | public static final int DEFAULT_PORT = 8080; 23 | public static final String SERVER_SIGNATURE = SERVER_NAME + "/" + SERVER_VERSION; 24 | 25 | private volatile boolean running = false; 26 | 27 | private final ExecutorService workerPool; 28 | private final ExecutorService dispatcherService; 29 | private final ServerSocket serverSocket; 30 | 31 | 32 | /** 33 | * Creates a new HTTP server. 34 | */ 35 | public BasicHttpServer() 36 | { 37 | this(BasicHttpServer.DEFAULT_PORT); 38 | } 39 | 40 | /** 41 | * Creates a new HTTP server bound to the given port. 42 | * 43 | * @param port 44 | * listening port 45 | * @throws IOException 46 | */ 47 | public BasicHttpServer(int port) 48 | { 49 | try 50 | { 51 | serverSocket = new ServerSocket(port); 52 | workerPool = Executors.newFixedThreadPool(16); 53 | dispatcherService = Executors.newSingleThreadExecutor(); 54 | } 55 | catch (IOException e) 56 | { 57 | throw new RuntimeException("Error while starting server", e); 58 | } 59 | 60 | } 61 | 62 | @Override 63 | public void dispatchRequest(Socket socket) 64 | { 65 | workerPool.submit(new BasicHttpWorker(socket, this)); 66 | } 67 | 68 | @Override 69 | public void start() 70 | { 71 | running = true; 72 | // Initiate the main server loop accepting incoming connections. 73 | dispatcherService.submit(new Runnable() 74 | { 75 | @Override 76 | public void run() 77 | { 78 | while (running) 79 | { 80 | try 81 | { 82 | Socket socket = serverSocket.accept(); 83 | dispatchRequest(socket); 84 | } 85 | catch (SocketException e) 86 | { 87 | // ignore due to close signaling 88 | } 89 | catch (IOException e) 90 | { 91 | e.printStackTrace(); 92 | } 93 | } 94 | } 95 | }); 96 | System.err.println("Webserver started on port " + serverSocket.getLocalPort() + "..."); 97 | 98 | } 99 | 100 | @Override 101 | public void stop() 102 | { 103 | try 104 | { 105 | running = false; 106 | dispatcherService.shutdown(); 107 | workerPool.shutdown(); 108 | serverSocket.close(); 109 | } 110 | catch (IOException e) 111 | { 112 | e.printStackTrace(); 113 | } 114 | finally 115 | { 116 | System.err.println("Webserver stopped."); 117 | } 118 | } 119 | 120 | @Override 121 | public String getServerSignature() 122 | { 123 | return BasicHttpServer.SERVER_SIGNATURE; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/de/ioexception/www/Http.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www; 2 | 3 | /** 4 | * Class for general HTTP-related constants. 5 | * 6 | * @author Benjamin Erb 7 | * 8 | */ 9 | public final class Http 10 | { 11 | public static final String CRLF = "\r\n"; 12 | public static final String HTTP = "HTTP"; 13 | public static final String PROTOCOL_DELIMITER = "/"; 14 | 15 | public static final String ACCEPT_RANGES = "Accept-Ranges"; 16 | public static final String AGE = "Age"; 17 | public static final String ALLOW = "Allow"; 18 | public static final String CACHE_CONTROL = "Cache-Control"; 19 | public static final String CONTENT_ENCODING = "Content-Encoding"; 20 | public static final String CONTENT_LANGUAGE = "Content-Language"; 21 | public static final String CONTENT_LENGTH = "Content-Length"; 22 | public static final String CONTENT_LOCATION = "Content-Location"; 23 | public static final String CONTENT_DISPOSITION = "Content-Disposition"; 24 | public static final String CONTENT_MD5 = "Content-MD5"; 25 | public static final String CONTENT_RANGE = "Content-Range"; 26 | public static final String CONTENT_TYPE = "Content-Type"; 27 | public static final String DATE = "Date"; 28 | public static final String ETAG = "ETag"; 29 | public static final String EXPIRES = "Expires"; 30 | public static final String LAST_MODIFIED = "Last-Modified"; 31 | public static final String LOCATION = "Location"; 32 | public static final String PRAGMA = "Pragma"; 33 | public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; 34 | public static final String REFRESH = "Refresh"; 35 | public static final String RETRY_AFTER = "Retry-After"; 36 | public static final String SERVER = "Server"; 37 | public static final String SET_COOKIE = "Set-Cookie"; 38 | public static final String TRAILER = "Trailer"; 39 | public static final String TRANSFER_ENCODING = "Transfer-Encoding"; 40 | public static final String VARY = "Vary"; 41 | public static final String VIA = "Via"; 42 | public static final String WARNING = "Warning"; 43 | public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; 44 | public static final String ACCEPT = "Accept"; 45 | public static final String ACCEPT_CHARSET = "Accept-Charset"; 46 | public static final String ACCEPT_ENCODING = "Accept-Encoding"; 47 | public static final String ACCEPT_LANGUAGE = "Accept-Language"; 48 | public static final String AUTHORIZATION = "Authorization"; 49 | public static final String CONNECTION = "Connection"; 50 | public static final String COOKIE = "Cookie"; 51 | public static final String EXPECT = "Expect"; 52 | public static final String FROM = "From"; 53 | public static final String HOST = "Host"; 54 | public static final String IF_MATCH = "If-Match"; 55 | public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; 56 | public static final String IF_NONE_MATCH = "If-None-Match"; 57 | public static final String IF_RANGE = "If-Range"; 58 | public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; 59 | public static final String MAX_FORWARDS = "Max-Forwards"; 60 | public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; 61 | public static final String RANGE = "Range"; 62 | public static final String REFERER = "Referer"; 63 | public static final String TE = "TE"; 64 | public static final String UPGRADE = "Upgrade"; 65 | public static final String USER_AGENT = "User-Agent"; 66 | 67 | private Http() 68 | { 69 | // no instances... 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/de/ioexception/www/http/HttpStatusCode.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.http; 2 | 3 | import java.util.EnumSet; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * An enum of available HTTP status codes. 9 | * 10 | * @see also http://tools.ietf.org/html/rfc2616.html#section-6.1.1 11 | * 12 | * @author Benjamin Erb 13 | * 14 | */ 15 | public enum HttpStatusCode 16 | { 17 | CONTINUE(100, "Continue"), 18 | SWITCHING_PROTOCOLS(101, "Switching Protocols"), 19 | OK(200, "OK"), 20 | CREATED(201, "Created"), 21 | ACCEPTED(202, "Accepted"), 22 | NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), 23 | NO_CONTENT(204, "No Content"), 24 | RESET_CONTENT(205, "Reset Content"), 25 | PARTIAL_CONTENT(206, "Partial Content"), 26 | MULTIPLE_CHOICES(300, "Multiple Choices"), 27 | MOVED_PERMANENTLY(301, "Moved Permanently"), 28 | FOUND(302, "Found"), 29 | SEE_OTHER(303, "See Other"), 30 | NOT_MODIFIED(304, "Not Modified"), 31 | USE_PROXY(305, "Use Proxy"), 32 | SWITCH_PROXY(306, "Switch Proxy"), 33 | TEMPORARY_REDIRECT(307, "Temporary Redirect "), 34 | BAD_REQUEST(400, "Bad Request"), 35 | UNAUTHORIZED(401, "Unauthorized"), 36 | PAYMENT_REQUIRED(402, "Payment Required"), 37 | FORBIDDEN(403, "Forbidden"), 38 | NOT_FOUND(404, "Not Found"), 39 | METHOD_NOT_ALLOWED(405, "Method Not Allowed"), 40 | NOT_ACCEPTABLE(406, "Not Acceptable"), 41 | PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), 42 | REQUEST_TIMEOUT(408, "Request Timeout"), 43 | CONFLICT(409, "Conflict"), 44 | GONE(410, "Gone"), 45 | LENGTH_REQUIRED(411, "Length Required"), 46 | PRECONDITION_FAILED(412, "Precondition Failed"), 47 | REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), 48 | REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"), 49 | UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), 50 | REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), 51 | EXPECTATION_FAILED(417, "Expectation Failed"), 52 | INTERNAL_SERVER_ERROR(500, "Internal Server Error"), 53 | NOT_IMPLEMENTED(501, "Not Implemented"), 54 | BAD_GATEWAY(502, "Bad Gateway"), 55 | SERVICE_UNAVAILABLE(503, "Service Unavailable"), 56 | GATEWAY_TIMEOUT(504, "Gateway Timeout"), 57 | HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"); 58 | 59 | private final int code; 60 | private final String reasonPhrase; 61 | 62 | private static final Map codeLookupTable = new HashMap(); 63 | 64 | static 65 | { 66 | for (HttpStatusCode s : EnumSet.allOf(HttpStatusCode.class)) 67 | { 68 | codeLookupTable.put(s.getCode(), s); 69 | } 70 | } 71 | 72 | private HttpStatusCode(int code, String reasonPhrase) 73 | { 74 | this.code = code; 75 | this.reasonPhrase = reasonPhrase; 76 | } 77 | 78 | /** 79 | * Returns the numerical code. 80 | * 81 | * @return Code 82 | */ 83 | public int getCode() 84 | { 85 | return code; 86 | } 87 | 88 | /** 89 | * Returns the verbatim code. 90 | * 91 | * @return reasons phrase 92 | */ 93 | public String getReasonPhrase() 94 | { 95 | return reasonPhrase; 96 | } 97 | 98 | /** 99 | * Gets the {@link HttpStatusCode} type by the code number. 100 | * 101 | * @param code 102 | * numerical code representation (i.e. 200) 103 | * @return assosciated status code 104 | */ 105 | public static HttpStatusCode getStatusCode(int code) 106 | { 107 | return codeLookupTable.get(code); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/de/ioexception/www/server/HttpWorker.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.server; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.Socket; 7 | import java.util.concurrent.Callable; 8 | 9 | import de.ioexception.www.http.HttpRequest; 10 | import de.ioexception.www.http.HttpResponse; 11 | 12 | /** 13 | * An abstract worker for processing incoming HTTP requests. 14 | * 15 | * Note that {@link Callable} is used, because it is able to bubble up 16 | * {@link Exception}s, as opposed to {@link Runnable}. 17 | * 18 | * @author Benjamin Erb 19 | * 20 | */ 21 | public abstract class HttpWorker implements Callable 22 | { 23 | protected final Socket socket; 24 | protected final HttpServer server; 25 | 26 | /** 27 | * Creates a new worker that handles the incoming request. 28 | * 29 | * @param socket 30 | * The socket this request is sent over. 31 | * @param server 32 | * A reference to the core server instance. 33 | */ 34 | public HttpWorker(Socket socket, HttpServer server) 35 | { 36 | this.socket = socket; 37 | this.server = server; 38 | } 39 | 40 | @Override 41 | public Void call() throws Exception 42 | { 43 | // Parse request from InputStream 44 | HttpRequest request = parseRequest(socket.getInputStream()); 45 | 46 | // Create appropriate response 47 | HttpResponse response = handleRequest(request); 48 | 49 | // Send response and close connection, if necessary 50 | if (keepAlive(request, response)) 51 | { 52 | sendResponse(response, socket.getOutputStream()); 53 | server.dispatchRequest(socket); 54 | } 55 | else 56 | { 57 | response.getHeaders().put("Connection", "close"); 58 | sendResponse(response, socket.getOutputStream()); 59 | socket.close(); 60 | } 61 | 62 | // We do not return anything here. 63 | return null; 64 | } 65 | 66 | /** 67 | * A helper method that reads an InputStream until it reads a CRLF (\r\n\). 68 | * Everything in front of the linefeed occured is returned as String. 69 | * 70 | * @param inputStream 71 | * The stream to read from. 72 | * @return The character sequence in front of the linefeed. 73 | * @throws IOException 74 | */ 75 | protected String readLine(InputStream inputStream) throws IOException 76 | { 77 | StringBuffer result = new StringBuffer(); 78 | boolean crRead = false; 79 | int n; 80 | while ((n = inputStream.read()) != -1) 81 | { 82 | if (n == '\r') 83 | { 84 | crRead = true; 85 | continue; 86 | } 87 | else if (n == '\n' && crRead) 88 | { 89 | return result.toString(); 90 | } 91 | else 92 | { 93 | result.append((char) n); 94 | } 95 | } 96 | return result.toString(); 97 | } 98 | 99 | /** 100 | * Parses an incoming request. Reads the {@link InputStream} and creates an 101 | * corresponding {@link HttpRequest} object, which will be returned. 102 | * 103 | * @param inputStream 104 | * The stream to read from. 105 | * @return 106 | * @throws IOException 107 | */ 108 | abstract protected HttpRequest parseRequest(InputStream inputStream) throws IOException; 109 | 110 | /** 111 | * Creates an appropriate {@link HttpResponse} to the given 112 | * {@link HttpRequest}. Note however, that this method is not yet sending 113 | * the response. 114 | * 115 | * @param request 116 | * The {@link HttpRequest} that must be handled. 117 | * @return 118 | */ 119 | abstract protected HttpResponse handleRequest(HttpRequest request); 120 | 121 | /** 122 | * Sends a given {@link HttpResponse} over the given {@link OutputStream}. 123 | * 124 | * @param response 125 | * @param outputStream 126 | * @throws IOException 127 | */ 128 | abstract protected void sendResponse(HttpResponse response, OutputStream outputStream) throws IOException; 129 | 130 | /** 131 | * Determines whether a connection should be kept alive or not on 132 | * server-side. This decision is made based upon the given ( 133 | * {@link HttpRequest}, {@link HttpResponse}) couple, respectively their 134 | * header values. 135 | * 136 | * @param request 137 | * @param response 138 | * @return true, if the server should keep open the connection, otherwise 139 | * false. 140 | */ 141 | abstract protected boolean keepAlive(HttpRequest request, HttpResponse response); 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/de/ioexception/www/server/impl/BasicHttpWorker.java: -------------------------------------------------------------------------------- 1 | package de.ioexception.www.server.impl; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.io.OutputStreamWriter; 11 | import java.net.Socket; 12 | import java.net.URLConnection; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | import de.ioexception.www.Http; 17 | import de.ioexception.www.http.HttpMethod; 18 | import de.ioexception.www.http.HttpRequest; 19 | import de.ioexception.www.http.HttpResponse; 20 | import de.ioexception.www.http.HttpStatusCode; 21 | import de.ioexception.www.http.HttpVersion; 22 | import de.ioexception.www.http.impl.BasicHttpRequest; 23 | import de.ioexception.www.http.impl.BasicHttpResponse; 24 | import de.ioexception.www.server.HttpWorker; 25 | 26 | /** 27 | * @author Benjamin Erb 28 | * 29 | */ 30 | public class BasicHttpWorker extends HttpWorker 31 | { 32 | public BasicHttpWorker(Socket socket, BasicHttpServer server) 33 | { 34 | super(socket, server); 35 | } 36 | 37 | @Override 38 | protected HttpRequest parseRequest(InputStream inputStream) throws IOException 39 | { 40 | String firstLine = readLine(inputStream); 41 | 42 | BasicHttpRequest request = new BasicHttpRequest(); 43 | 44 | request.setVersion(HttpVersion.extractVersion(firstLine)); 45 | request.setRequestUri(firstLine.split(" ", 3)[1]); 46 | request.setMethod(HttpMethod.extractMethod(firstLine)); 47 | 48 | Map headers = new HashMap(); 49 | 50 | String nextLine = ""; 51 | while (!(nextLine = readLine(inputStream)).equals("")) 52 | { 53 | String values[] = nextLine.split(":", 2); 54 | headers.put(values[0], values[1].trim()); 55 | } 56 | request.setHeaders(headers); 57 | 58 | if (headers.containsKey(Http.CONTENT_LENGTH)) 59 | { 60 | int size = Integer.parseInt(headers.get(Http.CONTENT_LENGTH)); 61 | byte[] data = new byte[size]; 62 | int n; 63 | for (int i = 0; i < size && (n = inputStream.read()) != -1; i++) 64 | { 65 | data[i] = (byte) n; 66 | } 67 | request.setEntity(data); 68 | } 69 | else 70 | { 71 | request.setEntity(null); 72 | } 73 | 74 | return request; 75 | } 76 | 77 | @Override 78 | protected HttpResponse handleRequest(HttpRequest request) 79 | { 80 | 81 | 82 | BasicHttpResponse response = new BasicHttpResponse(); 83 | response.setHeaders(new HashMap()); 84 | response.getHeaders().put(Http.SERVER, server.getServerSignature()); 85 | response.setVersion(request.getHttpVersion()); 86 | 87 | String requestUri = request.getRequestUri(); 88 | if (requestUri.equals("/")) 89 | { 90 | requestUri = "/index.html"; 91 | } 92 | File f = new File("webroot/" + requestUri); 93 | 94 | File rootDir = new File("webroot/"); 95 | try 96 | { 97 | if (!f.getCanonicalPath().startsWith(rootDir.getCanonicalPath())) 98 | { 99 | response.setStatusCode(HttpStatusCode.FORBIDDEN); 100 | return response; 101 | } 102 | } 103 | catch (IOException e1) 104 | { 105 | response.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR); 106 | return response; 107 | } 108 | 109 | if (f.exists()) 110 | { 111 | response.setStatusCode(HttpStatusCode.OK); 112 | InputStream inputStream; 113 | try 114 | { 115 | inputStream = new FileInputStream(f); 116 | byte fileContent[] = new byte[(int) f.length()]; 117 | inputStream.read(fileContent); 118 | inputStream.close(); 119 | response.setEntity(fileContent); 120 | 121 | // guess and set the content type 122 | response.getHeaders().put(Http.CONTENT_TYPE, URLConnection.guessContentTypeFromName(f.getAbsolutePath())); 123 | } 124 | catch (FileNotFoundException e) 125 | { 126 | response.setStatusCode(HttpStatusCode.NOT_FOUND); 127 | } 128 | catch (IOException e) 129 | { 130 | response.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR); 131 | } 132 | 133 | } 134 | else 135 | { 136 | response.setStatusCode(HttpStatusCode.NOT_FOUND); 137 | } 138 | 139 | return response; 140 | } 141 | 142 | @Override 143 | protected void sendResponse(HttpResponse response, OutputStream outputStream) throws IOException 144 | { 145 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); 146 | 147 | writer.write(response.getHttpVersion().toString()); 148 | writer.write(' '); 149 | writer.write("" + response.getStatusCode().getCode()); 150 | writer.write(' '); 151 | writer.write(response.getStatusCode().getReasonPhrase()); 152 | writer.write(Http.CRLF); 153 | 154 | if (response.getEntity() != null && response.getEntity().length > 0) 155 | { 156 | response.getHeaders().put(Http.CONTENT_LENGTH, "" + response.getEntity().length); 157 | } 158 | else 159 | { 160 | response.getHeaders().put(Http.CONTENT_LENGTH, "" + 0); 161 | } 162 | 163 | if (response.getHeaders() != null) 164 | { 165 | for (String key : response.getHeaders().keySet()) 166 | { 167 | writer.write(key + ": " + response.getHeaders().get(key) + Http.CRLF); 168 | } 169 | } 170 | writer.write(Http.CRLF); 171 | writer.flush(); 172 | 173 | if (response.getEntity() != null && response.getEntity().length > 0) 174 | { 175 | outputStream.write(response.getEntity()); 176 | } 177 | outputStream.flush(); 178 | 179 | } 180 | 181 | @Override 182 | protected boolean keepAlive(HttpRequest request, HttpResponse response) 183 | { 184 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html 185 | if (response.getHeaders().containsKey(Http.CONNECTION) && response.getHeaders().get(Http.CONNECTION).equalsIgnoreCase("close")) 186 | { 187 | return false; 188 | } 189 | if (request.getHttpVersion().equals(HttpVersion.VERSION_1_1)) 190 | { 191 | if (request.getHeaders().containsKey(Http.CONNECTION) && request.getHeaders().get(Http.CONNECTION).equalsIgnoreCase("close")) 192 | { 193 | return false; 194 | } 195 | else 196 | { 197 | return true; 198 | } 199 | } 200 | return false; 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/util/Base64.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.util.Arrays; 4 | 5 | /** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance 6 | * with RFC 2045.

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

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

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

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

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

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

36 | * 37 | * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com. 38 | * 39 | * Licence (BSD): 40 | * ============== 41 | * 42 | * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) 43 | * All rights reserved. 44 | * 45 | * Redistribution and use in source and binary forms, with or without modification, 46 | * are permitted provided that the following conditions are met: 47 | * Redistributions of source code must retain the above copyright notice, this list 48 | * of conditions and the following disclaimer. 49 | * Redistributions in binary form must reproduce the above copyright notice, this 50 | * list of conditions and the following disclaimer in the documentation and/or other 51 | * materials provided with the distribution. 52 | * Neither the name of the MiG InfoCom AB nor the names of its contributors may be 53 | * used to endorse or promote products derived from this software without specific 54 | * prior written permission. 55 | * 56 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 57 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 58 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 59 | * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 60 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 61 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 62 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 63 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 64 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 65 | * OF SUCH DAMAGE. 66 | * 67 | * @version 2.2 68 | * @author Mikael Grev 69 | * Date: 2004-aug-02 70 | * Time: 11:31:11 71 | */ 72 | 73 | public class Base64 74 | { 75 | private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 76 | private static final int[] IA = new int[256]; 77 | static { 78 | Arrays.fill(IA, -1); 79 | for (int i = 0, iS = CA.length; i < iS; i++) 80 | IA[CA[i]] = i; 81 | IA['='] = 0; 82 | } 83 | 84 | // **************************************************************************************** 85 | // * char[] version 86 | // **************************************************************************************** 87 | 88 | /** Encodes a raw byte array into a BASE64 char[] representation i accordance with RFC 2045. 89 | * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. 90 | * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
91 | * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a 92 | * little faster. 93 | * @return A BASE64 encoded array. Never null. 94 | */ 95 | public final static char[] encodeToChar(byte[] sArr, boolean lineSep) 96 | { 97 | // Check special case 98 | int sLen = sArr != null ? sArr.length : 0; 99 | if (sLen == 0) 100 | return new char[0]; 101 | 102 | int eLen = (sLen / 3) * 3; // Length of even 24-bits. 103 | int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count 104 | int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array 105 | char[] dArr = new char[dLen]; 106 | 107 | // Encode even 24-bits 108 | for (int s = 0, d = 0, cc = 0; s < eLen;) { 109 | // Copy next three bytes into lower 24 bits of int, paying attension to sign. 110 | int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); 111 | 112 | // Encode the int into four chars 113 | dArr[d++] = CA[(i >>> 18) & 0x3f]; 114 | dArr[d++] = CA[(i >>> 12) & 0x3f]; 115 | dArr[d++] = CA[(i >>> 6) & 0x3f]; 116 | dArr[d++] = CA[i & 0x3f]; 117 | 118 | // Add optional line separator 119 | if (lineSep && ++cc == 19 && d < dLen - 2) { 120 | dArr[d++] = '\r'; 121 | dArr[d++] = '\n'; 122 | cc = 0; 123 | } 124 | } 125 | 126 | // Pad and encode last bits if source isn't even 24 bits. 127 | int left = sLen - eLen; // 0 - 2. 128 | if (left > 0) { 129 | // Prepare the int 130 | int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); 131 | 132 | // Set last four chars 133 | dArr[dLen - 4] = CA[i >> 12]; 134 | dArr[dLen - 3] = CA[(i >>> 6) & 0x3f]; 135 | dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '='; 136 | dArr[dLen - 1] = '='; 137 | } 138 | return dArr; 139 | } 140 | 141 | /** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with 142 | * and without line separators. 143 | * @param sArr The source array. null or length 0 will return an empty array. 144 | * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters 145 | * (including '=') isn't divideable by 4. (I.e. definitely corrupted). 146 | */ 147 | public final static byte[] decode(char[] sArr) 148 | { 149 | // Check special case 150 | int sLen = sArr != null ? sArr.length : 0; 151 | if (sLen == 0) 152 | return new byte[0]; 153 | 154 | // Count illegal characters (including '\r', '\n') to know what size the returned array will be, 155 | // so we don't have to reallocate & copy it later. 156 | int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) 157 | 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. 158 | if (IA[sArr[i]] < 0) 159 | sepCnt++; 160 | 161 | // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. 162 | if ((sLen - sepCnt) % 4 != 0) 163 | return null; 164 | 165 | int pad = 0; 166 | for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) 167 | if (sArr[i] == '=') 168 | pad++; 169 | 170 | int len = ((sLen - sepCnt) * 6 >> 3) - pad; 171 | 172 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 173 | 174 | for (int s = 0, d = 0; d < len;) { 175 | // Assemble three bytes into an int from four "valid" characters. 176 | int i = 0; 177 | for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. 178 | int c = IA[sArr[s++]]; 179 | if (c >= 0) 180 | i |= c << (18 - j * 6); 181 | else 182 | j--; 183 | } 184 | // Add the bytes 185 | dArr[d++] = (byte) (i >> 16); 186 | if (d < len) { 187 | dArr[d++]= (byte) (i >> 8); 188 | if (d < len) 189 | dArr[d++] = (byte) i; 190 | } 191 | } 192 | return dArr; 193 | } 194 | 195 | /** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as 196 | * fast as {@link #decode(char[])}. The preconditions are:
197 | * + The array must have a line length of 76 chars OR no line separators at all (one line).
198 | * + Line separator must be "\r\n", as specified in RFC 2045 199 | * + The array must not contain illegal characters within the encoded string
200 | * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
201 | * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. 202 | * @return The decoded array of bytes. May be of length 0. 203 | */ 204 | public final static byte[] decodeFast(char[] sArr) 205 | { 206 | // Check special case 207 | int sLen = sArr.length; 208 | if (sLen == 0) 209 | return new byte[0]; 210 | 211 | int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. 212 | 213 | // Trim illegal chars from start 214 | while (sIx < eIx && IA[sArr[sIx]] < 0) 215 | sIx++; 216 | 217 | // Trim illegal chars from end 218 | while (eIx > 0 && IA[sArr[eIx]] < 0) 219 | eIx--; 220 | 221 | // get the padding count (=) (0, 1 or 2) 222 | int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. 223 | int cCnt = eIx - sIx + 1; // Content count including possible separators 224 | int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; 225 | 226 | int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes 227 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 228 | 229 | // Decode all but the last 0 - 2 bytes. 230 | int d = 0; 231 | for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { 232 | // Assemble three bytes into an int from four "valid" characters. 233 | int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; 234 | 235 | // Add the bytes 236 | dArr[d++] = (byte) (i >> 16); 237 | dArr[d++] = (byte) (i >> 8); 238 | dArr[d++] = (byte) i; 239 | 240 | // If line separator, jump over it. 241 | if (sepCnt > 0 && ++cc == 19) { 242 | sIx += 2; 243 | cc = 0; 244 | } 245 | } 246 | 247 | if (d < len) { 248 | // Decode last 1-3 bytes (incl '=') into 1-3 bytes 249 | int i = 0; 250 | for (int j = 0; sIx <= eIx - pad; j++) 251 | i |= IA[sArr[sIx++]] << (18 - j * 6); 252 | 253 | for (int r = 16; d < len; r -= 8) 254 | dArr[d++] = (byte) (i >> r); 255 | } 256 | 257 | return dArr; 258 | } 259 | 260 | // **************************************************************************************** 261 | // * byte[] version 262 | // **************************************************************************************** 263 | 264 | /** Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. 265 | * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. 266 | * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
267 | * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a 268 | * little faster. 269 | * @return A BASE64 encoded array. Never null. 270 | */ 271 | public final static byte[] encodeToByte(byte[] sArr, boolean lineSep) 272 | { 273 | // Check special case 274 | int sLen = sArr != null ? sArr.length : 0; 275 | if (sLen == 0) 276 | return new byte[0]; 277 | 278 | int eLen = (sLen / 3) * 3; // Length of even 24-bits. 279 | int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count 280 | int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array 281 | byte[] dArr = new byte[dLen]; 282 | 283 | // Encode even 24-bits 284 | for (int s = 0, d = 0, cc = 0; s < eLen;) { 285 | // Copy next three bytes into lower 24 bits of int, paying attension to sign. 286 | int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); 287 | 288 | // Encode the int into four chars 289 | dArr[d++] = (byte) CA[(i >>> 18) & 0x3f]; 290 | dArr[d++] = (byte) CA[(i >>> 12) & 0x3f]; 291 | dArr[d++] = (byte) CA[(i >>> 6) & 0x3f]; 292 | dArr[d++] = (byte) CA[i & 0x3f]; 293 | 294 | // Add optional line separator 295 | if (lineSep && ++cc == 19 && d < dLen - 2) { 296 | dArr[d++] = '\r'; 297 | dArr[d++] = '\n'; 298 | cc = 0; 299 | } 300 | } 301 | 302 | // Pad and encode last bits if source isn't an even 24 bits. 303 | int left = sLen - eLen; // 0 - 2. 304 | if (left > 0) { 305 | // Prepare the int 306 | int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); 307 | 308 | // Set last four chars 309 | dArr[dLen - 4] = (byte) CA[i >> 12]; 310 | dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f]; 311 | dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '='; 312 | dArr[dLen - 1] = '='; 313 | } 314 | return dArr; 315 | } 316 | 317 | /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with 318 | * and without line separators. 319 | * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. 320 | * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters 321 | * (including '=') isn't divideable by 4. (I.e. definitely corrupted). 322 | */ 323 | public final static byte[] decode(byte[] sArr) 324 | { 325 | // Check special case 326 | int sLen = sArr.length; 327 | 328 | // Count illegal characters (including '\r', '\n') to know what size the returned array will be, 329 | // so we don't have to reallocate & copy it later. 330 | int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) 331 | 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. 332 | if (IA[sArr[i] & 0xff] < 0) 333 | sepCnt++; 334 | 335 | // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. 336 | if ((sLen - sepCnt) % 4 != 0) 337 | return null; 338 | 339 | int pad = 0; 340 | for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;) 341 | if (sArr[i] == '=') 342 | pad++; 343 | 344 | int len = ((sLen - sepCnt) * 6 >> 3) - pad; 345 | 346 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 347 | 348 | for (int s = 0, d = 0; d < len;) { 349 | // Assemble three bytes into an int from four "valid" characters. 350 | int i = 0; 351 | for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. 352 | int c = IA[sArr[s++] & 0xff]; 353 | if (c >= 0) 354 | i |= c << (18 - j * 6); 355 | else 356 | j--; 357 | } 358 | 359 | // Add the bytes 360 | dArr[d++] = (byte) (i >> 16); 361 | if (d < len) { 362 | dArr[d++]= (byte) (i >> 8); 363 | if (d < len) 364 | dArr[d++] = (byte) i; 365 | } 366 | } 367 | 368 | return dArr; 369 | } 370 | 371 | 372 | /** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as 373 | * fast as {@link #decode(byte[])}. The preconditions are:
374 | * + The array must have a line length of 76 chars OR no line separators at all (one line).
375 | * + Line separator must be "\r\n", as specified in RFC 2045 376 | * + The array must not contain illegal characters within the encoded string
377 | * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
378 | * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. 379 | * @return The decoded array of bytes. May be of length 0. 380 | */ 381 | public final static byte[] decodeFast(byte[] sArr) 382 | { 383 | // Check special case 384 | int sLen = sArr.length; 385 | if (sLen == 0) 386 | return new byte[0]; 387 | 388 | int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. 389 | 390 | // Trim illegal chars from start 391 | while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) 392 | sIx++; 393 | 394 | // Trim illegal chars from end 395 | while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) 396 | eIx--; 397 | 398 | // get the padding count (=) (0, 1 or 2) 399 | int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. 400 | int cCnt = eIx - sIx + 1; // Content count including possible separators 401 | int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; 402 | 403 | int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes 404 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 405 | 406 | // Decode all but the last 0 - 2 bytes. 407 | int d = 0; 408 | for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { 409 | // Assemble three bytes into an int from four "valid" characters. 410 | int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; 411 | 412 | // Add the bytes 413 | dArr[d++] = (byte) (i >> 16); 414 | dArr[d++] = (byte) (i >> 8); 415 | dArr[d++] = (byte) i; 416 | 417 | // If line separator, jump over it. 418 | if (sepCnt > 0 && ++cc == 19) { 419 | sIx += 2; 420 | cc = 0; 421 | } 422 | } 423 | 424 | if (d < len) { 425 | // Decode last 1-3 bytes (incl '=') into 1-3 bytes 426 | int i = 0; 427 | for (int j = 0; sIx <= eIx - pad; j++) 428 | i |= IA[sArr[sIx++]] << (18 - j * 6); 429 | 430 | for (int r = 16; d < len; r -= 8) 431 | dArr[d++] = (byte) (i >> r); 432 | } 433 | 434 | return dArr; 435 | } 436 | 437 | // **************************************************************************************** 438 | // * String version 439 | // **************************************************************************************** 440 | 441 | /** Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. 442 | * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. 443 | * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
444 | * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a 445 | * little faster. 446 | * @return A BASE64 encoded array. Never null. 447 | */ 448 | public final static String encodeToString(byte[] sArr, boolean lineSep) 449 | { 450 | // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. 451 | return new String(encodeToChar(sArr, lineSep)); 452 | } 453 | 454 | /** Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with 455 | * and without line separators.
456 | * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That 457 | * will create a temporary array though. This version will use str.charAt(i) to iterate the string. 458 | * @param str The source string. null or length 0 will return an empty array. 459 | * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters 460 | * (including '=') isn't divideable by 4. (I.e. definitely corrupted). 461 | */ 462 | public final static byte[] decode(String str) 463 | { 464 | // Check special case 465 | int sLen = str != null ? str.length() : 0; 466 | if (sLen == 0) 467 | return new byte[0]; 468 | 469 | // Count illegal characters (including '\r', '\n') to know what size the returned array will be, 470 | // so we don't have to reallocate & copy it later. 471 | int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) 472 | 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. 473 | if (IA[str.charAt(i)] < 0) 474 | sepCnt++; 475 | 476 | // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. 477 | if ((sLen - sepCnt) % 4 != 0) 478 | return null; 479 | 480 | // Count '=' at end 481 | int pad = 0; 482 | for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) 483 | if (str.charAt(i) == '=') 484 | pad++; 485 | 486 | int len = ((sLen - sepCnt) * 6 >> 3) - pad; 487 | 488 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 489 | 490 | for (int s = 0, d = 0; d < len;) { 491 | // Assemble three bytes into an int from four "valid" characters. 492 | int i = 0; 493 | for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. 494 | int c = IA[str.charAt(s++)]; 495 | if (c >= 0) 496 | i |= c << (18 - j * 6); 497 | else 498 | j--; 499 | } 500 | // Add the bytes 501 | dArr[d++] = (byte) (i >> 16); 502 | if (d < len) { 503 | dArr[d++]= (byte) (i >> 8); 504 | if (d < len) 505 | dArr[d++] = (byte) i; 506 | } 507 | } 508 | return dArr; 509 | } 510 | 511 | /** Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as 512 | * fast as {@link #decode(String)}. The preconditions are:
513 | * + The array must have a line length of 76 chars OR no line separators at all (one line).
514 | * + Line separator must be "\r\n", as specified in RFC 2045 515 | * + The array must not contain illegal characters within the encoded string
516 | * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
517 | * @param s The source string. Length 0 will return an empty array. null will throw an exception. 518 | * @return The decoded array of bytes. May be of length 0. 519 | */ 520 | public final static byte[] decodeFast(String s) 521 | { 522 | // Check special case 523 | int sLen = s.length(); 524 | if (sLen == 0) 525 | return new byte[0]; 526 | 527 | int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. 528 | 529 | // Trim illegal chars from start 530 | while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) 531 | sIx++; 532 | 533 | // Trim illegal chars from end 534 | while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) 535 | eIx--; 536 | 537 | // get the padding count (=) (0, 1 or 2) 538 | int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. 539 | int cCnt = eIx - sIx + 1; // Content count including possible separators 540 | int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; 541 | 542 | int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes 543 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 544 | 545 | // Decode all but the last 0 - 2 bytes. 546 | int d = 0; 547 | for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { 548 | // Assemble three bytes into an int from four "valid" characters. 549 | int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; 550 | 551 | // Add the bytes 552 | dArr[d++] = (byte) (i >> 16); 553 | dArr[d++] = (byte) (i >> 8); 554 | dArr[d++] = (byte) i; 555 | 556 | // If line separator, jump over it. 557 | if (sepCnt > 0 && ++cc == 19) { 558 | sIx += 2; 559 | cc = 0; 560 | } 561 | } 562 | 563 | if (d < len) { 564 | // Decode last 1-3 bytes (incl '=') into 1-3 bytes 565 | int i = 0; 566 | for (int j = 0; sIx <= eIx - pad; j++) 567 | i |= IA[s.charAt(sIx++)] << (18 - j * 6); 568 | 569 | for (int r = 16; d < len; r -= 8) 570 | dArr[d++] = (byte) (i >> r); 571 | } 572 | 573 | return dArr; 574 | } 575 | } --------------------------------------------------------------------------------