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.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.apache.http.HttpEntity; 037import org.apache.http.HttpEntityEnclosingRequest; 038import org.apache.http.client.methods.CloseableHttpResponse; 039import org.apache.http.client.methods.HttpGet; 040import org.apache.http.client.methods.HttpPost; 041import org.apache.http.client.methods.HttpPut; 042import org.apache.http.client.methods.HttpUriRequest; 043import org.apache.http.entity.InputStreamEntity; 044import org.apache.http.impl.client.CloseableHttpClient; 045import org.apache.http.impl.client.HttpClients; 046import org.apache.http.util.EntityUtils; 047import org.nuxeo.ecm.platform.ui.web.cache.SimpleCacheFilter; 048import org.nuxeo.ecm.platform.web.common.ajax.service.AjaxProxyComponent; 049import org.nuxeo.ecm.platform.web.common.ajax.service.AjaxProxyService; 050import org.nuxeo.ecm.platform.web.common.ajax.service.ProxyURLConfigEntry; 051import org.nuxeo.ecm.platform.web.common.requestcontroller.service.LRUCachingMap; 052import org.nuxeo.runtime.api.Framework; 053 054/** 055 * Simple proxy servlets. 056 * <p> 057 * Used for Ajax requests that needs to be proxied to avoid XSiteScripting issues. 058 * <p> 059 * In order to avoid "open proxiying", only urls configured in the {@link AjaxProxyComponent} via the extension point 060 * "proxyableURL" can be proxied. 061 * 062 * @author tiry 063 */ 064public class AjaxProxyServlet extends HttpServlet { 065 066 public static final String X_METHOD_HEADER = "X-Requested-Method"; 067 068 protected static Map<String, String> requestsCache = new LRUCachingMap<String, String>(250); 069 070 protected static final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); 071 072 private static final long serialVersionUID = 1L; 073 074 private static final Log log = LogFactory.getLog(AjaxProxyServlet.class); 075 076 @Override 077 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 078 handleProxy(req.getMethod(), req, resp); 079 } 080 081 @Override 082 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 083 handleProxy(req.getHeader(X_METHOD_HEADER), req, resp); 084 } 085 086 @Override 087 protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 088 handleProxy(req.getMethod(), req, resp); 089 } 090 091 protected static void handleProxy(String method, HttpServletRequest req, HttpServletResponse resp) 092 throws IOException { 093 // fetch parameters 094 String requestType = req.getParameter("type"); 095 if (requestType == null) { 096 requestType = "text"; 097 } 098 String targetURL = req.getParameter("url"); 099 if (targetURL == null) { 100 return; 101 } 102 String cache = req.getParameter("cache"); 103 104 ProxyURLConfigEntry entry = Framework.getService(AjaxProxyService.class).getConfigForURL(targetURL); 105 if (entry == null || !entry.isGranted()) { 106 resp.sendError(HttpServletResponse.SC_FORBIDDEN); 107 log.warn("client requested proxying for unauthorized url " + targetURL); 108 return; 109 } 110 111 String body = null; 112 String cacheKey = targetURL; 113 114 if (entry.useCache()) { 115 if (entry.useCache()) { 116 cacheKey += getSessionId(req); 117 } 118 try { 119 cacheLock.readLock().lock(); 120 body = requestsCache.get(cacheKey); 121 } finally { 122 cacheLock.readLock().unlock(); 123 } 124 } 125 126 boolean foundInCache = true; 127 if (body == null) { 128 foundInCache = false; 129 body = doRequest(method, targetURL, req); 130 } 131 132 if (!foundInCache && entry.useCache()) { 133 try { 134 cacheLock.writeLock().lock(); 135 requestsCache.put(cacheKey, body); 136 } finally { 137 cacheLock.writeLock().unlock(); 138 } 139 } 140 141 if (requestType.equals("text")) { 142 resp.setContentType("text/plain"); 143 } else if (requestType.equals("xml")) { 144 resp.setContentType("text/xml"); 145 } 146 147 if (cache != null) { 148 SimpleCacheFilter.addCacheHeader(resp, cache); 149 } 150 151 resp.getWriter().write(body); 152 resp.setStatus(HttpServletResponse.SC_OK); 153 } 154 155 protected static String getSessionId(HttpServletRequest req) { 156 String jSessionId = null; 157 for (Cookie cookie : req.getCookies()) { 158 if ("JSESSIONID".equalsIgnoreCase(cookie.getName())) { 159 jSessionId = cookie.getValue(); 160 break; 161 } 162 } 163 return jSessionId; 164 } 165 166 protected static String doRequest(String method, String targetURL, HttpServletRequest req) throws IOException { 167 try (CloseableHttpClient httpClient = HttpClients.createDefault()) { 168 HttpUriRequest request; 169 if ("GET".equals(method)) { 170 request = new HttpGet(targetURL); 171 } else if ("POST".equals(method)) { 172 request = new HttpPost(targetURL); 173 } else if ("PUT".equals(method)) { 174 request = new HttpPut(targetURL); 175 } else { 176 throw new IllegalStateException("Unknown HTTP method: " + method); 177 } 178 if (request instanceof HttpEntityEnclosingRequest) { 179 HttpEntity entity = new InputStreamEntity(req.getInputStream()); 180 ((HttpEntityEnclosingRequest) request).setEntity(entity); 181 } 182 183 Map<String, String[]> params = req.getParameterMap(); 184 for (String paramName : params.keySet()) { 185 request.getParams().setParameter(paramName, params.get(paramName)); 186 } 187 188 try (CloseableHttpResponse response = httpClient.execute(request)) { 189 return EntityUtils.toString(response.getEntity()); 190 } 191 } 192 } 193 194}