0001: // httpdFileHandler.java
0002: // -----------------------
0003: // (C) by Michael Peter Christen; mc@anomic.de
0004: // first published on http://www.anomic.de
0005: // Frankfurt, Germany, 2004, 2005
0006: // last major change: 05.10.2005
0007: //
0008: // This program is free software; you can redistribute it and/or modify
0009: // it under the terms of the GNU General Public License as published by
0010: // the Free Software Foundation; either version 2 of the License, or
0011: // (at your option) any later version.
0012: //
0013: // This program is distributed in the hope that it will be useful,
0014: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: // GNU General Public License for more details.
0017: //
0018: // You should have received a copy of the GNU General Public License
0019: // along with this program; if not, write to the Free Software
0020: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0021: //
0022: // Using this software in any meaning (reading, learning, copying, compiling,
0023: // running) means that you agree that the Author(s) is (are) not responsible
0024: // for cost, loss of data or any harm that may be caused directly or indirectly
0025: // by usage of this softare or this documentation. The usage of this software
0026: // is on your own risk. The installation and usage (starting/running) of this
0027: // software may allow other people or application to access your computer and
0028: // any attached devices and is highly dependent on the configuration of the
0029: // software which must be done by the user of the software; the author(s) is
0030: // (are) also not responsible for proper configuration and usage of the
0031: // software, even if provoked by documentation provided together with
0032: // the software.
0033: //
0034: // Any changes to this file according to the GPL as documented in the file
0035: // gpl.txt aside this file in the shipment you received can be done to the
0036: // lines that follows this copyright notice here, but changes must not be
0037: // done inside the copyright notive above. A re-distribution must contain
0038: // the intact and unchanged copyright notice.
0039: // Contributions and changes to the program code must be marked as such.
0040:
0041: /*
0042: Class documentation:
0043: this class provides a file servlet and CGI interface
0044: for the httpd server.
0045: Whenever this server is addressed to load a local file,
0046: this class searches for the file in the local path as
0047: configured in the setting property 'rootPath'
0048: The servlet loads the file and returns it to the client.
0049: Every file can also act as an template for the built-in
0050: CGI interface. There is no specific path for CGI functions.
0051: CGI functionality is triggered, if for the file to-be-served
0052: 'template.html' also a file 'template.class' exists. Then,
0053: the class file is called with the GET/POST properties that
0054: are attached to the http call.
0055: Possible variable hand-over are:
0056: - form method GET
0057: - form method POST, enctype text/plain
0058: - form method POST, enctype multipart/form-data
0059: The class that creates the CGI respond must have at least one
0060: static method of the form
0061: public static java.util.Hashtable respond(java.util.HashMap, serverSwitch)
0062: In the HashMap, the GET/POST variables are handed over.
0063: The return value is a Property object that contains replacement
0064: key/value pairs for the patterns in the template file.
0065: The templates must have the form
0066: either '#['<name>']#' for single attributes, or
0067: '#{'<enumname>'}#' and '#{/'<enumname>'}#' for enumerations of
0068: values '#['<value>']#'.
0069: A single value in repetitions/enumerations in the template has
0070: the property key '_'<enumname><count>'_'<value>
0071: Please see also the example files 'test.html' and 'test.java'
0072: */
0073:
0074: package de.anomic.http;
0075:
0076: import java.awt.Image;
0077: import java.awt.image.BufferedImage;
0078: import java.io.BufferedInputStream;
0079: import java.io.ByteArrayInputStream;
0080: import java.io.File;
0081: import java.io.FileInputStream;
0082: import java.io.IOException;
0083: import java.io.InputStream;
0084: import java.io.OutputStream;
0085: import java.io.PushbackInputStream;
0086: import java.io.UnsupportedEncodingException;
0087: import java.lang.ref.SoftReference;
0088: import java.lang.reflect.InvocationTargetException;
0089: import java.lang.reflect.Method;
0090: import java.net.URLDecoder;
0091: import java.util.Date;
0092: import java.util.HashMap;
0093: import java.util.Iterator;
0094: import java.util.Map;
0095: import java.util.Properties;
0096: import java.util.logging.Level;
0097: import java.util.zip.GZIPInputStream;
0098: import java.util.zip.GZIPOutputStream;
0099:
0100: import de.anomic.plasma.plasmaParser;
0101: import de.anomic.plasma.plasmaSwitchboard;
0102: import de.anomic.server.serverByteBuffer;
0103: import de.anomic.server.serverClassLoader;
0104: import de.anomic.server.serverCore;
0105: import de.anomic.server.serverDate;
0106: import de.anomic.server.serverFileUtils;
0107: import de.anomic.server.serverObjects;
0108: import de.anomic.server.serverSwitch;
0109: import de.anomic.server.servletProperties;
0110: import de.anomic.server.logging.serverLog;
0111: import de.anomic.ymage.ymageMatrix;
0112:
0113: public final class httpdFileHandler {
0114:
0115: private static final boolean safeServletsMode = false; // if true then all servlets are called synchronized
0116:
0117: private static final Properties mimeTable = new Properties();
0118: private static final serverClassLoader provider;
0119: private static serverSwitch switchboard;
0120: private static plasmaSwitchboard sb = plasmaSwitchboard
0121: .getSwitchboard();
0122:
0123: private static File htRootPath = null;
0124: private static File htDocsPath = null;
0125: private static File htTemplatePath = null;
0126: private static String[] defaultFiles = null;
0127: private static File htDefaultPath = null;
0128: private static File htLocalePath = null;
0129:
0130: private static final HashMap<File, SoftReference<byte[]>> templateCache;
0131: private static final HashMap<File, SoftReference<Method>> templateMethodCache;
0132:
0133: public static boolean useTemplateCache = false;
0134:
0135: //private Properties connectionProperties = null;
0136: private static serverLog theLogger;
0137:
0138: static {
0139: serverSwitch switchboard = plasmaSwitchboard.getSwitchboard();
0140: useTemplateCache = switchboard.getConfig("enableTemplateCache",
0141: "true").equalsIgnoreCase("true");
0142: templateCache = (useTemplateCache) ? new HashMap<File, SoftReference<byte[]>>()
0143: : new HashMap<File, SoftReference<byte[]>>(0);
0144: templateMethodCache = (useTemplateCache) ? new HashMap<File, SoftReference<Method>>()
0145: : new HashMap<File, SoftReference<Method>>(0);
0146:
0147: // create a class loader
0148: provider = new serverClassLoader(/*this.getClass().getClassLoader()*/);
0149:
0150: // creating a logger
0151: theLogger = new serverLog("FILEHANDLER");
0152:
0153: if (httpdFileHandler.switchboard == null) {
0154: httpdFileHandler.switchboard = switchboard;
0155:
0156: if (mimeTable.size() == 0) {
0157: // load the mime table
0158: String mimeTablePath = switchboard.getConfig(
0159: "mimeConfig", "");
0160: BufferedInputStream mimeTableInputStream = null;
0161: try {
0162: serverLog.logConfig("HTTPDFiles",
0163: "Loading mime mapping file "
0164: + mimeTablePath);
0165: mimeTableInputStream = new BufferedInputStream(
0166: new FileInputStream(new File(switchboard
0167: .getRootPath(), mimeTablePath)));
0168: mimeTable.load(mimeTableInputStream);
0169: } catch (Exception e) {
0170: serverLog.logSevere("HTTPDFiles",
0171: "ERROR: path to configuration file or configuration invalid\n"
0172: + e);
0173: System.exit(1);
0174: } finally {
0175: if (mimeTableInputStream != null)
0176: try {
0177: mimeTableInputStream.close();
0178: } catch (Exception e1) {
0179: }
0180: }
0181: }
0182:
0183: // create default files array
0184: initDefaultPath();
0185:
0186: // create a htRootPath: system pages
0187: if (htRootPath == null) {
0188: htRootPath = new File(switchboard.getRootPath(),
0189: switchboard.getConfig("htRootPath", "htroot"));
0190: if (!(htRootPath.exists()))
0191: htRootPath.mkdir();
0192: }
0193:
0194: // create a htDocsPath: user defined pages
0195: if (htDocsPath == null) {
0196: htDocsPath = switchboard.getConfigPath(
0197: plasmaSwitchboard.HTDOCS_PATH,
0198: plasmaSwitchboard.HTDOCS_PATH_DEFAULT);
0199: if (!(htDocsPath.exists()))
0200: htDocsPath.mkdir();
0201: }
0202:
0203: // create a htTemplatePath
0204: if (htTemplatePath == null) {
0205: htTemplatePath = switchboard.getConfigPath(
0206: "htTemplatePath", "htroot/env/templates");
0207: if (!(htTemplatePath.exists()))
0208: htTemplatePath.mkdir();
0209: }
0210: //This is now handles by #%env/templates/foo%#
0211: //if (templates.size() == 0) templates.putAll(httpTemplate.loadTemplates(htTemplatePath));
0212:
0213: // create htLocaleDefault, htLocalePath
0214: if (htDefaultPath == null)
0215: htDefaultPath = switchboard.getConfigPath(
0216: "htDefaultPath", "htroot");
0217: if (htLocalePath == null)
0218: htLocalePath = switchboard.getConfigPath(
0219: "locale.translated_html", "DATA/LOCALE/htroot");
0220: }
0221:
0222: }
0223:
0224: public static final void initDefaultPath() {
0225: // create default files array
0226: defaultFiles = switchboard.getConfig("defaultFiles",
0227: "index.html").split(",");
0228: if (defaultFiles.length == 0)
0229: defaultFiles = new String[] { "index.html" };
0230: }
0231:
0232: /** Returns a path to the localized or default file according to the locale.language (from he switchboard)
0233: * @param path relative from htroot */
0234: public static File getLocalizedFile(String path) {
0235: return getLocalizedFile(path, switchboard.getConfig(
0236: "locale.language", "default"));
0237: }
0238:
0239: /** Returns a path to the localized or default file according to the parameter localeSelection
0240: * @param path relative from htroot
0241: * @param localeSelection language of localized file; locale.language from switchboard is used if localeSelection.equals("") */
0242: public static File getLocalizedFile(String path,
0243: String localeSelection) {
0244: if (htDefaultPath == null)
0245: htDefaultPath = switchboard.getConfigPath("htDefaultPath",
0246: "htroot");
0247: if (htLocalePath == null)
0248: htLocalePath = switchboard.getConfigPath(
0249: "locale.translated_html", "DATA/LOCALE/htroot");
0250:
0251: if (!(localeSelection.equals("default"))) {
0252: File localePath = new File(htLocalePath, localeSelection
0253: + "/" + path);
0254: if (localePath.exists()) // avoid "NoSuchFile" troubles if the "localeSelection" is misspelled
0255: return localePath;
0256: }
0257: return new File(htDefaultPath, path);
0258: }
0259:
0260: // private void textMessage(OutputStream out, int retcode, String body) throws IOException {
0261: // httpd.sendRespondHeader(
0262: // this.connectionProperties, // the connection properties
0263: // out, // the output stream
0264: // "HTTP/1.1", // the http version that should be used
0265: // retcode, // the http status code
0266: // null, // the http status message
0267: // "text/plain", // the mimetype
0268: // body.length(), // the content length
0269: // httpc.nowDate(), // the modification date
0270: // null, // the expires date
0271: // null, // cookies
0272: // null, // content encoding
0273: // null); // transfer encoding
0274: // out.write(body.getBytes());
0275: // out.flush();
0276: // }
0277:
0278: private static final httpHeader getDefaultHeaders(String path) {
0279: httpHeader headers = new httpHeader();
0280: String ext;
0281: int pos;
0282: if ((pos = path.lastIndexOf('.')) < 0) {
0283: ext = "";
0284: } else {
0285: ext = path.substring(pos + 1).toLowerCase();
0286: }
0287: headers.put(httpHeader.SERVER, "AnomicHTTPD (www.anomic.de)");
0288: headers.put(httpHeader.DATE, httpc.dateString(httpc.nowDate()));
0289: if (!(plasmaParser.mediaExtContains(ext))) {
0290: headers.put(httpHeader.PRAGMA, "no-cache");
0291: }
0292: return headers;
0293: }
0294:
0295: public static void doGet(Properties conProp,
0296: httpHeader requestHeader, OutputStream response) {
0297: doResponse(conProp, requestHeader, response, null);
0298: }
0299:
0300: public static void doHead(Properties conProp,
0301: httpHeader requestHeader, OutputStream response) {
0302: doResponse(conProp, requestHeader, response, null);
0303: }
0304:
0305: public static void doPost(Properties conProp,
0306: httpHeader requestHeader, OutputStream response,
0307: PushbackInputStream body) {
0308: doResponse(conProp, requestHeader, response, body);
0309: }
0310:
0311: public static void doResponse(Properties conProp,
0312: httpHeader requestHeader, OutputStream out, InputStream body) {
0313:
0314: String path = null;
0315: try {
0316: // getting some connection properties
0317: String method = conProp
0318: .getProperty(httpHeader.CONNECTION_PROP_METHOD);
0319: path = conProp.getProperty(httpHeader.CONNECTION_PROP_PATH);
0320: String argsString = conProp
0321: .getProperty(httpHeader.CONNECTION_PROP_ARGS); // is null if no args were given
0322: String httpVersion = conProp
0323: .getProperty(httpHeader.CONNECTION_PROP_HTTP_VER);
0324:
0325: // check hack attacks in path
0326: if (path.indexOf("..") >= 0) {
0327: httpd.sendRespondError(conProp, out, 4, 403, null,
0328: "Access not allowed", null);
0329: return;
0330: }
0331:
0332: // url decoding of path
0333: try {
0334: path = URLDecoder.decode(path, "UTF-8");
0335: } catch (UnsupportedEncodingException e) {
0336: // This should never occur
0337: assert (false) : "UnsupportedEncodingException: "
0338: + e.getMessage();
0339: }
0340:
0341: // check permission/granted access
0342: String authorization = (String) requestHeader
0343: .get(httpHeader.AUTHORIZATION);
0344: String adminAccountBase64MD5 = switchboard.getConfig(
0345: httpd.ADMIN_ACCOUNT_B64MD5, "");
0346:
0347: int pos = path.lastIndexOf(".");
0348:
0349: if ((path.substring(0, (pos == -1) ? path.length() : pos))
0350: .endsWith("_p")
0351: && (adminAccountBase64MD5.length() != 0)) {
0352: //authentication required
0353: //userDB
0354: if (sb.userDB.hasAdminRight(authorization, conProp
0355: .getProperty("CLIENTIP"), requestHeader
0356: .getHeaderCookies())) {
0357: //Authentication successful. remove brute-force flag
0358: serverCore.bfHost.remove(conProp
0359: .getProperty("CLIENTIP"));
0360: //static
0361: } else if (authorization != null
0362: && httpd.staticAdminAuthenticated(authorization
0363: .trim().substring(6), switchboard) == 4) {
0364: //Authentication successful. remove brute-force flag
0365: serverCore.bfHost.remove(conProp
0366: .getProperty("CLIENTIP"));
0367: //no auth
0368: } else if (authorization == null) {
0369: // no authorization given in response. Ask for that
0370: httpHeader headers = getDefaultHeaders(path);
0371: headers.put(httpHeader.WWW_AUTHENTICATE,
0372: "Basic realm=\"admin log-in\"");
0373: //httpd.sendRespondHeader(conProp,out,httpVersion,401,headers);
0374: servletProperties tp = new servletProperties();
0375: tp.put("returnto", path);
0376: //TODO: separate errorpage Wrong Login / No Login
0377: httpd.sendRespondError(conProp, out, 5, 401,
0378: "Wrong Authentication", "", new File(
0379: "proxymsg/authfail.inc"), tp, null,
0380: headers);
0381: return;
0382: } else {
0383: // a wrong authentication was given or the userDB user does not have admin access. Ask again
0384: String clientIP = conProp.getProperty("CLIENTIP",
0385: "unknown-host");
0386: serverLog.logInfo("HTTPD",
0387: "Wrong log-in for account 'admin' in http file handler for path '"
0388: + path + "' from host '" + clientIP
0389: + "'");
0390: Integer attempts = (Integer) serverCore.bfHost
0391: .get(clientIP);
0392: if (attempts == null)
0393: serverCore.bfHost.put(clientIP, new Integer(1));
0394: else
0395: serverCore.bfHost.put(clientIP, new Integer(
0396: attempts.intValue() + 1));
0397:
0398: httpHeader headers = getDefaultHeaders(path);
0399: headers.put(httpHeader.WWW_AUTHENTICATE,
0400: "Basic realm=\"admin log-in\"");
0401: httpd.sendRespondHeader(conProp, out, httpVersion,
0402: 401, headers);
0403: return;
0404: }
0405: }
0406:
0407: // parse arguments
0408: serverObjects args = new serverObjects();
0409: int argc;
0410: if (argsString == null) {
0411: // no args here, maybe a POST with multipart extension
0412: int length = 0;
0413: //System.out.println("HEADER: " + requestHeader.toString()); // DEBUG
0414: if (method.equals(httpHeader.METHOD_POST)) {
0415:
0416: GZIPInputStream gzipBody = null;
0417: if (requestHeader
0418: .containsKey(httpHeader.CONTENT_LENGTH)) {
0419: length = Integer
0420: .parseInt((String) requestHeader
0421: .get(httpHeader.CONTENT_LENGTH));
0422: } else if (requestHeader.gzip()) {
0423: length = -1;
0424: gzipBody = new GZIPInputStream(body);
0425: }
0426: // } else {
0427: // httpd.sendRespondError(conProp,out,4,403,null,"bad post values",null);
0428: // return;
0429: // }
0430:
0431: // if its a POST, it can be either multipart or as args in the body
0432: if ((requestHeader
0433: .containsKey(httpHeader.CONTENT_TYPE))
0434: && (((String) requestHeader
0435: .get(httpHeader.CONTENT_TYPE))
0436: .toLowerCase()
0437: .startsWith("multipart"))) {
0438: // parse multipart
0439: HashMap<String, byte[]> files = httpd
0440: .parseMultipart(requestHeader, args,
0441: (gzipBody != null) ? gzipBody
0442: : body, length);
0443: // integrate these files into the args
0444: if (files != null) {
0445: Iterator<Map.Entry<String, byte[]>> fit = files
0446: .entrySet().iterator();
0447: Map.Entry<String, byte[]> entry;
0448: while (fit.hasNext()) {
0449: entry = fit.next();
0450: args.put(entry.getKey() + "$file",
0451: entry.getValue());
0452: }
0453: }
0454: argc = Integer.parseInt((String) requestHeader
0455: .get("ARGC"));
0456: } else {
0457: // parse args in body
0458: argc = httpd.parseArgs(args,
0459: (gzipBody != null) ? gzipBody : body,
0460: length);
0461: }
0462: } else {
0463: // no args
0464: argsString = null;
0465: args = null;
0466: argc = 0;
0467: }
0468: } else {
0469: // simple args in URL (stuff after the "?")
0470: argc = httpd.parseArgs(args, argsString);
0471: }
0472:
0473: // check for cross site scripting - attacks in request arguments
0474: if (argc > 0) {
0475: // check all values for occurrences of script values
0476: Iterator<String> e = args.values().iterator(); // enumeration of values
0477: String val;
0478: while (e.hasNext()) {
0479: val = e.next();
0480: if ((val != null) && (val.indexOf("<script") >= 0)) {
0481: // deny request
0482: httpd.sendRespondError(conProp, out, 4, 403,
0483: null, "bad post values", null);
0484: return;
0485: }
0486: }
0487: }
0488:
0489: // we are finished with parsing
0490: // the result of value hand-over is in args and argc
0491: if (path.length() == 0) {
0492: httpd.sendRespondError(conProp, out, 4, 400, null,
0493: "Bad Request", null);
0494: out.flush();
0495: return;
0496: }
0497: File targetClass = null;
0498:
0499: // locate the file
0500: if (!(path.startsWith("/")))
0501: path = "/" + path; // attach leading slash
0502:
0503: // a different language can be desired (by i.e. ConfigBasic.html) than the one stored in the locale.language
0504: String localeSelection = switchboard.getConfig(
0505: "locale.language", "default");
0506: if (args != null && (args.containsKey("language"))) {
0507: // TODO 9.11.06 Bost: a class with information about available languages is needed.
0508: // the indexOf(".") is just a workaround because there from ConfigLanguage.html commes "de.lng" and
0509: // from ConfigBasic.html comes just "de" in the "language" parameter
0510: localeSelection = args.get("language", localeSelection);
0511: if (localeSelection.indexOf(".") != -1)
0512: localeSelection = localeSelection.substring(0,
0513: localeSelection.indexOf("."));
0514: }
0515:
0516: File targetFile = getLocalizedFile(path, localeSelection);
0517: String targetExt = conProp.getProperty("EXT", "");
0518: targetClass = rewriteClassFile(new File(htDefaultPath, path));
0519: if (path.endsWith("/")) {
0520: String testpath;
0521: // attach default file name
0522: for (int i = 0; i < defaultFiles.length; i++) {
0523: testpath = path + defaultFiles[i];
0524: targetFile = getOverlayedFile(testpath);
0525: targetClass = getOverlayedClass(testpath);
0526: if (targetFile.exists()) {
0527: path = testpath;
0528: break;
0529: }
0530: }
0531: //no defaultfile, send a dirlisting
0532: if (targetFile == null || !targetFile.exists()) {
0533: String dirlistFormat = (args == null) ? "html"
0534: : args.get("format", "html");
0535: targetExt = dirlistFormat; // this is needed to set the mime type correctly
0536: targetFile = getOverlayedFile("/htdocsdefault/dir."
0537: + dirlistFormat);
0538: targetClass = getOverlayedClass("/htdocsdefault/dir."
0539: + dirlistFormat);
0540: if (!((targetFile != null && targetFile.exists()) && (targetClass != null && targetClass
0541: .exists()))) {
0542: httpd.sendRespondError(conProp, out, 3, 500,
0543: "dir." + dirlistFormat
0544: + " or dir.class not found.",
0545: null, null);
0546: }
0547: }
0548: } else {
0549: //XXX: you cannot share a .png/.gif file with a name like a class in htroot.
0550: if (!(targetFile.exists())
0551: && !((path.endsWith("png")
0552: || path.endsWith("gif") || path
0553: .endsWith(".stream")) && targetClass != null)) {
0554: targetFile = new File(htDocsPath, path);
0555: targetClass = rewriteClassFile(new File(htDocsPath,
0556: path));
0557: }
0558:
0559: }
0560:
0561: //File targetClass = rewriteClassFile(targetFile);
0562: //We need tp here
0563: servletProperties tp = new servletProperties();
0564: Date targetDate;
0565: boolean nocache = false;
0566:
0567: if ((targetClass != null) && (path.endsWith("png"))) {
0568: // call an image-servlet to produce an on-the-fly - generated image
0569: Object img = null;
0570: try {
0571: requestHeader.put(
0572: httpHeader.CONNECTION_PROP_CLIENTIP,
0573: conProp.getProperty("CLIENTIP"));
0574: requestHeader.put(httpHeader.CONNECTION_PROP_PATH,
0575: path);
0576: // in case that there are no args given, args = null or empty hashmap
0577: img = invokeServlet(targetClass, requestHeader,
0578: args);
0579: } catch (InvocationTargetException e) {
0580: theLogger.logSevere("INTERNAL ERROR: "
0581: + e.toString()
0582: + ":"
0583: + e.getMessage()
0584: + " target exception at "
0585: + targetClass
0586: + ": "
0587: + e.getTargetException().toString()
0588: + ":"
0589: + e.getTargetException().getMessage()
0590: + "; java.awt.graphicsenv='"
0591: + System.getProperty(
0592: "java.awt.graphicsenv", "") + "'",
0593: e);
0594: targetClass = null;
0595: }
0596: if (img == null) {
0597: // error with image generation; send file-not-found
0598: httpd.sendRespondError(conProp, out, 3, 404,
0599: "File not Found", null, null);
0600: } else {
0601: if (img instanceof ymageMatrix) {
0602: ymageMatrix yp = (ymageMatrix) img;
0603: // send an image to client
0604: targetDate = new Date(System
0605: .currentTimeMillis());
0606: nocache = true;
0607: String mimeType = mimeTable.getProperty(
0608: targetExt, "text/html");
0609: serverByteBuffer result = ymageMatrix
0610: .exportImage(yp.getImage(), targetExt);
0611:
0612: // write the array to the client
0613: httpd.sendRespondHeader(conProp, out,
0614: httpVersion, 200, null, mimeType,
0615: result.length(), targetDate, null,
0616: null, null, null, nocache);
0617: if (!method.equals(httpHeader.METHOD_HEAD)) {
0618: result.writeTo(out);
0619: }
0620: }
0621: if (img instanceof Image) {
0622: Image i = (Image) img;
0623: // send an image to client
0624: targetDate = new Date(System
0625: .currentTimeMillis());
0626: nocache = true;
0627: String mimeType = mimeTable.getProperty(
0628: targetExt, "text/html");
0629:
0630: // generate an byte array from the generated image
0631: int width = i.getWidth(null);
0632: if (width < 0)
0633: width = 96; // bad hack
0634: int height = i.getHeight(null);
0635: if (height < 0)
0636: height = 96; // bad hack
0637: BufferedImage bi = new BufferedImage(width,
0638: height, BufferedImage.TYPE_INT_RGB);
0639: bi.createGraphics().drawImage(i, 0, 0, width,
0640: height, null);
0641: serverByteBuffer result = ymageMatrix
0642: .exportImage(bi, targetExt);
0643:
0644: // write the array to the client
0645: httpd.sendRespondHeader(conProp, out,
0646: httpVersion, 200, null, mimeType,
0647: result.length(), targetDate, null,
0648: null, null, null, nocache);
0649: if (!method.equals(httpHeader.METHOD_HEAD)) {
0650: result.writeTo(out);
0651: }
0652: }
0653: }
0654: } else if ((targetClass != null)
0655: && (path.endsWith(".stream"))) {
0656: // call rewrite-class
0657: requestHeader.put(httpHeader.CONNECTION_PROP_CLIENTIP,
0658: conProp.getProperty("CLIENTIP"));
0659: requestHeader
0660: .put(httpHeader.CONNECTION_PROP_PATH, path);
0661: //requestHeader.put(httpHeader.CONNECTION_PROP_INPUTSTREAM, body);
0662: //requestHeader.put(httpHeader.CONNECTION_PROP_OUTPUTSTREAM, out);
0663:
0664: httpd.sendRespondHeader(conProp, out, httpVersion, 200,
0665: null);
0666:
0667: // in case that there are no args given, args = null or empty hashmap
0668: /* servletProperties tp = (servlerObjects) */invokeServlet(
0669: targetClass, requestHeader, args);
0670: forceConnectionClose(conProp);
0671: return;
0672: } else if ((targetFile.exists()) && (targetFile.canRead())) {
0673: // we have found a file that can be written to the client
0674: // if this file uses templates, then we use the template
0675: // re-write - method to create an result
0676: String mimeType = mimeTable.getProperty(targetExt,
0677: "text/html");
0678: boolean zipContent = requestHeader.acceptGzip()
0679: && httpd.shallTransportZipped("."
0680: + conProp.getProperty("EXT", ""));
0681: if (path.endsWith("html") || path.endsWith("xml")
0682: || path.endsWith("rdf") || path.endsWith("rss")
0683: || path.endsWith("csv") || path.endsWith("pac")
0684: || path.endsWith("src") || path.endsWith("vcf")
0685: || path.endsWith("/")
0686: || path.equals("/robots.txt")) {
0687:
0688: /*targetFile = getLocalizedFile(path);
0689: if (!(targetFile.exists())) {
0690: // try to find that file in the htDocsPath
0691: File trialFile = new File(htDocsPath, path);
0692: if (trialFile.exists()) targetFile = trialFile;
0693: }*/
0694:
0695: // call rewrite-class
0696: if (targetClass == null) {
0697: targetDate = new Date(targetFile.lastModified());
0698: } else {
0699: // CGI-class: call the class to create a property for rewriting
0700: try {
0701: requestHeader
0702: .put(
0703: httpHeader.CONNECTION_PROP_CLIENTIP,
0704: conProp
0705: .getProperty("CLIENTIP"));
0706: requestHeader.put(
0707: httpHeader.CONNECTION_PROP_PATH,
0708: path);
0709: // in case that there are no args given, args = null or empty hashmap
0710: Object tmp = invokeServlet(targetClass,
0711: requestHeader, args);
0712: if (tmp == null) {
0713: // if no args given, then tp will be an empty Hashtable object (not null)
0714: tp = new servletProperties();
0715: } else if (tmp instanceof servletProperties) {
0716: tp = (servletProperties) tmp;
0717: } else {
0718: tp = new servletProperties(
0719: (serverObjects) tmp);
0720: }
0721: // check if the servlets requests authentification
0722: if (tp
0723: .containsKey(servletProperties.ACTION_AUTHENTICATE)) {
0724: // handle brute-force protection
0725: if (authorization != null) {
0726: String clientIP = conProp
0727: .getProperty("CLIENTIP",
0728: "unknown-host");
0729: serverLog.logInfo("HTTPD",
0730: "dynamic log-in for account 'admin' in http file handler for path '"
0731: + path
0732: + "' from host '"
0733: + clientIP + "'");
0734: Integer attempts = (Integer) serverCore.bfHost
0735: .get(clientIP);
0736: if (attempts == null)
0737: serverCore.bfHost.put(clientIP,
0738: new Integer(1));
0739: else
0740: serverCore.bfHost
0741: .put(
0742: clientIP,
0743: new Integer(
0744: attempts
0745: .intValue() + 1));
0746: }
0747: // send authentication request to browser
0748: httpHeader headers = getDefaultHeaders(path);
0749: headers
0750: .put(
0751: httpHeader.WWW_AUTHENTICATE,
0752: "Basic realm=\""
0753: + tp
0754: .get(
0755: servletProperties.ACTION_AUTHENTICATE,
0756: "")
0757: + "\"");
0758: httpd.sendRespondHeader(conProp, out,
0759: httpVersion, 401, headers);
0760: return;
0761: } else if (tp
0762: .containsKey(servletProperties.ACTION_LOCATION)) {
0763: String location = tp
0764: .get(
0765: servletProperties.ACTION_LOCATION,
0766: "");
0767: if (location.length() == 0)
0768: location = path;
0769:
0770: httpHeader headers = getDefaultHeaders(path);
0771: headers.setCookieVector(tp
0772: .getOutgoingHeader()
0773: .getCookieVector()); //put the cookies into the new header TODO: can we put all headerlines, without trouble?
0774: headers.put(httpHeader.LOCATION,
0775: location);
0776: httpd.sendRespondHeader(conProp, out,
0777: httpVersion, 302, headers);
0778: return;
0779: }
0780: // add the application version, the uptime and the client name to every rewrite table
0781: tp.put(servletProperties.PEER_STAT_VERSION,
0782: switchboard
0783: .getConfig("version", ""));
0784: tp
0785: .put(
0786: servletProperties.PEER_STAT_UPTIME,
0787: ((System
0788: .currentTimeMillis() - serverCore.startupTime) / 1000) / 60); // uptime in minutes
0789: tp
0790: .put(
0791: servletProperties.PEER_STAT_CLIENTNAME,
0792: switchboard.getConfig(
0793: "peerName",
0794: "anomic"));
0795: tp.put(servletProperties.PEER_STAT_MYTIME,
0796: serverDate.formatShortSecond());
0797: //System.out.println("respond props: " + ((tp == null) ? "null" : tp.toString())); // debug
0798: } catch (InvocationTargetException e) {
0799: if (e.getCause() instanceof InterruptedException) {
0800: throw new InterruptedException(e
0801: .getCause().getMessage());
0802: }
0803:
0804: theLogger.logSevere("INTERNAL ERROR: "
0805: + e.toString()
0806: + ":"
0807: + e.getMessage()
0808: + " target exception at "
0809: + targetClass
0810: + ": "
0811: + e.getTargetException().toString()
0812: + ":"
0813: + e.getTargetException()
0814: .getMessage(), e);
0815: targetClass = null;
0816: throw e;
0817: }
0818: targetDate = new Date(System
0819: .currentTimeMillis());
0820: nocache = true;
0821: }
0822:
0823: // rewrite the file
0824: InputStream fis = null;
0825:
0826: // read the file/template
0827: byte[] templateContent = null;
0828: if (useTemplateCache) {
0829: long fileSize = targetFile.length();
0830: if (fileSize <= 512 * 1024) {
0831: // read from cache
0832: SoftReference<byte[]> ref = templateCache
0833: .get(targetFile);
0834: if (ref != null) {
0835: templateContent = (byte[]) ref.get();
0836: if (templateContent == null)
0837: templateCache.remove(targetFile);
0838: }
0839:
0840: if (templateContent == null) {
0841: // loading the content of the template file into
0842: // a byte array
0843: templateContent = serverFileUtils
0844: .read(targetFile);
0845:
0846: // storing the content into the cache
0847: ref = new SoftReference<byte[]>(
0848: templateContent);
0849: templateCache.put(targetFile, ref);
0850: if (theLogger.isLoggable(Level.FINEST))
0851: theLogger
0852: .logFinest("Cache MISS for file "
0853: + targetFile);
0854: } else {
0855: if (theLogger.isLoggable(Level.FINEST))
0856: theLogger
0857: .logFinest("Cache HIT for file "
0858: + targetFile);
0859: }
0860:
0861: // creating an inputstream needed by the template
0862: // rewrite function
0863: fis = new ByteArrayInputStream(
0864: templateContent);
0865: templateContent = null;
0866: } else {
0867: // read from file directly
0868: fis = new BufferedInputStream(
0869: new FileInputStream(targetFile));
0870: }
0871: } else {
0872: fis = new BufferedInputStream(
0873: new FileInputStream(targetFile));
0874: }
0875:
0876: // write the array to the client
0877: // we can do that either in standard mode (whole thing completely) or in chunked mode
0878: // since yacy clients do not understand chunked mode (yet), we use this only for communication with the administrator
0879: boolean yacyClient = requestHeader.userAgent()
0880: .startsWith("yacy");
0881: boolean chunked = !method
0882: .equals(httpHeader.METHOD_HEAD)
0883: && !yacyClient
0884: && httpVersion
0885: .equals(httpHeader.HTTP_VERSION_1_1);
0886: if (chunked) {
0887: // send page in chunks and parse SSIs
0888: serverByteBuffer o = new serverByteBuffer();
0889: // apply templates
0890: httpTemplate.writeTemplate(fis, o, tp,
0891: "-UNRESOLVED_PATTERN-"
0892: .getBytes("UTF-8"));
0893: httpd.sendRespondHeader(conProp, out,
0894: httpVersion, 200, null, mimeType, -1,
0895: targetDate, null, tp
0896: .getOutgoingHeader(), null,
0897: "chunked", nocache);
0898: // send the content in chunked parts, see RFC 2616 section 3.6.1
0899: httpChunkedOutputStream chos = new httpChunkedOutputStream(
0900: out);
0901: httpSSI.writeSSI(o, chos, authorization);
0902: //chos.write(result);
0903: chos.finish();
0904: } else {
0905: // send page as whole thing, SSIs are not possible
0906: String contentEncoding = (zipContent) ? "gzip"
0907: : null;
0908: // apply templates
0909: serverByteBuffer o1 = new serverByteBuffer();
0910: httpTemplate.writeTemplate(fis, o1, tp,
0911: "-UNRESOLVED_PATTERN-"
0912: .getBytes("UTF-8"));
0913:
0914: serverByteBuffer o = new serverByteBuffer();
0915:
0916: if (zipContent) {
0917: GZIPOutputStream zippedOut = new GZIPOutputStream(
0918: o);
0919: httpSSI.writeSSI(o1, zippedOut,
0920: authorization);
0921: //httpTemplate.writeTemplate(fis, zippedOut, tp, "-UNRESOLVED_PATTERN-".getBytes("UTF-8"));
0922: zippedOut.finish();
0923: zippedOut.flush();
0924: zippedOut.close();
0925: zippedOut = null;
0926: } else {
0927: httpSSI.writeSSI(o1, o, authorization);
0928: //httpTemplate.writeTemplate(fis, o, tp, "-UNRESOLVED_PATTERN-".getBytes("UTF-8"));
0929: }
0930: if (method.equals(httpHeader.METHOD_HEAD)) {
0931: httpd.sendRespondHeader(conProp, out,
0932: httpVersion, 200, null, mimeType, o
0933: .length(), targetDate,
0934: null, tp.getOutgoingHeader(),
0935: contentEncoding, null, nocache);
0936: } else {
0937: byte[] result = o.getBytes(); // this interrupts streaming (bad idea!)
0938: httpd.sendRespondHeader(conProp, out,
0939: httpVersion, 200, null, mimeType,
0940: result.length, targetDate, null, tp
0941: .getOutgoingHeader(),
0942: contentEncoding, null, nocache);
0943: serverFileUtils.write(result, out);
0944: }
0945: }
0946: } else { // no html
0947:
0948: int statusCode = 200;
0949: int rangeStartOffset = 0;
0950: httpHeader header = new httpHeader();
0951:
0952: // adding the accept ranges header
0953: header.put(httpHeader.ACCEPT_RANGES, "bytes");
0954:
0955: // reading the files md5 hash if availabe and use it as ETAG of the resource
0956: String targetMD5 = null;
0957: File targetMd5File = new File(targetFile + ".md5");
0958: try {
0959: if (targetMd5File.exists()) {
0960: //String description = null;
0961: targetMD5 = new String(serverFileUtils
0962: .read(targetMd5File));
0963: pos = targetMD5.indexOf('\n');
0964: if (pos >= 0) {
0965: //description = targetMD5.substring(pos + 1);
0966: targetMD5 = targetMD5.substring(0, pos);
0967: }
0968:
0969: // using the checksum as ETAG header
0970: header.put(httpHeader.ETAG, targetMD5);
0971: }
0972: } catch (IOException e) {
0973: e.printStackTrace();
0974: }
0975:
0976: if (requestHeader.containsKey(httpHeader.RANGE)) {
0977: Object ifRange = requestHeader.ifRange();
0978: if ((ifRange == null)
0979: || (ifRange instanceof Date && targetFile
0980: .lastModified() == ((Date) ifRange)
0981: .getTime())
0982: || (ifRange instanceof String && ifRange
0983: .equals(targetMD5))) {
0984: String rangeHeaderVal = ((String) requestHeader
0985: .get(httpHeader.RANGE)).trim();
0986: if (rangeHeaderVal.startsWith("bytes=")) {
0987: String rangesVal = rangeHeaderVal
0988: .substring("bytes=".length());
0989: String[] ranges = rangesVal.split(",");
0990: if ((ranges.length == 1)
0991: && (ranges[0].endsWith("-"))) {
0992: rangeStartOffset = Integer
0993: .valueOf(
0994: ranges[0]
0995: .substring(
0996: 0,
0997: ranges[0]
0998: .length() - 1))
0999: .intValue();
1000: statusCode = 206;
1001: if (header == null)
1002: header = new httpHeader();
1003: header
1004: .put(
1005: httpHeader.CONTENT_RANGE,
1006: "bytes "
1007: + rangeStartOffset
1008: + "-"
1009: + (targetFile
1010: .length() - 1)
1011: + "/"
1012: + targetFile
1013: .length());
1014: }
1015: }
1016: }
1017: }
1018:
1019: // write the file to the client
1020: targetDate = new Date(targetFile.lastModified());
1021: long contentLength = (zipContent) ? -1 : targetFile
1022: .length()
1023: - rangeStartOffset;
1024: String contentEncoding = (zipContent) ? "gzip"
1025: : null;
1026: String transferEncoding = (!httpVersion
1027: .equals(httpHeader.HTTP_VERSION_1_1)) ? null
1028: : (zipContent) ? "chunked" : null;
1029: if (!httpVersion
1030: .equals(httpHeader.HTTP_VERSION_1_1)
1031: && zipContent)
1032: forceConnectionClose(conProp);
1033:
1034: httpd.sendRespondHeader(conProp, out, httpVersion,
1035: statusCode, null, mimeType, contentLength,
1036: targetDate, null, header, contentEncoding,
1037: transferEncoding, nocache);
1038:
1039: if (!method.equals(httpHeader.METHOD_HEAD)) {
1040: httpChunkedOutputStream chunkedOut = null;
1041: GZIPOutputStream zipped = null;
1042: OutputStream newOut = out;
1043:
1044: if (transferEncoding != null) {
1045: chunkedOut = new httpChunkedOutputStream(
1046: newOut);
1047: newOut = chunkedOut;
1048: }
1049: if (contentEncoding != null) {
1050: zipped = new GZIPOutputStream(newOut);
1051: newOut = zipped;
1052: }
1053:
1054: serverFileUtils.copyRange(targetFile, newOut,
1055: rangeStartOffset);
1056:
1057: if (zipped != null) {
1058: zipped.flush();
1059: zipped.finish();
1060: }
1061: if (chunkedOut != null) {
1062: chunkedOut.finish();
1063: }
1064: }
1065:
1066: // check mime type again using the result array: these are 'magics'
1067: // if (serverByteBuffer.equals(result, 1, "PNG".getBytes())) mimeType = mimeTable.getProperty("png","text/html");
1068: // else if (serverByteBuffer.equals(result, 0, "GIF89".getBytes())) mimeType = mimeTable.getProperty("gif","text/html");
1069: // else if (serverByteBuffer.equals(result, 6, "JFIF".getBytes())) mimeType = mimeTable.getProperty("jpg","text/html");
1070: //System.out.print("MAGIC:"); for (int i = 0; i < 10; i++) System.out.print(Integer.toHexString((int) result[i]) + ","); System.out.println();
1071: }
1072: } else {
1073: httpd.sendRespondError(conProp, out, 3, 404,
1074: "File not Found", null, null);
1075: return;
1076: }
1077: } catch (Exception e) {
1078: try {
1079: // doing some errorhandling ...
1080: int httpStatusCode = 400;
1081: String httpStatusText = null;
1082: StringBuffer errorMessage = new StringBuffer();
1083: Exception errorExc = null;
1084:
1085: String errorMsg = e.getMessage();
1086: if ((e instanceof InterruptedException)
1087: || ((errorMsg != null)
1088: && (errorMsg
1089: .startsWith("Socket closed")) && (Thread
1090: .currentThread().isInterrupted()))) {
1091: errorMessage
1092: .append("Interruption detected while processing query.");
1093: httpStatusCode = 503;
1094: } else {
1095: if ((errorMsg != null)
1096: && (errorMsg.startsWith("Broken pipe")
1097: || errorMsg
1098: .startsWith("Connection reset") || errorMsg
1099: .startsWith("Software caused connection abort"))) {
1100: // client closed the connection, so we just end silently
1101: errorMessage
1102: .append("Client unexpectedly closed connection while processing query.");
1103: } else if ((errorMsg != null)
1104: && (errorMsg
1105: .startsWith("Connection timed out"))) {
1106: errorMessage.append("Connection timed out.");
1107: } else {
1108: errorMessage
1109: .append("Unexpected error while processing query.");
1110: httpStatusCode = 500;
1111: errorExc = e;
1112: }
1113: }
1114:
1115: errorMessage.append("\nSession: ").append(
1116: Thread.currentThread().getName()).append(
1117: "\nQuery: ").append(path).append(
1118: "\nClient: ").append(
1119: conProp.getProperty(
1120: httpHeader.CONNECTION_PROP_CLIENTIP,
1121: "unknown")).append("\nReason: ")
1122: .append(e.toString());
1123:
1124: if (!conProp
1125: .containsKey(httpHeader.CONNECTION_PROP_PROXY_RESPOND_HEADER)) {
1126: // sending back an error message to the client
1127: // if we have not already send an http header
1128: httpd.sendRespondError(conProp, out, 4,
1129: httpStatusCode, httpStatusText, new String(
1130: errorMessage), errorExc);
1131: } else {
1132: // otherwise we close the connection
1133: forceConnectionClose(conProp);
1134: }
1135:
1136: // if it is an unexpected error we log it
1137: if (httpStatusCode == 500) {
1138: theLogger.logWarning(new String(errorMessage), e);
1139: }
1140:
1141: } catch (Exception ee) {
1142: forceConnectionClose(conProp);
1143: }
1144:
1145: } finally {
1146: try {
1147: out.flush();
1148: } catch (Exception e) {
1149: }
1150: if (((String) requestHeader.get(httpHeader.CONNECTION,
1151: "close")).indexOf("keep-alive") == -1) {
1152: // wait a little time until everything closes so that clients can read from the streams/sockets
1153: try {
1154: Thread.sleep(50);
1155: } catch (InterruptedException e) {
1156: } // FIXME: is this necessary?
1157: }
1158: }
1159: }
1160:
1161: public static final File getOverlayedClass(String path) {
1162: File targetClass;
1163: targetClass = rewriteClassFile(new File(htDefaultPath, path)); //works for default and localized files
1164: if (targetClass == null || !targetClass.exists()) {
1165: //works for htdocs
1166: targetClass = rewriteClassFile(new File(htDocsPath, path));
1167: }
1168: return targetClass;
1169: }
1170:
1171: public static final File getOverlayedFile(String path) {
1172: File targetFile;
1173: targetFile = getLocalizedFile(path);
1174: if (!(targetFile.exists())) {
1175: targetFile = new File(htDocsPath, path);
1176: }
1177: return targetFile;
1178: }
1179:
1180: private static final void forceConnectionClose(Properties conprop) {
1181: if (conprop != null) {
1182: conprop.setProperty(httpHeader.CONNECTION_PROP_PERSISTENT,
1183: "close");
1184: }
1185: }
1186:
1187: private static final File rewriteClassFile(File template) {
1188: try {
1189: String f = template.getCanonicalPath();
1190: int p = f.lastIndexOf(".");
1191: if (p < 0)
1192: return null;
1193: f = f.substring(0, p) + ".class";
1194: //System.out.println("constructed class path " + f);
1195: File cf = new File(f);
1196: if (cf.exists())
1197: return cf;
1198: return null;
1199: } catch (IOException e) {
1200: return null;
1201: }
1202: }
1203:
1204: private static final Method rewriteMethod(File classFile) {
1205: Method m = null;
1206: // now make a class out of the stream
1207: try {
1208: if (useTemplateCache) {
1209: SoftReference<Method> ref = templateMethodCache
1210: .get(classFile);
1211: if (ref != null) {
1212: m = (Method) ref.get();
1213: if (m == null) {
1214: templateMethodCache.remove(classFile);
1215: } else {
1216: return m;
1217: }
1218:
1219: }
1220: }
1221:
1222: Class<?> c = provider.loadClass(classFile);
1223: Class<?>[] params = new Class[] { httpHeader.class,
1224: serverObjects.class, serverSwitch.class };
1225: m = c.getMethod("respond", params);
1226:
1227: if (useTemplateCache) {
1228: // storing the method into the cache
1229: SoftReference<Method> ref = new SoftReference<Method>(m);
1230: templateMethodCache.put(classFile, ref);
1231: }
1232:
1233: } catch (ClassNotFoundException e) {
1234: System.out.println("INTERNAL ERROR: class " + classFile
1235: + " is missing:" + e.getMessage());
1236: } catch (NoSuchMethodException e) {
1237: System.out
1238: .println("INTERNAL ERROR: method respond not found in class "
1239: + classFile + ": " + e.getMessage());
1240: }
1241: //System.out.println("found method: " + m.toString());
1242: return m;
1243: }
1244:
1245: public static final Object invokeServlet(File targetClass,
1246: httpHeader request, serverObjects args)
1247: throws IllegalArgumentException, IllegalAccessException,
1248: InvocationTargetException {
1249: Object result;
1250: if (safeServletsMode)
1251: synchronized (switchboard) {
1252: result = rewriteMethod(targetClass).invoke(null,
1253: new Object[] { request, args, switchboard });
1254: }
1255: else {
1256: result = rewriteMethod(targetClass).invoke(null,
1257: new Object[] { request, args, switchboard });
1258: }
1259: return result;
1260: }
1261:
1262: public void doConnect(Properties conProp, httpHeader requestHeader,
1263: InputStream clientIn, OutputStream clientOut) {
1264: throw new UnsupportedOperationException();
1265: }
1266:
1267: }
|