// httpd.java // ----------------------- // (C) by Michael Peter Christen; mc@anomic.de // first published on http://www.anomic.de // Frankfurt, Germany, 2004 // // last major change: $LastChangedDate$ by $LastChangedBy$ // Revision: $LastChangedRevision$ // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Using this software in any meaning (reading, learning, copying, compiling, // running) means that you agree that the Author(s) is (are) not responsible // for cost, loss of data or any harm that may be caused directly or indirectly // by usage of this softare or this documentation. The usage of this software // is on your own risk. The installation and usage (starting/running) of this // software may allow other people or application to access your computer and // any attached devices and is highly dependent on the configuration of the // software which must be done by the user of the software; the author(s) is // (are) also not responsible for proper configuration and usage of the // software, even if provoked by documentation provided together with // the software. // // Any changes to this file according to the GPL as documented in the file // gpl.txt aside this file in the shipment you received can be done to the // lines that follows this copyright notice here, but changes must not be // done inside the copyright notive above. A re-distribution must contain // the intact and unchanged copyright notice. // Contributions and changes to the program code must be marked as such. package de.anomic.http; import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.StringTokenizer; import de.anomic.data.htmlTools; import de.anomic.data.userDB; import de.anomic.kelondro.kelondroBase64Order; import de.anomic.plasma.plasmaSwitchboard; import de.anomic.server.serverByteBuffer; import de.anomic.server.serverCodings; import de.anomic.server.serverCore; import de.anomic.server.serverDomains; import de.anomic.server.serverFileUtils; import de.anomic.server.serverHandler; import de.anomic.server.serverObjects; import de.anomic.server.serverSwitch; import de.anomic.server.logging.serverLog; import de.anomic.yacy.yacyCore; import de.anomic.yacy.yacySeed; import de.anomic.yacy.yacyURL; /** * Instances of this class can be passed as argument to the serverCore. * The generic server dispatches HTTP commands and calls the * method GET, HEAD or POST in this class * these methods parse the command line and decide wether to call * a proxy servlet or a file server servlet */ public final class httpd implements serverHandler { /** *
public static final String ADMIN_ACCOUNT_B64MD5 = "adminAccountBase64MD5"
Name of the setting holding the authentification hash for the static admin
-account. It is calculated
* by first encoding username:password
as Base64 and hashing it using {@link serverCodings#encodeMD5Hex(String)}.
true
if a persistent connection was requested or false
otherwise
*/
private boolean handlePersistentConnection(httpHeader header) {
if (!keepAliveSupport) {
this.prop.put(httpHeader.CONNECTION_PROP_PERSISTENT,"close");
return false;
}
// getting the http version that is used by the client
String httpVersion = this.prop.getProperty(httpHeader.CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
// managing keep-alive: in HTTP/0.9 and HTTP/1.0 every connection is closed
// afterwards. In HTTP/1.1 (and above, in the future?) connections are
// persistent by default, but closed with the "Connection: close"
// property.
boolean persistent = !(httpVersion.equals(httpHeader.HTTP_VERSION_0_9) || httpVersion.equals(httpHeader.HTTP_VERSION_1_0));
if (((String)header.get(httpHeader.CONNECTION, "keep-alive")).toLowerCase().indexOf("close") != -1 ||
((String)header.get(httpHeader.PROXY_CONNECTION, "keep-alive")).toLowerCase().indexOf("close") != -1) {
persistent = false;
}
String transferEncoding = (String) header.get(httpHeader.TRANSFER_ENCODING, "identity");
boolean isPostRequest = this.prop.getProperty(httpHeader.CONNECTION_PROP_METHOD).equals(httpHeader.METHOD_POST);
boolean hasContentLength = header.containsKey(httpHeader.CONTENT_LENGTH);
boolean hasTransferEncoding = header.containsKey(httpHeader.TRANSFER_ENCODING) && !transferEncoding.equalsIgnoreCase("identity");
// if the request does not contain a content-length we have to close the connection
// independently of the value of the connection header
if (persistent && isPostRequest && !(hasContentLength || hasTransferEncoding))
this.prop.put(httpHeader.CONNECTION_PROP_PERSISTENT,"close");
else this.prop.put(httpHeader.CONNECTION_PROP_PERSISTENT,persistent?"keep-alive":"close");
return persistent;
}
public static int staticAdminAuthenticated(String authorization, serverSwitch sw){
if (authorization==null) return 1;
//if (authorization.length() < 6) return 1; // no authentication information given
//authorization = authorization.trim().substring(6);
String adminAccountBase64MD5 = sw.getConfig(ADMIN_ACCOUNT_B64MD5, "");
if (adminAccountBase64MD5.length() == 0) return 2; // no passwrd stored
if (adminAccountBase64MD5.equals(serverCodings.encodeMD5Hex(authorization))) return 4; // hard-authenticated, all ok
return 1;
}
private boolean handleServerAuthentication(httpHeader header) throws IOException {
// getting the http version that is used by the client
String httpVersion = this.prop.getProperty(httpHeader.CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
// reading the authentication settings from switchboard
if (this.serverAccountBase64MD5 == null)
this.serverAccountBase64MD5 = switchboard.getConfig("serverAccountBase64MD5", "");
if (this.serverAccountBase64MD5.length() > 0) {
String auth = (String) header.get(httpHeader.AUTHORIZATION);
if (auth == null) {
// authorization requested, but no authorizeation given in header. Ask for authenticate:
this.session.out.write((httpVersion + " 401 log-in required" + serverCore.CRLF_STRING +
httpHeader.WWW_AUTHENTICATE + ": Basic realm=\"log-in\"" + serverCore.CRLF_STRING +
serverCore.CRLF_STRING).getBytes());
this.session.out.write((httpHeader.CONTENT_LENGTH + ": 0\r\n").getBytes());
this.session.out.write("\r\n".getBytes());
return false;
} else if (!this.serverAccountBase64MD5.equals(serverCodings.encodeMD5Hex(auth.trim().substring(6)))) {
// wrong password given: ask for authenticate again
serverLog.logInfo("HTTPD", "Wrong log-in for account 'server' in HTTPD.GET " + this.prop.getProperty("PATH") + " from IP " + this.clientIP);
this.session.out.write((httpVersion + " 401 log-in required" + serverCore.CRLF_STRING +
httpHeader.WWW_AUTHENTICATE + ": Basic realm=\"log-in\"" +
serverCore.CRLF_STRING).getBytes());
this.session.out.write((httpHeader.CONTENT_LENGTH + ": 0\r\n").getBytes());
this.session.out.write("\r\n".getBytes());
return false;
}
}
return true;
}
private boolean handleYaCyHopAuthentication(httpHeader header) {
// check if the user has allowed that his/her peer is used for hops
if (!this.allowYaCyHop) return false;
// proxy hops must identify with 4 criteria:
// the accessed port must not be port 80
String host = this.prop.getProperty(httpHeader.CONNECTION_PROP_HOST);
if (host == null) return false;
int pos;
if ((pos = host.indexOf(":")) < 0) {
// default port 80
return false; // not allowed
} else {
if (Integer.parseInt(host.substring(pos + 1)) == 80) return false;
}
// the access path must be into the yacy protocol path; it must start with 'yacy'
if (!(this.prop.getProperty(httpHeader.CONNECTION_PROP_PATH, "").startsWith("/yacy/"))) return false;
// the accessing client must identify with user:password, where
// user = addressed peer name
// pw = addressed peer hash (b64-hash)
String auth = (String) header.get(httpHeader.PROXY_AUTHORIZATION,"xxxxxx");
String test = kelondroBase64Order.standardCoder.encodeString(yacyCore.seedDB.mySeed().getName() + ":" + yacyCore.seedDB.mySeed().hash);
if (!test.equals(auth.trim().substring(6))) return false;
// the accessing client must use a yacy user-agent
if (!(((String) header.get(httpHeader.USER_AGENT,"")).startsWith("yacy"))) return false;
// furthermore, YaCy hops must not exceed a specific access frequency
// check access requester frequency: protection against DoS against this peer
String requester = this.prop.getProperty(httpHeader.CONNECTION_PROP_CLIENTIP);
if (requester == null) return false;
if (lastAccessDelta(YaCyHopAccessRequester, requester) < 10000) return false;
YaCyHopAccessRequester.put(requester, new Long(System.currentTimeMillis()));
// check access target frequecy: protection against DoS from a single peer by several different requesters
if (lastAccessDelta(YaCyHopAccessTargets, host) < 3000) return false;
YaCyHopAccessTargets.put(host, new Long(System.currentTimeMillis()));
// passed all tests
return true;
}
private static long lastAccessDelta(HashMap accessTable, String domain) {
Long lastAccess = (Long) accessTable.get(domain);
if (lastAccess == null) return Long.MAX_VALUE; // never accessed
return System.currentTimeMillis() - lastAccess.longValue();
}
private boolean handleProxyAuthentication(httpHeader header) throws IOException {
// getting the http version that is used by the client
String httpVersion = this.prop.getProperty("HTTP", "HTTP/0.9");
// reading the authentication settings from switchboard
if (this.proxyAccounts_init == false) {
this.use_proxyAccounts = (switchboard.getConfig("use_proxyAccounts", "false").equals("true") ? true : false);
this.proxyAccounts_init = true; // is initialised
}
if (this.use_proxyAccounts) {
String auth = (String) header.get(httpHeader.PROXY_AUTHORIZATION,"xxxxxx");
userDB.Entry entry=switchboard.userDB.ipAuth(this.clientIP);
if(entry == null){
entry=switchboard.userDB.proxyAuth(auth, this.clientIP);
}
if(entry != null){
int returncode=entry.surfRight();
if(returncode==userDB.Entry.PROXY_ALLOK){
return true;
}
serverObjects tp=new serverObjects();
if(returncode==userDB.Entry.PROXY_TIMELIMIT_REACHED){
tp.put("limit", "1");//time per day
tp.put("limit_timelimit", entry.getTimeLimit());
sendRespondError(this.prop, this.session.out, 403, "Internet-Timelimit reached", new File("proxymsg/proxylimits.inc"), tp, null);
}else if(returncode==userDB.Entry.PROXY_NORIGHT){
tp.put("limit", "0");
sendRespondError(this.prop, this.session.out, 403, "Proxy use forbidden", new File("proxymsg/proxylimits.inc"), tp, null);
}
return false;
}
// ask for authenticate
this.session.out.write((httpVersion + " 407 Proxy Authentication Required" + serverCore.CRLF_STRING +
httpHeader.PROXY_AUTHENTICATE + ": Basic realm=\"log-in\"" + serverCore.CRLF_STRING).getBytes());
this.session.out.write((httpHeader.CONTENT_LENGTH + ": 0\r\n").getBytes());
this.session.out.write("\r\n".getBytes());
return false;
}
return true;
}
public Boolean UNKNOWN(String requestLine) throws IOException {
int pos;
String unknownCommand = null, args = null;
if ((pos = requestLine.indexOf(" ")) > 0) {
unknownCommand = requestLine.substring(0,pos);
args = requestLine.substring(pos+1);
} else {
unknownCommand = requestLine;
args = "";
}
parseRequestLine(unknownCommand, args);
//String httpVersion = this.prop.getProperty(httpHeader.CONNECTION_PROP_HTTP_VER,"HTTP/0.9");
sendRespondError(this.prop,this.session.out,0,501,null,unknownCommand + " method not implemented",null);
return serverCore.TERMINATE_CONNECTION;
}
public Boolean EMPTY(String arg) throws IOException {
if (++this.emptyRequestCount > 10) return serverCore.TERMINATE_CONNECTION;
return serverCore.RESUME_CONNECTION;
}
public Boolean TRACE(String arg) throws IOException {
sendRespondError(this.prop,this.session.out,0,501,null,"TRACE method not implemented",null);
return serverCore.TERMINATE_CONNECTION;
}
public Boolean OPTIONS(String arg) throws IOException {
sendRespondError(this.prop,this.session.out,0,501,null,"OPTIONS method not implemented",null);
return serverCore.TERMINATE_CONNECTION;
}
public Boolean GET(String arg) {
try {
// parsing the http request line
parseRequestLine(httpHeader.METHOD_GET,arg);
// we now know the HTTP version. depending on that, we read the header
String httpVersion = this.prop.getProperty(httpHeader.CONNECTION_PROP_HTTP_VER, httpHeader.HTTP_VERSION_0_9);
httpHeader header = (httpVersion.equals(httpHeader.HTTP_VERSION_0_9))
? new httpHeader(reverseMappingCache)
: httpHeader.readHeader(this.prop,this.session);
// handling transparent proxy support
httpHeader.handleTransparentProxySupport(header, this.prop, virtualHost, httpdProxyHandler.isTransparentProxy);
// determines if the connection should be kept alive
handlePersistentConnection(header);
if (this.prop.getProperty(httpHeader.CONNECTION_PROP_HOST).equals(virtualHost)) {
// pass to server
if (this.allowServer) {
if (this.handleServerAuthentication(header)) {
httpdFileHandler.doGet(this.prop, header, this.session.out);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
this.session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.CRLF_STRING + serverCore.CRLF_STRING + "you are not allowed to connect to this server, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("serverClient", "*") + serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
} else {
// pass to proxy
if (((this.allowYaCyHop) && (handleYaCyHopAuthentication(header))) ||
((this.allowProxy) && (handleProxyAuthentication(header)))) {
httpdProxyHandler.doGet(this.prop, header, this.session.out);
} else {
// not authorized through firewall blocking (ip does not match filter)
this.session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.CRLF_STRING + serverCore.CRLF_STRING + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
}
return this.prop.getProperty(httpHeader.CONNECTION_PROP_PERSISTENT).equals("keep-alive") ? serverCore.RESUME_CONNECTION : serverCore.TERMINATE_CONNECTION;
} catch (Exception e) {
logUnexpectedError(e);
return serverCore.TERMINATE_CONNECTION;
} finally {
this.doUserAccounting(this.prop);
}
}
private void logUnexpectedError(Exception e) {
if (e instanceof InterruptedException) {
this.log.logInfo("Interruption detected");
} else {
String errorMsg = e.getMessage();
if (errorMsg != null) {
if (errorMsg.startsWith("Socket closed")) {
this.log.logInfo("httpd shutdown detected ...");
} else if ((errorMsg.startsWith("Broken pipe") || errorMsg.startsWith("Connection reset"))) {
// client closed the connection, so we just end silently
this.log.logInfo("Client unexpectedly closed connection");
} else if (errorMsg.equals("400 Bad request")) {
this.log.logInfo("Bad client request.");
} else {
this.log.logSevere("Unexpected Error. " + e.getClass().getName() + ": " + e.getMessage(),e);
}
} else {
this.log.logSevere("Unexpected Error. " + e.getClass().getName(),e);
}
}
}
public Boolean HEAD(String arg) {
try {
parseRequestLine(httpHeader.METHOD_HEAD,arg);
// we now know the HTTP version. depending on that, we read the header
httpHeader header;
String httpVersion = this.prop.getProperty(httpHeader.CONNECTION_PROP_HTTP_VER, httpHeader.HTTP_VERSION_0_9);
if (httpVersion.equals(httpHeader.HTTP_VERSION_0_9)) header = new httpHeader(reverseMappingCache);
else header = httpHeader.readHeader(this.prop,this.session);
// handle transparent proxy support
httpHeader.handleTransparentProxySupport(header, this.prop, virtualHost, httpdProxyHandler.isTransparentProxy);
// determines if the connection should be kept alive
handlePersistentConnection(header);
// return multi-line message
if (this.prop.getProperty(httpHeader.CONNECTION_PROP_HOST).equals(virtualHost)) {
// pass to server
if (allowServer) {
if (handleServerAuthentication(header)) {
httpdFileHandler.doHead(prop, header, this.session.out);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" +
serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
} else {
// pass to proxy
if (((this.allowYaCyHop) && (handleYaCyHopAuthentication(header))) ||
((this.allowProxy) && (handleProxyAuthentication(header)))) {
httpdProxyHandler.doHead(prop, header, this.session.out);
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" +
serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
}
return this.prop.getProperty(httpHeader.CONNECTION_PROP_PERSISTENT).equals("keep-alive") ? serverCore.RESUME_CONNECTION : serverCore.TERMINATE_CONNECTION;
} catch (Exception e) {
logUnexpectedError(e);
return serverCore.TERMINATE_CONNECTION;
} finally {
this.doUserAccounting(this.prop);
}
}
public Boolean POST(String arg) {
try {
parseRequestLine(httpHeader.METHOD_POST,arg);
// we now know the HTTP version. depending on that, we read the header
httpHeader header;
String httpVersion = this.prop.getProperty(httpHeader.CONNECTION_PROP_HTTP_VER, httpHeader.HTTP_VERSION_0_9);
if (httpVersion.equals(httpHeader.HTTP_VERSION_0_9)) header = new httpHeader(reverseMappingCache);
else header = httpHeader.readHeader(this.prop,this.session);
// handle transparent proxy support
httpHeader.handleTransparentProxySupport(header, this.prop, virtualHost, httpdProxyHandler.isTransparentProxy);
// determines if the connection should be kept alive
handlePersistentConnection(header);
// return multi-line message
if (prop.getProperty(httpHeader.CONNECTION_PROP_HOST).equals(virtualHost)) {
// pass to server
if (allowServer) {
if (handleServerAuthentication(header)) {
httpdFileHandler.doPost(prop, header, this.session.out, this.session.in);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.CRLF_STRING + serverCore.CRLF_STRING + "you are not allowed to connect to this server, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("serverClient", "*") + serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
} else {
// pass to proxy
if (((this.allowYaCyHop) && (handleYaCyHopAuthentication(header))) ||
((this.allowProxy) && (handleProxyAuthentication(header)))) {
httpdProxyHandler.doPost(prop, header, this.session.out, this.session.in);
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.CRLF_STRING + serverCore.CRLF_STRING + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
}
//return serverCore.RESUME_CONNECTION;
return this.prop.getProperty(httpHeader.CONNECTION_PROP_PERSISTENT).equals("keep-alive") ? serverCore.RESUME_CONNECTION : serverCore.TERMINATE_CONNECTION;
} catch (Exception e) {
logUnexpectedError(e);
return serverCore.TERMINATE_CONNECTION;
} finally {
this.doUserAccounting(this.prop);
}
}
public Boolean CONNECT(String arg) throws IOException {
// establish a ssh-tunneled http connection
// this is to support https
// parse HTTP version
int pos = arg.indexOf(" ");
String httpVersion = "HTTP/1.0";
if (pos >= 0) {
httpVersion = arg.substring(pos + 1);
arg = arg.substring(0, pos);
}
prop.setProperty(httpHeader.CONNECTION_PROP_HTTP_VER, httpVersion);
// parse hostname and port
prop.setProperty(httpHeader.CONNECTION_PROP_HOST, arg);
pos = arg.indexOf(":");
int port = 443;
if (pos >= 0) {
port = Integer.parseInt(arg.substring(pos + 1));
arg = arg.substring(0, pos);
}
// setting other connection properties
prop.setProperty(httpHeader.CONNECTION_PROP_CLIENTIP, this.clientIP);
prop.setProperty(httpHeader.CONNECTION_PROP_METHOD, httpHeader.METHOD_CONNECT);
prop.setProperty(httpHeader.CONNECTION_PROP_PATH, "/");
prop.setProperty(httpHeader.CONNECTION_PROP_EXT, "");
prop.setProperty(httpHeader.CONNECTION_PROP_URL, "");
// parse remaining lines
httpHeader header = httpHeader.readHeader(this.prop,this.session);
if (!(allowProxy)) {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.CRLF_STRING + serverCore.CRLF_STRING + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
if (port != 443 && switchboard.getConfig("secureHttps", "true").equals("true")) {
// security: connection only to ssl port
// we send a 403 (forbidden) error back
session.out.write((httpVersion + " 403 Connection to non-443 forbidden" +
serverCore.CRLF_STRING + serverCore.CRLF_STRING).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
// pass to proxy
if (((this.allowYaCyHop) && (handleYaCyHopAuthentication(header))) ||
((this.allowProxy) && (this.handleProxyAuthentication(header)))) {
httpdProxyHandler.doConnect(prop, header, this.session.in, this.session.out);
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.CRLF_STRING + serverCore.CRLF_STRING + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.CRLF_STRING).getBytes());
}
return serverCore.TERMINATE_CONNECTION;
}
private final void parseRequestLine(String cmd, String s) {
// parsing the header
httpHeader.parseRequestLine(cmd,s,this.prop,virtualHost);
// track the request
String path = this.prop.getProperty(httpHeader.CONNECTION_PROP_URL);
String args = this.prop.getProperty(httpHeader.CONNECTION_PROP_ARGS, "");
switchboard.track(this.userAddress.getHostName(), (args.length() > 0) ? path + "?" + args : path);
// reseting the empty request counter
this.emptyRequestCount = 0;
// counting the amount of received requests within this permanent conneciton
this.prop.setProperty(httpHeader.CONNECTION_PROP_KEEP_ALIVE_COUNT, Integer.toString(++this.keepAliveRequestCount));
// setting the client-IP
this.prop.setProperty(httpHeader.CONNECTION_PROP_CLIENTIP, this.clientIP);
}
// some static methods that needs to be used from any CGI
// and also by the httpdFileHandler
// but this belongs to the protocol handler, this class.
public static int parseArgs(serverObjects args, InputStream in, int length) throws IOException {
// this is a quick hack using a previously coded parseMultipart based on a buffer
// should be replaced sometime by a 'right' implementation
byte[] buffer = null;
// parsing post request bodies with a given length
if (length != -1) {
buffer = new byte[length];
in.read(buffer);
// parsing post request bodies which are gzip content-encoded
} else {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
serverFileUtils.copy(in,bout);
buffer = bout.toByteArray();
bout.close(); bout = null;
}
int argc = parseArgs(args, new String(buffer, "UTF-8"));
buffer = null;
return argc;
}
public static int parseArgs(serverObjects args, String argsString) {
// this parses a arg string that can either be attached to a URL query
// or can be given as result of a post method
// the String argsString is supposed to be constructed as
// This method basically does the same as {@link URLDecoder#decode(String, String) URLDecoder.decode(s, "UTF-8")} * would do with the exception of more lazyness in regard to current browser implementations as they do not * always comply with the standards.
*The following replacements are performed on the input-String
:
+
'-characters are replaced by space
* %HH
'-entities are replaced by their
* respective char
-representation%uHHHH
'-entities (sent by IE although rejected by the W3C) are replaced by their respective
* char
-representationchars
already encoded in UTF-8 are url-encoded and re-decoded due to internal restrictions,
* which slows down this method unnecessarilyString
to decode, note that the encoding used to URL-encode the original
* String
has to be UTF-8 (i.e. the "accept-charset
"-property of HTML
* <form>
-elements)
* @return the "normal" Java-String
(UTF-8) represented by the input or null
* if the passed argument encoding
is not supported
*/
private static String parseArg(String s) {
int pos = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream(s.length());
while (pos < s.length()) {
if (s.charAt(pos) == '+') {
baos.write(' ');
pos++;
} else if (s.charAt(pos) == '%') {
try {
if (s.length() >= pos + 6 && (s.charAt(pos + 1) == 'u' || s.charAt(pos + 1) == 'U')) {
// non-standard encoding of IE for unicode-chars
int bh = Integer.parseInt(s.substring(pos + 2, pos + 4), 16);
int bl = Integer.parseInt(s.substring(pos + 4, pos + 6), 16);
// TODO: needs conversion from UTF-16 to UTF-8
baos.write(bh);
baos.write(bl);
pos += 6;
} else if (s.length() >= pos + 3) {
baos.write(Integer.parseInt(s.substring(pos + 1, pos + 3), 16));
pos += 3;
} else {
baos.write(s.charAt(pos++));
}
} catch (NumberFormatException e) {
baos.write(s.charAt(pos++));
}
} else if (s.charAt(pos) > 127) {
// Unicode chars sent by client, see http://www.w3.org/International/O-URL-code.html
try {
// don't write anything but url-encode the unicode char
s = s.substring(0, pos) + URLEncoder.encode(s.substring(pos, pos + 1), "UTF-8") + s.substring(pos + 1);
} catch (UnsupportedEncodingException e) { return null; }
} else {
baos.write(s.charAt(pos++));
}
}
try {
return new String(baos.toByteArray(), "UTF-8");
} catch (UnsupportedEncodingException e) { return null; }
}
// 06.01.2007: decode HTML entities by [FB]
public static String decodeHtmlEntities(String s) {
// replace all entities defined in wikiCode.characters and htmlentities
s = htmlTools.decodeHtml2Unicode(s);
// replace all other
CharArrayWriter b = new CharArrayWriter(s.length());
int end;
for (int i=0; i