1 /*******************************************************************************
2  * Copyright (c) 2011, 2020 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     George Suaridze <suag@1c.ru> (1C-Soft LLC) - Bug 560168
14  *******************************************************************************/
15 package org.eclipse.help.internal.webapp.servlet;
16 
17 import java.io.ByteArrayInputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.OutputStream;
21 import java.io.PrintWriter;
22 import java.io.UnsupportedEncodingException;
23 import java.util.ArrayList;
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26 import java.util.List;
27 import java.util.Locale;
28 
29 import javax.servlet.ServletConfig;
30 import javax.servlet.ServletException;
31 import javax.servlet.ServletOutputStream;
32 import javax.servlet.http.HttpServlet;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 import javax.servlet.http.HttpServletResponseWrapper;
36 
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.core.runtime.IConfigurationElement;
39 import org.eclipse.core.runtime.Platform;
40 import org.eclipse.help.internal.webapp.HelpWebappPlugin;
41 import org.eclipse.help.internal.webapp.WebappResources;
42 import org.eclipse.help.internal.webapp.data.UrlUtil;
43 import org.eclipse.help.internal.webapp.utils.Utils;
44 
45 /*
46  * Class is responsible for implementing security protection.  All servlets
47  * who use the org.eclipse.help.webapp.validatedServlet extension point will
48  * will be processed for security failures by this class.
49  *
50  * Any URL that starts with <path>/vs<etc> will be redirected here for further
51  * processing.  If the validatedServlet extension point has an alias that
52  * matches the URL passed here, it will finish the processing and return
53  * results here for validation.  If there are no malicious threats detected,
54  * this class will return the output to the client.
55  *
56  */
57 public class ValidatorServlet extends HttpServlet {
58 
59 	private static final long serialVersionUID = -3783758607845176051L;
60 	private Hashtable<String, HttpServlet> servletTable = new Hashtable<>();
61 
process(HttpServletRequest req, HttpServletResponse resp)62 	protected void process(HttpServletRequest req, HttpServletResponse resp)
63 			throws ServletException, IOException {
64 
65 		String baseURL = req.getRequestURL().toString();
66 		baseURL = baseURL.substring(0, baseURL.indexOf(req.getServletPath()));
67 
68 		Locale locale = UrlUtil.getLocaleObj(req,resp);
69 
70 		String service = req.getRequestURL().toString().substring(
71 				(baseURL).length()+("/vs".length())); //$NON-NLS-1$
72 
73 		try {
74 			HttpServletResponseAdv response = new HttpServletResponseAdv(resp);
75 
76 			HttpServlet servlet = getServlet(service);
77 			ServletConfig config = getServletConfig();
78 			servlet.init(config);
79 			servlet.service(req, response);
80 
81 			if (isSecure(req, response))
82 				response.commitOutput();
83 
84 		} catch(Exception ex) {
85 
86 			String errorMsg = WebappResources.getString("cantCreateServlet", //$NON-NLS-1$
87 					locale, service);
88 			Platform.getLog(getClass()).error(errorMsg, ex);
89 
90 			@SuppressWarnings("resource")
91 			PrintWriter writer = resp.getWriter();
92 			writer.println(errorMsg);
93 			ex.printStackTrace(writer);
94 
95 			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
96 		}
97 	}
98 
getServlet(String name)99 	private HttpServlet getServlet(String name)
100 			throws CoreException {
101 
102 		HttpServlet servlet = servletTable.get(name);
103 
104 		if (servlet == null) {
105 
106 			IConfigurationElement[] configs =
107 				Platform.getExtensionRegistry().getConfigurationElementsFor(HelpWebappPlugin.PLUGIN_ID+".validatedServlet"); //$NON-NLS-1$
108 
109 			for (IConfigurationElement config2 : configs) {
110 
111 				String alias = config2.getAttribute("alias"); //$NON-NLS-1$
112 				if (alias != null) {
113 
114 					if (isMatch(alias, name)) {
115 						servlet = (HttpServlet)config2.createExecutableExtension("class"); //$NON-NLS-1$
116 						servletTable.put(name, servlet);
117 						break;
118 					}
119 				}
120 			}
121 		}
122 
123 		return servlet;
124 	}
125 
isMatch(String alias, String name)126 	private boolean isMatch(String alias, String name) {
127 
128 		int index = name.indexOf(alias);
129 		if (index == 0) {
130 			int offset = alias.length();
131 			if (name.length() == offset)
132 				return true;
133 			char ch = name.charAt(offset);
134 			if (ch == '/' || ch == '?')
135 				return true;
136 		}
137 		return false;
138 	}
139 
140 	@Override
doGet(HttpServletRequest req, HttpServletResponse resp)141 	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
142 			throws ServletException, IOException {
143 		process(req, resp);
144 	}
145 
146 	@Override
doPost(HttpServletRequest req, HttpServletResponse resp)147 	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
148 			throws ServletException, IOException {
149 		process(req, resp);
150 	}
151 
isSecure(HttpServletRequest req,HttpServletResponseAdv resp)152 	public boolean isSecure(HttpServletRequest req,HttpServletResponseAdv resp)
153 			throws SecurityException {
154 		Enumeration<String> names = req.getParameterNames();
155 		List<String> values = new ArrayList<>();
156 		List<String> scripts = new ArrayList<>();
157 
158 		while (names.hasMoreElements()) {
159 
160 			String name = names.nextElement();
161 			String val = req.getParameter(name);
162 			values.add(val);
163 			if (replaceAll(val, '+', "").indexOf("<script")>-1) //$NON-NLS-1$ //$NON-NLS-2$
164 				scripts.add(val);
165 		}
166 
167 		if (resp.getWriter() != null) {
168 			String data = resp.getString();
169 			for (int s=0; s < scripts.size(); s++)
170 				if (data.indexOf(scripts.get(s)) > -1)
171 					throw new SecurityException("Potential cross-site scripting detected."); //$NON-NLS-1$
172 		}
173 
174 		return true;
175 	}
176 
replaceAll(String str, char remove, String add)177 	public String replaceAll(String str, char remove, String add) {
178 
179 		StringBuilder buffer = new StringBuilder();
180 		for (int s=0; s < str.length(); s++) {
181 
182 			char ch = str.charAt(s);
183 			if (ch == remove)
184 				buffer.append(add);
185 			else
186 				buffer.append(ch);
187 		}
188 		return buffer.toString();
189 	}
190 
191 	private class HttpServletResponseAdv extends HttpServletResponseWrapper {
192 
193 		private HttpServletResponse response;
194 		private ServletPrintWriter writer;
195 		private ServletOutputStream stream;
196 
HttpServletResponseAdv(HttpServletResponse response)197 		public HttpServletResponseAdv(HttpServletResponse response) {
198 			super(response);
199 			this.response = response;
200 		}
201 
202 		@Override
getWriter()203 		public PrintWriter getWriter() {
204 
205 			if (writer == null && stream == null)
206 				writer = new ServletPrintWriter();
207 			return writer;
208 		}
209 
210 		@Override
getOutputStream()211 		public ServletOutputStream getOutputStream() throws IOException {
212 
213 			if (stream == null && writer == null)
214 				stream = response.getOutputStream();
215 			return stream;
216 		}
217 
218 		@SuppressWarnings("resource")
commitOutput()219 		public void commitOutput() throws IOException {
220 
221 			OutputStream os = response.getOutputStream();
222 			InputStream is = getInputStream();
223 			if (is != null) {
224 				Utils.transferContent(is, os);
225 			}
226 			os.flush();
227 		}
228 
getInputStream()229 		public InputStream getInputStream() {
230 			if (writer != null) {
231 
232 				try {
233 					return new ByteArrayInputStream(writer.toString().getBytes(getCharacterEncoding()));
234 				} catch (UnsupportedEncodingException e) {
235 					e.printStackTrace();
236 				}
237 			}
238 			return null;
239 		}
240 
getString()241 		public String getString() {
242 
243 			if (writer != null)
244 				return writer.toString();
245 
246 			return null;
247 		}
248 	}
249 }
250