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}