Discard request HTTP bodies for status code <400

The HTTP RFCs require a server to fully consume the request body before
it can return a non-error status code, which is any code below 400.

JGit returns most Git level errors inside of an HTTP 200 OK response,
and sometimes this happens before the entire request was consumed from
the servlet container. In such cases the body must be skipped or read
until EOF is reached, ensuring the HTTP keep-alive semantics will work
for the next request on the same TCP connection.

HTTP status codes >= 400 may be returned without consuming the body,
and a servlet container must set "Connection: close" in the response
headers when this happens, since the state of the request body is not
well defined with an early abort.

With the introduction of sendError() in GitSmartHttpTools there are
only a handful of locations that need to worry about the request body
being consumed, so sprinkle the call in as necessary.

Change-Id: I5381e110585f780c01a764df8e27c80aacf5146e
This commit is contained in:
Shawn O. Pearce 2011-11-30 17:36:32 -08:00
parent ac6cda955c
commit db00632db7
4 changed files with 59 additions and 7 deletions

View File

@ -49,11 +49,11 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -184,24 +184,27 @@ public static void sendError(HttpServletRequest req,
pck.writeString("# service=" + svc + "\n");
pck.end();
pck.writeString("ERR " + textForGit);
send(res, infoRefsResultType(svc), buf.toByteArray());
send(req, res, infoRefsResultType(svc), buf.toByteArray());
} else if (isUploadPack(req)) {
pck.writeString("ERR " + textForGit);
send(res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray());
send(req, res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray());
} else if (isReceivePack(req)) {
pck.writeString("ERR " + textForGit);
send(res, RECEIVE_PACK_RESULT_TYPE, buf.toByteArray());
send(req, res, RECEIVE_PACK_RESULT_TYPE, buf.toByteArray());
} else {
if (httpStatus < 400)
ServletUtils.consumeRequestBody(req);
res.sendError(httpStatus);
}
}
private static void send(HttpServletResponse res, String type, byte[] buf)
throws IOException {
private static void send(HttpServletRequest req, HttpServletResponse res,
String type, byte[] buf) throws IOException {
ServletUtils.consumeRequestBody(req);
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType(type);
res.setContentLength(buf.length);
ServletOutputStream os = res.getOutputStream();
OutputStream os = res.getOutputStream();
try {
os.write(buf);
} finally {

View File

@ -52,6 +52,7 @@
import static org.eclipse.jgit.http.server.GitSmartHttpTools.RECEIVE_PACK_RESULT_TYPE;
import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER;
import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody;
import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
@ -177,6 +178,7 @@ public void flush() throws IOException {
getServletContext().log(
HttpServerText.get().internalErrorDuringReceivePack,
e.getCause());
consumeRequestBody(req);
out.close();
} catch (Throwable e) {

View File

@ -118,6 +118,50 @@ else if (enc != null)
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.
}
}
}
private 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>

View File

@ -52,6 +52,7 @@
import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_RESULT_TYPE;
import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER;
import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody;
import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
@ -177,6 +178,7 @@ public void flush() throws IOException {
} catch (UploadPackMayNotContinueException e) {
if (e.isOutput()) {
consumeRequestBody(req);
out.close();
} else if (!rsp.isCommitted()) {
rsp.reset();
@ -189,6 +191,7 @@ public void flush() throws IOException {
getServletContext().log(
HttpServerText.get().internalErrorDuringUploadPack,
e.getCause());
consumeRequestBody(req);
out.close();
} catch (Throwable e) {