blob: 91c66bbbd316dcee44e153c92341efb6de77ff51 [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (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
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles;
16
Dave Borowitzc410f962014-09-23 10:49:26 -070017import static com.google.common.base.MoreObjects.firstNonNull;
Dave Borowitzb1c628f2013-01-11 11:28:20 -080018import static com.google.gitiles.FormatType.DEFAULT;
19import static com.google.gitiles.FormatType.HTML;
20import static com.google.gitiles.FormatType.JSON;
21import static com.google.gitiles.FormatType.TEXT;
David Pletcherd7bdaf32014-08-27 14:50:32 -070022import static java.nio.charset.StandardCharsets.UTF_8;
Dave Borowitzb1c628f2013-01-11 11:28:20 -080023import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
Dave Borowitz9de65952012-08-13 16:09:45 -070024import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
Dave Borowitzb1c628f2013-01-11 11:28:20 -080025import static javax.servlet.http.HttpServletResponse.SC_OK;
Shawn Pearcec4d3fd72015-02-10 14:32:37 -080026import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
Dave Borowitz9de65952012-08-13 16:09:45 -070027
AJ Ross001ea9b2016-08-23 13:40:04 -070028import com.google.common.base.Joiner;
Dave Borowitz4f568702014-05-01 19:54:57 -070029import com.google.common.base.Strings;
Dave Borowitz54271462013-11-11 11:43:11 -080030import com.google.common.collect.ImmutableMap;
31import com.google.common.collect.Maps;
32import com.google.common.net.HttpHeaders;
Masaya Suzuki5cecb862019-03-25 17:35:44 -070033import com.google.gitiles.GitilesRequestFailureException.FailureReason;
Dave Borowitz54271462013-11-11 11:43:11 -080034import com.google.gson.FieldNamingPolicy;
35import com.google.gson.GsonBuilder;
Dave Borowitzae171e62017-05-11 17:51:37 -040036import java.io.BufferedWriter;
Shawn Pearcec4d3fd72015-02-10 14:32:37 -080037import java.io.ByteArrayOutputStream;
Dave Borowitz9de65952012-08-13 16:09:45 -070038import java.io.IOException;
Dave Borowitzfc2f00a2014-07-29 17:34:43 -070039import java.io.OutputStream;
Dave Borowitz673d1982014-05-02 12:30:49 -070040import java.io.OutputStreamWriter;
41import java.io.Writer;
Dave Borowitzb1c628f2013-01-11 11:28:20 -080042import java.lang.reflect.Type;
David Pursehousec0037b82018-03-16 13:56:20 +090043import java.time.Instant;
Dave Borowitz9de65952012-08-13 16:09:45 -070044import java.util.Map;
David Pursehouse7a7f5472016-10-14 09:59:20 +090045import java.util.Optional;
AJ Ross001ea9b2016-08-23 13:40:04 -070046import java.util.regex.Pattern;
Shawn Pearcec4d3fd72015-02-10 14:32:37 -080047import java.util.zip.GZIPOutputStream;
Dave Borowitzb1c628f2013-01-11 11:28:20 -080048import javax.servlet.ServletException;
Dave Borowitz9de65952012-08-13 16:09:45 -070049import javax.servlet.http.HttpServlet;
50import javax.servlet.http.HttpServletRequest;
51import javax.servlet.http.HttpServletResponse;
52
53/** Base servlet class for Gitiles servlets that serve Soy templates. */
54public abstract class BaseServlet extends HttpServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080055 private static final long serialVersionUID = 1L;
Dave Borowitzc99d0bb2014-07-31 15:39:39 -070056 private static final String DATA_ATTRIBUTE = BaseServlet.class.getName() + "/Data";
57 private static final String STREAMING_ATTRIBUTE = BaseServlet.class.getName() + "/Streaming";
Dave Borowitz9de65952012-08-13 16:09:45 -070058
59 static void setNotCacheable(HttpServletResponse res) {
60 res.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate");
61 res.setHeader(HttpHeaders.PRAGMA, "no-cache");
Dave Borowitz70d84542015-08-16 15:08:35 -040062 res.setHeader(HttpHeaders.EXPIRES, "Mon, 01 Jan 1990 00:00:00 GMT");
David Pursehousec0037b82018-03-16 13:56:20 +090063 res.setDateHeader(HttpHeaders.DATE, Instant.now().toEpochMilli());
Dave Borowitz9de65952012-08-13 16:09:45 -070064 }
65
66 public static BaseServlet notFoundServlet() {
Dave Borowitz8d6d6872014-03-16 15:18:14 -070067 return new BaseServlet(null, null) {
Chad Horohoead23f142012-11-12 09:45:39 -080068 private static final long serialVersionUID = 1L;
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020069
Dave Borowitz9de65952012-08-13 16:09:45 -070070 @Override
71 public void service(HttpServletRequest req, HttpServletResponse res) {
72 res.setStatus(SC_NOT_FOUND);
73 }
74 };
75 }
76
77 public static Map<String, String> menuEntry(String text, String url) {
78 if (url != null) {
79 return ImmutableMap.of("text", text, "url", url);
Dave Borowitz9de65952012-08-13 16:09:45 -070080 }
David Pursehouseb3b630f2016-06-15 21:51:18 +090081 return ImmutableMap.of("text", text);
Dave Borowitz9de65952012-08-13 16:09:45 -070082 }
83
Dave Borowitzc99d0bb2014-07-31 15:39:39 -070084 public static boolean isStreamingResponse(HttpServletRequest req) {
Dave Borowitzc410f962014-09-23 10:49:26 -070085 return firstNonNull((Boolean) req.getAttribute(STREAMING_ATTRIBUTE), false);
Dave Borowitzc99d0bb2014-07-31 15:39:39 -070086 }
87
Dave Borowitzded109a2014-03-03 15:25:39 -050088 protected static ArchiveFormat getArchiveFormat(GitilesAccess access) throws IOException {
89 return ArchiveFormat.getDefault(access.getConfig());
90 }
91
Dave Borowitz113c9352013-03-27 18:13:41 -040092 /**
93 * Put a value into a request's Soy data map.
94 *
95 * @param req in-progress request.
96 * @param key key.
97 * @param value Soy data value.
98 */
99 public static void putSoyData(HttpServletRequest req, String key, Object value) {
100 getData(req).put(key, value);
101 }
102
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800103 @Override
104 protected void doGet(HttpServletRequest req, HttpServletResponse res)
105 throws IOException, ServletException {
Shawn Pearce10e68e62016-01-02 09:37:58 -0800106 Optional<FormatType> format = getFormat(req);
107 if (!format.isPresent()) {
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800108 res.sendError(SC_BAD_REQUEST);
109 return;
110 }
Shawn Pearce10e68e62016-01-02 09:37:58 -0800111 switch (format.get()) {
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800112 case HTML:
113 doGetHtml(req, res);
114 break;
115 case TEXT:
116 doGetText(req, res);
117 break;
118 case JSON:
119 doGetJson(req, res);
120 break;
David Pursehousecb91aaf2016-06-15 22:05:24 +0900121 case DEFAULT:
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800122 default:
Masaya Suzuki5cecb862019-03-25 17:35:44 -0700123 throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT);
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800124 }
125 }
126
Shawn Pearce10e68e62016-01-02 09:37:58 -0800127 protected Optional<FormatType> getFormat(HttpServletRequest req) {
128 Optional<FormatType> format = FormatType.getFormatType(req);
129 if (format.isPresent() && format.get() == DEFAULT) {
130 return Optional.of(getDefaultFormat(req));
131 }
132 return format;
133 }
134
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800135 /**
136 * @param req in-progress request.
Dave Borowitz40255d52016-08-19 16:16:22 -0400137 * @return the default {@link FormatType} used when {@code ?format=} is not specified.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800138 */
139 protected FormatType getDefaultFormat(HttpServletRequest req) {
140 return HTML;
141 }
142
143 /**
144 * Handle a GET request when the requested format type was HTML.
145 *
146 * @param req in-progress request.
147 * @param res in-progress response.
David Pursehouse3863a1e2019-06-01 15:34:47 +0900148 * @throws IOException if there was an error rendering the result.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800149 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200150 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Masaya Suzuki5cecb862019-03-25 17:35:44 -0700151 throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT);
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800152 }
153
154 /**
155 * Handle a GET request when the requested format type was plain text.
156 *
157 * @param req in-progress request.
158 * @param res in-progress response.
David Pursehouse3863a1e2019-06-01 15:34:47 +0900159 * @throws IOException if there was an error rendering the result.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800160 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200161 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
Masaya Suzuki5cecb862019-03-25 17:35:44 -0700162 throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT);
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800163 }
164
165 /**
166 * Handle a GET request when the requested format type was JSON.
167 *
168 * @param req in-progress request.
169 * @param res in-progress response.
David Pursehouse3863a1e2019-06-01 15:34:47 +0900170 * @throws IOException if there was an error rendering the result.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800171 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200172 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
Masaya Suzuki5cecb862019-03-25 17:35:44 -0700173 throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT);
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800174 }
175
Dave Borowitz9de65952012-08-13 16:09:45 -0700176 protected static Map<String, Object> getData(HttpServletRequest req) {
177 @SuppressWarnings("unchecked")
178 Map<String, Object> data = (Map<String, Object>) req.getAttribute(DATA_ATTRIBUTE);
179 if (data == null) {
180 data = Maps.newHashMap();
181 req.setAttribute(DATA_ATTRIBUTE, data);
182 }
183 return data;
184 }
185
186 protected final Renderer renderer;
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700187 private final GitilesAccess.Factory accessFactory;
Dave Borowitz9de65952012-08-13 16:09:45 -0700188
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700189 protected BaseServlet(Renderer renderer, GitilesAccess.Factory accessFactory) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700190 this.renderer = renderer;
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700191 this.accessFactory = accessFactory;
Dave Borowitz9de65952012-08-13 16:09:45 -0700192 }
193
194 /**
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800195 * Render data to HTML using Soy.
196 *
197 * @param req in-progress request.
198 * @param res in-progress response.
Dave Borowitz40255d52016-08-19 16:16:22 -0400199 * @param templateName Soy template name; must be in one of the template files defined in {@link
200 * Renderer}.
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700201 * @param soyData data for Soy.
202 * @throws IOException an error occurred during rendering.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800203 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200204 protected void renderHtml(
205 HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData)
206 throws IOException {
David Pursehouse28726042019-06-27 09:09:30 +0900207 renderer.renderHtml(req, res, templateName, startHtmlResponse(req, res, soyData));
Dave Borowitzfc2f00a2014-07-29 17:34:43 -0700208 }
209
210 /**
211 * Start a streaming HTML response with header and footer rendered by Soy.
Dave Borowitz40255d52016-08-19 16:16:22 -0400212 *
213 * <p>A streaming template includes the special template {@code gitiles.streamingPlaceholder} at
214 * the point where data is to be streamed. The template before and after this placeholder is
215 * rendered using the provided data map.
Dave Borowitzfc2f00a2014-07-29 17:34:43 -0700216 *
217 * @param req in-progress request.
218 * @param res in-progress response.
Dave Borowitz40255d52016-08-19 16:16:22 -0400219 * @param templateName Soy template name; must be in one of the template files defined in {@link
220 * Renderer}.
Dave Borowitzfc2f00a2014-07-29 17:34:43 -0700221 * @param soyData data for Soy.
Dave Borowitz40255d52016-08-19 16:16:22 -0400222 * @return output stream to render to. The portion of the template before the placeholder is
223 * already written and flushed; the portion after is written only on calling {@code close()}.
Dave Borowitzfc2f00a2014-07-29 17:34:43 -0700224 * @throws IOException an error occurred during rendering the header.
225 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200226 protected OutputStream startRenderStreamingHtml(
227 HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData)
228 throws IOException {
Dave Borowitzc99d0bb2014-07-31 15:39:39 -0700229 req.setAttribute(STREAMING_ATTRIBUTE, true);
David Pursehouse28726042019-06-27 09:09:30 +0900230 return renderer.renderHtmlStreaming(
231 res, false, templateName, startHtmlResponse(req, res, soyData));
Shawn Pearce6451fa52017-06-29 20:47:05 -0700232 }
233
234 /**
235 * Start a compressed, streaming HTML response with header and footer rendered by Soy.
236 *
237 * <p>A streaming template includes the special template {@code gitiles.streamingPlaceholder} at
238 * the point where data is to be streamed. The template before and after this placeholder is
239 * rendered using the provided data map.
240 *
241 * <p>The response will be gzip compressed (if the user agent supports it) to reduce bandwidth.
242 * This may delay rendering in the browser.
243 *
244 * @param req in-progress request.
245 * @param res in-progress response.
246 * @param templateName Soy template name; must be in one of the template files defined in {@link
247 * Renderer}.
248 * @param soyData data for Soy.
249 * @return output stream to render to. The portion of the template before the placeholder is
250 * already written and flushed; the portion after is written only on calling {@code close()}.
251 * @throws IOException an error occurred during rendering the header.
252 */
253 protected OutputStream startRenderCompressedStreamingHtml(
David Pursehousef39cadc2017-07-07 08:47:51 +0900254 HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData)
Shawn Pearce6451fa52017-06-29 20:47:05 -0700255 throws IOException {
256 req.setAttribute(STREAMING_ATTRIBUTE, true);
257 boolean gzip = false;
258 if (acceptsGzipEncoding(req)) {
259 res.addHeader(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
260 res.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
261 gzip = true;
262 }
David Pursehouse28726042019-06-27 09:09:30 +0900263 return renderer.renderHtmlStreaming(
264 res, gzip, templateName, startHtmlResponse(req, res, soyData));
Dave Borowitzfc2f00a2014-07-29 17:34:43 -0700265 }
266
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200267 private Map<String, ?> startHtmlResponse(
268 HttpServletRequest req, HttpServletResponse res, Map<String, ?> soyData) throws IOException {
Dave Borowitzb7fd3f32014-05-01 12:31:25 -0700269 res.setContentType(FormatType.HTML.getMimeType());
David Pletcherd7bdaf32014-08-27 14:50:32 -0700270 res.setCharacterEncoding(UTF_8.name());
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800271 setCacheHeaders(req, res);
Dave Borowitz9de65952012-08-13 16:09:45 -0700272
Dave Borowitzb7fd3f32014-05-01 12:31:25 -0700273 Map<String, Object> allData = getData(req);
Dave Borowitz76bbefd2014-03-11 16:57:45 -0700274
Björn Pedersenbc0eaaa2016-03-29 15:30:29 +0200275 // for backwards compatibility, first try to access the old customHeader config var,
276 // then read the new customVariant variable.
277 GitilesConfig.putVariant(getAccess(req).getConfig(), "customHeader", "customVariant", allData);
278 GitilesConfig.putVariant(getAccess(req).getConfig(), "customVariant", "customVariant", allData);
Dave Borowitzb7fd3f32014-05-01 12:31:25 -0700279 allData.putAll(soyData);
280 GitilesView view = ViewFilter.getView(req);
281 if (!allData.containsKey("repositoryName") && view.getRepositoryName() != null) {
282 allData.put("repositoryName", view.getRepositoryName());
Dave Borowitz9de65952012-08-13 16:09:45 -0700283 }
Shawn Pearce5c34e092017-06-29 21:18:30 -0700284 if (!allData.containsKey("breadcrumbs") && view.getRepositoryName() != null) {
Dave Borowitzb7fd3f32014-05-01 12:31:25 -0700285 allData.put("breadcrumbs", view.getBreadcrumbs());
286 }
287
288 res.setStatus(HttpServletResponse.SC_OK);
Dave Borowitzfc2f00a2014-07-29 17:34:43 -0700289 return allData;
Dave Borowitz9de65952012-08-13 16:09:45 -0700290 }
291
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800292 /**
293 * Render data to JSON using GSON.
294 *
295 * @param req in-progress request.
296 * @param res in-progress response.
Dave Borowitz876b9812015-09-16 15:17:58 -0400297 * @param src @see com.google.gson.Gson#toJson(Object, Type, Appendable)
298 * @param typeOfSrc @see com.google.gson.Gson#toJson(Object, Type, Appendable)
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800299 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200300 protected void renderJson(
301 HttpServletRequest req, HttpServletResponse res, Object src, Type typeOfSrc)
302 throws IOException {
AJ Ross001ea9b2016-08-23 13:40:04 -0700303 setApiHeaders(req, res, JSON);
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800304 res.setStatus(SC_OK);
Shawn Pearcec4d3fd72015-02-10 14:32:37 -0800305 try (Writer writer = newWriter(req, res)) {
306 newGsonBuilder(req).create().toJson(src, typeOfSrc, writer);
307 writer.write('\n');
308 }
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800309 }
310
Dave Borowitz438c5282014-07-09 20:15:34 -0700311 @SuppressWarnings("unused") // Used in subclasses.
312 protected GsonBuilder newGsonBuilder(HttpServletRequest req) throws IOException {
313 return new GsonBuilder()
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200314 .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
315 .setPrettyPrinting()
316 .generateNonExecutableJson();
Dave Borowitz438c5282014-07-09 20:15:34 -0700317 }
318
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800319 /**
Dave Borowitz4f568702014-05-01 19:54:57 -0700320 * @see #startRenderText(HttpServletRequest, HttpServletResponse)
321 * @param req in-progress request.
322 * @param res in-progress response.
323 * @param contentType contentType to set.
324 * @return the response's writer.
325 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200326 protected Writer startRenderText(
327 HttpServletRequest req, HttpServletResponse res, String contentType) throws IOException {
AJ Ross001ea9b2016-08-23 13:40:04 -0700328 setApiHeaders(req, res, contentType);
Shawn Pearcec4d3fd72015-02-10 14:32:37 -0800329 return newWriter(req, res);
Dave Borowitz4f568702014-05-01 19:54:57 -0700330 }
331
332 /**
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800333 * Prepare the response to render plain text.
Dave Borowitz40255d52016-08-19 16:16:22 -0400334 *
335 * <p>Unlike {@link #renderHtml(HttpServletRequest, HttpServletResponse, String, Map)} and {@link
336 * #renderJson(HttpServletRequest, HttpServletResponse, Object, Type)}, which assume the data to
337 * render is already completely prepared, this method does not write any data, only headers, and
338 * returns the response's ready-to-use writer.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800339 *
340 * @param req in-progress request.
341 * @param res in-progress response.
342 * @return the response's writer.
343 */
Dave Borowitz673d1982014-05-02 12:30:49 -0700344 protected Writer startRenderText(HttpServletRequest req, HttpServletResponse res)
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800345 throws IOException {
Dave Borowitz673d1982014-05-02 12:30:49 -0700346 return startRenderText(req, res, TEXT.getMimeType());
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800347 }
348
Dave Borowitzba9c1182013-03-13 14:16:43 -0700349 /**
350 * Render an error as plain text.
351 *
352 * @param req in-progress request.
353 * @param res in-progress response.
354 * @param statusCode HTTP status code.
355 * @param message full message text.
Dave Borowitzba9c1182013-03-13 14:16:43 -0700356 * @throws IOException
357 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200358 protected void renderTextError(
359 HttpServletRequest req, HttpServletResponse res, int statusCode, String message)
360 throws IOException {
Dave Borowitzba9c1182013-03-13 14:16:43 -0700361 res.setStatus(statusCode);
AJ Ross001ea9b2016-08-23 13:40:04 -0700362 setApiHeaders(req, res, TEXT);
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800363 setCacheHeaders(req, res);
Shawn Pearcec4d3fd72015-02-10 14:32:37 -0800364 try (Writer out = newWriter(req, res)) {
365 out.write(message);
366 }
Dave Borowitzba9c1182013-03-13 14:16:43 -0700367 }
368
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700369 protected GitilesAccess getAccess(HttpServletRequest req) {
Shawn Pearcedb394cc2017-07-01 11:45:20 -0700370 return GitilesAccess.getAccess(req, accessFactory);
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700371 }
372
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800373 protected void setCacheHeaders(HttpServletRequest req, HttpServletResponse res) {
David Pursehouseccaa85d2017-05-30 10:47:27 +0900374 if (Strings.nullToEmpty(req.getHeader(HttpHeaders.PRAGMA)).equalsIgnoreCase("no-cache")
375 || Strings.nullToEmpty(req.getHeader(HttpHeaders.CACHE_CONTROL))
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800376 .equalsIgnoreCase("no-cache")) {
377 setNotCacheable(res);
378 return;
379 }
380
381 GitilesView view = ViewFilter.getView(req);
382 Revision rev = view.getRevision();
383 if (rev.nameIsId()) {
David Pursehouseccaa85d2017-05-30 10:47:27 +0900384 res.setHeader(
385 HttpHeaders.CACHE_CONTROL, "private, max-age=7200, stale-while-revalidate=604800");
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800386 return;
387 }
388
Dave Borowitz9de65952012-08-13 16:09:45 -0700389 setNotCacheable(res);
390 }
Dave Borowitz0c944762013-04-04 11:01:42 -0700391
David Pursehouseccaa85d2017-05-30 10:47:27 +0900392 protected void setApiHeaders(HttpServletRequest req, HttpServletResponse res, String contentType)
393 throws IOException {
Dave Borowitz4f568702014-05-01 19:54:57 -0700394 if (!Strings.isNullOrEmpty(contentType)) {
395 res.setContentType(contentType);
396 }
David Pletcherd7bdaf32014-08-27 14:50:32 -0700397 res.setCharacterEncoding(UTF_8.name());
Dave Borowitz0c944762013-04-04 11:01:42 -0700398 res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment");
AJ Ross001ea9b2016-08-23 13:40:04 -0700399
400 GitilesAccess access = getAccess(req);
401 String[] allowOrigin = access.getConfig().getStringList("gitiles", null, "allowOriginRegex");
402
403 if (allowOrigin.length > 0) {
404 String origin = req.getHeader(HttpHeaders.ORIGIN);
405 Pattern allowOriginPattern = Pattern.compile(Joiner.on("|").join(allowOrigin));
406
407 if (!Strings.isNullOrEmpty(origin) && allowOriginPattern.matcher(origin).matches()) {
408 res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
AJ Ross066d93c2016-08-23 18:12:46 -0700409 res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "X-Requested-With");
410 res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
411 res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET");
AJ Ross001ea9b2016-08-23 13:40:04 -0700412 }
413 } else {
414 res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
415 }
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800416 setCacheHeaders(req, res);
Dave Borowitz0c944762013-04-04 11:01:42 -0700417 }
Dave Borowitz6d9bc5a2013-06-19 09:12:52 -0700418
AJ Ross001ea9b2016-08-23 13:40:04 -0700419 protected void setApiHeaders(HttpServletRequest req, HttpServletResponse res, FormatType type)
420 throws IOException {
421 setApiHeaders(req, res, type.getMimeType());
Dave Borowitz4f568702014-05-01 19:54:57 -0700422 }
423
David Pursehouseccaa85d2017-05-30 10:47:27 +0900424 protected void setDownloadHeaders(
425 HttpServletRequest req, HttpServletResponse res, String filename, String contentType) {
Dave Borowitz6d9bc5a2013-06-19 09:12:52 -0700426 res.setContentType(contentType);
427 res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
Andrew Bonventrecc3418b2016-12-01 20:18:37 -0800428 setCacheHeaders(req, res);
Dave Borowitz6d9bc5a2013-06-19 09:12:52 -0700429 }
Dave Borowitz673d1982014-05-02 12:30:49 -0700430
Dave Borowitz29914cb2014-08-20 14:37:57 -0700431 protected static Writer newWriter(OutputStream os, HttpServletResponse res) throws IOException {
Dave Borowitzae171e62017-05-11 17:51:37 -0400432 // StreamEncoder#write(int) is wasteful with its allocations, and we don't have much control
433 // over whether library code calls that variant as opposed to the saner write(char[], int, int).
434 // Protect against this by buffering.
435 return new BufferedWriter(new OutputStreamWriter(os, res.getCharacterEncoding()));
Dave Borowitz673d1982014-05-02 12:30:49 -0700436 }
Dave Borowitzbf72ab72014-09-17 16:15:19 -0700437
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200438 private Writer newWriter(HttpServletRequest req, HttpServletResponse res) throws IOException {
Shawn Pearcec4d3fd72015-02-10 14:32:37 -0800439 OutputStream out;
440 if (acceptsGzipEncoding(req)) {
Shawn Pearceed3c2d12016-05-30 15:59:02 -0700441 res.addHeader(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
Shawn Pearcec4d3fd72015-02-10 14:32:37 -0800442 res.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
443 out = new GZIPOutputStream(res.getOutputStream());
444 } else {
445 out = res.getOutputStream();
446 }
447 return newWriter(out, res);
448 }
449
450 protected static boolean acceptsGzipEncoding(HttpServletRequest req) {
451 String accepts = req.getHeader(HttpHeaders.ACCEPT_ENCODING);
452 if (accepts == null) {
453 return false;
454 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200455 for (int b = 0; b < accepts.length(); ) {
Shawn Pearcec4d3fd72015-02-10 14:32:37 -0800456 int comma = accepts.indexOf(',', b);
457 int e = 0 <= comma ? comma : accepts.length();
458 String term = accepts.substring(b, e).trim();
459 if (term.equals(ENCODING_GZIP)) {
460 return true;
461 }
462 b = e + 1;
463 }
464 return false;
465 }
466
467 protected static byte[] gzip(byte[] raw) throws IOException {
468 ByteArrayOutputStream out = new ByteArrayOutputStream();
469 try (GZIPOutputStream gz = new GZIPOutputStream(out)) {
470 gz.write(raw);
471 }
472 return out.toByteArray();
Dave Borowitzbf72ab72014-09-17 16:15:19 -0700473 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700474}