1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jgit.transport;
15
16 import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
17 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
18 import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_PEELED;
19 import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_SYMREF_TARGET;
20 import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_1;
21 import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2;
22
23 import java.io.EOFException;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.text.MessageFormat;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.LinkedHashMap;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38
39 import org.eclipse.jgit.annotations.NonNull;
40 import org.eclipse.jgit.errors.InvalidObjectIdException;
41 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
42 import org.eclipse.jgit.errors.PackProtocolException;
43 import org.eclipse.jgit.errors.RemoteRepositoryException;
44 import org.eclipse.jgit.errors.TransportException;
45 import org.eclipse.jgit.internal.JGitText;
46 import org.eclipse.jgit.lib.Constants;
47 import org.eclipse.jgit.lib.ObjectId;
48 import org.eclipse.jgit.lib.ObjectIdRef;
49 import org.eclipse.jgit.lib.Ref;
50 import org.eclipse.jgit.lib.Repository;
51 import org.eclipse.jgit.lib.SymbolicRef;
52 import org.eclipse.jgit.util.StringUtils;
53 import org.eclipse.jgit.util.io.InterruptTimer;
54 import org.eclipse.jgit.util.io.TimeoutInputStream;
55 import org.eclipse.jgit.util.io.TimeoutOutputStream;
56
57
58
59
60
61
62
63
64
65 abstract class BasePackConnection extends BaseConnection {
66
67 protected static final String CAPABILITY_SYMREF_PREFIX = "symref=";
68
69
70 protected final Repository local;
71
72
73 protected final URIish uri;
74
75
76 protected final Transport transport;
77
78
79 protected TimeoutInputStream timeoutIn;
80
81
82 protected TimeoutOutputStream timeoutOut;
83
84
85 private InterruptTimer myTimer;
86
87
88 protected InputStream in;
89
90
91 protected OutputStream out;
92
93
94 protected PacketLineIn pckIn;
95
96
97 protected PacketLineOut pckOut;
98
99
100 protected boolean outNeedsEnd;
101
102
103 protected boolean statelessRPC;
104
105
106 private final Map<String, String> remoteCapabilities = new HashMap<>();
107
108
109 protected final Set<ObjectId> additionalHaves = new HashSet<>();
110
111 private TransferConfig.ProtocolVersion protocol = TransferConfig.ProtocolVersion.V0;
112
113 BasePackConnection(PackTransport packTransport) {
114 transport = (Transport) packTransport;
115 local = transport.local;
116 uri = transport.uri;
117 }
118
119 TransferConfig.ProtocolVersion getProtocolVersion() {
120 return protocol;
121 }
122
123 void setProtocolVersion(@NonNull TransferConfig.ProtocolVersion protocol) {
124 this.protocol = protocol;
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138 protected final void init(InputStream myIn, OutputStream myOut) {
139 final int timeout = transport.getTimeout();
140 if (timeout > 0) {
141 final Thread caller = Thread.currentThread();
142 if (myTimer == null) {
143 myTimer = new InterruptTimer(caller.getName() + "-Timer");
144 }
145 timeoutIn = new TimeoutInputStream(myIn, myTimer);
146 timeoutOut = new TimeoutOutputStream(myOut, myTimer);
147 timeoutIn.setTimeout(timeout * 1000);
148 timeoutOut.setTimeout(timeout * 1000);
149 myIn = timeoutIn;
150 myOut = timeoutOut;
151 }
152
153 in = myIn;
154 out = myOut;
155
156 pckIn = new PacketLineIn(in);
157 pckOut = new PacketLineOut(out);
158 outNeedsEnd = true;
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177 protected boolean readAdvertisedRefs() throws TransportException {
178 try {
179 return readAdvertisedRefsImpl();
180 } catch (TransportException err) {
181 close();
182 throw err;
183 } catch (IOException | RuntimeException err) {
184 close();
185 throw new TransportException(err.getMessage(), err);
186 }
187 }
188
189 private String readLine() throws IOException {
190 String line = pckIn.readString();
191 if (PacketLineIn.isEnd(line)) {
192 return null;
193 }
194 if (line.startsWith("ERR ")) {
195
196
197 throw new RemoteRepositoryException(uri, line.substring(4));
198 }
199 return line;
200 }
201
202 private boolean readAdvertisedRefsImpl() throws IOException {
203 final Map<String, Ref> avail = new LinkedHashMap<>();
204 final Map<String, String> symRefs = new LinkedHashMap<>();
205 for (boolean first = true;; first = false) {
206 String line;
207
208 if (first) {
209 boolean isV1 = false;
210 try {
211 line = readLine();
212 } catch (EOFException e) {
213 TransportException noRepo = noRepository();
214 noRepo.initCause(e);
215 throw noRepo;
216 }
217 if (line != null && VERSION_1.equals(line)) {
218
219
220 setProtocolVersion(TransferConfig.ProtocolVersion.V0);
221 isV1 = true;
222 line = readLine();
223 }
224 if (line == null) {
225 break;
226 }
227 final int nul = line.indexOf('\0');
228 if (nul >= 0) {
229
230
231 for (String capability : line.substring(nul + 1)
232 .split(" ")) {
233 if (capability.startsWith(CAPABILITY_SYMREF_PREFIX)) {
234 String[] parts = capability
235 .substring(
236 CAPABILITY_SYMREF_PREFIX.length())
237 .split(":", 2);
238 if (parts.length == 2) {
239 symRefs.put(parts[0], parts[1]);
240 }
241 } else {
242 addCapability(capability);
243 }
244 }
245 line = line.substring(0, nul);
246 setProtocolVersion(TransferConfig.ProtocolVersion.V0);
247 } else if (!isV1 && VERSION_2.equals(line)) {
248
249
250 setProtocolVersion(TransferConfig.ProtocolVersion.V2);
251 readCapabilitiesV2();
252
253
254 return false;
255 } else {
256 setProtocolVersion(TransferConfig.ProtocolVersion.V0);
257 }
258 } else {
259 line = readLine();
260 if (line == null) {
261 break;
262 }
263 }
264
265
266 if (line.length() < 41 || line.charAt(40) != ' ') {
267 throw invalidRefAdvertisementLine(line);
268 }
269 String name = line.substring(41, line.length());
270 if (first && name.equals("capabilities^{}")) {
271
272
273 continue;
274 }
275
276 final ObjectId id = toId(line, line.substring(0, 40));
277 if (name.equals(".have")) {
278 additionalHaves.add(id);
279 } else {
280 processLineV1(name, id, avail);
281 }
282 }
283 updateWithSymRefs(avail, symRefs);
284 available(avail);
285 return true;
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299 protected void lsRefs(Collection<RefSpec> refSpecs,
300 String... additionalPatterns) throws TransportException {
301 try {
302 lsRefsImpl(refSpecs, additionalPatterns);
303 } catch (TransportException err) {
304 close();
305 throw err;
306 } catch (IOException | RuntimeException err) {
307 close();
308 throw new TransportException(err.getMessage(), err);
309 }
310 }
311
312 private void lsRefsImpl(Collection<RefSpec> refSpecs,
313 String... additionalPatterns) throws IOException {
314 pckOut.writeString("command=" + COMMAND_LS_REFS);
315
316 String agent = UserAgent.get();
317 if (agent != null && isCapableOf(OPTION_AGENT)) {
318 pckOut.writeString(OPTION_AGENT + '=' + agent);
319 }
320 pckOut.writeDelim();
321 pckOut.writeString("peel");
322 pckOut.writeString("symrefs");
323 for (String refPrefix : getRefPrefixes(refSpecs, additionalPatterns)) {
324 pckOut.writeString("ref-prefix " + refPrefix);
325 }
326 pckOut.end();
327 final Map<String, Ref> avail = new LinkedHashMap<>();
328 final Map<String, String> symRefs = new LinkedHashMap<>();
329 for (;;) {
330 String line = readLine();
331 if (line == null) {
332 break;
333 }
334
335 if (line.length() < 41 || line.charAt(40) != ' ') {
336 throw invalidRefAdvertisementLine(line);
337 }
338 String name = line.substring(41, line.length());
339 final ObjectId id = toId(line, line.substring(0, 40));
340 if (name.equals(".have")) {
341 additionalHaves.add(id);
342 } else {
343 processLineV2(line, id, name, avail, symRefs);
344 }
345 }
346 updateWithSymRefs(avail, symRefs);
347 available(avail);
348 }
349
350 private Collection<String> getRefPrefixes(Collection<RefSpec> refSpecs,
351 String... additionalPatterns) {
352 if (refSpecs.isEmpty() && (additionalPatterns == null
353 || additionalPatterns.length == 0)) {
354 return Collections.emptyList();
355 }
356 Set<String> patterns = new HashSet<>();
357 if (additionalPatterns != null) {
358 Arrays.stream(additionalPatterns).filter(Objects::nonNull)
359 .forEach(patterns::add);
360 }
361 for (RefSpec spec : refSpecs) {
362
363
364
365 String src = spec.getSource();
366 if (ObjectId.isId(src)) {
367 continue;
368 }
369 if (spec.isWildcard()) {
370 patterns.add(src.substring(0, src.indexOf('*')));
371 } else {
372 patterns.add(src);
373 patterns.add(Constants.R_REFS + src);
374 patterns.add(Constants.R_HEADS + src);
375 patterns.add(Constants.R_TAGS + src);
376 }
377 }
378 return patterns;
379 }
380
381 private void readCapabilitiesV2() throws IOException {
382
383
384
385
386 for (;;) {
387 String line = readLine();
388 if (line == null) {
389 break;
390 }
391 addCapability(line);
392 }
393 }
394
395 private void addCapability(String capability) {
396 String parts[] = capability.split("=", 2);
397 if (parts.length == 2) {
398 remoteCapabilities.put(parts[0], parts[1]);
399 }
400 remoteCapabilities.put(capability, null);
401 }
402
403 private ObjectId toId(String line, String value)
404 throws PackProtocolException {
405 try {
406 return ObjectId.fromString(value);
407 } catch (InvalidObjectIdException e) {
408 PackProtocolException ppe = invalidRefAdvertisementLine(line);
409 ppe.initCause(e);
410 throw ppe;
411 }
412 }
413
414 private void processLineV1(String name, ObjectId id, Map<String, Ref> avail)
415 throws IOException {
416 if (name.endsWith("^{}")) {
417 name = name.substring(0, name.length() - 3);
418 final Ref prior = avail.get(name);
419 if (prior == null) {
420 throw new PackProtocolException(uri, MessageFormat.format(
421 JGitText.get().advertisementCameBefore, name, name));
422 }
423 if (prior.getPeeledObjectId() != null) {
424 throw duplicateAdvertisement(name + "^{}");
425 }
426 avail.put(name, new ObjectIdRef.PeeledTag(Ref.Storage.NETWORK, name,
427 prior.getObjectId(), id));
428 } else {
429 final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
430 Ref.Storage.NETWORK, name, id));
431 if (prior != null) {
432 throw duplicateAdvertisement(name);
433 }
434 }
435 }
436
437 private void processLineV2(String line, ObjectId id, String rest,
438 Map<String, Ref> avail, Map<String, String> symRefs)
439 throws IOException {
440 String[] parts = rest.split(" ");
441 String name = parts[0];
442
443 String symRefTarget = null;
444 String peeled = null;
445 for (int i = 1; i < parts.length; i++) {
446 if (parts[i].startsWith(REF_ATTR_SYMREF_TARGET)) {
447 if (symRefTarget != null) {
448 throw new PackProtocolException(uri, MessageFormat.format(
449 JGitText.get().duplicateRefAttribute, line));
450 }
451 symRefTarget = parts[i]
452 .substring(REF_ATTR_SYMREF_TARGET.length());
453 } else if (parts[i].startsWith(REF_ATTR_PEELED)) {
454 if (peeled != null) {
455 throw new PackProtocolException(uri, MessageFormat.format(
456 JGitText.get().duplicateRefAttribute, line));
457 }
458 peeled = parts[i].substring(REF_ATTR_PEELED.length());
459 }
460 if (peeled != null && symRefTarget != null) {
461 break;
462 }
463 }
464 Ref idRef;
465 if (peeled != null) {
466 idRef = new ObjectIdRef.PeeledTag(Ref.Storage.NETWORK, name, id,
467 toId(line, peeled));
468 } else {
469 idRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NETWORK, name, id);
470 }
471 Ref prior = avail.put(name, idRef);
472 if (prior != null) {
473 throw duplicateAdvertisement(name);
474 }
475 if (!StringUtils.isEmptyOrNull(symRefTarget)) {
476 symRefs.put(name, symRefTarget);
477 }
478 }
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529 static void updateWithSymRefs(Map<String, Ref> refMap, Map<String, String> symRefs) {
530 boolean haveNewRefMapEntries = !refMap.isEmpty();
531 while (!symRefs.isEmpty() && haveNewRefMapEntries) {
532 haveNewRefMapEntries = false;
533 final Iterator<Map.Entry<String, String>> iterator = symRefs.entrySet().iterator();
534 while (iterator.hasNext()) {
535 final Map.Entry<String, String> symRef = iterator.next();
536 if (!symRefs.containsKey(symRef.getValue())) {
537 final Ref r = refMap.get(symRef.getValue());
538 if (r != null) {
539 refMap.put(symRef.getKey(), new SymbolicRef(symRef.getKey(), r));
540 haveNewRefMapEntries = true;
541 iterator.remove();
542 }
543 }
544 }
545 }
546
547
548
549 String headRefName = symRefs.get(Constants.HEAD);
550 if (headRefName != null && !refMap.containsKey(headRefName)) {
551 Ref headRef = refMap.get(Constants.HEAD);
552 if (headRef != null) {
553 ObjectId headObj = headRef.getObjectId();
554 headRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NETWORK,
555 headRefName, headObj);
556 refMap.put(headRefName, headRef);
557 headRef = new SymbolicRef(Constants.HEAD, headRef);
558 refMap.put(Constants.HEAD, headRef);
559 symRefs.remove(Constants.HEAD);
560 }
561 }
562 }
563
564
565
566
567
568
569
570
571
572
573 protected TransportException noRepository() {
574 return new NoRemoteRepositoryException(uri, JGitText.get().notFound);
575 }
576
577
578
579
580
581
582
583
584 protected boolean isCapableOf(String option) {
585 return remoteCapabilities.containsKey(option);
586 }
587
588
589
590
591
592
593
594
595
596
597 protected boolean wantCapability(StringBuilder b, String option) {
598 if (!isCapableOf(option))
599 return false;
600 b.append(' ');
601 b.append(option);
602 return true;
603 }
604
605
606
607
608
609
610
611
612 protected String getCapability(String option) {
613 return remoteCapabilities.get(option);
614 }
615
616
617
618
619
620
621
622 protected void addUserAgentCapability(StringBuilder b) {
623 String a = UserAgent.get();
624 if (a != null && remoteCapabilities.get(OPTION_AGENT) != null) {
625 b.append(' ').append(OPTION_AGENT).append('=').append(a);
626 }
627 }
628
629
630 @Override
631 public String getPeerUserAgent() {
632 String agent = remoteCapabilities.get(OPTION_AGENT);
633 return agent != null ? agent : super.getPeerUserAgent();
634 }
635
636 private PackProtocolException duplicateAdvertisement(String name) {
637 return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
638 }
639
640 private PackProtocolException invalidRefAdvertisementLine(String line) {
641 return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line));
642 }
643
644
645 @Override
646 public void close() {
647 if (out != null) {
648 try {
649 if (outNeedsEnd) {
650 outNeedsEnd = false;
651 pckOut.end();
652 }
653 out.close();
654 } catch (IOException err) {
655
656 } finally {
657 out = null;
658 pckOut = null;
659 }
660 }
661
662 if (in != null) {
663 try {
664 in.close();
665 } catch (IOException err) {
666
667 } finally {
668 in = null;
669 pckIn = null;
670 }
671 }
672
673 if (myTimer != null) {
674 try {
675 myTimer.terminate();
676 } finally {
677 myTimer = null;
678 timeoutIn = null;
679 timeoutOut = null;
680 }
681 }
682 }
683
684
685
686
687 protected void endOut() {
688 if (outNeedsEnd && out != null) {
689 try {
690 outNeedsEnd = false;
691 pckOut.end();
692 } catch (IOException e) {
693 try {
694 out.close();
695 } catch (IOException err) {
696
697 } finally {
698 out = null;
699 pckOut = null;
700 }
701 }
702 }
703 }
704 }