diff --git a/src/helma/main/ApplicationManager.java b/src/helma/main/ApplicationManager.java index 3cbb0d05..facb41ef 100644 --- a/src/helma/main/ApplicationManager.java +++ b/src/helma/main/ApplicationManager.java @@ -108,11 +108,11 @@ public class ApplicationManager { if (server.websrv == null) { Naming.rebind ("//:"+port+"/"+appName, app); } else { - AcmeServletClient servlet = new AcmeServletClient (app); - if ("base".equalsIgnoreCase (appName)) + boolean isRoot = "base".equalsIgnoreCase (appName); + EmbeddedServletClient servlet = new EmbeddedServletClient (appName, isRoot); + if (isRoot) server.websrv.setDefaultServlet (servlet); else { - // server.websrv.addServlet ("/"+appName+"/", servlet); server.websrv.addServlet ("/"+appName+"/*", servlet); } } @@ -147,14 +147,17 @@ public class ApplicationManager { } /** - * Get an enumeration of all currently running applications. + * Get an array containing all currently running applications. */ public Object[] getApplications () { return applications.values ().toArray (); } - public Application getApplication(String name) { - return (Application)applications.get(name); - } + /** + * Get an application by name. + */ + public Application getApplication(String name) { + return (Application)applications.get(name); + } } diff --git a/src/helma/servlet/AbstractServletClient.java b/src/helma/servlet/AbstractServletClient.java index 2795c6d2..c1927c7d 100644 --- a/src/helma/servlet/AbstractServletClient.java +++ b/src/helma/servlet/AbstractServletClient.java @@ -21,13 +21,22 @@ import helma.util.*; */ public abstract class AbstractServletClient extends HttpServlet { - + + // host on which Helma app is running String host = null; + // port of Helma RMI server int port = 0; - int uploadLimit; // limit to HTTP uploads in kB + // limit to HTTP uploads in kB + int uploadLimit; + // RMI url of Helma app String hopUrl; + // cookie domain to use String cookieDomain; + // default encoding for requests + String defaultEncoding; + // allow caching of responses boolean caching; + // enable debug output boolean debug; static final byte HTTP_GET = 0; @@ -35,7 +44,7 @@ public abstract class AbstractServletClient extends HttpServlet { public void init (ServletConfig init) throws ServletException { super.init (init); - + host = init.getInitParameter ("host"); if (host == null) host = "localhost"; @@ -49,6 +58,8 @@ public abstract class AbstractServletClient extends HttpServlet { hopUrl = "//" + host + ":" + port + "/"; + defaultEncoding = init.getInitParameter ("charset"); + debug = ("true".equalsIgnoreCase (init.getInitParameter ("debug"))); caching = ! ("false".equalsIgnoreCase (init.getInitParameter ("caching"))); @@ -88,13 +99,15 @@ public abstract class AbstractServletClient extends HttpServlet { try { // read and set http parameters - for (Enumeration e = request.getParameterNames(); e.hasMoreElements(); ) { - String nextKey = (String)e.nextElement(); - String[] paramValues = request.getParameterValues(nextKey); - if (paramValues != null) { - reqtrans.set (nextKey, paramValues[0]); // set to single string value - if (paramValues.length > 1) - reqtrans.set (nextKey+"_array", paramValues); // set string array + Map parameters = parseParameters (request); + for (Iterator i=parameters.entrySet().iterator(); i.hasNext(); ) { + Map.Entry entry = (Map.Entry) i.next (); + String key = (String) entry.getKey (); + String[] values = (String[]) entry.getValue (); + if (values != null && values.length > 0) { + reqtrans.set (key, values[0]); // set to single string value + if (values.length > 1) + reqtrans.set (key+"_array", values); // set string array } } @@ -102,13 +115,13 @@ public abstract class AbstractServletClient extends HttpServlet { String contentType = request.getContentType(); if (contentType != null && contentType.indexOf("multipart/form-data")==0) { // File Upload - Uploader up; try { - if ((up = getUpload (request)) != null) { - Hashtable upload = up.getParts (); - for (Enumeration e = upload.keys(); e.hasMoreElements(); ) { + FileUpload upload = getUpload (request); + if (upload != null) { + Hashtable parts = upload.getParts (); + for (Enumeration e = parts.keys(); e.hasMoreElements(); ) { String nextKey = (String) e.nextElement (); - Object nextPart = upload.get (nextKey); + Object nextPart = parts.get (nextKey); reqtrans.set (nextKey, nextPart); } } @@ -226,6 +239,13 @@ public abstract class AbstractServletClient extends HttpServlet { res.setHeader( "WWW-Authenticate", "Basic realm=\"" + trans.realm + "\"" ); if (trans.status > 0) res.setStatus (trans.status); + // if we don't know which charset to use for parsing HTTP params, + // take the one from the response. This usually works because + // browsers send parrameters in the same encoding as the page + // containing the form has. Problem is we can do this only per servlet, + // not per session or even per page, which would produce too much overhead + if (defaultEncoding == null) + defaultEncoding = trans.charset; res.setContentLength (trans.getContentLength ()); res.setContentType (trans.getContentType ()); try { @@ -245,10 +265,10 @@ public abstract class AbstractServletClient extends HttpServlet { } - public Uploader getUpload (HttpServletRequest request) throws Exception { + public FileUpload getUpload (HttpServletRequest request) throws Exception { int contentLength = request.getContentLength (); BufferedInputStream in = new BufferedInputStream (request.getInputStream ()); - Uploader up = null; + FileUpload upload = null; try { if (contentLength > uploadLimit*1024) { // consume all input to make Apache happy @@ -259,42 +279,165 @@ public abstract class AbstractServletClient extends HttpServlet { throw new RuntimeException ("Upload exceeds limit of "+uploadLimit+" kb."); } String contentType = request.getContentType (); - up = new Uploader(uploadLimit); - up.load (in, contentType, contentLength); + upload = new FileUpload(uploadLimit); + upload.load (in, contentType, contentLength); } finally { - try { in.close (); } catch (Exception ignore) {} + try { + in.close (); + } catch (Exception ignore) {} } - return up; + return upload; } - public Object getUploadPart(Uploader up, String name) { - return up.getParts().get(name); + public Object getUploadPart(FileUpload upload, String name) { + return upload.getParts().get(name); } + /** + * Put name value pair in map. + * + * @param b the character value byte + * + * Put name and value pair in map. When name already exist, add value + * to array of values. + */ + private static void putMapEntry( Map map, String name, String value) { + String[] newValues = null; + String[] oldValues = (String[]) map.get(name); + if (oldValues == null) { + newValues = new String[1]; + newValues[0] = value; + } else { + newValues = new String[oldValues.length + 1]; + System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); + newValues[oldValues.length] = value; + } + map.put(name, newValues); + } + + + + protected Map parseParameters (HttpServletRequest request) { + + String encoding = request.getCharacterEncoding (); + if (encoding == null) + // no encoding from request, use standard one + encoding = defaultEncoding; + if (encoding == null) + encoding = "ISO-8859-1"; + + HashMap parameters = new HashMap (); + // Parse any query string parameters from the request + try { + parseParameters (parameters, request.getQueryString().getBytes(), encoding); + } catch (Exception e) { + } + + // Parse any posted parameters in the input stream + if ("POST".equals(request.getMethod()) && + "application/x-www-form-urlencoded".equals(request.getContentType())) { + try { + int max = request.getContentLength(); + int len = 0; + byte buf[] = new byte[max]; + ServletInputStream is = request.getInputStream(); + while (len < max) { + int next = is.read(buf, len, max - len); + if (next < 0 ) { + break; + } + len += next; + } + // is.close(); + parseParameters(parameters, buf, encoding); + } catch (IllegalArgumentException e) { + } catch (IOException e) { + } + } + + return parameters; + } + + /** + * Append request parameters from the specified String to the specified + * Map. It is presumed that the specified Map is not accessed from any + * other thread, so no synchronization is performed. + *

+ * IMPLEMENTATION NOTE: URL decoding is performed + * individually on the parsed name and value elements, rather than on + * the entire query string ahead of time, to properly deal with the case + * where the name or value includes an encoded "=" or "&" character + * that would otherwise be interpreted as a delimiter. + * + * NOTE: byte array data is modified by this method. Caller beware. + * + * @param map Map that accumulates the resulting parameters + * @param data Input string containing request parameters + * @param encoding Encoding to use for converting hex + * + * @exception UnsupportedEncodingException if the data is malformed + */ + public static void parseParameters (Map map, byte[] data, String encoding) + throws UnsupportedEncodingException { + + if (data != null && data.length > 0) { + int pos = 0; + int ix = 0; + int ox = 0; + String key = null; + String value = null; + while (ix < data.length) { + byte c = data[ix++]; + switch ((char) c) { + case '&': + value = new String(data, 0, ox, encoding); + if (key != null) { + putMapEntry(map, key, value); + key = null; + } + ox = 0; + break; + case '=': + key = new String(data, 0, ox, encoding); + ox = 0; + break; + case '+': + data[ox++] = (byte)' '; + break; + case '%': + data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4) + + convertHexDigit(data[ix++])); + break; + default: + data[ox++] = c; + } + } + //The last value does not end in '&'. So save it now. + if (key != null) { + value = new String(data, 0, ox, encoding); + putMapEntry(map, key, value); + } + } + } + + /** + * Convert a byte character value to hexidecimal digit value. + * + * @param b the character value byte + */ + private static byte convertHexDigit( byte b ) { + if ((b >= '0') && (b <= '9')) return (byte)(b - '0'); + if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10); + if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10); + return 0; + } + public String getServletInfo(){ - return new String("Hop Servlet Client"); + return new String("Helma Servlet Client"); } } - - - - - - - - - - - - - - - - - - diff --git a/src/helma/servlet/AcmeServletClient.java b/src/helma/servlet/AcmeServletClient.java deleted file mode 100644 index 1c294f5c..00000000 --- a/src/helma/servlet/AcmeServletClient.java +++ /dev/null @@ -1,271 +0,0 @@ -// AcmeServletClient.java -// Copyright (c) Hannes Wallnoefer, Raphael Spannocchi 1998-2000 - -/* Portierung von helma.asp.AspClient auf Servlets */ -/* Author: Raphael Spannocchi Datum: 27.11.1998 */ - -package helma.servlet; - -import javax.servlet.*; -import javax.servlet.http.*; -import java.io.*; -import java.util.*; -import helma.framework.*; -import helma.framework.core.Application; -import helma.util.Uploader; - -/** - * This is the Hop servlet adapter that uses the Acme servlet API clone and communicates - * directly with hop applications instead of using RMI. - */ - -public class AcmeServletClient extends HttpServlet { - - private int uploadLimit; // limit to HTTP uploads in kB - private Hashtable apps; - private Application app; - private String cookieDomain; - private boolean debug; - - static final byte HTTP_GET = 0; - static final byte HTTP_POST = 1; - - public AcmeServletClient (Application app) { - this.app = app; - this.uploadLimit = 1024; // generous 1mb upload limit - } - - public void init (ServletConfig config) throws ServletException { - super.init (config); - // do nothing - } - - public void doGet (HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - execute (request, response, HTTP_GET); - } - - public void doPost (HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - execute (request, response, HTTP_POST); - } - - private void execute (HttpServletRequest request, HttpServletResponse response, byte method) { - String protocol = request.getProtocol (); - Cookie[] cookies = request.getCookies(); - try { - RequestTrans reqtrans = new RequestTrans (method); - - // read and set http parameters - for (Enumeration e = request.getParameterNames(); e.hasMoreElements(); ) { - // Params parsen - String nextKey = (String)e.nextElement(); - String[] paramValues = request.getParameterValues(nextKey); - if (paramValues != null) { - reqtrans.set (nextKey, paramValues[0]); // set to single string value - if (paramValues.length > 1) - reqtrans.set (nextKey+"_array", paramValues); // set string array - } - } - - // check for MIME file uploads - String contentType = request.getContentType(); - if (contentType != null && contentType.indexOf("multipart/form-data")==0) { - // File Upload - Uploader up; - try { - if ((up = getUpload (uploadLimit, request)) != null) { - Hashtable upload = up.getParts (); - for (Enumeration e = upload.keys(); e.hasMoreElements(); ) { - String nextKey = (String) e.nextElement (); - Object nextPart = upload.get (nextKey); - reqtrans.set (nextKey, nextPart); - } - } - } catch (Exception upx) { - String uploadErr = upx.getMessage (); - if (uploadErr == null || uploadErr.length () == 0) - uploadErr = upx.toString (); - reqtrans.set ("uploadError", uploadErr); - } - } - - // HACK - sessions not fully supported in Acme.Serve - // Thats ok, we dont need the session object, just the id. - reqtrans.session = request.getRequestedSessionId(); - - // get Cookies - if (cookies != null) { - for (int i=0; i < cookies.length;i++) try { - String nextKey = cookies[i].getName (); - String nextPart = cookies[i].getValue (); - reqtrans.set (nextKey, nextPart); - } catch (Exception badCookie) {} - } - - // get optional path info - String pathInfo = request.getServletPath (); - if (pathInfo != null) { - if (pathInfo.indexOf (app.getName()) == 1) - pathInfo = pathInfo.substring (app.getName().length()+1); - reqtrans.path = trim (pathInfo); - } else - reqtrans.path = ""; - - // do standard HTTP variables - String host = request.getHeader ("Host"); - if (host != null) { - host = host.toLowerCase(); - reqtrans.set ("http_host", host); - } - - String referer = request.getHeader ("Referer"); - if (referer != null) - reqtrans.set ("http_referer", referer); - - String remotehost = request.getRemoteAddr (); - if (remotehost != null) - reqtrans.set ("http_remotehost", remotehost); - - String browser = request.getHeader ("User-Agent"); - if (browser != null) - reqtrans.set ("http_browser", browser); - - String authorization = request.getHeader("authorization"); - if ( authorization != null ) - reqtrans.set ("authorization", authorization ); - - ResponseTrans restrans = null; - restrans = app.execute (reqtrans); - writeResponse (response, restrans, cookies, protocol); - - } catch (Exception x) { - x.printStackTrace (); - try { - response.setContentType ("text/html"); - Writer out = response.getWriter (); - if (debug) - out.write ("Error:
" +x); - else - out.write ("This server is temporarily unavailable. Please check back later."); - out.flush (); - } catch (Exception io_e) {} - } - } - - - private void writeResponse (HttpServletResponse res, ResponseTrans trans, Cookie[] cookies, String protocol) { - for (int i = 0; i < trans.countCookies(); i++) try { - Cookie c = new Cookie(trans.getKeyAt(i), trans.getValueAt(i)); - c.setPath ("/"); - if (cookieDomain != null) - c.setDomain (cookieDomain); - int expires = trans.getDaysAt(i); - if (expires > 0) - c.setMaxAge(expires * 60*60*24); // Cookie time to live, days -> seconds - res.addCookie(c); - } catch (Exception ign) {} - - if (trans.getRedirect () != null) { - try { - res.sendRedirect(trans.getRedirect ()); - } catch(Exception io_e) {} - - } else { - - if (!trans.cache) { - // Disable caching of response. - if (protocol == null || !protocol.endsWith ("1.1")) - res.setHeader ("Pragma", "no-cache"); // for HTTP 1.0 - else - res.setHeader ("Cache-Control", "no-cache"); // for HTTP 1.1 - } - if ( trans.realm!=null ) - res.setHeader( "WWW-Authenticate", "Basic realm=\"" + trans.realm + "\"" ); - if (trans.status > 0) - res.setStatus (trans.status); - res.setContentLength (trans.getContentLength ()); - res.setContentType (trans.getContentType ()); - try { - OutputStream out = res.getOutputStream (); - out.write (trans.getContent ()); - out.close (); - } catch(Exception io_e) { System.out.println ("Error in writeResponse: "+io_e); } - } - } - - private void redirectResponse (HttpServletRequest request, HttpServletResponse res, ResponseTrans trans, String url) { - try { - res.sendRedirect(url); - } catch (Exception e) { - System.err.println ("Exception in redirect: " + e + e.getMessage()); - } - } - - - public Uploader getUpload (HttpServletRequest request) throws Exception { - return getUpload (500, request); - } - - public Uploader getUpload (int maxKbytes, HttpServletRequest request) throws Exception { - int contentLength = request.getContentLength (); - BufferedInputStream in = new BufferedInputStream (request.getInputStream ()); - Uploader up = null; - if (contentLength > maxKbytes*1024) { - throw new RuntimeException ("Upload exceeds limit of "+maxKbytes+" kb."); - } - String contentType = request.getContentType (); - up = new Uploader(maxKbytes); - up.load (in, contentType, contentLength); - return up; - } - - - public Object getUploadPart(Uploader up, String name) { - return up.getParts().get(name); - } - - - public String getServletInfo (){ - return new String("Hop ServletClient"); - } - - - private String trim (String str) { - - if (str == null) - return null; - char[] val = str.toCharArray (); - int len = val.length; - int st = 0; - - while ((st < len) && (val[st] <= ' ' || val[st] == '/')) { - st++; - } - while ((st < len) && (val[len - 1] <= ' ' || val[len - 1] == '/')) { - len--; - } - return ((st > 0) || (len < val.length)) ? new String (val, st, len-st) : str; - } - -} - - - - - - - - - - - - - - - - - - - - diff --git a/src/helma/servlet/EmbeddedServletClient.java b/src/helma/servlet/EmbeddedServletClient.java new file mode 100644 index 00000000..67817fb5 --- /dev/null +++ b/src/helma/servlet/EmbeddedServletClient.java @@ -0,0 +1,78 @@ +// EmbeddedServletClient.java +// Copyright (c) Hannes Wallnöfer, 2002 + +package helma.servlet; + +import javax.servlet.*; +import java.io.*; +import java.util.*; +import helma.framework.*; +import helma.framework.core.Application; +import helma.main.*; +import helma.util.*; + +/** + * Servlet client that runs a Helma application for the embedded + * web server + */ + +public final class EmbeddedServletClient extends AbstractServletClient { + + private Application app = null; + private String appName; + + // tells us whether the application is mounted as root or by its name + // depending on this we know whether we have to transform the request path + boolean root; + + public EmbeddedServletClient (String appName, boolean isRoot) { + this.appName = appName; + this.root = isRoot; + } + + + IRemoteApp getApp (String appID) { + if (app == null) + app = Server.getServer().getApplication (appName); + return app; + } + + + void invalidateApp (String appID) { + // do nothing + } + + String getAppID (String path) { + return appName; + } + + String getRequestPath (String path) { + if (path == null) + return ""; + if (root) + return trim (path); + int appInPath = path.indexOf (appName); + if (appInPath > 0) + return trim (path.substring (appInPath+appName.length())); + else + return trim (path); + } + + String trim (String str) { + char[] val = str.toCharArray (); + int len = val.length; + int st = 0; + + while ((st < len) && (val[st] <= ' ' || val[st] == '/')) + st++; + + while ((st < len) && (val[len - 1] <= ' ' || val[len - 1] == '/')) + len--; + + return ((st > 0) || (len < val.length)) ? new String (val, st, len-st) : str; + } + +} + + + diff --git a/src/helma/servlet/MultiServletClient.java b/src/helma/servlet/MultiServletClient.java index c4e1d5b3..d758fdf0 100644 --- a/src/helma/servlet/MultiServletClient.java +++ b/src/helma/servlet/MultiServletClient.java @@ -70,17 +70,17 @@ public class MultiServletClient extends AbstractServletClient { int len = val.length; int st = 0; - // advance to start of path + // advance to start of path, eating up any slashes while ((st < len) && (val[st] <= ' ' || val[st] == '/')) st++; - // eat characters of first path element + // advance until slash ending the first path element while (st < len && val[st] != '/') st++; if (st < len && val[st] == '/') st++; - // eat away noise at end of path + // eat away spaces and slashes at end of path while ((st < len) && (val[len - 1] <= ' ' || val[len - 1] == '/')) len--; diff --git a/src/helma/servlet/ServletClient.java b/src/helma/servlet/ServletClient.java index 67604adc..0dccd4c0 100644 --- a/src/helma/servlet/ServletClient.java +++ b/src/helma/servlet/ServletClient.java @@ -1,44 +1,36 @@ // ServletClient.java -// Copyright (c) Hannes Wallnöfer, Raphael Spannocchi 1998-2000 +// Copyright (c) Hannes Wallnöfer, Raphael Spannocchi 1998-2002 -/* Portierung von helma.asp.AspClient auf Servlets */ -/* Author: Raphael Spannocchi Datum: 27.11.1998 */ package helma.servlet; import javax.servlet.*; -import javax.servlet.http.*; import java.io.*; import java.rmi.Naming; import java.rmi.RemoteException; import java.util.*; import helma.framework.*; -import helma.util.*; /** - * This is the HOP servlet adapter. This class communicates with just - * one Hop application. + * This is the standard Helma servlet adapter. This class represents a servlet + * that is dedicated to one Helma application over RMI. */ public class ServletClient extends AbstractServletClient { private IRemoteApp app = null; - private String appName; + private String appName = null; public void init (ServletConfig init) throws ServletException { super.init (init); - appName = init.getInitParameter ("application"); - if (appName == null) - appName = "base"; - - super.init (init); } IRemoteApp getApp (String appID) throws Exception { if (app != null) return app; - + if (appName == null) + throw new ServletException ("Helma application name not specified for helma.servlet.ServletClient"); app = (IRemoteApp) Naming.lookup (hopUrl + appName); return app; } @@ -76,7 +68,7 @@ public class ServletClient extends AbstractServletClient { // for testing public static void main (String args[]) { AbstractServletClient client = new ServletClient (); - String path = "///appname/do/it/for/me///"; + String path = "///appname/some/random/path///"; System.out.println (client.getAppID (path)); System.out.println (client.getRequestPath (path)); } @@ -85,20 +77,3 @@ public class ServletClient extends AbstractServletClient { } - - - - - - - - - - - - - - - - - diff --git a/src/helma/servlet/StandaloneServletClient.java b/src/helma/servlet/StandaloneServletClient.java index a81b3329..372aef16 100644 --- a/src/helma/servlet/StandaloneServletClient.java +++ b/src/helma/servlet/StandaloneServletClient.java @@ -4,16 +4,15 @@ package helma.servlet; import javax.servlet.*; -import javax.servlet.http.*; import java.io.*; import java.util.*; import helma.framework.*; import helma.framework.core.Application; -import helma.objectmodel.*; import helma.util.*; /** - * This is a standalone Hop servlet client, running a Hop application by itself. + * Standalone servlet client that runs a Helma application all by itself + * in embedded mode without relying on helma.main.Server. */ public final class StandaloneServletClient extends AbstractServletClient { @@ -22,7 +21,7 @@ public final class StandaloneServletClient extends AbstractServletClient { private String appName; private String serverProps; - + public void init (ServletConfig init) throws ServletException { super.init (init); appName = init.getInitParameter ("application"); @@ -105,13 +104,11 @@ public final class StandaloneServletClient extends AbstractServletClient { // for testing public static void main (String args[]) { AbstractServletClient client = new ServletClient (); - String path = "///appname/do/it/for/me///"; + String path = "///appname/some/random/path///"; System.out.println (client.getAppID (path)); System.out.println (client.getRequestPath (path)); } - - } diff --git a/src/helma/util/Uploader.java b/src/helma/util/FileUpload.java similarity index 93% rename from src/helma/util/Uploader.java rename to src/helma/util/FileUpload.java index 6618d424..d22e5aad 100644 --- a/src/helma/util/Uploader.java +++ b/src/helma/util/FileUpload.java @@ -1,4 +1,4 @@ -// Uploader.java +// FileUpload.java // Copyright (c) Hannes Wallnöfer 1996-2000 package helma.util; @@ -8,19 +8,19 @@ import java.io.*; import java.util.*; /** - * Utility class for file uploads via HTTP POST. + * Utility class for MIME file uploads via HTTP POST. */ -public class Uploader { +public class FileUpload { public Hashtable parts; int maxKbytes; - public Uploader () { - maxKbytes = 500; + public FileUpload () { + maxKbytes = 1024; } - public Uploader (int max) { + public FileUpload (int max) { maxKbytes = max; }