LEFT | RIGHT |
1 // Copyright (C) 2008 Google Inc. | 1 // Copyright (C) 2008 Google Inc. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 package com.google.caja.service; | 15 package com.google.caja.service; |
16 | 16 |
| 17 import com.google.caja.reporting.BuildInfo; |
17 import com.google.caja.util.Pair; | 18 import com.google.caja.util.Pair; |
18 import com.google.caja.lexer.ExternalReference; | 19 import com.google.caja.lexer.ExternalReference; |
19 import com.google.caja.opensocial.UriCallback; | 20 import com.google.caja.opensocial.UriCallback; |
20 import com.google.caja.opensocial.UriCallbackException; | 21 import com.google.caja.opensocial.UriCallbackException; |
21 import com.google.caja.reporting.BuildInfo; | |
22 | |
23 import javax.servlet.ServletException; | |
24 import javax.servlet.http.HttpServlet; | |
25 import javax.servlet.http.HttpServletRequest; | |
26 import javax.servlet.http.HttpServletResponse; | |
27 | 22 |
28 import java.io.ByteArrayInputStream; | 23 import java.io.ByteArrayInputStream; |
29 import java.io.ByteArrayOutputStream; | 24 import java.io.ByteArrayOutputStream; |
30 import java.io.IOException; | 25 import java.io.IOException; |
31 import java.io.InputStream; | 26 import java.io.InputStream; |
32 import java.io.InputStreamReader; | 27 import java.io.InputStreamReader; |
33 import java.io.OutputStream; | 28 import java.io.OutputStream; |
34 import java.io.Reader; | 29 import java.io.Reader; |
35 import java.net.URI; | 30 import java.net.URI; |
36 import java.net.URISyntaxException; | 31 import java.net.URISyntaxException; |
37 import java.net.URLConnection; | 32 import java.net.URLConnection; |
38 import java.util.List; | 33 import java.util.List; |
39 import java.util.Vector; | 34 import java.util.Vector; |
40 | 35 |
| 36 import javax.servlet.ServletException; |
| 37 import javax.servlet.http.HttpServlet; |
| 38 import javax.servlet.http.HttpServletRequest; |
| 39 import javax.servlet.http.HttpServletResponse; |
| 40 |
41 /** | 41 /** |
42 * A cajoling service which proxies connections:<ul> | 42 * A cajoling service which proxies connections:<ul> |
43 * <li> cajole any javascript | 43 * <li> cajole any javascript |
44 * <li> cajoles any gadgets | 44 * <li> cajoles any gadgets |
45 * <li> checks requested and retrieved mime-types | 45 * <li> checks requested and retrieved mime-types |
46 * </ul> | 46 * </ul> |
47 * | 47 * |
48 * @author jasvir@gmail.com (Jasvir Nagra) | 48 * @author jasvir@gmail.com (Jasvir Nagra) |
49 */ | 49 */ |
50 public class CajolingService extends HttpServlet { | 50 public class CajolingService extends HttpServlet { |
51 private List<ContentHandler> handlers = new Vector<ContentHandler>(); | 51 private List<ContentHandler> handlers = new Vector<ContentHandler>(); |
52 private ContentTypeCheck typeCheck = new LooseContentTypeCheck(); | 52 private ContentTypeCheck typeCheck = new LooseContentTypeCheck(); |
| 53 private String host = "http://caja.appspot.com/cajoler"; |
53 | 54 |
54 public CajolingService(BuildInfo buildInfo) { | 55 public CajolingService(BuildInfo buildInfo) { |
| 56 registerHandlers(buildInfo); |
| 57 } |
| 58 |
| 59 public CajolingService(BuildInfo buildInfo, String host) { |
| 60 this.host = host; |
55 registerHandlers(buildInfo); | 61 registerHandlers(buildInfo); |
56 } | 62 } |
57 | 63 |
58 /** | 64 /** |
59 * Read the remainder of the input request, send a BAD_REQUEST http status | 65 * Read the remainder of the input request, send a BAD_REQUEST http status |
60 * to browser and close the connection | 66 * to browser and close the connection |
61 */ | 67 */ |
62 private static void closeBadRequest(HttpServletResponse resp) | 68 private static void closeBadRequest(HttpServletResponse resp) |
63 throws ServletException { | 69 throws ServletException { |
64 try { | 70 try { |
65 resp.sendError(HttpServletResponse.SC_FORBIDDEN); | 71 resp.sendError(HttpServletResponse.SC_FORBIDDEN); |
66 resp.getWriter().close(); | 72 resp.getWriter().close(); |
67 } catch (IOException ex) { | 73 } catch (IOException ex) { |
68 throw (ServletException) new ServletException().initCause(ex); | 74 throw (ServletException) new ServletException().initCause(ex); |
69 } | 75 } |
70 } | 76 } |
71 | 77 |
| 78 /** |
| 79 * Fetch query parameter from request |
| 80 */ |
| 81 private String getParam(HttpServletRequest r, String param, boolean required) |
| 82 throws ServletException { |
| 83 String result = r.getParameter(param); |
| 84 if (required && result == null) { |
| 85 throw new ServletException( |
| 86 "Missing parameter \"" + param + "\" is required: " + |
| 87 r.getRequestURI()); |
| 88 } |
| 89 return result; |
| 90 } |
| 91 |
72 @Override | 92 @Override |
73 public void doGet(HttpServletRequest req, HttpServletResponse resp) | 93 public void doGet(HttpServletRequest req, HttpServletResponse resp) |
74 throws ServletException { | 94 throws ServletException { |
75 | 95 |
76 String gadgetUrlString = req.getParameter("url"); | 96 String gadgetUrlString = getParam(req, "url", true /* required */); |
77 if (gadgetUrlString == null) { | |
78 throw new ServletException( | |
79 "Missing parameter \"url\" is required: " + req.getRequestURI()); | |
80 } | |
81 URI gadgetUrl; | 97 URI gadgetUrl; |
82 try { | 98 try { |
83 gadgetUrl = new URI(gadgetUrlString); | 99 gadgetUrl = new URI(gadgetUrlString); |
84 } catch (URISyntaxException ex) { | 100 } catch (URISyntaxException ex) { |
85 throw (ServletException) new ServletException().initCause(ex); | 101 throw (ServletException) new ServletException().initCause(ex); |
86 } | 102 } |
87 | 103 |
88 String expectedMimeType = req.getParameter("mime-type"); | 104 String expectedMimeType = getParam(req, "mime-type", true /* required */); |
89 if (expectedMimeType == null) { | 105 Transform transform; |
90 throw new ServletException( | 106 try { |
91 "Missing parameter \"mime-type\" is required: " | 107 transform = Transform.valueOf( |
92 + req.getRequestURI()); | 108 getParam(req, "transform", false /* required */)); |
| 109 } catch (Exception e ) { |
| 110 transform = null; |
93 } | 111 } |
94 | 112 |
95 String contentType, contentCharSet; | 113 String contentType, contentCharSet; |
96 byte[] content; | 114 byte[] content; |
97 | 115 |
98 try { | 116 try { |
99 FetchedData fetched = fetch(gadgetUrl); | 117 FetchedData fetched = fetch(gadgetUrl); |
100 contentType = fetched.contentType; | 118 contentType = fetched.contentType; |
101 content = fetched.content; | 119 content = fetched.content; |
102 contentCharSet = fetched.charSet; | 120 contentCharSet = fetched.charSet; |
103 } catch (IOException ex) { | 121 } catch (IOException ex) { |
104 closeBadRequest(resp); | 122 closeBadRequest(resp); |
105 return; | 123 return; |
106 } | 124 } |
107 | 125 |
108 if (!typeCheck.check(expectedMimeType, contentType)) { | 126 if (!typeCheck.check(expectedMimeType, contentType)) { |
109 closeBadRequest(resp); | 127 closeBadRequest(resp); |
110 return; | 128 return; |
111 } | 129 } |
112 | 130 |
113 ByteArrayOutputStream intermediateResponse = new ByteArrayOutputStream(); | 131 ByteArrayOutputStream intermediateResponse = new ByteArrayOutputStream(); |
114 Pair<String, String> contentInfo; | 132 Pair<String, String> contentInfo; |
115 try { | 133 try { |
116 contentInfo = applyHandler( | 134 contentInfo = applyHandler( |
117 URI.create(gadgetUrl.toString()), contentType, contentCharSet, | 135 URI.create(gadgetUrl.toString()), |
| 136 transform, contentType, contentCharSet, |
118 content, intermediateResponse); | 137 content, intermediateResponse); |
119 } catch (UnsupportedContentTypeException e) { | 138 } catch (UnsupportedContentTypeException e) { |
120 closeBadRequest(resp); | 139 closeBadRequest(resp); |
121 return; | 140 return; |
122 } | 141 } |
123 | 142 |
124 byte[] response = intermediateResponse.toByteArray(); | 143 byte[] response = intermediateResponse.toByteArray(); |
125 int responseLength = response.length; | 144 int responseLength = response.length; |
126 | 145 |
127 resp.setStatus(HttpServletResponse.SC_OK); | 146 resp.setStatus(HttpServletResponse.SC_OK); |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 buffer.write(barr, 0, n); | 181 buffer.write(barr, 0, n); |
163 } | 182 } |
164 } finally { | 183 } finally { |
165 stream.close(); | 184 stream.close(); |
166 } | 185 } |
167 byte[] content = buffer.toByteArray(); | 186 byte[] content = buffer.toByteArray(); |
168 return new FetchedData(content, contentType, contentCharSet); | 187 return new FetchedData(content, contentType, contentCharSet); |
169 } | 188 } |
170 | 189 |
171 public void registerHandlers(BuildInfo buildInfo) { | 190 public void registerHandlers(BuildInfo buildInfo) { |
172 handlers.add(new JsHandler(buildInfo)); | 191 UriCallback retriever = new UriCallback() { |
173 handlers.add(new ImageHandler()); | |
174 handlers.add(new GadgetHandler(buildInfo, new UriCallback() { | |
175 public Reader retrieve(ExternalReference extref, String mimeType) | 192 public Reader retrieve(ExternalReference extref, String mimeType) |
176 throws UriCallbackException { | 193 throws UriCallbackException { |
177 try { | 194 try { |
178 FetchedData data = fetch(extref.getUri()); | 195 FetchedData data = fetch(extref.getUri()); |
179 if (data == null) { return null; } | 196 if (data == null) { return null; } |
180 return new InputStreamReader( | 197 return new InputStreamReader( |
181 new ByteArrayInputStream(data.content), data.charSet); | 198 new ByteArrayInputStream(data.content), data.charSet); |
182 } catch (IOException ex) { | 199 } catch (IOException ex) { |
183 throw new UriCallbackException(extref, ex); | 200 throw new UriCallbackException(extref, ex); |
184 } | 201 } |
185 } | 202 } |
186 | 203 |
187 public URI rewrite(ExternalReference extref, String mimeType) { | 204 public URI rewrite(ExternalReference extref, String mimeType) { |
188 return null; | 205 return null; |
189 } | 206 } |
190 })); | 207 }; |
191 } | 208 handlers.add(new JsHandler(buildInfo)); |
192 | 209 handlers.add(new ImageHandler()); |
193 private Pair<String, String> applyHandler(URI uri, | 210 handlers.add(new GadgetHandler(buildInfo, retriever)); |
194 String contentType, String charSet, | 211 handlers.add(new InnocentHandler()); |
| 212 handlers.add(new HtmlHandler(buildInfo, host, retriever)); |
| 213 } |
| 214 |
| 215 private Pair<String, String> applyHandler( |
| 216 URI uri, Transform t, String contentType, String charSet, |
195 byte[] content, OutputStream response) | 217 byte[] content, OutputStream response) |
196 throws UnsupportedContentTypeException { | 218 throws UnsupportedContentTypeException { |
197 for (ContentHandler handler : handlers) { | 219 for (ContentHandler handler : handlers) { |
198 if (handler.canHandle(uri, contentType, typeCheck)) { | 220 if (handler.canHandle(uri, t, contentType, typeCheck)) { |
199 return handler.apply(uri, contentType, charSet, content, response); | 221 return handler.apply(uri, t, contentType, charSet, content, response); |
200 } | 222 } |
201 } | 223 } |
202 throw new UnsupportedContentTypeException(); | 224 throw new UnsupportedContentTypeException(); |
203 } | 225 } |
204 | 226 |
205 public static final class FetchedData { | 227 public static final class FetchedData { |
206 final byte[] content; | 228 final byte[] content; |
207 final String contentType; | 229 final String contentType; |
208 final String charSet; | 230 final String charSet; |
209 FetchedData(byte[] content, String contentType, String charSet) { | 231 FetchedData(byte[] content, String contentType, String charSet) { |
210 this.content = content; | 232 this.content = content; |
211 this.contentType = contentType; | 233 this.contentType = contentType; |
212 this.charSet = charSet; | 234 this.charSet = charSet; |
213 } | 235 } |
214 } | 236 } |
215 | 237 |
216 // Used to protect against header splitting attacks. | 238 // Used to protect against header splitting attacks. |
217 private static boolean containsNewline(String s) { | 239 private static boolean containsNewline(String s) { |
218 return s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0; | 240 return s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0; |
219 } | 241 } |
| 242 |
| 243 public static enum Transform { |
| 244 INNOCENT, |
| 245 VALIJA, |
| 246 CAJITA; |
| 247 } |
220 } | 248 } |
LEFT | RIGHT |