blob: cf3ec65e50c21ad8dde1d5a607609aaf8ec60391 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.roller.weblogger.ui.rendering;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.weblogger.config.WebloggerConfig;
import org.apache.roller.weblogger.business.WebloggerFactory;
import org.apache.roller.weblogger.pojos.Weblog;
/**
* Handles rendering requests for Roller pages/feeds by routing to the appropriate Servlet.
*
* This request mapper is used to map all weblog specific urls of the form
* /<weblog handle>/* to the appropriate servlet for handling the actual
* request.
*
* TODO: we should try and make this class easier to extend and build upon
*/
public class WeblogRequestMapper implements RequestMapper {
private static Log log = LogFactory.getLog(WeblogRequestMapper.class);
private static final String PAGE_SERVLET = "/roller-ui/rendering/page";
private static final String FEED_SERVLET = "/roller-ui/rendering/feed";
private static final String RESOURCE_SERVLET = "/roller-ui/rendering/resources";
private static final String MEDIA_SERVLET = "/roller-ui/rendering/media-resources";
private static final String SEARCH_SERVLET = "/roller-ui/rendering/search";
private static final String RSD_SERVLET = "/roller-ui/rendering/rsd";
private static final String COMMENT_SERVLET = "/roller-ui/rendering/comment";
private static final String TRACKBACK_SERVLET = "/roller-ui/rendering/trackback";
// url patterns that are not allowed to be considered weblog handles
Set<String> restricted = null;
public WeblogRequestMapper() {
this.restricted = new HashSet<String>();
// build roller restricted list
String restrictList =
WebloggerConfig.getProperty("rendering.weblogMapper.rollerProtectedUrls");
if(restrictList != null && restrictList.trim().length() > 0) {
String[] restrict = restrictList.split(",");
this.restricted.addAll(Arrays.asList(restrict));
}
// add user restricted list
restrictList =
WebloggerConfig.getProperty("rendering.weblogMapper.userProtectedUrls");
if(restrictList != null && restrictList.trim().length() > 0) {
String[] restrict = restrictList.split(",");
this.restricted.addAll(Arrays.asList(restrict));
}
}
public boolean handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// kinda silly, but we need to keep track of whether or not the url had
// a trailing slash so that we can act accordingly
boolean trailingSlash = false;
String weblogHandle = null;
String weblogLocale = null;
String weblogRequestContext = null;
String weblogRequestData = null;
log.debug("evaluating ["+request.getRequestURI()+"]");
// figure out potential weblog handle
String servlet = request.getRequestURI();
String pathInfo = null;
if(servlet != null && servlet.trim().length() > 1) {
if(request.getContextPath() != null) {
servlet = servlet.substring(request.getContextPath().length());
}
// strip off the leading slash
servlet = servlet.substring(1);
// strip off trailing slash if needed
if(servlet.endsWith("/")) {
servlet = servlet.substring(0, servlet.length() - 1);
trailingSlash = true;
}
if(servlet.indexOf('/') != -1) {
weblogHandle = servlet.substring(0, servlet.indexOf('/'));
pathInfo = servlet.substring(servlet.indexOf('/')+1);
} else {
weblogHandle = servlet;
}
}
log.debug("potential weblog handle = "+weblogHandle);
// check if it's a valid weblog handle
if(restricted.contains(weblogHandle) || !this.isWeblog(weblogHandle)) {
log.debug("SKIPPED "+weblogHandle);
return false;
}
String weblogAbsoluteURL =
WebloggerConfig.getProperty("weblog.absoluteurl." + weblogHandle);
// If an absolute URL is specified for this weblog, make sure request URL matches
if (weblogAbsoluteURL != null && !request.getRequestURL().toString().startsWith(weblogAbsoluteURL)) {
log.debug("SKIPPED " + weblogHandle);
return false;
}
log.debug("WEBLOG_URL "+request.getServletPath());
// parse the rest of the url and build forward url
if(pathInfo != null) {
// parse the next portion of the url
// we expect [locale/]<context>/<extra>/<info>
String[] urlPath = pathInfo.split("/", 3);
// if we have a locale, deal with it
if(this.isLocale(urlPath[0])) {
weblogLocale = urlPath[0];
// no extra path info specified
if(urlPath.length == 2) {
weblogRequestContext = urlPath[1];
weblogRequestData = null;
// request contains extra path info
} else if(urlPath.length == 3) {
weblogRequestContext = urlPath[1];
weblogRequestData = urlPath[2];
}
// otherwise locale is empty
} else {
weblogLocale = null;
weblogRequestContext = urlPath[0];
// last part of request is extra path info
if(urlPath.length == 2) {
weblogRequestData = urlPath[1];
// if we didn't have a locale then we have split too much
// so we reassemble the last 2 path elements together
} else if(urlPath.length == 3) {
weblogRequestData = urlPath[1] + "/" + urlPath[2];
}
}
}
// special handling for trailing slash issue
// we need this because by http standards the urls /foo and /foo/ are
// supposed to be considered different, so we must enforce that
if(weblogRequestContext == null && !trailingSlash) {
// this means someone referred to a weblog index page with the
// shortest form of url /<weblog> or /<weblog>/<locale> and we need
// to do a redirect to /<weblog>/ or /<weblog>/<locale>/
String redirectUrl = request.getRequestURI() + "/";
if(request.getQueryString() != null) {
redirectUrl += "?"+request.getQueryString();
}
response.sendRedirect(redirectUrl);
return true;
} else if(weblogRequestContext != null &&
"tags".equals(weblogRequestContext)) {
// tags section can have an index page at /<weblog>/tags/ and
// a tags query at /<weblog>/tags/tag1+tag2, buth that's it
if((weblogRequestData == null && !trailingSlash) ||
(weblogRequestData != null && trailingSlash)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return true;
}
} else if(weblogRequestContext != null && trailingSlash) {
// this means that someone has accessed a weblog url and included
// a trailing slash, like /<weblog>/entry/<anchor>/ which is not
// supported, so we need to offer up a 404 Not Found
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return true;
}
// calculate forward url
String forwardUrl = calculateForwardUrl(request, weblogHandle, weblogLocale,
weblogRequestContext, weblogRequestData);
// if we don't have a forward url then the request was invalid somehow
if(forwardUrl == null) {
return false;
}
// dispatch to forward url
log.debug("forwarding to "+forwardUrl);
RequestDispatcher dispatch = request.getRequestDispatcher(forwardUrl);
dispatch.forward(request, response);
// we dealt with this request ourselves, so return "true"
return true;
}
/**
* Convenience method for caculating the servlet forward url given a set
* of information to make the decision with.
*
* handle is always assumed valid, all other params may be null.
*/
private String calculateForwardUrl(HttpServletRequest request,
String handle, String locale,
String context, String data) {
log.debug(handle+","+locale+","+context+","+data);
StringBuilder forwardUrl = new StringBuilder();
// POST urls, like comment and trackback servlets
if("POST".equals(request.getMethod())) {
// posting to permalink, this means comment or trackback
if(context.equals("entry")) {
// trackback requests are required to have an "excerpt" param
if(request.getParameter("excerpt") != null) {
forwardUrl.append(TRACKBACK_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(locale != null) {
forwardUrl.append("/");
forwardUrl.append(locale);
}
forwardUrl.append("/");
forwardUrl.append(context);
if(data != null) {
forwardUrl.append("/");
forwardUrl.append(data);
}
// comment requests are required to have a "content" param
} else if(request.getParameter("content") != null) {
forwardUrl.append(COMMENT_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(locale != null) {
forwardUrl.append("/");
forwardUrl.append(locale);
}
forwardUrl.append("/");
forwardUrl.append(context);
if(data != null) {
forwardUrl.append("/");
forwardUrl.append(data);
}
}
} else {
// someone posting data where they aren't supposed to
return null;
}
} else {
// no context means weblog homepage
if(context == null) {
forwardUrl.append(PAGE_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(locale != null) {
forwardUrl.append("/");
forwardUrl.append(locale);
}
// requests handled by PageServlet
} else if(context.equals("page") || context.equals("entry") ||
context.equals("date") || context.equals("category")
|| context.equals("tags")) {
forwardUrl.append(PAGE_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(locale != null) {
forwardUrl.append("/");
forwardUrl.append(locale);
}
forwardUrl.append("/");
forwardUrl.append(context);
if(data != null) {
forwardUrl.append("/");
forwardUrl.append(data);
}
// requests handled by FeedServlet
} else if(context.equals("feed")) {
forwardUrl.append(FEED_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(locale != null) {
forwardUrl.append("/");
forwardUrl.append(locale);
}
if(data != null) {
forwardUrl.append("/");
forwardUrl.append(data);
}
// requests handled by ResourceServlet
} else if(context.equals("resource")) {
forwardUrl.append(RESOURCE_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(data != null) {
forwardUrl.append("/");
forwardUrl.append(data);
}
// requests handled by MediaResourceServlet
} else if(context.equals("mediaresource")) {
forwardUrl.append(MEDIA_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(data != null) {
forwardUrl.append("/");
forwardUrl.append(data);
}
// requests handled by SearchServlet
} else if(context.equals("search")) {
forwardUrl.append(SEARCH_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
if(locale != null) {
forwardUrl.append("/");
forwardUrl.append(locale);
}
// requests handled by RSDServlet
} else if(context.equals("rsd")) {
forwardUrl.append(RSD_SERVLET);
forwardUrl.append("/");
forwardUrl.append(handle);
// unsupported url
} else {
return null;
}
}
log.debug("FORWARD_URL "+forwardUrl.toString());
return forwardUrl.toString();
}
/**
* convenience method which determines if the given string is a valid
* weblog handle.
*
* TODO 3.0: some kind of caching
*/
private boolean isWeblog(String potentialHandle) {
log.debug("checking weblog handle "+potentialHandle);
boolean isWeblog = false;
try {
Weblog weblog = WebloggerFactory.getWeblogger().getWeblogManager()
.getWeblogByHandle(potentialHandle);
if(weblog != null) {
isWeblog = true;
}
} catch(Exception ex) {
// doesn't really matter to us why it's not a valid website
}
return isWeblog;
}
/**
* Convenience method which determines if the given string is a valid
* locale string.
*/
private boolean isLocale(String potentialLocale) {
boolean isLocale = false;
// we only support 2 or 5 character locale strings, so check that first
if(potentialLocale != null &&
(potentialLocale.length() == 2 || potentialLocale.length() == 5)) {
// now make sure that the format is proper ... e.g. "en_US"
// we are not going to be picky about capitalization
String[] langCountry = potentialLocale.split("_");
if(langCountry.length == 1 &&
langCountry[0] != null && langCountry[0].length() == 2) {
isLocale = true;
} else if(langCountry.length == 2 &&
langCountry[0] != null && langCountry[0].length() == 2 &&
langCountry[1] != null && langCountry[1].length() == 2) {
isLocale = true;
}
}
return isLocale;
}
}