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