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}