001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: hannes $
013: * $Revision: 8641 $
014: * $Date: 2007-11-14 11:00:46 +0100 (Mit, 14 Nov 2007) $
015: */
016:
017: package helma.framework;
018:
019: import helma.util.Base64;
020: import helma.util.SystemMap;
021: import helma.util.StringUtils;
022:
023: import javax.servlet.http.HttpServletRequest;
024: import javax.servlet.http.HttpServletResponse;
025: import javax.servlet.http.Cookie;
026: import java.io.*;
027: import java.util.*;
028: import java.util.regex.Pattern;
029: import java.util.regex.Matcher;
030:
031: /**
032: * A Transmitter for a request from the servlet client. Objects of this
033: * class are directly exposed to JavaScript as global property req.
034: */
035: public class RequestTrans implements Serializable {
036:
037: static final long serialVersionUID = 5398880083482000580L;
038:
039: // HTTP methods
040: public final static String GET = "GET";
041: public final static String POST = "POST";
042: public final static String DELETE = "DELETE";
043: public final static String HEAD = "HEAD";
044: public final static String OPTIONS = "OPTIONS";
045: public final static String PUT = "PUT";
046: public final static String TRACE = "TRACE";
047: // Helma pseudo-methods
048: public final static String XMLRPC = "XMLRPC";
049: public final static String EXTERNAL = "EXTERNAL";
050: public final static String INTERNAL = "INTERNAL";
051:
052: // the servlet request and response, may be null
053: final HttpServletRequest request;
054: final HttpServletResponse response;
055:
056: // the uri path of the request
057: private final String path;
058:
059: // the request's session id
060: private String session;
061:
062: // the map of form and cookie data
063: private final Map values = new DataComboMap();
064:
065: private ParamComboMap params;
066: private ParameterMap queryParams, postParams, cookies;
067:
068: // the HTTP request method
069: private String method;
070:
071: // timestamp of client-cached version, if present in request
072: private long ifModifiedSince = -1;
073:
074: // set of ETags the client sent with If-None-Match header
075: private final Set etags = new HashSet();
076:
077: // when was execution started on this request?
078: private final long startTime;
079:
080: // the name of the action being invoked
081: private String action;
082: private String httpUsername;
083: private String httpPassword;
084:
085: static private final Pattern paramPattern = Pattern
086: .compile("\\[(.+?)\\]");
087:
088: /**
089: * Create a new Request transmitter with an empty data map.
090: */
091: public RequestTrans(String method, String path) {
092: this .method = method;
093: this .path = path;
094: this .request = null;
095: this .response = null;
096: startTime = System.currentTimeMillis();
097: }
098:
099: /**
100: * Create a new request transmitter with the given data map.
101: */
102: public RequestTrans(HttpServletRequest request,
103: HttpServletResponse response, String path) {
104: this .method = request.getMethod();
105: this .request = request;
106: this .response = response;
107: this .path = path;
108: startTime = System.currentTimeMillis();
109:
110: // do standard HTTP variables
111: String header = request.getHeader("Host");
112: if (header != null) {
113: values.put("http_host", header.toLowerCase());
114: }
115:
116: header = request.getHeader("Referer");
117: if (header != null) {
118: values.put("http_referer", header);
119: }
120:
121: try {
122: long ifModifiedSince = request
123: .getDateHeader("If-Modified-Since");
124: if (ifModifiedSince > -1) {
125: setIfModifiedSince(ifModifiedSince);
126: }
127: } catch (IllegalArgumentException ignore) {
128: // not a date header
129: }
130:
131: header = request.getHeader("If-None-Match");
132: if (header != null) {
133: setETags(header);
134: }
135:
136: header = request.getRemoteAddr();
137: if (header != null) {
138: values.put("http_remotehost", header);
139: }
140:
141: header = request.getHeader("User-Agent");
142: if (header != null) {
143: values.put("http_browser", header);
144: }
145:
146: header = request.getHeader("Accept-Language");
147: if (header != null) {
148: values.put("http_language", header);
149: }
150:
151: header = request.getHeader("authorization");
152: if (header != null) {
153: values.put("authorization", header);
154: }
155: }
156:
157: /**
158: * Return true if we should try to handle this as XML-RPC request.
159: *
160: * @return true if this might be an XML-RPC request.
161: */
162: public synchronized boolean checkXmlRpc() {
163: return "POST".equals(method)
164: && "text/xml".equals(request.getContentType());
165: }
166:
167: /**
168: * Return true if this request is in fact handled as XML-RPC request.
169: * This implies that {@link #checkXmlRpc()} returns true and a matching
170: * XML-RPC action was found.
171: *
172: * @return true if this request is handled as XML-RPC request.
173: */
174: public synchronized boolean isXmlRpc() {
175: return XMLRPC.equals(method);
176: }
177:
178: /**
179: * Set a cookie
180: * @param name the cookie name
181: * @param cookie the cookie
182: */
183: public void setCookie(String name, Cookie cookie) {
184: if (cookies == null) {
185: cookies = new ParameterMap();
186: }
187: cookies.put(name, cookie);
188: }
189:
190: /**
191: * @return a map containing the cookies sent with this request
192: */
193: public Map getCookies() {
194: if (cookies == null) {
195: cookies = new ParameterMap();
196: }
197: return cookies;
198: }
199:
200: /**
201: * @return the combined query and post parameters for this request
202: */
203: public Map getParams() {
204: if (params == null) {
205: params = new ParamComboMap();
206: }
207: return params;
208: }
209:
210: /**
211: * @return get the query parameters for this request
212: */
213: public Map getQueryParams() {
214: if (queryParams == null) {
215: queryParams = new ParameterMap();
216: }
217: return queryParams;
218: }
219:
220: /**
221: * @return get the post parameters for this request
222: */
223: public Map getPostParams() {
224: if (postParams == null) {
225: postParams = new ParameterMap();
226: }
227: return postParams;
228: }
229:
230: /**
231: * set the request parameters
232: */
233: public void setParameters(Map parameters, boolean isPost) {
234: if (isPost) {
235: postParams = new ParameterMap(parameters);
236: } else {
237: queryParams = new ParameterMap(parameters);
238: }
239: }
240:
241: /**
242: * Add a post parameter to the request
243: * @param name the parameter name
244: * @param value the parameter value
245: */
246: public void addPostParam(String name, Object value) {
247: if (postParams == null) {
248: postParams = new ParameterMap();
249: }
250: Object previous = postParams.getRaw(name);
251: if (previous instanceof Object[]) {
252: Object[] array = (Object[]) previous;
253: Object[] values = new Object[array.length + 1];
254: System.arraycopy(array, 0, values, 0, array.length);
255: values[array.length] = value;
256: postParams.put(name, values);
257: } else if (previous == null) {
258: postParams.put(name, new Object[] { value });
259: }
260: }
261:
262: /**
263: * Set a parameter value in this request transmitter. This
264: * parses foo[bar][baz] as nested objects/maps.
265: */
266: public void set(String name, Object value) {
267: values.put(name, value);
268: }
269:
270: /**
271: * Get a value from the requests map by key.
272: */
273: public Object get(String name) {
274: try {
275: return values.get(name);
276: } catch (Exception x) {
277: return null;
278: }
279: }
280:
281: /**
282: * Get the data map for this request transmitter.
283: */
284: public Map getRequestData() {
285: return values;
286: }
287:
288: /**
289: * Returns the Servlet request represented by this RequestTrans instance.
290: * Returns null for internal and XML-RPC requests.
291: */
292: public HttpServletRequest getServletRequest() {
293: return request;
294: }
295:
296: /**
297: * Proxy to HttpServletRequest.getHeader().
298: * @param name the header name
299: * @return the header value, or null
300: */
301: public String getHeader(String name) {
302: return request == null ? null : request.getHeader(name);
303: }
304:
305: /**
306: * Proxy to HttpServletRequest.getHeaders(), returns header values as string array.
307: * @param name the header name
308: * @return the header values as string array
309: */
310: public String[] getHeaders(String name) {
311: return request == null ? null : StringUtils.collect(request
312: .getHeaders(name));
313: }
314:
315: /**
316: * Proxy to HttpServletRequest.getIntHeader(), fails silently by returning -1.
317: * @param name the header name
318: * @return the header parsed as integer or -1
319: */
320: public int getIntHeader(String name) {
321: try {
322: return request == null ? -1 : getIntHeader(name);
323: } catch (NumberFormatException nfe) {
324: return -1;
325: }
326: }
327:
328: /**
329: * Proxy to HttpServletRequest.getDateHeader(), fails silently by returning -1.
330: * @param name the header name
331: * @return the date in milliseconds, or -1
332: */
333: public long getDateHeader(String name) {
334: try {
335: return request == null ? -1 : getDateHeader(name);
336: } catch (NumberFormatException nfe) {
337: return -1;
338: }
339: }
340:
341: /**
342: * Returns the Servlet response for this request.
343: * Returns null for internal and XML-RPC requests.
344: */
345: public HttpServletResponse getServletResponse() {
346: return response;
347: }
348:
349: /**
350: * The hash code is computed from the session id if available. This is used to
351: * detect multiple identic requests.
352: */
353: public int hashCode() {
354: if (session == null || path == null) {
355: return super .hashCode();
356: } else {
357: return 17 + (37 * session.hashCode())
358: + (37 * path.hashCode());
359: }
360: }
361:
362: /**
363: * A request is considered equal to another one if it has the same method,
364: * path, session, request data, and conditional get data. This is used to
365: * evaluate multiple simultanous identical requests only once.
366: */
367: public boolean equals(Object what) {
368: if (what instanceof RequestTrans) {
369: if (session == null || path == null) {
370: return super .equals(what);
371: } else {
372: RequestTrans other = (RequestTrans) what;
373: return (session.equals(other.session)
374: && path.equalsIgnoreCase(other.path)
375: && values.equals(other.values)
376: && ifModifiedSince == other.ifModifiedSince && etags
377: .equals(other.etags));
378: }
379: }
380: return false;
381: }
382:
383: /**
384: * Return the method of the request. This may either be a HTTP method or
385: * one of the Helma pseudo methods defined in this class.
386: */
387: public synchronized String getMethod() {
388: return method;
389: }
390:
391: /**
392: * Set the method of this request.
393: *
394: * @param method the method.
395: */
396: public synchronized void setMethod(String method) {
397: this .method = method;
398: }
399:
400: /**
401: * Return true if this object represents a HTTP GET Request.
402: */
403: public boolean isGet() {
404: return GET.equalsIgnoreCase(method);
405: }
406:
407: /**
408: * Return true if this object represents a HTTP GET Request.
409: */
410: public boolean isPost() {
411: return POST.equalsIgnoreCase(method);
412: }
413:
414: /**
415: * Get the request's session id
416: */
417: public String getSession() {
418: return session;
419: }
420:
421: /**
422: * Set the request's session id
423: */
424: public void setSession(String session) {
425: this .session = session;
426: }
427:
428: /**
429: * Get the request's path
430: */
431: public String getPath() {
432: return path;
433: }
434:
435: /**
436: * Get the request's action.
437: */
438: public String getAction() {
439: return action;
440: }
441:
442: /**
443: * Set the request's action.
444: */
445: public void setAction(String action) {
446: this .action = action
447: .substring(0, action.lastIndexOf("_action"));
448: }
449:
450: /**
451: * Get the time the request was created.
452: */
453: public long getStartTime() {
454: return startTime;
455: }
456:
457: /**
458: *
459: *
460: * @param since ...
461: */
462: public void setIfModifiedSince(long since) {
463: ifModifiedSince = since;
464: }
465:
466: /**
467: *
468: *
469: * @return ...
470: */
471: public long getIfModifiedSince() {
472: return ifModifiedSince;
473: }
474:
475: /**
476: *
477: *
478: * @param etagHeader ...
479: */
480: public void setETags(String etagHeader) {
481: if (etagHeader.indexOf(",") > -1) {
482: StringTokenizer st = new StringTokenizer(etagHeader,
483: ", \r\n");
484: while (st.hasMoreTokens())
485: etags.add(st.nextToken());
486: } else {
487: etags.add(etagHeader);
488: }
489: }
490:
491: /**
492: *
493: *
494: * @return ...
495: */
496: public Set getETags() {
497: return etags;
498: }
499:
500: /**
501: *
502: *
503: * @param etag ...
504: *
505: * @return ...
506: */
507: public boolean hasETag(String etag) {
508: if ((etags == null) || (etag == null)) {
509: return false;
510: }
511:
512: return etags.contains(etag);
513: }
514:
515: /**
516: *
517: *
518: * @return ...
519: */
520: public String getUsername() {
521: if (httpUsername != null) {
522: return httpUsername;
523: }
524:
525: String auth = (String) get("authorization");
526:
527: if ((auth == null) || "".equals(auth)) {
528: return null;
529: }
530:
531: decodeHttpAuth(auth);
532:
533: return httpUsername;
534: }
535:
536: /**
537: *
538: *
539: * @return ...
540: */
541: public String getPassword() {
542: if (httpPassword != null) {
543: return httpPassword;
544: }
545:
546: String auth = (String) get("authorization");
547:
548: if ((auth == null) || "".equals(auth)) {
549: return null;
550: }
551:
552: decodeHttpAuth(auth);
553:
554: return httpPassword;
555: }
556:
557: private void decodeHttpAuth(String auth) {
558: if (auth == null) {
559: return;
560: }
561:
562: StringTokenizer tok;
563:
564: if (auth.startsWith("Basic ")) {
565: tok = new StringTokenizer(new String(Base64.decode((auth
566: .substring(6)).toCharArray())), ":");
567: } else {
568: tok = new StringTokenizer(new String(Base64.decode(auth
569: .toCharArray())), ":");
570: }
571:
572: try {
573: httpUsername = tok.nextToken();
574: } catch (NoSuchElementException e) {
575: httpUsername = null;
576: }
577:
578: try {
579: httpPassword = tok.nextToken();
580: } catch (NoSuchElementException e) {
581: httpPassword = null;
582: }
583: }
584:
585: class ParameterMap extends SystemMap {
586:
587: public ParameterMap() {
588: super ();
589: }
590:
591: public ParameterMap(Map map) {
592: super ((int) (map.size() / 0.75f) + 1);
593: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
594: Map.Entry e = (Map.Entry) i.next();
595: put(e.getKey(), e.getValue());
596: }
597: }
598:
599: public Object put(Object key, Object value) {
600: if (key instanceof String) {
601: String name = (String) key;
602: int bracket = name.indexOf('[');
603: if (bracket > -1 && name.endsWith("]")) {
604: Matcher matcher = paramPattern.matcher(name);
605: String partName = name.substring(0, bracket);
606: return putInternal(partName, matcher, value);
607: }
608: }
609: Object previous = super .get(key);
610: if (previous != null
611: && (previous instanceof Map || value instanceof Map))
612: throw new RuntimeException(
613: "Conflicting HTTP Parameters for '" + key + "'");
614: return super .put(key, value);
615: }
616:
617: private Object putInternal(String name, Matcher matcher,
618: Object value) {
619: Object previous = super .get(name);
620: if (matcher.find()) {
621: ParameterMap map = null;
622: if (previous instanceof ParameterMap) {
623: map = (ParameterMap) previous;
624: } else if (previous == null) {
625: map = new ParameterMap();
626: super .put(name, map);
627: } else {
628: throw new RuntimeException(
629: "Conflicting HTTP Parameters for '" + name
630: + "'");
631: }
632: String partName = matcher.group(1);
633: return map.putInternal(partName, matcher, value);
634: }
635: if (previous != null
636: && (previous instanceof Map || value instanceof Map))
637: throw new RuntimeException(
638: "Conflicting HTTP Parameters for '" + name
639: + "'");
640: return super .put(name, value);
641: }
642:
643: public Object get(Object key) {
644: if (key instanceof String) {
645: Object value = super .get(key);
646: String name = (String) key;
647: if (name.endsWith("_array") && value == null) {
648: value = super .get(name.substring(0,
649: name.length() - 6));
650: return value instanceof Object[] ? value : null;
651: } else if (name.endsWith("_cookie") && value == null) {
652: value = super .get(name.substring(0,
653: name.length() - 7));
654: return value instanceof Cookie ? value : null;
655: } else if (value instanceof Object[]) {
656: Object[] values = ((Object[]) value);
657: return values.length > 0 ? values[0] : null;
658: } else if (value instanceof Cookie) {
659: Cookie cookie = (Cookie) value;
660: return cookie.getValue();
661: }
662: }
663: return super .get(key);
664: }
665:
666: protected Object getRaw(Object key) {
667: return super .get(key);
668: }
669: }
670:
671: class DataComboMap extends SystemMap {
672:
673: public Object get(Object key) {
674: Object value = super .get(key);
675: if (value != null)
676: return value;
677: if (postParams != null
678: && (value = postParams.get(key)) != null)
679: return value;
680: if (queryParams != null
681: && (value = queryParams.get(key)) != null)
682: return value;
683: if (cookies != null && (value = cookies.get(key)) != null)
684: return value;
685: return null;
686: }
687:
688: public boolean containsKey(Object key) {
689: return get(key) != null;
690: }
691:
692: public Set entrySet() {
693: Set entries = new HashSet(super .entrySet());
694: if (postParams != null)
695: entries.addAll(postParams.entrySet());
696: if (queryParams != null)
697: entries.addAll(queryParams.entrySet());
698: if (cookies != null)
699: entries.addAll(cookies.entrySet());
700: return entries;
701: }
702:
703: public Set keySet() {
704: Set keys = new HashSet(super .keySet());
705: if (postParams != null)
706: keys.addAll(postParams.keySet());
707: if (queryParams != null)
708: keys.addAll(queryParams.keySet());
709: if (cookies != null)
710: keys.addAll(cookies.keySet());
711: return keys;
712: }
713: }
714:
715: class ParamComboMap extends SystemMap {
716: public Object get(Object key) {
717: Object value;
718: if (postParams != null
719: && (value = postParams.get(key)) != null)
720: return value;
721: if (queryParams != null
722: && (value = queryParams.get(key)) != null)
723: return value;
724: return null;
725: }
726:
727: public boolean containsKey(Object key) {
728: return get(key) != null;
729: }
730:
731: public Set entrySet() {
732: Set entries = new HashSet();
733: if (postParams != null)
734: entries.addAll(postParams.entrySet());
735: if (queryParams != null)
736: entries.addAll(queryParams.entrySet());
737: return entries;
738: }
739:
740: public Set keySet() {
741: Set keys = new HashSet();
742: if (postParams != null)
743: keys.addAll(postParams.keySet());
744: if (queryParams != null)
745: keys.addAll(queryParams.keySet());
746: return keys;
747: }
748: }
749: }
|