001: package pygmy.handlers;
002:
003: import pygmy.core.*;
004:
005: import java.io.IOException;
006: import java.net.HttpURLConnection;
007: import java.util.regex.Pattern;
008: import java.util.regex.Matcher;
009: import java.util.logging.Logger;
010: import java.util.logging.Level;
011:
012: /**
013: * <p>
014: * This redirects or rewrites URLs based on a regular expression. It tests the requested
015: * URLs against a regular expression. If it finds a match it then uses the substiution
016: * expression to rewrite a new URL. For a description of the regular expression language see
017: * {@see http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html}. This handler
018: * operates in two modes either using external redirects (i.e. 302 HTTP code), or internal
019: * redirects. The new URL expression can reference groups in the regular expression using
020: * ${<group number>}. Substitution expressions can also reference configuration
021: * properties by using the notation. For example, ${http.port} would return the port of
022: * the pygmy server.
023: * </p>
024: *
025: * <p>
026: * Here is an example of a URL rule and substition expression for creating a URL to user's
027: * home directories. Remember to escape "e;\"e; character in your properties files
028: * otherwise your expression will not work. To help you debug these problems this Handler
029: * will log a message at the debug level so you can see what the regular expression has been
030: * set to.
031: * </p>
032: *
033: * <blockquote>
034: * aRedirect.class=pygmy.handlers.RedirectHandler
035: * aRedirect.rule=/~(\\w+)
036: * aRedirect.subst=/home/${1}/public_html
037: * </blockquote>
038: *
039: * <p>
040: * The new URL built from the subst expression by default will be sent back to the client
041: * in a 302 HTTP status code. But, there are cases when you don't want to expose the
042: * rewritten URL to the outside world. This handler can internally redirect so that the
043: * new URL won't be sent back to the client. In our example above we might want to keep
044: * the URL to the user's directory private. Using internal redirects the browser won't see
045: * the new URL containing: /home/chuck/public_html. It could be a serious security hole if
046: * someone is allowed to request /home/chuck!
047: * </p>
048: *
049: * <p>
050: * RedirectHandler <b>only</b> responds to non-internal requests. <b>This handler will not
051: * redirect or rewrite interal requests.</b> This is so redirects don't get into an
052: * infinite loop when processing. Something to look out for when using external redirects.
053: * Most clients fail if they are redirected too many times.
054: * </p>
055: *
056: * <p>
057: * <table class="inner">
058: * <tr class="header"><td>Parameter Name</td><td>Explanation</td><td>Default Value</td><td>Required</td></tr>
059: * <tr class="row"><td>rule</td><td>The regular expression rule to use for matching on the requested URL.</td><td>None</td><td>Yes</td></tr>
060: * <tr class="altrow"><td>subst</td><td>The string to use for rewriting a new URL that will be used in another request.</td><td>None</td><td>Yes</td></tr>
061: * <tr class="row"><td>useInternal</td><td>Indicates the new URL will be internally redirected.
062: * If it is true, then the new URL will be used internally redirected.
063: * If false, then the new URL will be sent back to the client with the HTTP code specified by redirectCode.</td><td>false</td><td>No</td></tr>
064: * <tr class="altrow"><td>redirectCode</td><td>This defines the HTTP code that will be sent back when we substitue or rewrite a URL.</td><td>302</td><td>No, but ignored if useInternal is true.</td></tr>
065: * </table>
066: * </p>
067: */
068: public class RedirectHandler extends AbstractHandler {
069:
070: private static final Logger log = Logger
071: .getLogger(RedirectHandler.class.getName());
072:
073: public static final ConfigOption RULE_OPTION = new ConfigOption(
074: "rule", true, "Regular expression for matching URLs.");
075: public static final ConfigOption SUBST_OPTION = new ConfigOption(
076: "subst", true,
077: "The substiution expression to re-writing the new URL.");
078: public static final ConfigOption INTERNAL_OPTION = new ConfigOption(
079: "useInternal", "false",
080: "Internal redirect without sending a response.");
081: public static final ConfigOption REDIRECT_CODE_OPTION = new ConfigOption(
082: "redirectCode", "302",
083: "The HTTP code to send back to the client when the URL matches the rule.");
084:
085: Pattern rule;
086: String substitution;
087: boolean isInternalRedirect;
088: int redirectHttpCode = HttpURLConnection.HTTP_MOVED_TEMP;
089:
090: public boolean initialize(String handlerName, Server server) {
091: try {
092: super .initialize(handlerName, server);
093:
094: rule = Pattern.compile(RULE_OPTION.getProperty(server,
095: handlerName), Pattern.CASE_INSENSITIVE);
096: substitution = SUBST_OPTION
097: .getProperty(server, handlerName);
098: isInternalRedirect = INTERNAL_OPTION.getBoolean(server,
099: handlerName).booleanValue();
100: try {
101: redirectHttpCode = REDIRECT_CODE_OPTION.getInteger(
102: server, handlerName).intValue();
103: } catch (NumberFormatException e) {
104: log
105: .warning("redirectCode was not a number! Defaulting to "
106: + redirectHttpCode);
107: }
108:
109: if (log.isLoggable(Level.FINE)) {
110: log.fine("Rule=" + rule.pattern() + ",subst="
111: + substitution + ",useInternal="
112: + isInternalRedirect + ",redirectCode="
113: + redirectHttpCode);
114: }
115: return true;
116: } catch (IllegalArgumentException e) {
117: log.severe(e.toString());
118: return false;
119: }
120: }
121:
122: protected boolean isRequestdForHandler(HttpRequest request) {
123: return !request.isInternal();
124: }
125:
126: protected boolean handleBody(HttpRequest request,
127: HttpResponse response) throws IOException {
128: Matcher urlMatch = rule.matcher(request.getUrl());
129: StringBuffer buffer = null;
130: if (urlMatch.find()) {
131: if (buffer == null) {
132: buffer = new StringBuffer(substitution);
133: }
134: int lastIndex = 0;
135: do {
136: lastIndex = replaceGroupInSubst(buffer, urlMatch);
137: } while (lastIndex < buffer.length());
138:
139: if (isInternalRedirect) {
140: return server.post(new HttpRequest(buffer.toString(),
141: server.getConfig(), true), response);
142: } else {
143: response.setStatusCode(redirectHttpCode);
144: response.addHeader("Location", buffer.toString());
145: return true;
146: }
147: } else {
148: return false;
149: }
150: }
151:
152: private int replaceGroupInSubst(StringBuffer buffer,
153: Matcher urlMatch) {
154: int index = buffer.indexOf("${");
155: if (index >= 0) {
156: int endIndex = substitution.indexOf("}");
157: String reference = substitution.substring(index + 2,
158: endIndex);
159: String subst = null;
160: if (Character.isDigit(reference.charAt(0))) {
161: int group = Integer.parseInt(reference);
162: subst = urlMatch.group(group);
163: } else {
164: subst = server.getProperty(subst);
165: }
166: if (subst != null) {
167: buffer.replace(index, endIndex + 1, subst);
168: }
169: return endIndex + 1;
170: } else {
171: return buffer.length();
172: }
173: }
174: }
|