/* * (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed 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. * * Contributors: * Nuxeo - initial API and implementation * * $Id$ */ package org.nuxeo.ecm.platform.web.common.ajax; import java.io.IOException; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.InputStreamRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.platform.ui.web.cache.SimpleCacheFilter; import org.nuxeo.ecm.platform.web.common.ajax.service.AjaxProxyComponent; import org.nuxeo.ecm.platform.web.common.ajax.service.AjaxProxyService; import org.nuxeo.ecm.platform.web.common.ajax.service.ProxyURLConfigEntry; import org.nuxeo.ecm.platform.web.common.requestcontroller.service.LRUCachingMap; import org.nuxeo.runtime.api.Framework; /** * Simple proxy servlets. * <p> * Used for Ajax requests that needs to be proxied to avoid XSiteScripting issues. * <p> * In order to avoid "open proxiying", only urls configured in the {@link AjaxProxyComponent} via the extension point * "proxyableURL" can be proxied. * * @author tiry */ public class AjaxProxyServlet extends HttpServlet { public static final String X_METHOD_HEADER = "X-Requested-Method"; protected static AjaxProxyService service; protected static Map<String, String> requestsCache = new LRUCachingMap<String, String>(250); protected static final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(AjaxProxyServlet.class); protected static AjaxProxyService getService() { if (service == null) { service = Framework.getLocalService(AjaxProxyService.class); } return service; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { handleProxy(req.getMethod(), req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { handleProxy(req.getHeader(X_METHOD_HEADER), req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { handleProxy(req.getMethod(), req, resp); } protected static void handleProxy(String method, HttpServletRequest req, HttpServletResponse resp) throws IOException { // fetch parameters String requestType = req.getParameter("type"); if (requestType == null) { requestType = "text"; } String targetURL = req.getParameter("url"); if (targetURL == null) { return; } String cache = req.getParameter("cache"); ProxyURLConfigEntry entry = getService().getConfigForURL(targetURL); if (entry == null || !entry.isGranted()) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); log.warn("client requested proxying for unauthorized url " + targetURL); return; } String body = null; String cacheKey = targetURL; if (entry.useCache()) { if (entry.useCache()) { cacheKey += getSessionId(req); } try { cacheLock.readLock().lock(); body = requestsCache.get(cacheKey); } finally { cacheLock.readLock().unlock(); } } boolean foundInCache = true; if (body == null) { foundInCache = false; body = doRequest(method, targetURL, req); } if (!foundInCache && entry.useCache()) { try { cacheLock.writeLock().lock(); requestsCache.put(cacheKey, body); } finally { cacheLock.writeLock().unlock(); } } if (requestType.equals("text")) { resp.setContentType("text/plain"); } else if (requestType.equals("xml")) { resp.setContentType("text/xml"); } if (cache != null) { SimpleCacheFilter.addCacheHeader(resp, cache); } resp.getWriter().write(body); resp.setStatus(HttpServletResponse.SC_OK); } protected static String getSessionId(HttpServletRequest req) { String jSessionId = null; for (Cookie cookie : req.getCookies()) { if ("JSESSIONID".equalsIgnoreCase(cookie.getName())) { jSessionId = cookie.getValue(); break; } } return jSessionId; } protected static String doRequest(String method, String targetURL, HttpServletRequest req) throws IOException { HttpClient client = new HttpClient(); HttpMethod httpMethod; if ("GET".equals(method)) { httpMethod = new GetMethod(targetURL); } else if ("POST".equals(method)) { httpMethod = new PostMethod(targetURL); ((PostMethod) httpMethod).setRequestEntity(new InputStreamRequestEntity(req.getInputStream())); } else if ("PUT".equals(method)) { httpMethod = new PutMethod(targetURL); ((PutMethod) httpMethod).setRequestEntity(new InputStreamRequestEntity(req.getInputStream())); } else { throw new IllegalStateException("Unknown HTTP method: " + method); } Map<String, String[]> params = req.getParameterMap(); for (String paramName : params.keySet()) { httpMethod.getParams().setParameter(paramName, params.get(paramName)); } client.executeMethod(httpMethod); String body = httpMethod.getResponseBodyAsString(); httpMethod.releaseConnection(); return body; } }