View Javadoc
1   /*
2    * Copyright (C) 2015, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_REPORT_STATUS;
47  import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
48  import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
49  import static org.junit.Assert.assertEquals;
50  import static org.junit.Assert.fail;
51  
52  import java.io.IOException;
53  import java.io.StringWriter;
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.HashMap;
57  import java.util.List;
58  import java.util.Map;
59  
60  import org.eclipse.jgit.errors.TransportException;
61  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
62  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
63  import org.eclipse.jgit.junit.TestRepository;
64  import org.eclipse.jgit.lib.Constants;
65  import org.eclipse.jgit.lib.NullProgressMonitor;
66  import org.eclipse.jgit.lib.ObjectId;
67  import org.eclipse.jgit.lib.ObjectInserter;
68  import org.eclipse.jgit.lib.RefUpdate;
69  import org.eclipse.jgit.lib.Repository;
70  import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
71  import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
72  import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
73  import org.junit.After;
74  import org.junit.Before;
75  import org.junit.Test;
76  
77  public class PushConnectionTest {
78  	private URIish uri;
79  	private TestProtocol<Object> testProtocol;
80  	private Object ctx = new Object();
81  	private InMemoryRepository server;
82  	private InMemoryRepository client;
83  	private List<String> processedRefs;
84  	private ObjectId obj1;
85  	private ObjectId obj2;
86  	private ObjectId obj3;
87  	private String refName = "refs/tags/blob";
88  
89  	@Before
90  	public void setUp() throws Exception {
91  		server = newRepo("server");
92  		client = newRepo("client");
93  		processedRefs = new ArrayList<>();
94  		testProtocol = new TestProtocol<>(
95  				null,
96  				new ReceivePackFactory<Object>() {
97  					@Override
98  					public ReceivePack create(Object req, Repository db)
99  							throws ServiceNotEnabledException,
100 							ServiceNotAuthorizedException {
101 						ReceivePack rp = new ReceivePack(db);
102 						rp.setPreReceiveHook(
103 								new PreReceiveHook() {
104 									@Override
105 									public void onPreReceive(ReceivePack receivePack,
106 											Collection<ReceiveCommand> cmds) {
107 										for (ReceiveCommand cmd : cmds) {
108 											processedRefs.add(cmd.getRefName());
109 										}
110 									}
111 								});
112 						return rp;
113 					}
114 				});
115 		uri = testProtocol.register(ctx, server);
116 
117 		try (ObjectInserter ins = server.newObjectInserter()) {
118 			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
119 			obj3 = ins.insert(Constants.OBJ_BLOB, Constants.encode("not"));
120 			ins.flush();
121 
122 			RefUpdate u = server.updateRef(refName);
123 			u.setNewObjectId(obj1);
124 			assertEquals(RefUpdate.Result.NEW, u.update());
125 		}
126 
127 		try (ObjectInserter ins = client.newObjectInserter()) {
128 			obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
129 			ins.flush();
130 		}
131 	}
132 
133 	@After
134 	public void tearDown() {
135 		Transport.unregister(testProtocol);
136 	}
137 
138 	private static InMemoryRepository newRepo(String name) {
139 		return new InMemoryRepository(new DfsRepositoryDescription(name));
140 	}
141 
142 	@Test
143 	public void testWrongOldIdDoesNotReplace() throws IOException {
144 		RemoteRefUpdate rru = new RemoteRefUpdate(null, null, obj2, refName,
145 				false, null, obj3);
146 
147 		Map<String, RemoteRefUpdate> updates = new HashMap<>();
148 		updates.put(rru.getRemoteName(), rru);
149 
150 		try (Transport tn = testProtocol.open(uri, client, "server");
151 				PushConnection connection = tn.openPush()) {
152 			connection.push(NullProgressMonitor.INSTANCE, updates);
153 		}
154 
155 		assertEquals(REJECTED_OTHER_REASON, rru.getStatus());
156 		assertEquals("invalid old id sent", rru.getMessage());
157 	}
158 
159 	@Test
160 	public void invalidCommand() throws IOException {
161 		try (Transport tn = testProtocol.open(uri, client, "server");
162 				InternalPushConnection c = (InternalPushConnection) tn.openPush()) {
163 			StringWriter msgs = new StringWriter();
164 			PacketLineOut pckOut = c.pckOut;
165 
166 			@SuppressWarnings("resource")
167 			SideBandInputStream in = new SideBandInputStream(c.in,
168 					NullProgressMonitor.INSTANCE, msgs, null);
169 
170 			// Explicitly invalid command, but sane enough capabilities.
171 			StringBuilder buf = new StringBuilder();
172 			buf.append("42");
173 			buf.append(' ');
174 			buf.append(obj2.name());
175 			buf.append(' ');
176 			buf.append("refs/heads/A" + obj2.name());
177 			buf.append('\0').append(CAPABILITY_SIDE_BAND_64K);
178 			buf.append(' ').append(CAPABILITY_REPORT_STATUS);
179 			buf.append('\n');
180 			pckOut.writeString(buf.toString());
181 			pckOut.end();
182 
183 			try {
184 				in.read();
185 				fail("expected TransportException");
186 			} catch (TransportException e) {
187 				assertEquals(
188 						"remote: error: invalid protocol: wanted 'old new ref'",
189 						e.getMessage());
190 			}
191 		}
192 	}
193 
194 	@Test
195 	public void limitCommandBytes() throws IOException {
196 		Map<String, RemoteRefUpdate> updates = new HashMap<>();
197 		for (int i = 0; i < 4; i++) {
198 			RemoteRefUpdate rru = new RemoteRefUpdate(
199 					null, null, obj2, "refs/test/T" + i,
200 					false, null, ObjectId.zeroId());
201 			updates.put(rru.getRemoteName(), rru);
202 		}
203 
204 		server.getConfig().setInt("receive", null, "maxCommandBytes", 195);
205 		try (Transport tn = testProtocol.open(uri, client, "server");
206 				PushConnection connection = tn.openPush()) {
207 			try {
208 				connection.push(NullProgressMonitor.INSTANCE, updates);
209 				fail("server did not abort");
210 			} catch (TransportException e) {
211 				String msg = e.getMessage();
212 				assertEquals("remote: Too many commands", msg);
213 			}
214 		}
215 	}
216 
217 	@Test
218 	public void commandOrder() throws Exception {
219 		List<RemoteRefUpdate> updates = new ArrayList<>();
220 		try (TestRepository<?> tr = new TestRepository<>(client)) {
221 			// Arbitrary non-sorted order.
222 			for (int i = 9; i >= 0; i--) {
223 				String name = "refs/heads/b" + i;
224 				tr.branch(name).commit().create();
225 				RemoteRefUpdate rru = new RemoteRefUpdate(client, name, name,
226 						false, null, ObjectId.zeroId());
227 				updates.add(rru);
228 			}
229 		}
230 
231 		PushResult result;
232 		try (Transport tn = testProtocol.open(uri, client, "server")) {
233 			result = tn.push(NullProgressMonitor.INSTANCE, updates);
234 		}
235 
236 		for (RemoteRefUpdate remoteUpdate : result.getRemoteUpdates()) {
237 			assertEquals(
238 					"update should succeed on " + remoteUpdate.getRemoteName(),
239 					RemoteRefUpdate.Status.OK, remoteUpdate.getStatus());
240 		}
241 
242 		List<String> expected = remoteRefNames(updates);
243 		assertEquals(
244 				"ref names processed by ReceivePack should match input ref names in order",
245 				expected, processedRefs);
246 		assertEquals(
247 				"remote ref names should match input ref names in order",
248 				expected, remoteRefNames(result.getRemoteUpdates()));
249 	}
250 
251 	private static List<String> remoteRefNames(Collection<RemoteRefUpdate> updates) {
252 		List<String> result = new ArrayList<>();
253 		for (RemoteRefUpdate u : updates) {
254 			result.add(u.getRemoteName());
255 		}
256 		return result;
257 	}
258 }