diff options
author | Dave Borowitz <dborowitz@google.com> | 2012-03-07 11:56:56 -0800 |
---|---|---|
committer | Dave Borowitz <dborowitz@google.com> | 2012-03-07 15:04:45 -0800 |
commit | 039c785d9f0eac3fcb78b9dc2bf61796b82d3401 (patch) | |
tree | 144caccb852cde0253564bdd2a87a6c46fc24614 /org.eclipse.jgit.http.server | |
parent | d2787d481efcd5f883d52eed5029be380196f0ef (diff) | |
download | jgit-039c785d9f0eac3fcb78b9dc2bf61796b82d3401.tar.gz jgit-039c785d9f0eac3fcb78b9dc2bf61796b82d3401.zip |
Try to send HTTP error messages over sideband
When a client POSTs to /git-{upload,receive}-pack, the first line
includes their client capabilities. As soon as the C git client sends
side-band(-64k), it goes into a state where it chokes on data not sent
in a valid sideband channel.
GitSmartHttpTools.sendError() is called early in the request, likely
before a {Upload,Receive}Pack handler is assigned or, even so, before it
has read the request. In some cases we must read the first line manually
within sendError() to tell whether sideband is needed.
Change-Id: I8277fd45a4ec3b71fa8f87404b4f5d1a09e0f384
Diffstat (limited to 'org.eclipse.jgit.http.server')
-rw-r--r-- | org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java | 133 |
1 files changed, 121 insertions, 12 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java index 8bd1704bc4..e6b287ff06 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java @@ -43,6 +43,12 @@ package org.eclipse.jgit.http.server; +import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; +import static org.eclipse.jgit.transport.BasePackFetchConnection.OPTION_SIDE_BAND; +import static org.eclipse.jgit.transport.BasePackFetchConnection.OPTION_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; +import static org.eclipse.jgit.transport.SideBandOutputStream.SMALL_BUF; import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; @@ -58,7 +64,12 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.transport.PacketLineIn; import org.eclipse.jgit.transport.PacketLineOut; +import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.RequestNotYetReadException; +import org.eclipse.jgit.transport.SideBandOutputStream; +import org.eclipse.jgit.transport.UploadPack; /** * Utility functions for handling the Git-over-HTTP protocol. @@ -141,6 +152,11 @@ public class GitSmartHttpTools { * to a Git protocol client using an HTTP 200 OK response with the error * embedded in the payload. If the request was not issued by a Git client, * an HTTP response code is returned instead. + * <p> + * This method may only be called before handing off the request to + * {@link UploadPack#upload(java.io.InputStream, OutputStream, OutputStream)} + * or + * {@link ReceivePack#receive(java.io.InputStream, OutputStream, OutputStream)}. * * @param req * current request. @@ -176,21 +192,12 @@ public class GitSmartHttpTools { } } - ByteArrayOutputStream buf = new ByteArrayOutputStream(128); - PacketLineOut pck = new PacketLineOut(buf); - if (isInfoRefs(req)) { - String svc = req.getParameter("service"); - pck.writeString("# service=" + svc + "\n"); - pck.end(); - pck.writeString("ERR " + textForGit); - send(req, res, infoRefsResultType(svc), buf.toByteArray()); + sendInfoRefsError(req, res, textForGit); } else if (isUploadPack(req)) { - pck.writeString("ERR " + textForGit); - send(req, res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray()); + sendUploadPackError(req, res, textForGit); } else if (isReceivePack(req)) { - pck.writeString("ERR " + textForGit); - send(req, res, RECEIVE_PACK_RESULT_TYPE, buf.toByteArray()); + sendReceivePackError(req, res, textForGit); } else { if (httpStatus < 400) ServletUtils.consumeRequestBody(req); @@ -198,6 +205,108 @@ public class GitSmartHttpTools { } } + private static void sendInfoRefsError(HttpServletRequest req, + HttpServletResponse res, String textForGit) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(128); + PacketLineOut pck = new PacketLineOut(buf); + String svc = req.getParameter("service"); + pck.writeString("# service=" + svc + "\n"); + pck.end(); + pck.writeString("ERR " + textForGit); + send(req, res, infoRefsResultType(svc), buf.toByteArray()); + } + + private static void sendUploadPackError(HttpServletRequest req, + HttpServletResponse res, String textForGit) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(128); + PacketLineOut pckOut = new PacketLineOut(buf); + + boolean sideband; + UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); + if (up != null) { + try { + sideband = up.isSideBand(); + } catch (RequestNotYetReadException e) { + sideband = isUploadPackSideBand(req); + } + } else + sideband = isUploadPackSideBand(req); + + if (sideband) + writeSideBand(buf, textForGit); + else + writePacket(pckOut, textForGit); + send(req, res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray()); + } + + private static boolean isUploadPackSideBand(HttpServletRequest req) { + try { + // The client may be in a state where they have sent the sideband + // capability and are expecting a response in the sideband, but we might + // not have an UploadPack, or it might not have read any of the request. + // So, cheat and read the first line. + String line = new PacketLineIn(req.getInputStream()).readStringRaw(); + UploadPack.FirstLine parsed = new UploadPack.FirstLine(line); + return (parsed.getOptions().contains(OPTION_SIDE_BAND) + || parsed.getOptions().contains(OPTION_SIDE_BAND_64K)); + } catch (IOException e) { + // Probably the connection is closed and a subsequent write will fail, but + // try it just in case. + return false; + } + } + + private static void sendReceivePackError(HttpServletRequest req, + HttpServletResponse res, String textForGit) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(128); + PacketLineOut pckOut = new PacketLineOut(buf); + + boolean sideband; + ReceivePack rp = (ReceivePack) req.getAttribute(ATTRIBUTE_HANDLER); + if (rp != null) { + try { + sideband = rp.isSideBand(); + } catch (RequestNotYetReadException e) { + sideband = isReceivePackSideBand(req); + } + } else + sideband = isReceivePackSideBand(req); + + if (sideband) + writeSideBand(buf, textForGit); + else + writePacket(pckOut, textForGit); + send(req, res, RECEIVE_PACK_RESULT_TYPE, buf.toByteArray()); + } + + private static boolean isReceivePackSideBand(HttpServletRequest req) { + try { + // The client may be in a state where they have sent the sideband + // capability and are expecting a response in the sideband, but we might + // not have a ReceivePack, or it might not have read any of the request. + // So, cheat and read the first line. + String line = new PacketLineIn(req.getInputStream()).readStringRaw(); + ReceivePack.FirstLine parsed = new ReceivePack.FirstLine(line); + return parsed.getCapabilities().contains(CAPABILITY_SIDE_BAND_64K); + } catch (IOException e) { + // Probably the connection is closed and a subsequent write will fail, but + // try it just in case. + return false; + } + } + + private static void writeSideBand(OutputStream out, String textForGit) + throws IOException { + OutputStream msg = new SideBandOutputStream(CH_ERROR, SMALL_BUF, out); + msg.write(Constants.encode("error: " + textForGit)); + msg.flush(); + } + + private static void writePacket(PacketLineOut pckOut, String textForGit) + throws IOException { + pckOut.writeString("error: " + textForGit); + } + private static void send(HttpServletRequest req, HttpServletResponse res, String type, byte[] buf) throws IOException { ServletUtils.consumeRequestBody(req); |