001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018:
019: package org.apache.roller.ui.rendering;
020:
021: import java.io.IOException;
022: import java.util.HashSet;
023: import java.util.Set;
024: import javax.servlet.RequestDispatcher;
025: import javax.servlet.ServletException;
026: import javax.servlet.http.HttpServletRequest;
027: import javax.servlet.http.HttpServletResponse;
028:
029: import org.apache.commons.lang.StringUtils;
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.apache.roller.config.RollerConfig;
033: import org.apache.roller.business.RollerFactory;
034: import org.apache.roller.business.UserManager;
035: import org.apache.roller.pojos.WebsiteData;
036:
037: /**
038: * Roller's weblog request mapper.
039: *
040: * This request mapper is used to map all weblog specific urls of the form
041: * /<weblog handle>/* to the appropriate servlet for handling the actual
042: * request.
043: *
044: * TODO: we should try and make this class easier to extend and build upon
045: */
046: public class WeblogRequestMapper implements RequestMapper {
047:
048: private static Log log = LogFactory
049: .getLog(WeblogRequestMapper.class);
050:
051: private static final String PAGE_SERVLET = "/roller-ui/rendering/page";
052: private static final String FEED_SERVLET = "/roller-ui/rendering/feed";
053: private static final String RESOURCE_SERVLET = "/roller-ui/rendering/resources";
054: private static final String SEARCH_SERVLET = "/roller-ui/rendering/search";
055: private static final String RSD_SERVLET = "/roller-ui/rendering/rsd";
056:
057: private static final String COMMENT_SERVLET = "/roller-ui/rendering/comment";
058: private static final String TRACKBACK_SERVLET = "/roller-ui/rendering/trackback";
059:
060: // url patterns that are not allowed to be considered weblog handles
061: Set restricted = null;
062:
063: public WeblogRequestMapper() {
064:
065: this .restricted = new HashSet();
066:
067: // build roller restricted list
068: String restrictList = RollerConfig
069: .getProperty("rendering.weblogMapper.rollerProtectedUrls");
070: if (restrictList != null && restrictList.trim().length() > 0) {
071: String[] restrict = restrictList.split(",");
072: for (int i = 0; i < restrict.length; i++) {
073: this .restricted.add(restrict[i]);
074: }
075: }
076:
077: // add user restricted list
078: restrictList = RollerConfig
079: .getProperty("rendering.weblogMapper.userProtectedUrls");
080: if (restrictList != null && restrictList.trim().length() > 0) {
081: String[] restrict = restrictList.split(",");
082: for (int i = 0; i < restrict.length; i++) {
083: this .restricted.add(restrict[i]);
084: }
085: }
086: }
087:
088: public boolean handleRequest(HttpServletRequest request,
089: HttpServletResponse response) throws ServletException,
090: IOException {
091:
092: // kinda silly, but we need to keep track of whether or not the url had
093: // a trailing slash so that we can act accordingly
094: boolean trailingSlash = false;
095:
096: String weblogHandle = null;
097: String weblogLocale = null;
098: String weblogRequestContext = null;
099: String weblogRequestData = null;
100:
101: log.debug("evaluating [" + request.getRequestURI() + "]");
102:
103: // figure out potential weblog handle
104: String servlet = request.getRequestURI();
105: String pathInfo = null;
106:
107: if (servlet != null && servlet.trim().length() > 1) {
108:
109: if (request.getContextPath() != null)
110: servlet = servlet.substring(request.getContextPath()
111: .length());
112:
113: // strip off the leading slash
114: servlet = servlet.substring(1);
115:
116: // strip off trailing slash if needed
117: if (servlet.endsWith("/")) {
118: servlet = servlet.substring(0, servlet.length() - 1);
119: trailingSlash = true;
120: }
121:
122: if (servlet.indexOf("/") != -1) {
123: weblogHandle = servlet.substring(0, servlet
124: .indexOf("/"));
125: pathInfo = servlet.substring(servlet.indexOf("/") + 1);
126: } else {
127: weblogHandle = servlet;
128: }
129: }
130:
131: log.debug("potential weblog handle = " + weblogHandle);
132:
133: // check if it's a valid weblog handle
134: if (restricted.contains(weblogHandle)
135: || !this .isWeblog(weblogHandle)) {
136: log.debug("SKIPPED " + weblogHandle);
137: return false;
138: }
139:
140: log.debug("WEBLOG_URL " + request.getServletPath());
141:
142: // parse the rest of the url and build forward url
143: if (pathInfo != null) {
144:
145: // parse the next portion of the url
146: // we expect [locale/]<context>/<extra>/<info>
147: String[] urlPath = pathInfo.split("/", 3);
148:
149: // if we have a locale, deal with it
150: if (this .isLocale(urlPath[0])) {
151: weblogLocale = urlPath[0];
152:
153: // no extra path info specified
154: if (urlPath.length == 2) {
155: weblogRequestContext = urlPath[1];
156: weblogRequestData = null;
157:
158: // request contains extra path info
159: } else if (urlPath.length == 3) {
160: weblogRequestContext = urlPath[1];
161: weblogRequestData = urlPath[2];
162: }
163:
164: // otherwise locale is empty
165: } else {
166: weblogLocale = null;
167: weblogRequestContext = urlPath[0];
168:
169: // last part of request is extra path info
170: if (urlPath.length == 2) {
171: weblogRequestData = urlPath[1];
172:
173: // if we didn't have a locale then we have split too much
174: // so we reassemble the last 2 path elements together
175: } else if (urlPath.length == 3) {
176: weblogRequestData = urlPath[1] + "/" + urlPath[2];
177: }
178: }
179:
180: }
181:
182: // special handling for trailing slash issue
183: // we need this because by http standards the urls /foo and /foo/ are
184: // supposed to be considered different, so we must enforce that
185: if (weblogRequestContext == null && !trailingSlash) {
186: // this means someone referred to a weblog index page with the
187: // shortest form of url /<weblog> or /<weblog>/<locale> and we need
188: // to do a redirect to /<weblog>/ or /<weblog>/<locale>/
189: String redirectUrl = request.getRequestURI() + "/";
190: if (request.getQueryString() != null) {
191: redirectUrl += "?" + request.getQueryString();
192: }
193:
194: response.sendRedirect(redirectUrl);
195: return true;
196:
197: } else if (weblogRequestContext != null && trailingSlash) {
198: // this means that someone has accessed a weblog url and included
199: // a trailing slash, like /<weblog>/entry/<anchor>/ which is not
200: // supported, so we need to offer up a 404 Not Found
201: response.sendError(HttpServletResponse.SC_NOT_FOUND);
202: return true;
203: }
204:
205: // calculate forward url
206: String forwardUrl = calculateForwardUrl(request, weblogHandle,
207: weblogLocale, weblogRequestContext, weblogRequestData);
208:
209: // if we don't have a forward url then the request was invalid somehow
210: if (forwardUrl == null) {
211: return false;
212: }
213:
214: // dispatch to forward url
215: log.debug("forwarding to " + forwardUrl);
216: RequestDispatcher dispatch = request
217: .getRequestDispatcher(forwardUrl);
218: dispatch.forward(request, response);
219:
220: // we dealt with this request ourselves, so return "true"
221: return true;
222: }
223:
224: /**
225: * Convenience method for caculating the servlet forward url given a set
226: * of information to make the decision with.
227: *
228: * handle is always assumed valid, all other params may be null.
229: */
230: private String calculateForwardUrl(HttpServletRequest request,
231: String handle, String locale, String context, String data) {
232:
233: log.debug(handle + "," + locale + "," + context + "," + data);
234:
235: StringBuffer forwardUrl = new StringBuffer();
236:
237: // POST urls, like comment and trackback servlets
238: if ("POST".equals(request.getMethod())) {
239: // posting to permalink, this means comment or trackback
240: if (context.equals("entry")) {
241: // trackback requests are required to have an "excerpt" param
242: if (request.getParameter("excerpt") != null) {
243:
244: forwardUrl.append(TRACKBACK_SERVLET);
245: forwardUrl.append("/");
246: forwardUrl.append(handle);
247: if (locale != null) {
248: forwardUrl.append("/");
249: forwardUrl.append(locale);
250: }
251: forwardUrl.append("/");
252: forwardUrl.append(context);
253: if (data != null) {
254: forwardUrl.append("/");
255: forwardUrl.append(data);
256: }
257:
258: // comment requests are required to have a "content" param
259: } else if (request.getParameter("content") != null) {
260:
261: forwardUrl.append(COMMENT_SERVLET);
262: forwardUrl.append("/");
263: forwardUrl.append(handle);
264: if (locale != null) {
265: forwardUrl.append("/");
266: forwardUrl.append(locale);
267: }
268: forwardUrl.append("/");
269: forwardUrl.append(context);
270: if (data != null) {
271: forwardUrl.append("/");
272: forwardUrl.append(data);
273: }
274: }
275:
276: } else {
277: // someone posting data where they aren't supposed to
278: return null;
279: }
280:
281: } else {
282: // no context means weblog homepage
283: if (context == null) {
284:
285: forwardUrl.append(PAGE_SERVLET);
286: forwardUrl.append("/");
287: forwardUrl.append(handle);
288: if (locale != null) {
289: forwardUrl.append("/");
290: forwardUrl.append(locale);
291: }
292:
293: // requests handled by PageServlet
294: } else if (context.equals("page")
295: || context.equals("entry")
296: || context.equals("date")
297: || context.equals("category")
298: || context.equals("tags")) {
299:
300: forwardUrl.append(PAGE_SERVLET);
301: forwardUrl.append("/");
302: forwardUrl.append(handle);
303: if (locale != null) {
304: forwardUrl.append("/");
305: forwardUrl.append(locale);
306: }
307: forwardUrl.append("/");
308: forwardUrl.append(context);
309: if (data != null) {
310: forwardUrl.append("/");
311: forwardUrl.append(data);
312: }
313:
314: // requests handled by FeedServlet
315: } else if (context.equals("feed")) {
316:
317: forwardUrl.append(FEED_SERVLET);
318: forwardUrl.append("/");
319: forwardUrl.append(handle);
320: if (locale != null) {
321: forwardUrl.append("/");
322: forwardUrl.append(locale);
323: }
324: if (data != null) {
325: forwardUrl.append("/");
326: forwardUrl.append(data);
327: }
328:
329: // requests handled by ResourceServlet
330: } else if (context.equals("resource")) {
331:
332: forwardUrl.append(RESOURCE_SERVLET);
333: forwardUrl.append("/");
334: forwardUrl.append(handle);
335: if (data != null) {
336: forwardUrl.append("/");
337: forwardUrl.append(data);
338: }
339:
340: // requests handled by SearchServlet
341: } else if (context.equals("search")) {
342:
343: forwardUrl.append(SEARCH_SERVLET);
344: forwardUrl.append("/");
345: forwardUrl.append(handle);
346:
347: // requests handled by RSDServlet
348: } else if (context.equals("rsd")) {
349:
350: forwardUrl.append(RSD_SERVLET);
351: forwardUrl.append("/");
352: forwardUrl.append(handle);
353:
354: // unsupported url
355: } else {
356: return null;
357: }
358: }
359:
360: log.debug("FORWARD_URL " + forwardUrl.toString());
361:
362: return forwardUrl.toString();
363: }
364:
365: /**
366: * convenience method which determines if the given string is a valid
367: * weblog handle.
368: *
369: * TODO 3.0: some kind of caching
370: */
371: private boolean isWeblog(String potentialHandle) {
372:
373: log.debug("checking weblog handle " + potentialHandle);
374:
375: boolean isWeblog = false;
376:
377: try {
378: UserManager mgr = RollerFactory.getRoller()
379: .getUserManager();
380: WebsiteData weblog = mgr
381: .getWebsiteByHandle(potentialHandle);
382:
383: if (weblog != null) {
384: isWeblog = true;
385: }
386: } catch (Exception ex) {
387: // doesn't really matter to us why it's not a valid website
388: }
389:
390: return isWeblog;
391: }
392:
393: /**
394: * Convenience method which determines if the given string is a valid
395: * locale string.
396: */
397: private boolean isLocale(String potentialLocale) {
398:
399: boolean isLocale = false;
400:
401: // we only support 2 or 5 character locale strings, so check that first
402: if (potentialLocale != null
403: && (potentialLocale.length() == 2 || potentialLocale
404: .length() == 5)) {
405:
406: // now make sure that the format is proper ... e.g. "en_US"
407: // we are not going to be picky about capitalization
408: String[] langCountry = potentialLocale.split("_");
409: if (langCountry.length == 1 && langCountry[0] != null
410: && langCountry[0].length() == 2) {
411: isLocale = true;
412:
413: } else if (langCountry.length == 2
414: && langCountry[0] != null
415: && langCountry[0].length() == 2
416: && langCountry[1] != null
417: && langCountry[1].length() == 2) {
418:
419: isLocale = true;
420: }
421: }
422:
423: return isLocale;
424: }
425:
426: }
|