--- /dev/null
+/* Written and copyright 2001-2003 Benjamin Kohl.\r
+ * Distributed under the GNU General Public License; see the README file.\r
+ * This code comes with NO WARRANTY.\r
+ */\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.BufferedInputStream;\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+\r
+\r
+/**\r
+ File: Jhttpp2BufferedFilterStream.java\r
+ @author Benjamin Kohl\r
+*/\r
+public class Jhttpp2ClientInputStream extends BufferedInputStream {\r
+ private boolean filter;\r
+ private String buf;\r
+ private int lread;\r
+ /**\r
+ * The length of the header (with body, if one)\r
+ */\r
+ private int header_length;\r
+ /**\r
+ * The length of the (optional) body of the actual request\r
+ */\r
+ private int content_len;\r
+ /**\r
+ * This is set to true with requests with bodies, like "POST"\r
+ */\r
+ private boolean body;\r
+ private static Jhttpp2Server server;\r
+ private Jhttpp2HTTPSession connection;\r
+ private InetAddress remote_host;\r
+ private String remote_host_name;\r
+ private String errordescription;\r
+ private int statuscode;\r
+\r
+ public String url;\r
+ public String method;\r
+ public int remote_port;\r
+ public int post_data_len;\r
+ public boolean ssl;\r
+\r
+ public int getHeaderLength() {\r
+ return header_length;\r
+ }\r
+\r
+ public InetAddress getRemoteHost() { return remote_host; }\r
+ public String getRemoteHostName() { return remote_host_name; }\r
+\r
+ private void init() {\r
+ lread = 0;\r
+ header_length = 0;\r
+ content_len = 0;\r
+ ssl = false;\r
+ body = false;\r
+ remote_port = 0;\r
+ post_data_len = 0;\r
+ }\r
+\r
+ public Jhttpp2ClientInputStream(Jhttpp2Server server,Jhttpp2HTTPSession connection,InputStream a) {\r
+ super(a);\r
+ init();\r
+ this.server = server;\r
+ this.connection=connection;\r
+ }\r
+ /**\r
+ * Handler for the actual HTTP request\r
+ * @exception IOException\r
+ */\r
+ public int read(byte[] a) {\r
+ statuscode = connection.SC_OK;\r
+ if (ssl) return super.read(a);\r
+ boolean cookies_enabled=server.enableCookiesByDefault();\r
+ String rq="";\r
+ header_length=0;\r
+ post_data_len = 0;\r
+ content_len = 0;\r
+ boolean start_line=true;\r
+ buf = getLine(); // reads the first line\r
+ \r
+ boolean cnt=true;\r
+ while (lread>2&&cnt) {\r
+ if (start_line) {\r
+ start_line = false;\r
+ int methodID = server.getHttpMethod(buf);\r
+ if (methodID==-1) {\r
+ statuscode = connection.SC_NOT_SUPPORTED;\r
+ } else {\r
+ if (methodID==2) {\r
+ ssl = true;\r
+ }\r
+ InetAddress host = parseRequest(buf,methodID);\r
+ \r
+ if (statuscode == connection.SC_OK) {\r
+ if (!server.use_proxy && !ssl) {\r
+ /* creates a new request without the hostname */\r
+ buf = method + " " + url + " " + server.getHttpVersion() + "\r\n";\r
+ lread = buf.length();\r
+ }\r
+ if ((server.use_proxy && connection.notConnected()) || !host.equals(remote_host)) {\r
+ if (server.debug) server.writeLog("read_f: STATE_CONNECT_TO_NEW_HOST");\r
+ statuscode = connection.SC_CONNECTING_TO_HOST;\r
+ remote_host = host;\r
+ }\r
+ /* -------------------------\r
+ * url blocking (only "GET" method)\r
+ * -------------------------*/\r
+ if (server.block_urls && methodID==0 && statuscode!=connection.SC_FILE_REQUEST) {\r
+ if (server.debug) System.printString("Searching match...");\r
+ Jhttpp2URLMatch match=server.findMatch(this.remote_host_name+url);\r
+ if (match!=null){\r
+ if (server.debug) System.printString("Match found!");\r
+ cookies_enabled=match.getCookiesEnabled();\r
+ if (match.getActionIndex()==-1) cnt=false; else {\r
+ OnURLAction action=(OnURLAction)server.getURLActions().elementAt(match.getActionIndex());\r
+ if (action.onAccesssDeny()) {\r
+ statuscode=connection.SC_URL_BLOCKED;\r
+ if (action.onAccessDenyWithCustomText()) errordescription=action.getCustomErrorText();\r
+ } else if (action.onAccessRedirect()) {\r
+ statuscode=connection.SC_MOVED_PERMANENTLY;\r
+ errordescription=action.newLocation();\r
+ }\r
+ }\r
+ }//end if match!=null)\r
+ } //end if (server.block...\r
+ }\r
+ }\r
+ } else { // end if(startline)\r
+ /*-----------------------------------------------\r
+ * Content-Length parsing\r
+ *-----------------------------------------------*/\r
+ if(server.startsWith(buf.toUpperCase(),"CONTENT-LENGTH")) {\r
+ String clen=buf.substring(16);\r
+ if (clen.indexOf("\r")!=-1) clen=clen.substring(0,clen.indexOf("\r"));\r
+ else if(clen.indexOf("\n")!=-1) clen=clen.substring(0,clen.indexOf("\n"));\r
+ content_len=Integer.parseInt(clen);\r
+ if (server.debug) server.writeLog("read_f: content_len: " + content_len);\r
+ if (!ssl) body=true; // Note: in HTTP/1.1 any method can have a body, not only "POST"\r
+ }\r
+ else if (server.startsWith(buf,"Proxy-Connection:")) {\r
+ if (!server.use_proxy) buf=null;\r
+ else {\r
+ buf="Proxy-Connection: Keep-Alive\r\n";\r
+ lread=buf.length();\r
+ }\r
+ }\r
+ /*-----------------------------------------------\r
+ * cookie crunch section\r
+ *-----------------------------------------------*/\r
+ else if(server.startsWith(buf,"Cookie:")) {\r
+ if (!cookies_enabled) buf=null;\r
+ }\r
+ /*------------------------------------------------\r
+ * Http-Header filtering section\r
+ *------------------------------------------------*/\r
+ else if (server.filter_http) {\r
+ if(server.startsWith(buf,"Referer:")) {// removes "Referer"\r
+ buf=null;\r
+ } else if(server.startsWith(buf,"User-Agent")) // changes User-Agent\r
+ {\r
+ buf="User-Agent: " + server.getUserAgent() + "\r\n";\r
+ lread=buf.length();\r
+ }\r
+ }\r
+ }\r
+ if (buf!=null) {\r
+ rq+=buf;\r
+ if (server.debug) server.writeLog(buf);\r
+ header_length+=lread;\r
+ }\r
+ buf=getLine();\r
+ }\r
+ rq+=buf; //adds last line (should be an empty line) to the header String\r
+ header_length+=lread;\r
+ \r
+ if (header_length==0) {\r
+ if (server.debug) server.writeLog("header_length=0, setting status to SC_CONNECTION_CLOSED (buggy request)");\r
+ statuscode=connection.SC_CONNECTION_CLOSED;\r
+ }\r
+ \r
+ for (int i=0;i<header_length;i++) a[i]=(byte)rq.charAt(i);\r
+ \r
+ if(body) {// read the body, if "Content-Length" given\r
+ post_data_len = 0;\r
+ while(post_data_len < content_len) {\r
+ a[header_length + post_data_len]=(byte)read(); // writes data into the array\r
+ post_data_len ++;\r
+ }\r
+ header_length += content_len; // add the body-length to the header-length\r
+ body = false;\r
+ }\r
+ if (statuscode==connection.SC_OK) {\r
+ return header_length;\r
+ } else {\r
+ return -1;\r
+ }\r
+ }\r
+ /**\r
+ * reads a line\r
+ * @exception IOException\r
+ */\r
+ public String getLine()\r
+ {\r
+ int l=0; String line=""; lread=0;\r
+ boolean cnt=true;\r
+ while(l!='\n'&&cnt) {\r
+ l=read();\r
+ if (l!=-1) {\r
+ line+=(char)l;\r
+ lread++;\r
+ } else\r
+ cnt=false;\r
+ }\r
+ return line;\r
+ }\r
+ /**\r
+ * Parser for the first (!) line from the HTTP request<BR>\r
+ * Sets up the URL, method and remote hostname.\r
+ * @return an InetAddress for the hostname, null on errors with a statuscode!=SC_OK\r
+ */\r
+ public InetAddress parseRequest(String a,int method_index) {\r
+ if (server.debug) \r
+ server.writeLog(a);\r
+ String f; \r
+ int pos; \r
+ url="";\r
+ if (ssl) {\r
+ f = a.substring(8);\r
+ } else {\r
+ method = a.substring(0,a.indexOf(" ")); //first word in the line\r
+ pos = a.indexOf(":"); // locate first :\r
+ if (pos == -1) { // occours with "GET / HTTP/1.1"\r
+ url = a.substring(a.indexOf(" ")+1,a.lastIndexOf(" "));\r
+ if (method_index == 0) { // method_index==0 --> GET\r
+ if (url.indexOf(server.WEB_CONFIG_FILE) != -1) {\r
+ statuscode = connection.SC_CONFIG_RQ;\r
+ } else { \r
+ statuscode = connection.SC_FILE_REQUEST;\r
+ }\r
+ } else {\r
+ if (method_index == 1 && url.indexOf(server.WEB_CONFIG_FILE) != -1) { // allow "POST" for admin log in\r
+ statuscode = connection.SC_CONFIG_RQ;\r
+ } else {\r
+ statuscode=connection.SC_INTERNAL_SERVER_ERROR;\r
+ errordescription="This WWW proxy supports only the \"GET\" method while acting as webserver.";\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ f = a.substring(pos+3); //removes "http://"\r
+ }\r
+ pos=f.indexOf(" "); // locate space, should be the space before "HTTP/1.1"\r
+ if (pos==-1) { // buggy request\r
+ statuscode=connection.SC_CLIENT_ERROR;\r
+ errordescription="Your browser sent an invalid request: \""+ a + "\"";\r
+ return null;\r
+ }\r
+ f = f.substring(0,pos); //removes all after space\r
+ // if the url contains a space... it's not our mistake...(url's must never contain a space character)\r
+ pos=f.indexOf("/"); // locate the first slash\r
+ if (pos!=-1) {\r
+ url=f.substring(pos); // saves path without hostname\r
+ f=f.substring(0,pos); // reduce string to the hostname\r
+ }\r
+ else url="/"; // occurs with this request: "GET http://localhost HTTP/1.1"\r
+ pos = f.indexOf(":"); // check for the portnumber\r
+ if (pos!=-1) {\r
+ String l_port =f.substring(pos+1);\r
+ if (l_port.indexOf(" ")!=-1)\r
+ l_port=l_port.substring(0,l_port.indexOf(" "));\r
+ int i_port=80;\r
+ //BCD\r
+ i_port = Integer.parseInt(l_port);\r
+ f = f.substring(0,pos);\r
+ remote_port=i_port;\r
+ } else\r
+ remote_port = 80;\r
+ remote_host_name = f;\r
+ InetAddress address = null;\r
+ if (server.log_access) \r
+ server.logAccess(method + " " + getFullURL());\r
+\r
+ address = InetAddress.getByName(f);\r
+\r
+ if (remote_port == server.port && address.equals(InetAddress.getLocalHost())) {\r
+ if (url.indexOf(server.WEB_CONFIG_FILE) != -1 && (method_index == 0 || method_index == 1))\r
+ statuscode = connection.SC_CONFIG_RQ;\r
+ else if (method_index > 0 ) {\r
+ statuscode=connection.SC_INTERNAL_SERVER_ERROR;\r
+ errordescription="This WWW proxy supports only the \"GET\" method while acting as webserver.";\r
+ } else\r
+ statuscode = connection.SC_FILE_REQUEST;\r
+ }\r
+ return address;\r
+ }\r
+ /**\r
+ * @return boolean whether the actual connection was established with the CONNECT method.\r
+ * @since 0.2.21\r
+ */\r
+ public boolean isTunnel() {\r
+ return ssl;\r
+ }\r
+ /**\r
+ * @return the full qualified URL of the actual request.\r
+ * @since 0.4.0\r
+ */\r
+ public String getFullURL() {\r
+ String sh="";\r
+ if (ssl)\r
+ sh="s";\r
+ if (remote_port!=80)\r
+ return "http" + sh + "://" + getRemoteHostName()\r
+ + ":" + remote_port + url;\r
+ else\r
+ return "http" + sh + "://" + getRemoteHostName()\r
+ + url;\r
+ }\r
+ /**\r
+ * @return status-code for the actual request\r
+ * @since 0.3.5\r
+ */\r
+ public int getStatusCode()\r
+ {\r
+ return statuscode;\r
+ }\r
+ /**\r
+ * @return the (optional) error-description for this request\r
+ */\r
+ public String getErrorDescription()\r
+ {\r
+ return errordescription;\r
+ }\r
+}\r
--- /dev/null
+/* Written and copyright 2001-2003 Benjamin Kohl.
+ * Distributed under the GNU General Public License; see the README file.
+ * This code comes with NO WARRANTY.
+ */
+
+import java.net.Socket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.File;
+import java.io.FileInputStream;
+
+
+/**
+ One HTTP connection
+ @file Jhttpp2HTTPSession.java
+ @author Benjamin Kohl
+*/
+public class Jhttpp2HTTPSession extends Thread {
+
+ public final int SC_OK;
+ public final int SC_CONNECTING_TO_HOST;
+ public final int SC_HOST_NOT_FOUND;
+ public final int SC_URL_BLOCKED;
+ public final int SC_CLIENT_ERROR;
+ public final int SC_INTERNAL_SERVER_ERROR;
+ public final int SC_NOT_SUPPORTED;
+ public final int SC_REMOTE_DEBUG_MODE;
+ public final int SC_CONNECTION_CLOSED;
+ public final int SC_HTTP_OPTIONS_THIS;
+ public final int SC_FILE_REQUEST;
+ public final int SC_MOVED_PERMANENTLY;
+ public final int SC_CONFIG_RQ;
+
+
+ void init() {
+ SC_OK=0;
+ SC_CONNECTING_TO_HOST=1;
+ SC_HOST_NOT_FOUND=2;
+ SC_URL_BLOCKED=3;
+ SC_CLIENT_ERROR=4;
+ SC_INTERNAL_SERVER_ERROR=5;
+ SC_NOT_SUPPORTED=6;
+ SC_REMOTE_DEBUG_MODE=7;
+ SC_CONNECTION_CLOSED=8;
+ SC_HTTP_OPTIONS_THIS=9;
+ SC_FILE_REQUEST=10;
+ SC_MOVED_PERMANENTLY=11;
+ SC_CONFIG_RQ = 12;
+ }
+
+ private Jhttpp2Server server;
+
+ /** downstream connections */
+ private Socket client;
+ private BufferedOutputStream out;
+ private Jhttpp2ClientInputStream in;
+
+ /** upstream connections */
+ private Socket HTTP_Socket;
+ private BufferedOutputStream HTTP_out;
+ private Jhttpp2ServerInputStream HTTP_in;
+
+ public Jhttpp2HTTPSession(Jhttpp2Server server,Socket client) {
+ init();
+ in = new Jhttpp2ClientInputStream(server,this,client.getInputStream());//,true);
+ out = new BufferedOutputStream(client.getOutputStream());
+ this.server=server;
+ this.client=client;
+ start();
+ }
+ public Socket getLocalSocket() {
+ return client;
+ }
+ public Socket getRemoteSocket() {
+ return HTTP_Socket;
+ }
+ public boolean isTunnel() {
+ return in.isTunnel();
+ }
+ public boolean notConnected() {
+ return HTTP_Socket==null;
+ }
+ public void sendHeader(int a,boolean b) {
+ sendHeader(a);
+ endHeader();
+ out.flush();
+ }
+ public void sendHeader(int status, String content_type, long content_length) {
+ sendHeader(status);
+ sendLine("Content-Length", String.valueOf(content_length));
+ sendLine("Content-Type", content_type );
+ }
+ public void sendLine(String s) {
+ write(out,s + "\r\n");
+ }
+ public void sendLine(String header, String s) {
+ write(out,header + ": " + s + "\r\n");
+ }
+ public void endHeader() {
+ write(out,"\r\n");
+ }
+ public void run() {
+ if (server.debug)server.writeLog("begin http session");
+ server.increaseNumConnections();
+ handleRequest();
+ in.close(); // since 0.4.10b
+ out.close();
+ client.close();
+ // close upstream connections (webserver or other proxy)
+ if (!notConnected()) {
+ HTTP_Socket.close();
+ HTTP_out.close();
+ HTTP_in.close();
+ }
+ server.decreaseNumConnections();
+ if (server.debug)server.writeLog("end http session");
+ }
+ /** sends a message to the user */
+ public void sendErrorMSG(int a,String info) {
+ String statuscode = sendHeader(a);
+ String localhost = "localhost"+":"+server.port;
+ String msg = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html>\r"
+ + "<!-- jHTTPp2 error message --><HEAD>\r"
+ + "<TITLE>" + statuscode + "</TITLE>\r"
+ + "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://" + localhost + "/style.css\"></HEAD>\r" // use css style sheet in htdocs
+ + "<BODY BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\" LINK=\"#000080\" VLINK=\"#000080\" ALINK=\"#000080\">\r"
+ + "<h2 class=\"headline\">HTTP " + statuscode + " </h2>\r"
+ + "<HR size=\"4\">\r"
+ + "<p class=\"i30\">Your request for the following URL failed:</p>"
+ + "<p class=\"tiagtext\"><a href=\"" + in.getFullURL() + "\">" + in.getFullURL() + "</A> </p>\r"
+ + "<P class=\"i25\">Reason: " + info + "</P>"
+ + "<HR size=\"4\">\r"
+ + "<p class=\"i25\"><A HREF=\"http://jhttp2.sourceforge.net/\">jHTTPp2</A> HTTP Proxy, Version " + server.getServerVersion() + " at " + localhost
+ + "<br>Copyright © 2001-2003 <A HREF=\"mailto:bkohl@users.sourceforge.net\">Benjamin Kohl</A></p>\r"
+ + "<p class=\"i25\"><A HREF=\"http://" + localhost + "/\">jHTTPp2 local website</A> <A HREF=\"http://" + localhost + "/" + server.WEB_CONFIG_FILE + "\">Configuration</A></p>"
+ + "</BODY></HTML>";
+ sendLine("Content-Length",String.valueOf(msg.length()));
+ sendLine("Content-Type","text/html; charset=iso-8859-1");
+ endHeader();
+ write(out,msg);
+ out.flush();
+ }
+
+ public String sendHeader(int a) {
+ String stat;
+ if (a==200)
+ stat="200 OK";
+ else if (a==202)
+ stat="202 Accepted";
+ else if (a==300)
+ stat="300 Ambiguous";
+ else if (a==301)
+ stat="301 Moved Permanently";
+ else if (a==400)
+ stat="400 Bad Request";
+ else if (a==401)
+ stat="401 Denied";
+ else if (a==403)
+ stat="403 Forbidden";
+ else if (a==404)
+ stat="404 Not Found";
+ else if (a==405)
+ stat="405 Bad Method";
+ else if (a==413)
+ stat="413 Request Entity Too Large";
+ else if (a==415)
+ stat="415 Unsupported Media";
+ else if (a==501)
+ stat="501 Not Implemented";
+ else if (a==502)
+ stat="502 Bad Gateway";
+ else if (a==504)
+ stat="504 Gateway Timeout";
+ else if (a==505)
+ stat="505 HTTP Version Not Supported";
+ else
+ stat="500 Internal Server Error";
+ sendLine(server.getHttpVersion() + " " + stat);
+ sendLine("Server",server.getServerIdentification());
+ if (a==501)
+ sendLine("Allow","GET, HEAD, POST, PUT, DELETE, CONNECT");
+ sendLine("Cache-Control", "no-cache, must-revalidate");
+ sendLine("Connection","close");
+ return stat;
+ }
+
+ /** the main routine, where it all happens */
+ public void handleRequest() {
+ InetAddress remote_host;
+ Jhttpp2Read remote_in=null;
+ int remote_port;
+ byte[] b=new byte[65536];
+ int numread=in.read(b);
+ boolean cnt=true;
+ while(cnt) { // with this loop we support persistent connections
+ if (numread==-1) { // -1 signals an error
+ if (in.getStatusCode()!=SC_CONNECTING_TO_HOST) {
+ int code=in.getStatusCode();
+ if (code==SC_CONNECTION_CLOSED) {
+ } else if (code==SC_CLIENT_ERROR) {
+ sendErrorMSG(400,"Your client sent a request that this proxy could not understand. (" + in.getErrorDescription() + ")");
+ } else if (code==SC_HOST_NOT_FOUND)
+ sendErrorMSG(504,"Host not found.<BR>jHTTPp2 was unable to resolve the hostname of this request. <BR>Perhaps the hostname was misspelled, the server is down or you have no connection to the internet.");
+ else if (code==SC_INTERNAL_SERVER_ERROR)
+ sendErrorMSG(500,"Server Error! (" + in.getErrorDescription() + ")");
+ else if (code==SC_NOT_SUPPORTED)
+ sendErrorMSG(501,"Your client used a HTTP method that this proxy doesn't support: (" + in.getErrorDescription() + ")");
+ else if (code==SC_URL_BLOCKED) {
+ if (in.getErrorDescription()!=null && in.getErrorDescription().length()>0)
+ sendErrorMSG(403,in.getErrorDescription());
+ else
+ sendErrorMSG(403,"The request for this URL was denied by the jHTTPp2 URL-Filter.");
+ } else if (code==SC_HTTP_OPTIONS_THIS) {
+ sendHeader(200); endHeader();
+ } else if (code==SC_FILE_REQUEST)
+ file_handler();
+ else if (code==SC_CONFIG_RQ)
+ admin_handler(b);
+ //case SC_HTTP_TRACE:
+ else if (code==SC_MOVED_PERMANENTLY) {
+ sendHeader(301);
+ write(out,"Location: " + in.getErrorDescription() + "\r\n");
+ endHeader();
+ out.flush();
+ }
+ cnt=false;// return from main loop.
+ } else { // also an error because we are not connected (or to the wrong host)
+ // Creates a new connection to a remote host.
+ if (!notConnected()) {
+ HTTP_Socket.close();
+ }
+ numread=in.getHeaderLength(); // get the header length
+ if (!server.use_proxy) {// sets up hostname and port
+ remote_host=in.getRemoteHost();
+ remote_port=in.remote_port;
+ } else {
+ remote_host=server.proxy;
+ remote_port=server.proxy_port;
+ }
+ connect(remote_host,remote_port);
+ if (!in.isTunnel() || (in.isTunnel() && server.use_proxy))
+ { // no SSL-Tunnel or SSL-Tunnel with another remote proxy: simply forward the request
+ HTTP_out.write(b, 0, numread);
+ HTTP_out.flush();
+ }
+ else
+ { // SSL-Tunnel with "CONNECT": creates a tunnel connection with the server
+ sendLine(server.getHttpVersion() + " 200 Connection established");
+ sendLine("Proxy-Agent",server.getServerIdentification());
+ endHeader(); out.flush();
+ }
+ remote_in = new Jhttpp2Read(server,this, HTTP_in, out); // reads data from the remote server
+ server.addBytesWritten(numread);
+ }
+ }
+ if (cnt) {
+ while(cnt) { // reads data from the client
+ numread=in.read(b);
+ if (numread!=-1) {
+ HTTP_out.write(b, 0, numread);
+ HTTP_out.flush();
+ server.addBytesWritten(numread);
+ } else cnt=false;
+ } // end of inner loop
+ cnt=true;
+ }
+ }// end of main loop
+ out.flush();
+ if (!notConnected() && remote_in != null)
+ remote_in.close(); // close Jhttpp2Read thread
+ return;
+ }
+ /** connects to the given host and port */
+ public void connect(InetAddress host,int port) {
+ HTTP_Socket = new Socket(host,port);
+ HTTP_in = new Jhttpp2ServerInputStream(server,this,HTTP_Socket.getInputStream(),false);
+ HTTP_out = new BufferedOutputStream(HTTP_Socket.getOutputStream());
+ }
+ /** converts an String into a Byte-Array to write it with the OutputStream */
+ public void write(BufferedOutputStream o,String p) {
+ o.write(p.getBytes(),0,p.length());
+ }
+
+ /**
+ * Small webserver for local files in {app}/htdocs
+ * @since 0.4.04
+ */
+ public void file_handler() {
+ if (!server.www_server) {
+ sendErrorMSG(500, "The jHTTPp2 built-in WWW server module is disabled.");
+ return;
+ }
+ String filename=in.url;
+ if (filename.equals("/")) filename="index.html"; // convert / to index.html
+ else if (filename.startsWith("/")) filename=filename.substring(1);
+ if (filename.endsWith("/")) filename+="index.html"; // add index.html, if ending with /
+ File file = new File("htdocs/" + filename); // access only files in "htdocs"
+ if (true// !file.exists() || !file.canRead() // be sure that we can read the file
+ || filename.indexOf("..")!=-1 // don't allow ".." !!!
+ // || file.isDirectory()
+) { // dont't read if it's a directory
+ sendErrorMSG(404,"The requested file /" + filename + " was not found or the path is invalid.");
+ return;
+ }
+ int pos = filename.lastIndexOf("."); // MIME type of the specified file
+ String content_type="text/plain"; // all unknown content types will be marked as text/plain
+ if (pos != -1) {
+ String extension = filename.substring(pos+1);
+ if (extension.equalsIgnoreCase("htm") || (extension.equalsIgnoreCase("html"))) content_type="text/html; charset=iso-8859-1";
+ else if (extension.equalsIgnoreCase("jpg") || (extension.equalsIgnoreCase("jpeg"))) content_type="image/jpeg";
+ else if (extension.equalsIgnoreCase("gif")) content_type = "image/gif";
+ else if (extension.equalsIgnoreCase("png")) content_type = "image/png";
+ else if (extension.equalsIgnoreCase("css")) content_type = "text/css";
+ else if (extension.equalsIgnoreCase("pdf")) content_type = "application/pdf";
+ else if (extension.equalsIgnoreCase("ps") || extension.equalsIgnoreCase("eps")) content_type = "application/postscript";
+ else if (extension.equalsIgnoreCase("xml")) content_type = "text/xml";
+ }
+ sendHeader(200,content_type, file.length() );
+ endHeader();
+ BufferedInputStream file_in = new BufferedInputStream(new FileInputStream(file));
+ byte[] buffer=new byte[4096];
+ int a=file_in.read(buffer);
+ while (a!=-1) { // read until EOF
+ out.write(buffer,0,a);
+ a = file_in.read(buffer);
+ }
+ out.flush();
+ file_in.close(); // finished!
+ }
+ /**
+ * @since 0.4.10b
+ */
+ public int getStatus()
+ {
+ return in.getStatusCode();
+ }
+ /**
+ * @since 0.4.20a
+ * admin webpage
+ */
+ public void admin_handler(byte[] b) {
+ }
+}
+
--- /dev/null
+/* Written and copyright 2001-2003 Benjamin Kohl.
+ * Distributed under the GNU General Public License; see the README file.
+ * This code comes with NO WARRANTY.
+ */
+
+//import java.awt.Dimension;
+//import java.awt.Toolkit;
+//import javax.swing.UIManager;
+
+//import Jhttpp2MainFrame;
+/**
+ * Title: jHTTPp2: Java HTTP Filter Proxy
+ * Description: starts thwe Swing GUI or the console-mode only proxy
+ * Copyright: Copyright (c) 2001-2003
+ *
+ * @author Benjamin Kohl
+ */
+
+public class Jhttpp2Launcher {
+
+ public static void main(String[] args) {
+ Jhttpp2Server server = new Jhttpp2Server(true);
+ if (server.error) {
+ System.printString("Error: " + server.error_msg);
+ }
+ else {
+ server.start();
+ System.printString("Running on port " + server.port+"\n");
+ }
+ }
+}
--- /dev/null
+/* Written and copyright 2001-2003 Benjamin Kohl.\r
+ * Distributed under the GNU General Public License; see the README file.\r
+ * This code comes with NO WARRANTY.\r
+ */\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.BufferedOutputStream;\r
+import java.io.IOException;\r
+/**\r
+ File: Jhttpp2Read.java\r
+ reads from a Jhttpp2ClientInputStream and writes to the BufferedOutputStream\r
+\r
+ @author Benjamin Kohl\r
+*/\r
+public class Jhttpp2Read extends Thread\r
+{\r
+ private final int BUFFER_SIZE;\r
+ private BufferedInputStream in;\r
+ private BufferedOutputStream out;\r
+ private Jhttpp2HTTPSession connection;\r
+ private Jhttpp2Server server;\r
+\r
+ public Jhttpp2Read(Jhttpp2Server server,Jhttpp2HTTPSession connection,BufferedInputStream l_in, BufferedOutputStream l_out) {\r
+ BUFFER_SIZE=96000;\r
+ in=l_in;\r
+ out=l_out;\r
+ this.connection=connection;\r
+ this.server=server;\r
+ start();\r
+ }\r
+ public void run() {\r
+ read();\r
+ }\r
+ private void read() {\r
+ int bytes_read=0;\r
+ byte[] buf=new byte[BUFFER_SIZE];\r
+ boolean cnt=true;\r
+ while(cnt) {\r
+ bytes_read=in.read(buf);\r
+ if (bytes_read!=-1) {\r
+ out.write(buf,0,bytes_read);\r
+ out.flush();\r
+ server.addBytesRead(bytes_read);\r
+ } else cnt=false;\r
+ }\r
+ if (connection.getStatus()!=connection.SC_CONNECTING_TO_HOST) // *uaaahhh*: fixes a very strange bug\r
+ connection.getLocalSocket().close();\r
+ // why? If we are connecting to a new host (and this thread is already running!) , the upstream\r
+ // socket will be closed. So we get here and close our own downstream socket..... and the browser\r
+ // displays an empty page because jhttpp2\r
+ // closes the connection..... so close the downstream socket only when NOT connecting to a new host....\r
+ }\r
+ public void close() {\r
+ in.close();\r
+ }\r
+}\r
+\r
+\r
--- /dev/null
+/* Written and copyright 2001-2003 Benjamin Kohl.\r
+ * Distributed under the GNU General Public License; see the README file.\r
+ * This code comes with NO WARRANTY.\r
+ * More Information and documentation: HTTP://jhttp2.sourceforge.net/\r
+ */\r
+\r
+import java.net.ServerSocket;\r
+import java.net.Socket;\r
+import java.net.UnknownHostException;\r
+import java.net.InetAddress;\r
+import java.net.BindException;\r
+\r
+import java.io.*;\r
+\r
+import java.util.Vector;\r
+import java.util.Date;\r
+\r
+public class Jhttpp2Server extends Thread\r
+{\r
+ private static final String CRLF;\r
+ private final String VERSION;\r
+ private final String V_SPECIAL;\r
+ private final String HTTP_VERSION;\r
+ private final String MAIN_LOGFILE;\r
+\r
+ private final String DATA_FILE;\r
+ private final String SERVER_PROPERTIES_FILE;\r
+\r
+ private String http_useragent;\r
+ private ServerSocket listen;\r
+ private BufferedWriter logfile;\r
+ private BufferedWriter access_logfile;\r
+ private long bytesread;\r
+ private long byteswritten;\r
+ private int numconnections;\r
+\r
+ private boolean enable_cookies_by_default;\r
+ private WildcardDictionary dic;\r
+ private Vector urlactions;\r
+\r
+ public final int DEFAULT_SERVER_PORT;\r
+ public final String WEB_CONFIG_FILE;\r
+\r
+ public int port;\r
+ public InetAddress proxy;\r
+ public int proxy_port;\r
+\r
+ public long config_auth;\r
+ public long config_session_id;\r
+ public String config_user;\r
+ public String config_password;\r
+\r
+ public static boolean error;\r
+ public static String error_msg;\r
+\r
+ public boolean use_proxy;\r
+ public boolean block_urls;\r
+ public boolean filter_http;\r
+ public boolean debug;\r
+ public boolean log_access;\r
+ public String log_access_filename;\r
+ public boolean webconfig;\r
+ public boolean www_server;\r
+ \r
+\r
+ public initvars() {\r
+ CRLF="\r\n";\r
+ VERSION = "0.4.62";\r
+ V_SPECIAL = " 2003-05-20";\r
+ HTTP_VERSION = "HTTP/1.1";\r
+ MAIN_LOGFILE = "server.log";\r
+ DATA_FILE = "server.data";\r
+ SERVER_PROPERTIES_FILE = "server.properties";\r
+ http_useragent = "Mozilla/4.0 (compatible; MSIE 4.0; WindowsNT 5.0)";\r
+ enable_cookies_by_default=true;\r
+ dic = new WildcardDictionary();\r
+ urlactions = new Vector();\r
+ DEFAULT_SERVER_PORT = 8088;\r
+ WEB_CONFIG_FILE = "admin/jp2-config";\r
+ port = DEFAULT_SERVER_PORT;\r
+ proxy_port = 0;\r
+ config_auth = 0;\r
+ config_session_id = 0;\r
+ config_user = "root";\r
+ config_password = "geheim";\r
+ use_proxy=false;\r
+ block_urls=false;\r
+ filter_http=false;\r
+ debug=false;\r
+ log_access = true;\r
+ log_access_filename="paccess.log";\r
+ webconfig = true;\r
+ www_server = true;\r
+}\r
+\r
+ void init()\r
+ {\r
+ if(log_access) {\r
+ access_logfile=new BufferedWriter(new FileWriter(log_access_filename,true)); \r
+ }\r
+\r
+ logfile=new BufferedWriter(new FileWriter(MAIN_LOGFILE,true));\r
+ writeLog("server startup...");\r
+\r
+ listen = new ServerSocket(port);\r
+ \r
+ if (error) {\r
+ writeLog(error_msg);\r
+ return;\r
+ }\r
+ }\r
+ public Jhttpp2Server() {\r
+ initvars();\r
+ init();\r
+ }\r
+ public Jhttpp2Server(boolean b)\r
+ {\r
+ initvars();\r
+ System.printString("jHTTPp2 HTTP Proxy Server Release " + getServerVersion() + "\r\n"\r
+ +"Copyright (c) 2001-2003 Benjamin Kohl <bkohl@users.sourceforge.net>\r\n"\r
+ +"This software comes with ABSOLUTELY NO WARRANTY OF ANY KIND.\r\n"\r
+ +"http://jhttp2.sourceforge.net/\n");\r
+ init();\r
+ }\r
+ /** calls init(), sets up the serverport and starts for each connection\r
+ * new Jhttpp2Connection\r
+ */\r
+ void serve()\r
+ {\r
+ writeLog("Server running.");\r
+ while(true)\r
+ {\r
+ Socket client = listen.accept();\r
+ new Jhttpp2HTTPSession(this,client);\r
+ }\r
+ }\r
+ public void run()\r
+ {\r
+ serve();\r
+ }\r
+ public void setErrorMsg(String a)\r
+ {\r
+ error=true;\r
+ error_msg=a;\r
+ }\r
+ /**\r
+ * Tests what method is used with the reqest\r
+ * @return -1 if the server doesn't support the method\r
+ */\r
+ public int getHttpMethod(String d)\r
+ {\r
+ if (startsWith(d,"GET") || startsWith(d,"HEAD")) return 0;\r
+ if (startsWith(d,"POST") || startsWith(d,"PUT")) return 1;\r
+ if (startsWith(d,"CONNECT")) return 2;\r
+ if (startsWith(d,"OPTIONS")) return 3;\r
+\r
+ return -1;/* No match...\r
+\r
+ Following methods are not implemented:\r
+ || startsWith(d,"TRACE") */\r
+ }\r
+ public boolean startsWith(String a,String what)\r
+ {\r
+ int l=what.length();\r
+ int l2=a.length();\r
+ if (l2>l)\r
+ return a.substring(0,l).equals(what);\r
+ else\r
+ return false;\r
+ }\r
+ /**\r
+ *@return the Server response-header field\r
+ */\r
+ public String getServerIdentification()\r
+ {\r
+ return "jHTTPp2/" + getServerVersion();\r
+ }\r
+ public String getServerVersion()\r
+ {\r
+ return VERSION + V_SPECIAL;\r
+ }\r
+ /**\r
+ * saves all settings with a ObjectOutputStream into a file\r
+ * @since 0.2.10\r
+ */\r
+ /** restores all Jhttpp2 options from "settings.dat"\r
+ * @since 0.2.10\r
+ */\r
+ /**\r
+ * @return the HTTP version used by jHTTPp2\r
+ */\r
+ public String getHttpVersion()\r
+ {\r
+ return HTTP_VERSION;\r
+ }\r
+ /** the User-Agent header field\r
+ * @since 0.2.17\r
+ * @return User-Agent String\r
+ */\r
+ public String getUserAgent()\r
+ {\r
+ return http_useragent;\r
+ }\r
+ public void setUserAgent(String ua)\r
+ {\r
+ http_useragent=ua;\r
+ }\r
+ /**\r
+ * writes into the server log file and adds a new line\r
+ * @since 0.2.21\r
+ */\r
+ public void writeLog(String s)\r
+ {\r
+ writeLog(s,true);\r
+ }\r
+ /** writes to the server log file\r
+ * @since 0.2.21\r
+ */\r
+ public void writeLog(String s,boolean b)\r
+ {\r
+ s=new Date().toString() + " " + s;\r
+ logfile.write(s,0,s.length());\r
+ if (b) logfile.newLine();\r
+ logfile.flush();\r
+ if (debug)System.printString(s);\r
+ }\r
+\r
+ public void closeLog()\r
+ {\r
+ writeLog("Server shutdown.");\r
+ logfile.flush();\r
+ logfile.close();\r
+ access_logfile.close();\r
+ }\r
+\r
+ public void addBytesRead(long read)\r
+ {\r
+ bytesread+=read;\r
+ }\r
+ /**\r
+ * Functions for the jHTTPp2 statistics:\r
+ * How many connections\r
+ * Bytes read/written\r
+ * @since 0.3.0\r
+ */\r
+ public void addBytesWritten(int written)\r
+ {\r
+ byteswritten+=written;\r
+ }\r
+ public int getServerConnections()\r
+ {\r
+ return numconnections;\r
+ }\r
+ public long getBytesRead()\r
+ {\r
+ return bytesread;\r
+ }\r
+ public long getBytesWritten()\r
+ {\r
+ return byteswritten;\r
+ }\r
+ public void increaseNumConnections()\r
+ {\r
+ numconnections++;\r
+ }\r
+ public void decreaseNumConnections()\r
+ {\r
+ numconnections--;\r
+ }\r
+ public void AuthenticateUser(String u,String p) {\r
+ if (config_user.equals(u) && config_password.equals(p)) {\r
+ config_auth = 1;\r
+ } else config_auth = 0;\r
+ }\r
+ public String getGMTString()\r
+ {\r
+ return new Date().toString();\r
+ }\r
+ public Jhttpp2URLMatch findMatch(String url)\r
+ {\r
+ return (Jhttpp2URLMatch)dic.get(url);\r
+ }\r
+ public WildcardDictionary getWildcardDictionary()\r
+ {\r
+ return dic;\r
+ }\r
+ public Vector getURLActions()\r
+ {\r
+ return urlactions;\r
+ }\r
+ public boolean enableCookiesByDefault()\r
+ {\r
+ return this.enable_cookies_by_default;\r
+ }\r
+ public void enableCookiesByDefault(boolean a)\r
+ {\r
+ enable_cookies_by_default=a;\r
+ }\r
+ public void resetStat()\r
+ {\r
+ bytesread=0;\r
+ byteswritten=0;\r
+ }\r
+ /**\r
+ * @since 0.4.10a\r
+ */\r
+ /**\r
+ * @since 0.4.10a\r
+ */\r
+ /**\r
+ * @since 0.4.10a\r
+ */\r
+ public void logAccess(String s)\r
+ {\r
+ access_logfile.write("[" + new Date().toString() + "] " + s + "\r\n");\r
+ access_logfile.flush();\r
+ }\r
+ public void shutdownServer() {\r
+ closeLog();\r
+ System.exit(0);\r
+ }\r
+\r
+}\r
--- /dev/null
+/* Written and copyright 2001 Benjamin Kohl.\r
+ * Distributed under the GNU General Public License; see the README file.\r
+ * This code comes with NO WARRANTY.\r
+ */\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.InputStream;\r
+import java.io.IOException;\r
+\r
+public class Jhttpp2ServerInputStream extends BufferedInputStream {\r
+ private Jhttpp2HTTPSession connection;\r
+\r
+ public Jhttpp2ServerInputStream(Jhttpp2Server server,Jhttpp2HTTPSession connection,InputStream a,boolean filter) {\r
+ super(a);\r
+ this.connection=connection;\r
+ }\r
+ public int read_f(byte[] b) {\r
+ return read(b);\r
+ }\r
+}\r
+\r
--- /dev/null
+/**
+ * Title: jHTTPp2: Java HTTP Filter Proxy
+ * Copyright: Copyright (c) 2001-2003 Benjamin Kohl
+ * @author Benjamin Kohl
+ * @version 0.2.8
+ */
+
+public class Jhttpp2URLMatch {
+ String match;
+ String desc;
+ boolean cookies_enabled;
+ int actionindex;
+
+ public Jhttpp2URLMatch(String match,boolean cookies_enabled,int actionindex,String description)
+ {
+ this.match=match;
+ this.cookies_enabled=cookies_enabled;
+ this.actionindex=actionindex;
+ this.desc=description;
+ }
+ public String getMatch()
+ {
+ return match;
+ }
+ public boolean getCookiesEnabled()
+ {
+ return cookies_enabled;
+ }
+ public int getActionIndex()
+ {
+ return actionindex;
+ }
+ public String getDescription()
+ {
+ return desc;
+ }
+ public String toString()
+ {
+ return "\"" + match + "\" " + desc;
+ }
+}
--- /dev/null
+
+
+/**
+ * Title: jHTTPp2: Java HTTP Filter Proxy
+ * Description: static utility routines
+ * Copyright: Copyright (c) 2001 Benjamin Kohl
+
+ * @author Benjamin Kohl
+ * @version 0.4.22a
+ */
+
+public class Jhttpp2Utils
+{
+
+
+//
+// Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.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:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. 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.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+ /// URLDecoder to go along with java.net.URLEncoder. Why there isn't
+ // already a decoder in the standard library is a mystery to me.
+ public static String urlDecoder( String encoded )
+ {
+ StringBuffer decoded = new StringBuffer();
+ int len = encoded.length();
+ for ( int i = 0; i < len; ++i )
+ {
+ if ( encoded.charAt( i ) == '%' && i + 2 < len )
+ {
+ int d1 = Character.digit( encoded.charAt( i + 1 ), 16 );
+ int d2 = Character.digit( encoded.charAt( i + 2 ), 16 );
+ if ( d1 != -1 && d2 != -1 )
+ decoded.append( (char) ( ( d1 << 4 ) + d2 ) );
+ i += 2;
+ }
+ else if ( encoded.charAt( i ) == '+' )
+ decoded.append( ' ' );
+ else
+ decoded.append( encoded.charAt( i ) );
+ }
+ return decoded.toString();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/* Written and copyright 2001 Benjamin Kohl.
+ * Distributed under the GNU General Public License; see the README file.
+ * This code comes with NO WARRANTY.
+ *
+ * Title: jHTTPp2: Java HTTP Filter Proxy
+ * Description: An OpenSource HTTP Proxy
+ * Copyright: Copyright (c) 2001 Benjamin Kohl
+ * @author Benjamin Kohl
+ */
+
+public class OnURLAction {
+
+ private String customerrortext, desc, httppath, newlocation;
+ private boolean log,block,customtext,http_rq,anotherlocation;
+ public OnURLAction(String desc)
+ {
+ this.desc=desc;
+ }
+ public void denyAccess(String customerrortext)
+ {
+ this.block=true;
+ this.customtext=true;
+ this.customerrortext=customerrortext;
+ }
+ public void denyAccess()
+ {
+ block=true;
+ }
+ public void logAccess()
+ {
+ log=true;
+ }
+ public void anotherLocation(String newlocation)
+ {
+ this.anotherlocation=true;
+ this.newlocation=newlocation;
+ }
+
+ public boolean onAccesssDeny()
+ {
+ return block;
+ }
+ public boolean onAccessLog()
+ {
+ return log;
+ }
+ public boolean onAccessDenyWithCustomText()
+ {
+ return customtext;
+ }
+ public boolean onAccessSendHTTPRequest()
+ {
+ return http_rq;
+ }
+ public boolean onAccessRedirect()
+ {
+ return this.anotherlocation;
+ }
+ public String newLocation()
+ {
+ return this.newlocation;
+ }
+ public void setHTTPAction(boolean http_rq, String httppath)
+ {
+ this.http_rq=http_rq;
+ this.httppath=httppath;
+ }
+ public String getCustomErrorText()
+ {
+ return customerrortext;
+ }
+ public String getDescription()
+ {
+ return desc;
+ }
+ public String toString()
+ {
+ return desc;
+ }
+
+}
--- /dev/null
+// WildcardDictionary - a dictionary with wildcard lookups
+//
+// Copyright (C) 1996 by Jef Poskanzer <jef@acme.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:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. 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.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+// package Acme;
+
+import java.util.*;
+
+/// A dictionary with wildcard lookups.
+// <P>
+// The keys in this dictionary are wildcard patterns. When you do a get(),
+// the string you pass in is matched against all the patterns, and the
+// first match is returned.
+// <P>
+// The wildcard matcher is fairly simple, it implements * meaning any
+// string, ? meaning any single character, and | separating multiple
+// patterns. All other characters must match literally.
+// <P>
+// <A HREF="/resources/classes/Acme/WildcardDictionary.java">Fetch the software.</A><BR>
+// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
+// <P>
+// @see Acme.Utils#match
+
+public class WildcardDictionary extends Dictionary {
+
+ private Vector keys;
+ private Vector elements;
+
+ /// Constructor.
+ public WildcardDictionary() {
+ keys = new Vector();
+ elements = new Vector();
+ }
+
+ /// Returns the number of elements contained within the dictionary.
+ public int size() {
+ return elements.size();
+ }
+
+ /// Returns true if the dictionary contains no elements.
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ /// Returns an enumeration of the dictionary's keys.
+ public Enumeration keys() {
+ return keys.elements();
+ }
+
+ /// Returns an enumeration of the elements. Use the Enumeration methods
+ // on the returned object to fetch the elements sequentially.
+ public Enumeration elements() {
+ return elements.elements();
+ }
+
+ /// Gets the object associated with the specified key in the dictionary.
+ // The key is assumed to be a String, which is matched against
+ // the wildcard-pattern keys in the dictionary.
+ // @param key the string to match
+ // @returns the element for the key, or null if there's no match
+ // @see Acme.Utils#match
+ public synchronized Object get( Object key )
+ {
+ String sKey = (String) key;
+ for ( int i = 0; i < keys.size(); ++i )
+ {
+ String thisKey = (String) keys.elementAt( i );
+ if ( match( thisKey, sKey ) )
+ return elements.elementAt( i );
+ }
+ return null;
+ }
+
+ /// Puts the specified element into the Dictionary, using the specified
+ // key. The element may be retrieved by doing a get() with the same
+ // key. The key and the element cannot be null.
+ // @param key the specified wildcard-pattern key
+ // @param value the specified element
+ // @return the old value of the key, or null if it did not have one.
+ // @exception NullPointerException If the value of the specified
+ // element is null.
+ public synchronized Object put( Object key, Object element )
+ {
+ int i = keys.indexOf( key );
+ if ( i != -1 )
+ {
+ Object oldElement = elements.elementAt( i );
+ elements.setElementAt( element, i );
+ return oldElement;
+ }
+ else
+ {
+ keys.addElement( key );
+ elements.addElement( element );
+ return null;
+ }
+ }
+
+ /// Removes the element corresponding to the key. Does nothing if the
+ // key is not present.
+ // @param key the key that needs to be removed
+ // @return the value of key, or null if the key was not found.
+ public synchronized Object remove( Object key )
+ {
+ int i = keys.indexOf( key );
+ if ( i != -1 )
+ {
+ Object oldElement = elements.elementAt( i );
+ keys.removeElementAt( i );
+ elements.removeElementAt( i );
+ return oldElement;
+ }
+ else
+ return null;
+ }
+
+ /** Checks whether a string matches a given wildcard pattern.
+ * Only does ? and *, and multiple patterns separated by |.
+ */
+ public static boolean match( String pattern, String string ) {
+ for ( int p = 0; true; ++p ) {
+ boolean cnt=true;
+ for ( int s = 0; cnt; ++p, ++s ) {
+ boolean sEnd = ( s >= string.length() );
+ boolean pEnd = ( p >= pattern.length() ||
+ pattern.charAt( p ) == '|' );
+ if ( sEnd && pEnd )
+ return true;
+ if ( sEnd || pEnd )
+ cnt=false;
+ else if ( pattern.charAt( p ) != '?' ) {
+ if ( pattern.charAt( p ) == '*' ) {
+ int i;
+ ++p;
+ for ( i = string.length(); i >= s; --i )
+ if ( match(
+ pattern.substring( p ),
+ string.substring( i ) ) ) /* not quite right */
+ return true;
+ cnt=false;
+ }
+ if ( pattern.charAt( p ) != string.charAt( s ) )
+ cnt=false;
+ }
+ }
+ p = pattern.indexOf( '|', p );
+ if ( p == -1 )
+ return false;
+ }
+ }
+ /**
+ * Deletes all elements and keys.
+ */
+ public void removeAllElements() {
+ elements.clear();
+ keys.clear();
+ }
+}