xref: /JGit/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java (revision 5c5f7c6b146b24f2bd4afae1902df85ad6e57ea3)
15e33a1deSShawn O. Pearce /*
2*5c5f7c6bSMatthias Sohn  * Copyright (C) 2009-2010, Google Inc. and others
35e33a1deSShawn O. Pearce  *
4*5c5f7c6bSMatthias Sohn  * This program and the accompanying materials are made available under the
5*5c5f7c6bSMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
6*5c5f7c6bSMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
75e33a1deSShawn O. Pearce  *
8*5c5f7c6bSMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
95e33a1deSShawn O. Pearce  */
105e33a1deSShawn O. Pearce 
115e33a1deSShawn O. Pearce package org.eclipse.jgit.http.server;
125e33a1deSShawn O. Pearce 
13fbf6ce65SDavid Pursehouse import static java.nio.charset.StandardCharsets.UTF_8;
145e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
1588037184SZhen Chen import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
165e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
175e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
185e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_ETAG;
195e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.TEXT_PLAIN;
205e33a1deSShawn O. Pearce 
215e33a1deSShawn O. Pearce import java.io.ByteArrayOutputStream;
225e33a1deSShawn O. Pearce import java.io.IOException;
235e33a1deSShawn O. Pearce import java.io.InputStream;
245e33a1deSShawn O. Pearce import java.io.OutputStream;
255e33a1deSShawn O. Pearce import java.security.MessageDigest;
26f3d8a8ecSSasa Zivkov import java.text.MessageFormat;
275e33a1deSShawn O. Pearce import java.util.zip.GZIPInputStream;
285e33a1deSShawn O. Pearce import java.util.zip.GZIPOutputStream;
295e33a1deSShawn O. Pearce 
305e33a1deSShawn O. Pearce import javax.servlet.ServletRequest;
315e33a1deSShawn O. Pearce import javax.servlet.http.HttpServletRequest;
325e33a1deSShawn O. Pearce import javax.servlet.http.HttpServletResponse;
335e33a1deSShawn O. Pearce 
345e33a1deSShawn O. Pearce import org.eclipse.jgit.lib.Constants;
355e33a1deSShawn O. Pearce import org.eclipse.jgit.lib.ObjectId;
365e33a1deSShawn O. Pearce import org.eclipse.jgit.lib.Repository;
375e33a1deSShawn O. Pearce 
383b00041cSMatthias Sohn /**
393b00041cSMatthias Sohn  * Common utility functions for servlets.
403b00041cSMatthias Sohn  */
415e33a1deSShawn O. Pearce public final class ServletUtils {
425e33a1deSShawn O. Pearce 	/** Request attribute which stores the {@link Repository} instance. */
435e33a1deSShawn O. Pearce 	public static final String ATTRIBUTE_REPOSITORY = "org.eclipse.jgit.Repository";
445e33a1deSShawn O. Pearce 
45af3562f7SShawn O. Pearce 	/** Request attribute storing either UploadPack or ReceivePack. */
46af3562f7SShawn O. Pearce 	public static final String ATTRIBUTE_HANDLER = "org.eclipse.jgit.transport.UploadPackOrReceivePack";
47af3562f7SShawn O. Pearce 
485e33a1deSShawn O. Pearce 	/**
495e33a1deSShawn O. Pearce 	 * Get the selected repository from the request.
505e33a1deSShawn O. Pearce 	 *
515e33a1deSShawn O. Pearce 	 * @param req
525e33a1deSShawn O. Pearce 	 *            the current request.
535e33a1deSShawn O. Pearce 	 * @return the repository; never null.
545e33a1deSShawn O. Pearce 	 * @throws IllegalStateException
555e33a1deSShawn O. Pearce 	 *             the repository was not set by the filter, the servlet is
565e33a1deSShawn O. Pearce 	 *             being invoked incorrectly and the programmer should ensure
575e33a1deSShawn O. Pearce 	 *             the filter runs before the servlet.
585e33a1deSShawn O. Pearce 	 * @see #ATTRIBUTE_REPOSITORY
595e33a1deSShawn O. Pearce 	 */
getRepository(ServletRequest req)606d370d83SHan-Wen Nienhuys 	public static Repository getRepository(ServletRequest req) {
615e33a1deSShawn O. Pearce 		Repository db = (Repository) req.getAttribute(ATTRIBUTE_REPOSITORY);
625e33a1deSShawn O. Pearce 		if (db == null)
63f3d8a8ecSSasa Zivkov 			throw new IllegalStateException(HttpServerText.get().expectedRepositoryAttribute);
645e33a1deSShawn O. Pearce 		return db;
655e33a1deSShawn O. Pearce 	}
665e33a1deSShawn O. Pearce 
675e33a1deSShawn O. Pearce 	/**
685e33a1deSShawn O. Pearce 	 * Open the request input stream, automatically inflating if necessary.
695e33a1deSShawn O. Pearce 	 * <p>
705e33a1deSShawn O. Pearce 	 * This method automatically inflates the input stream if the request
715e33a1deSShawn O. Pearce 	 * {@code Content-Encoding} header was set to {@code gzip} or the legacy
725e33a1deSShawn O. Pearce 	 * {@code x-gzip}.
735e33a1deSShawn O. Pearce 	 *
745e33a1deSShawn O. Pearce 	 * @param req
755e33a1deSShawn O. Pearce 	 *            the incoming request whose input stream needs to be opened.
765e33a1deSShawn O. Pearce 	 * @return an input stream to read the raw, uncompressed request body.
775e33a1deSShawn O. Pearce 	 * @throws IOException
785e33a1deSShawn O. Pearce 	 *             if an input or output exception occurred.
795e33a1deSShawn O. Pearce 	 */
getInputStream(HttpServletRequest req)806d370d83SHan-Wen Nienhuys 	public static InputStream getInputStream(HttpServletRequest req)
815e33a1deSShawn O. Pearce 			throws IOException {
825e33a1deSShawn O. Pearce 		InputStream in = req.getInputStream();
835e33a1deSShawn O. Pearce 		final String enc = req.getHeader(HDR_CONTENT_ENCODING);
84e271e2f6SMatthias Sohn 		if (ENCODING_GZIP.equals(enc) || ENCODING_X_GZIP.equals(enc))
855e33a1deSShawn O. Pearce 			in = new GZIPInputStream(in);
865e33a1deSShawn O. Pearce 		else if (enc != null)
87f3d8a8ecSSasa Zivkov 			throw new IOException(MessageFormat.format(HttpServerText.get().encodingNotSupportedByThisLibrary
88f3d8a8ecSSasa Zivkov 					, HDR_CONTENT_ENCODING, enc));
895e33a1deSShawn O. Pearce 		return in;
905e33a1deSShawn O. Pearce 	}
915e33a1deSShawn O. Pearce 
925e33a1deSShawn O. Pearce 	/**
93db00632dSShawn O. Pearce 	 * Consume the entire request body, if one was supplied.
94db00632dSShawn O. Pearce 	 *
95db00632dSShawn O. Pearce 	 * @param req
96db00632dSShawn O. Pearce 	 *            the request whose body must be consumed.
97db00632dSShawn O. Pearce 	 */
consumeRequestBody(HttpServletRequest req)98db00632dSShawn O. Pearce 	public static void consumeRequestBody(HttpServletRequest req) {
99db00632dSShawn O. Pearce 		if (0 < req.getContentLength() || isChunked(req)) {
100db00632dSShawn O. Pearce 			try {
101db00632dSShawn O. Pearce 				consumeRequestBody(req.getInputStream());
102db00632dSShawn O. Pearce 			} catch (IOException e) {
103db00632dSShawn O. Pearce 				// Ignore any errors obtaining the input stream.
104db00632dSShawn O. Pearce 			}
105db00632dSShawn O. Pearce 		}
106db00632dSShawn O. Pearce 	}
107db00632dSShawn O. Pearce 
isChunked(HttpServletRequest req)1081c6c73c5SShawn O. Pearce 	static boolean isChunked(HttpServletRequest req) {
109db00632dSShawn O. Pearce 		return "chunked".equals(req.getHeader("Transfer-Encoding"));
110db00632dSShawn O. Pearce 	}
111db00632dSShawn O. Pearce 
112db00632dSShawn O. Pearce 	/**
113db00632dSShawn O. Pearce 	 * Consume the rest of the input stream and discard it.
114db00632dSShawn O. Pearce 	 *
115db00632dSShawn O. Pearce 	 * @param in
116db00632dSShawn O. Pearce 	 *            the stream to discard, closed if not null.
117db00632dSShawn O. Pearce 	 */
consumeRequestBody(InputStream in)118db00632dSShawn O. Pearce 	public static void consumeRequestBody(InputStream in) {
119db00632dSShawn O. Pearce 		if (in == null)
120db00632dSShawn O. Pearce 			return;
121db00632dSShawn O. Pearce 		try {
122db00632dSShawn O. Pearce 			while (0 < in.skip(2048) || 0 <= in.read()) {
123db00632dSShawn O. Pearce 				// Discard until EOF.
124db00632dSShawn O. Pearce 			}
125db00632dSShawn O. Pearce 		} catch (IOException err) {
126db00632dSShawn O. Pearce 			// Discard IOException during read or skip.
127db00632dSShawn O. Pearce 		} finally {
128db00632dSShawn O. Pearce 			try {
129db00632dSShawn O. Pearce 				in.close();
130db00632dSShawn O. Pearce 			} catch (IOException err) {
131db00632dSShawn O. Pearce 				// Discard IOException during close of input stream.
132db00632dSShawn O. Pearce 			}
133db00632dSShawn O. Pearce 		}
134db00632dSShawn O. Pearce 	}
135db00632dSShawn O. Pearce 
136db00632dSShawn O. Pearce 	/**
1375e33a1deSShawn O. Pearce 	 * Send a plain text response to a {@code GET} or {@code HEAD} HTTP request.
1385e33a1deSShawn O. Pearce 	 * <p>
1395e33a1deSShawn O. Pearce 	 * The text response is encoded in the Git character encoding, UTF-8.
1405e33a1deSShawn O. Pearce 	 * <p>
1415e33a1deSShawn O. Pearce 	 * If the user agent supports a compressed transfer encoding and the content
1425e33a1deSShawn O. Pearce 	 * is large enough, the content may be compressed before sending.
1435e33a1deSShawn O. Pearce 	 * <p>
1445e33a1deSShawn O. Pearce 	 * The {@code ETag} and {@code Content-Length} headers are automatically set
1455e33a1deSShawn O. Pearce 	 * by this method. {@code Content-Encoding} is conditionally set if the user
1465e33a1deSShawn O. Pearce 	 * agent supports a compressed transfer. Callers are responsible for setting
1475e33a1deSShawn O. Pearce 	 * any cache control headers.
1485e33a1deSShawn O. Pearce 	 *
1495e33a1deSShawn O. Pearce 	 * @param content
1505e33a1deSShawn O. Pearce 	 *            to return to the user agent as this entity's body.
1515e33a1deSShawn O. Pearce 	 * @param req
1525e33a1deSShawn O. Pearce 	 *            the incoming request.
1535e33a1deSShawn O. Pearce 	 * @param rsp
1545e33a1deSShawn O. Pearce 	 *            the outgoing response.
1555e33a1deSShawn O. Pearce 	 * @throws IOException
1565e33a1deSShawn O. Pearce 	 *             the servlet API rejected sending the body.
1575e33a1deSShawn O. Pearce 	 */
sendPlainText(final String content, final HttpServletRequest req, final HttpServletResponse rsp)1585e33a1deSShawn O. Pearce 	public static void sendPlainText(final String content,
1595e33a1deSShawn O. Pearce 			final HttpServletRequest req, final HttpServletResponse rsp)
1605e33a1deSShawn O. Pearce 			throws IOException {
161fbf6ce65SDavid Pursehouse 		final byte[] raw = content.getBytes(UTF_8);
1625e33a1deSShawn O. Pearce 		rsp.setContentType(TEXT_PLAIN);
163fbf6ce65SDavid Pursehouse 		rsp.setCharacterEncoding(UTF_8.name());
1645e33a1deSShawn O. Pearce 		send(raw, req, rsp);
1655e33a1deSShawn O. Pearce 	}
1665e33a1deSShawn O. Pearce 
1675e33a1deSShawn O. Pearce 	/**
1685e33a1deSShawn O. Pearce 	 * Send a response to a {@code GET} or {@code HEAD} HTTP request.
1695e33a1deSShawn O. Pearce 	 * <p>
1705e33a1deSShawn O. Pearce 	 * If the user agent supports a compressed transfer encoding and the content
1715e33a1deSShawn O. Pearce 	 * is large enough, the content may be compressed before sending.
1725e33a1deSShawn O. Pearce 	 * <p>
1735e33a1deSShawn O. Pearce 	 * The {@code ETag} and {@code Content-Length} headers are automatically set
1745e33a1deSShawn O. Pearce 	 * by this method. {@code Content-Encoding} is conditionally set if the user
1755e33a1deSShawn O. Pearce 	 * agent supports a compressed transfer. Callers are responsible for setting
1765e33a1deSShawn O. Pearce 	 * {@code Content-Type} and any cache control headers.
1775e33a1deSShawn O. Pearce 	 *
1785e33a1deSShawn O. Pearce 	 * @param content
1795e33a1deSShawn O. Pearce 	 *            to return to the user agent as this entity's body.
1805e33a1deSShawn O. Pearce 	 * @param req
1815e33a1deSShawn O. Pearce 	 *            the incoming request.
1825e33a1deSShawn O. Pearce 	 * @param rsp
1835e33a1deSShawn O. Pearce 	 *            the outgoing response.
1845e33a1deSShawn O. Pearce 	 * @throws IOException
1855e33a1deSShawn O. Pearce 	 *             the servlet API rejected sending the body.
1865e33a1deSShawn O. Pearce 	 */
send(byte[] content, final HttpServletRequest req, final HttpServletResponse rsp)1875e33a1deSShawn O. Pearce 	public static void send(byte[] content, final HttpServletRequest req,
1885e33a1deSShawn O. Pearce 			final HttpServletResponse rsp) throws IOException {
1895e33a1deSShawn O. Pearce 		content = sendInit(content, req, rsp);
1905c70be00SDavid Pursehouse 		try (OutputStream out = rsp.getOutputStream()) {
1915e33a1deSShawn O. Pearce 			out.write(content);
1925e33a1deSShawn O. Pearce 			out.flush();
1935e33a1deSShawn O. Pearce 		}
1945e33a1deSShawn O. Pearce 	}
1955e33a1deSShawn O. Pearce 
sendInit(byte[] content, final HttpServletRequest req, final HttpServletResponse rsp)1965e33a1deSShawn O. Pearce 	private static byte[] sendInit(byte[] content,
1975e33a1deSShawn O. Pearce 			final HttpServletRequest req, final HttpServletResponse rsp)
1985e33a1deSShawn O. Pearce 			throws IOException {
1995e33a1deSShawn O. Pearce 		rsp.setHeader(HDR_ETAG, etag(content));
2005e33a1deSShawn O. Pearce 		if (256 < content.length && acceptsGzipEncoding(req)) {
2015e33a1deSShawn O. Pearce 			content = compress(content);
2025e33a1deSShawn O. Pearce 			rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP);
2035e33a1deSShawn O. Pearce 		}
2045e33a1deSShawn O. Pearce 		rsp.setContentLength(content.length);
2055e33a1deSShawn O. Pearce 		return content;
2065e33a1deSShawn O. Pearce 	}
2075e33a1deSShawn O. Pearce 
acceptsGzipEncoding(HttpServletRequest req)208f3ec7cf3SHan-Wen Nienhuys 	static boolean acceptsGzipEncoding(HttpServletRequest req) {
20918e822a7SShawn O. Pearce 		return acceptsGzipEncoding(req.getHeader(HDR_ACCEPT_ENCODING));
21018e822a7SShawn O. Pearce 	}
21118e822a7SShawn O. Pearce 
acceptsGzipEncoding(String accepts)21218e822a7SShawn O. Pearce 	static boolean acceptsGzipEncoding(String accepts) {
21318e822a7SShawn O. Pearce 		if (accepts == null)
21418e822a7SShawn O. Pearce 			return false;
21518e822a7SShawn O. Pearce 
21618e822a7SShawn O. Pearce 		int b = 0;
21718e822a7SShawn O. Pearce 		while (b < accepts.length()) {
21818e822a7SShawn O. Pearce 			int comma = accepts.indexOf(',', b);
21918e822a7SShawn O. Pearce 			int e = 0 <= comma ? comma : accepts.length();
22018e822a7SShawn O. Pearce 			String term = accepts.substring(b, e).trim();
22118e822a7SShawn O. Pearce 			if (term.equals(ENCODING_GZIP))
22218e822a7SShawn O. Pearce 				return true;
22318e822a7SShawn O. Pearce 			b = e + 1;
22418e822a7SShawn O. Pearce 		}
22518e822a7SShawn O. Pearce 		return false;
2265e33a1deSShawn O. Pearce 	}
2275e33a1deSShawn O. Pearce 
compress(byte[] raw)2286d370d83SHan-Wen Nienhuys 	private static byte[] compress(byte[] raw) throws IOException {
2295e33a1deSShawn O. Pearce 		final int maxLen = raw.length + 32;
2305e33a1deSShawn O. Pearce 		final ByteArrayOutputStream out = new ByteArrayOutputStream(maxLen);
2315e33a1deSShawn O. Pearce 		final GZIPOutputStream gz = new GZIPOutputStream(out);
2325e33a1deSShawn O. Pearce 		gz.write(raw);
2335e33a1deSShawn O. Pearce 		gz.finish();
2345e33a1deSShawn O. Pearce 		gz.flush();
2355e33a1deSShawn O. Pearce 		return out.toByteArray();
2365e33a1deSShawn O. Pearce 	}
2375e33a1deSShawn O. Pearce 
etag(byte[] content)2386d370d83SHan-Wen Nienhuys 	private static String etag(byte[] content) {
2395e33a1deSShawn O. Pearce 		final MessageDigest md = Constants.newMessageDigest();
2405e33a1deSShawn O. Pearce 		md.update(content);
2415e33a1deSShawn O. Pearce 		return ObjectId.fromRaw(md.digest()).getName();
2425e33a1deSShawn O. Pearce 	}
2435e33a1deSShawn O. Pearce 
identify(Repository git)244ae592cc6SShawn Pearce 	static String identify(Repository git) {
2458cd07cb8SDavid Ostrovsky 		String identifier = git.getIdentifier();
2468cd07cb8SDavid Ostrovsky 		if (identifier == null) {
247ae592cc6SShawn Pearce 			return "unknown";
248ae592cc6SShawn Pearce 		}
2498cd07cb8SDavid Ostrovsky 		return identifier;
2508cd07cb8SDavid Ostrovsky 	}
251ae592cc6SShawn Pearce 
ServletUtils()2525e33a1deSShawn O. Pearce 	private ServletUtils() {
2535e33a1deSShawn O. Pearce 		// static utility class only
2545e33a1deSShawn O. Pearce 	}
2555e33a1deSShawn O. Pearce }
256