001/* 002 * (C) Copyright 2006-2009 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.platform.web.common.ajax; 021 022import java.io.IOException; 023import java.util.Map; 024import java.util.concurrent.locks.ReentrantReadWriteLock; 025 026import javax.servlet.ServletException; 027import javax.servlet.http.Cookie; 028import javax.servlet.http.HttpServlet; 029import javax.servlet.http.HttpServletRequest; 030import javax.servlet.http.HttpServletResponse; 031 032import org.apache.commons.httpclient.HttpClient; 033import org.apache.commons.httpclient.HttpMethod; 034import org.apache.commons.httpclient.methods.GetMethod; 035import org.apache.commons.httpclient.methods.InputStreamRequestEntity; 036import org.apache.commons.httpclient.methods.PostMethod; 037import org.apache.commons.httpclient.methods.PutMethod; 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.ecm.platform.ui.web.cache.SimpleCacheFilter; 041import org.nuxeo.ecm.platform.web.common.ajax.service.AjaxProxyComponent; 042import org.nuxeo.ecm.platform.web.common.ajax.service.AjaxProxyService; 043import org.nuxeo.ecm.platform.web.common.ajax.service.ProxyURLConfigEntry; 044import org.nuxeo.ecm.platform.web.common.requestcontroller.service.LRUCachingMap; 045import org.nuxeo.runtime.api.Framework; 046 047/** 048 * Simple proxy servlets. 049 * <p> 050 * Used for Ajax requests that needs to be proxied to avoid XSiteScripting issues. 051 * <p> 052 * In order to avoid "open proxiying", only urls configured in the {@link AjaxProxyComponent} via the extension point 053 * "proxyableURL" can be proxied. 054 * 055 * @author tiry 056 */ 057public class AjaxProxyServlet extends HttpServlet { 058 059 public static final String X_METHOD_HEADER = "X-Requested-Method"; 060 061 protected static AjaxProxyService service; 062 063 protected static Map<String, String> requestsCache = new LRUCachingMap<String, String>(250); 064 065 protected static final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); 066 067 private static final long serialVersionUID = 1L; 068 069 private static final Log log = LogFactory.getLog(AjaxProxyServlet.class); 070 071 protected static AjaxProxyService getService() { 072 if (service == null) { 073 service = Framework.getLocalService(AjaxProxyService.class); 074 } 075 return service; 076 } 077 078 @Override 079 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 080 handleProxy(req.getMethod(), req, resp); 081 } 082 083 @Override 084 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 085 handleProxy(req.getHeader(X_METHOD_HEADER), req, resp); 086 } 087 088 @Override 089 protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 090 handleProxy(req.getMethod(), req, resp); 091 } 092 093 protected static void handleProxy(String method, HttpServletRequest req, HttpServletResponse resp) 094 throws IOException { 095 // fetch parameters 096 String requestType = req.getParameter("type"); 097 if (requestType == null) { 098 requestType = "text"; 099 } 100 String targetURL = req.getParameter("url"); 101 if (targetURL == null) { 102 return; 103 } 104 String cache = req.getParameter("cache"); 105 106 ProxyURLConfigEntry entry = getService().getConfigForURL(targetURL); 107 if (entry == null || !entry.isGranted()) { 108 resp.sendError(HttpServletResponse.SC_FORBIDDEN); 109 log.warn("client requested proxying for unauthorized url " + targetURL); 110 return; 111 } 112 113 String body = null; 114 String cacheKey = targetURL; 115 116 if (entry.useCache()) { 117 if (entry.useCache()) { 118 cacheKey += getSessionId(req); 119 } 120 try { 121 cacheLock.readLock().lock(); 122 body = requestsCache.get(cacheKey); 123 } finally { 124 cacheLock.readLock().unlock(); 125 } 126 } 127 128 boolean foundInCache = true; 129 if (body == null) { 130 foundInCache = false; 131 body = doRequest(method, targetURL, req); 132 } 133 134 if (!foundInCache && entry.useCache()) { 135 try { 136 cacheLock.writeLock().lock(); 137 requestsCache.put(cacheKey, body); 138 } finally { 139 cacheLock.writeLock().unlock(); 140 } 141 } 142 143 if (requestType.equals("text")) { 144 resp.setContentType("text/plain"); 145 } else if (requestType.equals("xml")) { 146 resp.setContentType("text/xml"); 147 } 148 149 if (cache != null) { 150 SimpleCacheFilter.addCacheHeader(resp, cache); 151 } 152 153 resp.getWriter().write(body); 154 resp.setStatus(HttpServletResponse.SC_OK); 155 } 156 157 protected static String getSessionId(HttpServletRequest req) { 158 String jSessionId = null; 159 for (Cookie cookie : req.getCookies()) { 160 if ("JSESSIONID".equalsIgnoreCase(cookie.getName())) { 161 jSessionId = cookie.getValue(); 162 break; 163 } 164 } 165 return jSessionId; 166 } 167 168 protected static String doRequest(String method, String targetURL, HttpServletRequest req) throws IOException { 169 HttpClient client = new HttpClient(); 170 HttpMethod httpMethod; 171 172 if ("GET".equals(method)) { 173 httpMethod = new GetMethod(targetURL); 174 } else if ("POST".equals(method)) { 175 httpMethod = new PostMethod(targetURL); 176 ((PostMethod) httpMethod).setRequestEntity(new InputStreamRequestEntity(req.getInputStream())); 177 } else if ("PUT".equals(method)) { 178 httpMethod = new PutMethod(targetURL); 179 ((PutMethod) httpMethod).setRequestEntity(new InputStreamRequestEntity(req.getInputStream())); 180 } else { 181 throw new IllegalStateException("Unknown HTTP method: " + method); 182 } 183 184 Map<String, String[]> params = req.getParameterMap(); 185 for (String paramName : params.keySet()) { 186 httpMethod.getParams().setParameter(paramName, params.get(paramName)); 187 } 188 189 client.executeMethod(httpMethod); 190 String body = httpMethod.getResponseBodyAsString(); 191 httpMethod.releaseConnection(); 192 return body; 193 } 194 195}