diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java index 6af28ba0d..27bee85d8 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java @@ -158,17 +158,18 @@ class ReceivePackServlet extends HttpServlet { return; } + SmartOutputStream out = new SmartOutputStream(req, rsp) { + @Override + public void flush() throws IOException { + doFlush(); + } + }; + ReceivePack rp = (ReceivePack) req.getAttribute(ATTRIBUTE_HANDLER); try { rp.setBiDirectionalPipe(false); rsp.setContentType(RECEIVE_PACK_RESULT_TYPE); - final SmartOutputStream out = new SmartOutputStream(req, rsp) { - @Override - public void flush() throws IOException { - doFlush(); - } - }; rp.receive(getInputStream(req), out, null); out.close(); } catch (UnpackException e) { @@ -176,8 +177,9 @@ class ReceivePackServlet extends HttpServlet { getServletContext().log( HttpServerText.get().internalErrorDuringReceivePack, e.getCause()); + out.close(); - } catch (IOException e) { + } catch (Throwable e) { getServletContext().log(HttpServerText.get().internalErrorDuringReceivePack, e); if (!rsp.isCommitted()) { rsp.reset(); diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index c7891dfc7..33bfff6d4 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java @@ -160,22 +160,25 @@ class UploadPackServlet extends HttpServlet { return; } + SmartOutputStream out = new SmartOutputStream(req, rsp) { + @Override + public void flush() throws IOException { + doFlush(); + } + }; + UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); try { up.setBiDirectionalPipe(false); rsp.setContentType(UPLOAD_PACK_RESULT_TYPE); - final SmartOutputStream out = new SmartOutputStream(req, rsp) { - @Override - public void flush() throws IOException { - doFlush(); - } - }; up.upload(getInputStream(req), out, null); out.close(); } catch (UploadPackMayNotContinueException e) { - if (!e.isOutput() && !rsp.isCommitted()) { + if (e.isOutput()) { + out.close(); + } else if (!rsp.isCommitted()) { rsp.reset(); sendError(req, rsp, SC_FORBIDDEN, e.getMessage()); } @@ -186,8 +189,9 @@ class UploadPackServlet extends HttpServlet { getServletContext().log( HttpServerText.get().internalErrorDuringUploadPack, e.getCause()); + out.close(); - } catch (IOException e) { + } catch (Throwable e) { getServletContext().log(HttpServerText.get().internalErrorDuringUploadPack, e); if (!rsp.isCommitted()) { rsp.reset(); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java new file mode 100644 index 000000000..8cb9e087d --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 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.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.server.GitSmartHttpTools; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.http.HttpTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.transport.PacketLineIn; +import org.eclipse.jgit.transport.PacketLineOut; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.resolver.RepositoryResolver; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.util.NB; +import org.junit.Before; +import org.junit.Test; + +public class ProtocolErrorTest extends HttpTestCase { + private FileRepository remoteRepository; + + private URIish remoteURI; + + private RevBlob a_blob; + + @Before + public void setUp() throws Exception { + super.setUp(); + + final TestRepository src = createTestRepository(); + final String srcName = src.getRepository().getDirectory().getName(); + + ServletContextHandler app = server.addContext("/git"); + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver(new RepositoryResolver() { + public Repository open(HttpServletRequest req, String name) + throws RepositoryNotFoundException, + ServiceNotEnabledException { + if (!name.equals(srcName)) + throw new RepositoryNotFoundException(name); + + final Repository db = src.getRepository(); + db.incrementOpen(); + return db; + } + }); + app.addServlet(new ServletHolder(gs), "/*"); + + server.setUp(); + + remoteRepository = src.getRepository(); + remoteURI = toURIish(app, srcName); + + FileBasedConfig cfg = remoteRepository.getConfig(); + cfg.setBoolean("http", null, "receivepack", true); + cfg.save(); + + a_blob = src.blob("a"); + } + + @Test + public void testPush_UnpackError_TruncatedPack() throws Exception { + StringBuilder sb = new StringBuilder(); + sb.append(ObjectId.zeroId().name()); + sb.append(' '); + sb.append(a_blob.name()); + sb.append(' '); + sb.append("refs/objects/A"); + sb.append('\0'); + sb.append("report-status"); + + ByteArrayOutputStream reqbuf = new ByteArrayOutputStream(); + PacketLineOut reqpck = new PacketLineOut(reqbuf); + reqpck.writeString(sb.toString()); + reqpck.end(); + + packHeader(reqbuf, 1); + + byte[] reqbin = reqbuf.toByteArray(); + + URL u = new URL(remoteURI.toString() + "/git-receive-pack"); + HttpURLConnection c = (HttpURLConnection) u.openConnection(); + try { + c.setRequestMethod("POST"); + c.setDoOutput(true); + c.setRequestProperty("Content-Type", + GitSmartHttpTools.RECEIVE_PACK_REQUEST_TYPE); + c.setFixedLengthStreamingMode(reqbin.length); + OutputStream out = c.getOutputStream(); + try { + out.write(reqbin); + } finally { + out.close(); + } + + assertEquals(200, c.getResponseCode()); + assertEquals(GitSmartHttpTools.RECEIVE_PACK_RESULT_TYPE, + c.getContentType()); + + InputStream rawin = c.getInputStream(); + try { + PacketLineIn pckin = new PacketLineIn(rawin); + assertEquals("unpack error " + + JGitText.get().packfileIsTruncated, + pckin.readString()); + assertEquals("ng refs/objects/A n/a (unpacker error)", + pckin.readString()); + assertSame(PacketLineIn.END, pckin.readString()); + } finally { + rawin.close(); + } + } finally { + c.disconnect(); + } + } + + private void packHeader(ByteArrayOutputStream tinyPack, int cnt) + throws IOException { + final byte[] hdr = new byte[8]; + NB.encodeInt32(hdr, 0, 2); + NB.encodeInt32(hdr, 4, cnt); + + tinyPack.write(Constants.PACK_SIGNATURE); + tinyPack.write(hdr, 0, 8); + } +}