| /* |
| * Copyright (C) 2009-2010, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.http.server; |
| |
| import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; |
| import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP; |
| import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING; |
| import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; |
| import static org.eclipse.jgit.util.HttpSupport.HDR_ETAG; |
| import static org.eclipse.jgit.util.HttpSupport.TEXT_PLAIN; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.security.MessageDigest; |
| import java.text.MessageFormat; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.GZIPOutputStream; |
| |
| import javax.servlet.ServletRequest; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.jgit.internal.storage.dfs.DfsRepository; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Repository; |
| |
| /** Common utility functions for servlets. */ |
| public final class ServletUtils { |
| /** Request attribute which stores the {@link Repository} instance. */ |
| public static final String ATTRIBUTE_REPOSITORY = "org.eclipse.jgit.Repository"; |
| |
| /** Request attribute storing either UploadPack or ReceivePack. */ |
| public static final String ATTRIBUTE_HANDLER = "org.eclipse.jgit.transport.UploadPackOrReceivePack"; |
| |
| /** |
| * Get the selected repository from the request. |
| * |
| * @param req |
| * the current request. |
| * @return the repository; never null. |
| * @throws IllegalStateException |
| * the repository was not set by the filter, the servlet is |
| * being invoked incorrectly and the programmer should ensure |
| * the filter runs before the servlet. |
| * @see #ATTRIBUTE_REPOSITORY |
| */ |
| public static Repository getRepository(final ServletRequest req) { |
| Repository db = (Repository) req.getAttribute(ATTRIBUTE_REPOSITORY); |
| if (db == null) |
| throw new IllegalStateException(HttpServerText.get().expectedRepositoryAttribute); |
| return db; |
| } |
| |
| /** |
| * Open the request input stream, automatically inflating if necessary. |
| * <p> |
| * This method automatically inflates the input stream if the request |
| * {@code Content-Encoding} header was set to {@code gzip} or the legacy |
| * {@code x-gzip}. |
| * |
| * @param req |
| * the incoming request whose input stream needs to be opened. |
| * @return an input stream to read the raw, uncompressed request body. |
| * @throws IOException |
| * if an input or output exception occurred. |
| */ |
| public static InputStream getInputStream(final HttpServletRequest req) |
| throws IOException { |
| InputStream in = req.getInputStream(); |
| final String enc = req.getHeader(HDR_CONTENT_ENCODING); |
| if (ENCODING_GZIP.equals(enc) || ENCODING_X_GZIP.equals(enc)) //$NON-NLS-1$ |
| in = new GZIPInputStream(in); |
| else if (enc != null) |
| throw new IOException(MessageFormat.format(HttpServerText.get().encodingNotSupportedByThisLibrary |
| , HDR_CONTENT_ENCODING, enc)); |
| return in; |
| } |
| |
| /** |
| * Consume the entire request body, if one was supplied. |
| * |
| * @param req |
| * the request whose body must be consumed. |
| */ |
| public static void consumeRequestBody(HttpServletRequest req) { |
| if (0 < req.getContentLength() || isChunked(req)) { |
| try { |
| consumeRequestBody(req.getInputStream()); |
| } catch (IOException e) { |
| // Ignore any errors obtaining the input stream. |
| } |
| } |
| } |
| |
| static boolean isChunked(HttpServletRequest req) { |
| return "chunked".equals(req.getHeader("Transfer-Encoding")); |
| } |
| |
| /** |
| * Consume the rest of the input stream and discard it. |
| * |
| * @param in |
| * the stream to discard, closed if not null. |
| */ |
| public static void consumeRequestBody(InputStream in) { |
| if (in == null) |
| return; |
| try { |
| while (0 < in.skip(2048) || 0 <= in.read()) { |
| // Discard until EOF. |
| } |
| } catch (IOException err) { |
| // Discard IOException during read or skip. |
| } finally { |
| try { |
| in.close(); |
| } catch (IOException err) { |
| // Discard IOException during close of input stream. |
| } |
| } |
| } |
| |
| /** |
| * Send a plain text response to a {@code GET} or {@code HEAD} HTTP request. |
| * <p> |
| * The text response is encoded in the Git character encoding, UTF-8. |
| * <p> |
| * If the user agent supports a compressed transfer encoding and the content |
| * is large enough, the content may be compressed before sending. |
| * <p> |
| * The {@code ETag} and {@code Content-Length} headers are automatically set |
| * by this method. {@code Content-Encoding} is conditionally set if the user |
| * agent supports a compressed transfer. Callers are responsible for setting |
| * any cache control headers. |
| * |
| * @param content |
| * to return to the user agent as this entity's body. |
| * @param req |
| * the incoming request. |
| * @param rsp |
| * the outgoing response. |
| * @throws IOException |
| * the servlet API rejected sending the body. |
| */ |
| public static void sendPlainText(final String content, |
| final HttpServletRequest req, final HttpServletResponse rsp) |
| throws IOException { |
| final byte[] raw = content.getBytes(Constants.CHARACTER_ENCODING); |
| rsp.setContentType(TEXT_PLAIN); |
| rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING); |
| send(raw, req, rsp); |
| } |
| |
| /** |
| * Send a response to a {@code GET} or {@code HEAD} HTTP request. |
| * <p> |
| * If the user agent supports a compressed transfer encoding and the content |
| * is large enough, the content may be compressed before sending. |
| * <p> |
| * The {@code ETag} and {@code Content-Length} headers are automatically set |
| * by this method. {@code Content-Encoding} is conditionally set if the user |
| * agent supports a compressed transfer. Callers are responsible for setting |
| * {@code Content-Type} and any cache control headers. |
| * |
| * @param content |
| * to return to the user agent as this entity's body. |
| * @param req |
| * the incoming request. |
| * @param rsp |
| * the outgoing response. |
| * @throws IOException |
| * the servlet API rejected sending the body. |
| */ |
| public static void send(byte[] content, final HttpServletRequest req, |
| final HttpServletResponse rsp) throws IOException { |
| content = sendInit(content, req, rsp); |
| final OutputStream out = rsp.getOutputStream(); |
| try { |
| out.write(content); |
| out.flush(); |
| } finally { |
| out.close(); |
| } |
| } |
| |
| private static byte[] sendInit(byte[] content, |
| final HttpServletRequest req, final HttpServletResponse rsp) |
| throws IOException { |
| rsp.setHeader(HDR_ETAG, etag(content)); |
| if (256 < content.length && acceptsGzipEncoding(req)) { |
| content = compress(content); |
| rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP); |
| } |
| rsp.setContentLength(content.length); |
| return content; |
| } |
| |
| static boolean acceptsGzipEncoding(final HttpServletRequest req) { |
| return acceptsGzipEncoding(req.getHeader(HDR_ACCEPT_ENCODING)); |
| } |
| |
| static boolean acceptsGzipEncoding(String accepts) { |
| if (accepts == null) |
| return false; |
| |
| int b = 0; |
| while (b < accepts.length()) { |
| int comma = accepts.indexOf(',', b); |
| int e = 0 <= comma ? comma : accepts.length(); |
| String term = accepts.substring(b, e).trim(); |
| if (term.equals(ENCODING_GZIP)) |
| return true; |
| b = e + 1; |
| } |
| return false; |
| } |
| |
| private static byte[] compress(final byte[] raw) throws IOException { |
| final int maxLen = raw.length + 32; |
| final ByteArrayOutputStream out = new ByteArrayOutputStream(maxLen); |
| final GZIPOutputStream gz = new GZIPOutputStream(out); |
| gz.write(raw); |
| gz.finish(); |
| gz.flush(); |
| return out.toByteArray(); |
| } |
| |
| private static String etag(final byte[] content) { |
| final MessageDigest md = Constants.newMessageDigest(); |
| md.update(content); |
| return ObjectId.fromRaw(md.digest()).getName(); |
| } |
| |
| static String identify(Repository git) { |
| if (git instanceof DfsRepository) { |
| return ((DfsRepository) git).getDescription().getRepositoryName(); |
| } else if (git.getDirectory() != null) { |
| return git.getDirectory().getPath(); |
| } |
| return "unknown"; |
| } |
| |
| private ServletUtils() { |
| // static utility class only |
| } |
| } |