1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 package org.eclipse.jgit.transport;
48
49 import static org.eclipse.jgit.lib.Constants.HEAD;
50 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
51 import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
52 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
53 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
54 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
55 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
56 import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION;
57 import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
58 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
59 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
60 import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
61 import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
62
63 import java.io.BufferedReader;
64 import java.io.ByteArrayInputStream;
65 import java.io.FileNotFoundException;
66 import java.io.IOException;
67 import java.io.InputStream;
68 import java.io.InputStreamReader;
69 import java.io.OutputStream;
70 import java.net.MalformedURLException;
71 import java.net.Proxy;
72 import java.net.ProxySelector;
73 import java.net.URISyntaxException;
74 import java.net.URL;
75 import java.security.cert.CertPathBuilderException;
76 import java.security.cert.CertPathValidatorException;
77 import java.security.cert.CertificateException;
78 import java.text.MessageFormat;
79 import java.util.ArrayList;
80 import java.util.Arrays;
81 import java.util.Collection;
82 import java.util.Collections;
83 import java.util.EnumSet;
84 import java.util.HashSet;
85 import java.util.LinkedHashSet;
86 import java.util.Locale;
87 import java.util.Map;
88 import java.util.Set;
89 import java.util.TreeMap;
90 import java.util.zip.GZIPInputStream;
91 import java.util.zip.GZIPOutputStream;
92
93 import javax.net.ssl.SSLHandshakeException;
94
95 import org.eclipse.jgit.errors.ConfigInvalidException;
96 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
97 import org.eclipse.jgit.errors.NotSupportedException;
98 import org.eclipse.jgit.errors.PackProtocolException;
99 import org.eclipse.jgit.errors.TransportException;
100 import org.eclipse.jgit.internal.JGitText;
101 import org.eclipse.jgit.internal.storage.file.RefDirectory;
102 import org.eclipse.jgit.lib.Constants;
103 import org.eclipse.jgit.lib.ObjectId;
104 import org.eclipse.jgit.lib.ObjectIdRef;
105 import org.eclipse.jgit.lib.ProgressMonitor;
106 import org.eclipse.jgit.lib.Ref;
107 import org.eclipse.jgit.lib.Repository;
108 import org.eclipse.jgit.lib.StoredConfig;
109 import org.eclipse.jgit.lib.SymbolicRef;
110 import org.eclipse.jgit.storage.file.FileBasedConfig;
111 import org.eclipse.jgit.transport.HttpAuthMethod.Type;
112 import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
113 import org.eclipse.jgit.transport.http.HttpConnection;
114 import org.eclipse.jgit.util.FS;
115 import org.eclipse.jgit.util.HttpSupport;
116 import org.eclipse.jgit.util.IO;
117 import org.eclipse.jgit.util.RawParseUtils;
118 import org.eclipse.jgit.util.SystemReader;
119 import org.eclipse.jgit.util.TemporaryBuffer;
120 import org.eclipse.jgit.util.io.DisabledOutputStream;
121 import org.eclipse.jgit.util.io.UnionInputStream;
122 import org.slf4j.Logger;
123 import org.slf4j.LoggerFactory;
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 public class TransportHttp extends HttpTransport implements WalkTransport,
142 PackTransport {
143
144 private static final Logger LOG = LoggerFactory
145 .getLogger(TransportHttp.class);
146
147 private static final String SVC_UPLOAD_PACK = "git-upload-pack";
148
149 private static final String SVC_RECEIVE_PACK = "git-receive-pack";
150
151
152
153
154
155
156
157 public enum AcceptEncoding {
158
159
160
161
162 UNSPECIFIED,
163
164
165
166
167 GZIP
168 }
169
170 static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
171 private final String[] schemeNames = { "http", "https" };
172
173 private final Set<String> schemeSet = Collections
174 .unmodifiableSet(new LinkedHashSet<>(Arrays
175 .asList(schemeNames)));
176
177 @Override
178 public String getName() {
179 return JGitText.get().transportProtoHTTP;
180 }
181
182 @Override
183 public Set<String> getSchemes() {
184 return schemeSet;
185 }
186
187 @Override
188 public Set<URIishField> getRequiredFields() {
189 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
190 URIishField.PATH));
191 }
192
193 @Override
194 public Set<URIishField> getOptionalFields() {
195 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
196 URIishField.PASS, URIishField.PORT));
197 }
198
199 @Override
200 public int getDefaultPort() {
201 return 80;
202 }
203
204 @Override
205 public Transport open(URIish uri, Repository local, String remoteName)
206 throws NotSupportedException {
207 return new TransportHttp(local, uri);
208 }
209
210 @Override
211 public Transport open(URIish uri) throws NotSupportedException {
212 return new TransportHttp(uri);
213 }
214 };
215
216 static final TransportProtocol PROTO_FTP = new TransportProtocol() {
217 @Override
218 public String getName() {
219 return JGitText.get().transportProtoFTP;
220 }
221
222 @Override
223 public Set<String> getSchemes() {
224 return Collections.singleton("ftp");
225 }
226
227 @Override
228 public Set<URIishField> getRequiredFields() {
229 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
230 URIishField.PATH));
231 }
232
233 @Override
234 public Set<URIishField> getOptionalFields() {
235 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
236 URIishField.PASS, URIishField.PORT));
237 }
238
239 @Override
240 public int getDefaultPort() {
241 return 21;
242 }
243
244 @Override
245 public Transport open(URIish uri, Repository local, String remoteName)
246 throws NotSupportedException {
247 return new TransportHttp(local, uri);
248 }
249 };
250
251
252
253
254
255
256 private URIish currentUri;
257
258 private URL baseUrl;
259
260 private URL objectsUrl;
261
262 private final HttpConfig http;
263
264 private final ProxySelector proxySelector;
265
266 private boolean useSmartHttp = true;
267
268 private HttpAuthMethod authMethod = HttpAuthMethod.Type.NONE.method(null);
269
270 private Map<String, String> headers;
271
272 private boolean sslVerify;
273
274 private boolean sslFailure = false;
275
276 TransportHttp(Repository local, URIish uri)
277 throws NotSupportedException {
278 super(local, uri);
279 setURI(uri);
280 http = new HttpConfig(local.getConfig(), uri);
281 proxySelector = ProxySelector.getDefault();
282 sslVerify = http.isSslVerify();
283 }
284
285 private URL toURL(URIish urish) throws MalformedURLException {
286 String uriString = urish.toString();
287 if (!uriString.endsWith("/")) {
288 uriString += '/';
289 }
290 return new URL(uriString);
291 }
292
293
294
295
296
297
298
299
300
301 protected void setURI(URIish uri) throws NotSupportedException {
302 try {
303 currentUri = uri;
304 baseUrl = toURL(uri);
305 objectsUrl = new URL(baseUrl, "objects/");
306 } catch (MalformedURLException e) {
307 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
308 }
309 }
310
311
312
313
314
315
316
317 TransportHttp(URIish uri) throws NotSupportedException {
318 super(uri);
319 setURI(uri);
320 http = new HttpConfig(uri);
321 proxySelector = ProxySelector.getDefault();
322 sslVerify = http.isSslVerify();
323 }
324
325
326
327
328
329
330
331
332
333
334
335 public void setUseSmartHttp(boolean on) {
336 useSmartHttp = on;
337 }
338
339 @SuppressWarnings("resource")
340 private FetchConnection getConnection(HttpConnection c, InputStream in,
341 String service) throws IOException {
342 BaseConnection f;
343 if (isSmartHttp(c, service)) {
344 readSmartHeaders(in, service);
345 f = new SmartHttpFetchConnection(in);
346 } else {
347
348
349 f = newDumbConnection(in);
350 }
351 f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
352 return (FetchConnection) f;
353 }
354
355
356 @Override
357 public FetchConnection openFetch() throws TransportException,
358 NotSupportedException {
359 final String service = SVC_UPLOAD_PACK;
360 try {
361 final HttpConnection c = connect(service);
362 try (InputStream in = openInputStream(c)) {
363 return getConnection(c, in, service);
364 }
365 } catch (NotSupportedException err) {
366 throw err;
367 } catch (TransportException err) {
368 throw err;
369 } catch (IOException err) {
370 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
371 }
372 }
373
374 private WalkFetchConnection newDumbConnection(InputStream in)
375 throws IOException, PackProtocolException {
376 HttpObjectDB d = new HttpObjectDB(objectsUrl);
377 Map<String, Ref> refs;
378 try (BufferedReader br = toBufferedReader(in)) {
379 refs = d.readAdvertisedImpl(br);
380 }
381
382 if (!refs.containsKey(HEAD)) {
383
384
385
386
387 HttpConnection conn = httpOpen(
388 METHOD_GET,
389 new URL(baseUrl, HEAD),
390 AcceptEncoding.GZIP);
391 int status = HttpSupport.response(conn);
392 switch (status) {
393 case HttpConnection.HTTP_OK: {
394 try (BufferedReader br = toBufferedReader(
395 openInputStream(conn))) {
396 String line = br.readLine();
397 if (line != null && line.startsWith(RefDirectory.SYMREF)) {
398 String target = line.substring(RefDirectory.SYMREF.length());
399 Ref r = refs.get(target);
400 if (r == null)
401 r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
402 r = new SymbolicRef(HEAD, r);
403 refs.put(r.getName(), r);
404 } else if (line != null && ObjectId.isId(line)) {
405 Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
406 HEAD, ObjectId.fromString(line));
407 refs.put(r.getName(), r);
408 }
409 }
410 break;
411 }
412
413 case HttpConnection.HTTP_NOT_FOUND:
414 break;
415
416 default:
417 throw new TransportException(uri, MessageFormat.format(
418 JGitText.get().cannotReadHEAD, Integer.valueOf(status),
419 conn.getResponseMessage()));
420 }
421 }
422
423 WalkFetchConnection wfc = new WalkFetchConnection(this, d);
424 wfc.available(refs);
425 return wfc;
426 }
427
428 private BufferedReader toBufferedReader(InputStream in) {
429 return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
430 }
431
432
433 @Override
434 public PushConnection openPush() throws NotSupportedException,
435 TransportException {
436 final String service = SVC_RECEIVE_PACK;
437 try {
438 final HttpConnection c = connect(service);
439 try (InputStream in = openInputStream(c)) {
440 if (isSmartHttp(c, service)) {
441 return smartPush(service, c, in);
442 } else if (!useSmartHttp) {
443 final String msg = JGitText.get().smartHTTPPushDisabled;
444 throw new NotSupportedException(msg);
445
446 } else {
447 final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
448 throw new NotSupportedException(msg);
449 }
450 }
451 } catch (NotSupportedException err) {
452 throw err;
453 } catch (TransportException err) {
454 throw err;
455 } catch (IOException err) {
456 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
457 }
458 }
459
460 private PushConnection smartPush(String service, HttpConnection c,
461 InputStream in) throws IOException, TransportException {
462 readSmartHeaders(in, service);
463 SmartHttpPushConnection p = new SmartHttpPushConnection(in);
464 p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
465 return p;
466 }
467
468
469 @Override
470 public void close() {
471
472 }
473
474
475
476
477
478
479
480
481
482 public void setAdditionalHeaders(Map<String, String> headers) {
483 this.headers = headers;
484 }
485
486 private HttpConnection connect(String service)
487 throws TransportException, NotSupportedException {
488 URL u = getServiceURL(service);
489 int authAttempts = 1;
490 int redirects = 0;
491 Collection<Type> ignoreTypes = null;
492 for (;;) {
493 try {
494 final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
495 if (useSmartHttp) {
496 String exp = "application/x-" + service + "-advertisement";
497 conn.setRequestProperty(HDR_ACCEPT, exp + ", */*");
498 } else {
499 conn.setRequestProperty(HDR_ACCEPT, "*/*");
500 }
501 final int status = HttpSupport.response(conn);
502 switch (status) {
503 case HttpConnection.HTTP_OK:
504
505
506
507
508 if (authMethod.getType() == HttpAuthMethod.Type.NONE
509 && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
510 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
511 return conn;
512
513 case HttpConnection.HTTP_NOT_FOUND:
514 throw new NoRemoteRepositoryException(uri,
515 MessageFormat.format(JGitText.get().uriNotFound, u));
516
517 case HttpConnection.HTTP_UNAUTHORIZED:
518 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
519 if (authMethod.getType() == HttpAuthMethod.Type.NONE)
520 throw new TransportException(uri, MessageFormat.format(
521 JGitText.get().authenticationNotSupported, uri));
522 CredentialsProvider credentialsProvider = getCredentialsProvider();
523 if (credentialsProvider == null)
524 throw new TransportException(uri,
525 JGitText.get().noCredentialsProvider);
526 if (authAttempts > 1)
527 credentialsProvider.reset(currentUri);
528 if (3 < authAttempts
529 || !authMethod.authorize(currentUri,
530 credentialsProvider)) {
531 throw new TransportException(uri,
532 JGitText.get().notAuthorized);
533 }
534 authAttempts++;
535 continue;
536
537 case HttpConnection.HTTP_FORBIDDEN:
538 throw new TransportException(uri, MessageFormat.format(
539 JGitText.get().serviceNotPermitted, baseUrl,
540 service));
541
542 case HttpConnection.HTTP_MOVED_PERM:
543 case HttpConnection.HTTP_MOVED_TEMP:
544 case HttpConnection.HTTP_SEE_OTHER:
545 case HttpConnection.HTTP_11_MOVED_TEMP:
546
547
548
549 if (http.getFollowRedirects() == HttpRedirectMode.FALSE) {
550 throw new TransportException(uri,
551 MessageFormat.format(
552 JGitText.get().redirectsOff,
553 Integer.valueOf(status)));
554 }
555 URIish newUri = redirect(conn.getHeaderField(HDR_LOCATION),
556 Constants.INFO_REFS, redirects++);
557 setURI(newUri);
558 u = getServiceURL(service);
559 authAttempts = 1;
560 break;
561 default:
562 String err = status + " " + conn.getResponseMessage();
563 throw new TransportException(uri, err);
564 }
565 } catch (NotSupportedException e) {
566 throw e;
567 } catch (TransportException e) {
568 throw e;
569 } catch (SSLHandshakeException e) {
570 handleSslFailure(e);
571 continue;
572 } catch (IOException e) {
573 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
574 if (ignoreTypes == null) {
575 ignoreTypes = new HashSet<>();
576 }
577
578 ignoreTypes.add(authMethod.getType());
579
580
581 authMethod = HttpAuthMethod.Type.NONE.method(null);
582 authAttempts = 1;
583
584 continue;
585 }
586
587 throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
588 }
589 }
590 }
591
592 private static class CredentialItems {
593 CredentialItem.InformationalMessage message;
594
595
596 CredentialItem.YesNoType now;
597
598
599
600
601
602
603 CredentialItem.YesNoType forRepo;
604
605
606 CredentialItem.YesNoType always;
607
608 public CredentialItem[] items() {
609 if (forRepo == null) {
610 return new CredentialItem[] { message, now, always };
611 } else {
612 return new CredentialItem[] { message, now, forRepo, always };
613 }
614 }
615 }
616
617 private void handleSslFailure(Throwable e) throws TransportException {
618 if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
619 throw new TransportException(uri,
620 MessageFormat.format(
621 JGitText.get().sslFailureExceptionMessage,
622 currentUri.setPass(null)),
623 e);
624 }
625 sslFailure = true;
626 }
627
628 private boolean trustInsecureSslConnection(Throwable cause) {
629 if (cause instanceof CertificateException
630 || cause instanceof CertPathBuilderException
631 || cause instanceof CertPathValidatorException) {
632
633
634 CredentialsProvider provider = getCredentialsProvider();
635 if (provider != null) {
636 CredentialItems trust = constructSslTrustItems(cause);
637 CredentialItem[] items = trust.items();
638 if (provider.supports(items)) {
639 boolean answered = provider.get(uri, items);
640 if (answered) {
641
642 boolean trustNow = trust.now.getValue();
643 boolean trustLocal = trust.forRepo != null
644 && trust.forRepo.getValue();
645 boolean trustAlways = trust.always.getValue();
646 if (trustNow || trustLocal || trustAlways) {
647 sslVerify = false;
648 if (trustAlways) {
649 updateSslVerifyUser(false);
650 } else if (trustLocal) {
651 updateSslVerify(local.getConfig(), false);
652 }
653 return true;
654 }
655 }
656 }
657 }
658 }
659 return false;
660 }
661
662 private CredentialItems constructSslTrustItems(Throwable cause) {
663 CredentialItems items = new CredentialItems();
664 String info = MessageFormat.format(JGitText.get().sslFailureInfo,
665 currentUri.setPass(null));
666 String sslMessage = cause.getLocalizedMessage();
667 if (sslMessage == null) {
668 sslMessage = cause.toString();
669 }
670 sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
671 sslMessage);
672 items.message = new CredentialItem.InformationalMessage(info + '\n'
673 + sslMessage + '\n'
674 + JGitText.get().sslFailureTrustExplanation);
675 items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
676 if (local != null) {
677 items.forRepo = new CredentialItem.YesNoType(
678 MessageFormat.format(JGitText.get().sslTrustForRepo,
679 local.getDirectory()));
680 }
681 items.always = new CredentialItem.YesNoType(
682 JGitText.get().sslTrustAlways);
683 return items;
684 }
685
686 private void updateSslVerify(StoredConfig config, boolean value) {
687
688
689
690 String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
691 int port = uri.getPort();
692 if (port > 0) {
693 uriPattern += ":" + port;
694 }
695 config.setBoolean(HttpConfig.HTTP, uriPattern,
696 HttpConfig.SSL_VERIFY_KEY, value);
697 try {
698 config.save();
699 } catch (IOException e) {
700 LOG.error(JGitText.get().sslVerifyCannotSave, e);
701 }
702 }
703
704 private void updateSslVerifyUser(boolean value) {
705 FileBasedConfig userConfig = SystemReader.getInstance()
706 .openUserConfig(null, FS.DETECTED);
707 try {
708 userConfig.load();
709 updateSslVerify(userConfig, value);
710 } catch (IOException | ConfigInvalidException e) {
711
712 LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
713 userConfig.getFile().getAbsolutePath(), e));
714 }
715 }
716
717 private URIish redirect(String location, String checkFor, int redirects)
718 throws TransportException {
719 if (location == null || location.isEmpty()) {
720 throw new TransportException(uri,
721 MessageFormat.format(JGitText.get().redirectLocationMissing,
722 baseUrl));
723 }
724 if (redirects >= http.getMaxRedirects()) {
725 throw new TransportException(uri,
726 MessageFormat.format(JGitText.get().redirectLimitExceeded,
727 Integer.valueOf(http.getMaxRedirects()), baseUrl,
728 location));
729 }
730 try {
731 if (!isValidRedirect(baseUrl, location, checkFor)) {
732 throw new TransportException(uri,
733 MessageFormat.format(JGitText.get().redirectBlocked,
734 baseUrl, location));
735 }
736 location = location.substring(0, location.indexOf(checkFor));
737 URIish result = new URIish(location);
738 if (LOG.isInfoEnabled()) {
739 LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
740 uri.setPass(null),
741 Integer.valueOf(redirects), baseUrl, result));
742 }
743 return result;
744 } catch (URISyntaxException e) {
745 throw new TransportException(uri,
746 MessageFormat.format(JGitText.get().invalidRedirectLocation,
747 baseUrl, location),
748 e);
749 }
750 }
751
752 private boolean isValidRedirect(URL current, String next, String checkFor) {
753
754
755 String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT);
756 int schemeEnd = next.indexOf("://"); //$NON-NLS-1$
757 if (schemeEnd < 0) {
758 return false;
759 }
760 String newProtocol = next.substring(0, schemeEnd)
761 .toLowerCase(Locale.ROOT);
762 if (!oldProtocol.equals(newProtocol)) {
763 if (!"https".equals(newProtocol)) {
764 return false;
765 }
766 }
767
768
769 if (next.indexOf(checkFor) < 0) {
770 return false;
771 }
772
773
774
775 return true;
776 }
777
778 private URL getServiceURL(String service)
779 throws NotSupportedException {
780 try {
781 final StringBuilder b = new StringBuilder();
782 b.append(baseUrl);
783
784 if (b.charAt(b.length() - 1) != '/') {
785 b.append('/');
786 }
787 b.append(Constants.INFO_REFS);
788
789 if (useSmartHttp) {
790 b.append(b.indexOf("?") < 0 ? '?' : '&');
791 b.append("service=");
792 b.append(service);
793 }
794
795 return new URL(b.toString());
796 } catch (MalformedURLException e) {
797 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
798 }
799 }
800
801
802
803
804
805
806
807
808
809
810
811 protected HttpConnection httpOpen(String method, URL u,
812 AcceptEncoding acceptEncoding) throws IOException {
813 if (method == null || u == null || acceptEncoding == null) {
814 throw new NullPointerException();
815 }
816
817 final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
818 HttpConnection conn = connectionFactory.create(u, proxy);
819
820 if (!sslVerify && "https".equals(u.getProtocol())) {
821 HttpSupport.disableSslVerify(conn);
822 }
823
824
825
826 conn.setInstanceFollowRedirects(false);
827
828 conn.setRequestMethod(method);
829 conn.setUseCaches(false);
830 if (acceptEncoding == AcceptEncoding.GZIP) {
831 conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
832 }
833 conn.setRequestProperty(HDR_PRAGMA, "no-cache");
834 if (UserAgent.get() != null) {
835 conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
836 }
837 int timeOut = getTimeout();
838 if (timeOut != -1) {
839 int effTimeOut = timeOut * 1000;
840 conn.setConnectTimeout(effTimeOut);
841 conn.setReadTimeout(effTimeOut);
842 }
843 if (this.headers != null && !this.headers.isEmpty()) {
844 for (Map.Entry<String, String> entry : this.headers.entrySet())
845 conn.setRequestProperty(entry.getKey(), entry.getValue());
846 }
847 authMethod.configureRequest(conn);
848 return conn;
849 }
850
851 final InputStream openInputStream(HttpConnection conn)
852 throws IOException {
853 InputStream input = conn.getInputStream();
854 if (isGzipContent(conn))
855 input = new GZIPInputStream(input);
856 return input;
857 }
858
859 IOException wrongContentType(String expType, String actType) {
860 final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
861 return new TransportException(uri, why);
862 }
863
864 private boolean isSmartHttp(HttpConnection c, String service) {
865 final String expType = "application/x-" + service + "-advertisement";
866 final String actType = c.getContentType();
867 return expType.equals(actType);
868 }
869
870 private boolean isGzipContent(HttpConnection c) {
871 return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
872 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
873 }
874
875 private void readSmartHeaders(InputStream in, String service)
876 throws IOException {
877
878
879
880
881
882 final byte[] magic = new byte[5];
883 IO.readFully(in, magic, 0, magic.length);
884 if (magic[4] != '#') {
885 throw new TransportException(uri, MessageFormat.format(
886 JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
887 }
888
889 final PacketLineIn pckIn = new PacketLineIn(new UnionInputStream(
890 new ByteArrayInputStream(magic), in));
891 final String exp = "# service=" + service;
892 final String act = pckIn.readString();
893 if (!exp.equals(act)) {
894 throw new TransportException(uri, MessageFormat.format(
895 JGitText.get().expectedGot, exp, act));
896 }
897
898 while (pckIn.readString() != PacketLineIn.END) {
899
900 }
901 }
902
903 class HttpObjectDB extends WalkRemoteObjectDatabase {
904 private final URL httpObjectsUrl;
905
906 HttpObjectDB(URL b) {
907 httpObjectsUrl = b;
908 }
909
910 @Override
911 URIish getURI() {
912 return new URIish(httpObjectsUrl);
913 }
914
915 @Override
916 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
917 try {
918 return readAlternates(INFO_HTTP_ALTERNATES);
919 } catch (FileNotFoundException err) {
920
921 }
922
923 try {
924 return readAlternates(INFO_ALTERNATES);
925 } catch (FileNotFoundException err) {
926
927 }
928
929 return null;
930 }
931
932 @Override
933 WalkRemoteObjectDatabase openAlternate(String location)
934 throws IOException {
935 return new HttpObjectDB(new URL(httpObjectsUrl, location));
936 }
937
938 @Override
939 BufferedReader openReader(String path) throws IOException {
940
941
942 InputStream is = open(path, AcceptEncoding.GZIP).in;
943 return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
944 }
945
946 @Override
947 Collection<String> getPackNames() throws IOException {
948 final Collection<String> packs = new ArrayList<>();
949 try (BufferedReader br = openReader(INFO_PACKS)) {
950 for (;;) {
951 final String s = br.readLine();
952 if (s == null || s.length() == 0)
953 break;
954 if (!s.startsWith("P pack-") || !s.endsWith(".pack"))
955 throw invalidAdvertisement(s);
956 packs.add(s.substring(2));
957 }
958 return packs;
959 } catch (FileNotFoundException err) {
960 return packs;
961 }
962 }
963
964 @Override
965 FileStream open(String path) throws IOException {
966 return open(path, AcceptEncoding.UNSPECIFIED);
967 }
968
969 FileStream open(String path, AcceptEncoding acceptEncoding)
970 throws IOException {
971 final URL base = httpObjectsUrl;
972 final URL u = new URL(base, path);
973 final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
974 switch (HttpSupport.response(c)) {
975 case HttpConnection.HTTP_OK:
976 final InputStream in = openInputStream(c);
977
978
979
980 if (!isGzipContent(c)) {
981 final int len = c.getContentLength();
982 return new FileStream(in, len);
983 }
984 return new FileStream(in);
985 case HttpConnection.HTTP_NOT_FOUND:
986 throw new FileNotFoundException(u.toString());
987 default:
988 throw new IOException(u.toString() + ": "
989 + HttpSupport.response(c) + " "
990 + c.getResponseMessage());
991 }
992 }
993
994 Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
995 throws IOException, PackProtocolException {
996 final TreeMap<String, Ref> avail = new TreeMap<>();
997 for (;;) {
998 String line = br.readLine();
999 if (line == null)
1000 break;
1001
1002 final int tab = line.indexOf('\t');
1003 if (tab < 0)
1004 throw invalidAdvertisement(line);
1005
1006 String name;
1007 final ObjectId id;
1008
1009 name = line.substring(tab + 1);
1010 id = ObjectId.fromString(line.substring(0, tab));
1011 if (name.endsWith("^{}")) {
1012 name = name.substring(0, name.length() - 3);
1013 final Ref prior = avail.get(name);
1014 if (prior == null)
1015 throw outOfOrderAdvertisement(name);
1016
1017 if (prior.getPeeledObjectId() != null)
1018 throw duplicateAdvertisement(name + "^{}");
1019
1020 avail.put(name, new ObjectIdRef.PeeledTag(
1021 Ref.Storage.NETWORK, name,
1022 prior.getObjectId(), id));
1023 } else {
1024 Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
1025 Ref.Storage.NETWORK, name, id));
1026 if (prior != null)
1027 throw duplicateAdvertisement(name);
1028 }
1029 }
1030 return avail;
1031 }
1032
1033 private PackProtocolException outOfOrderAdvertisement(String n) {
1034 return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
1035 }
1036
1037 private PackProtocolException invalidAdvertisement(String n) {
1038 return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
1039 }
1040
1041 private PackProtocolException duplicateAdvertisement(String n) {
1042 return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
1043 }
1044
1045 @Override
1046 void close() {
1047
1048 }
1049 }
1050
1051 class SmartHttpFetchConnection extends BasePackFetchConnection {
1052 private MultiRequestService svc;
1053
1054 SmartHttpFetchConnection(InputStream advertisement)
1055 throws TransportException {
1056 super(TransportHttp.this);
1057 statelessRPC = true;
1058
1059 init(advertisement, DisabledOutputStream.INSTANCE);
1060 outNeedsEnd = false;
1061 readAdvertisedRefs();
1062 }
1063
1064 @Override
1065 protected void doFetch(final ProgressMonitor monitor,
1066 final Collection<Ref> want, final Set<ObjectId> have,
1067 final OutputStream outputStream) throws TransportException {
1068 try {
1069 svc = new MultiRequestService(SVC_UPLOAD_PACK);
1070 init(svc.getInputStream(), svc.getOutputStream());
1071 super.doFetch(monitor, want, have, outputStream);
1072 } finally {
1073 svc = null;
1074 }
1075 }
1076
1077 @Override
1078 protected void onReceivePack() {
1079 svc.finalRequest = true;
1080 }
1081 }
1082
1083 class SmartHttpPushConnection extends BasePackPushConnection {
1084 SmartHttpPushConnection(InputStream advertisement)
1085 throws TransportException {
1086 super(TransportHttp.this);
1087 statelessRPC = true;
1088
1089 init(advertisement, DisabledOutputStream.INSTANCE);
1090 outNeedsEnd = false;
1091 readAdvertisedRefs();
1092 }
1093
1094 @Override
1095 protected void doPush(final ProgressMonitor monitor,
1096 final Map<String, RemoteRefUpdate> refUpdates,
1097 OutputStream outputStream) throws TransportException {
1098 final Service svc = new MultiRequestService(SVC_RECEIVE_PACK);
1099 init(svc.getInputStream(), svc.getOutputStream());
1100 super.doPush(monitor, refUpdates, outputStream);
1101 }
1102 }
1103
1104
1105 abstract class Service {
1106 protected final String serviceName;
1107
1108 protected final String requestType;
1109
1110 protected final String responseType;
1111
1112 protected HttpConnection conn;
1113
1114 protected HttpOutputStream out;
1115
1116 protected final HttpExecuteStream execute;
1117
1118 final UnionInputStream in;
1119
1120 Service(String serviceName) {
1121 this.serviceName = serviceName;
1122 this.requestType = "application/x-" + serviceName + "-request";
1123 this.responseType = "application/x-" + serviceName + "-result";
1124
1125 this.out = new HttpOutputStream();
1126 this.execute = new HttpExecuteStream();
1127 this.in = new UnionInputStream(execute);
1128 }
1129
1130 void openStream() throws IOException {
1131 conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
1132 AcceptEncoding.GZIP);
1133 conn.setInstanceFollowRedirects(false);
1134 conn.setDoOutput(true);
1135 conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
1136 conn.setRequestProperty(HDR_ACCEPT, responseType);
1137 }
1138
1139 void sendRequest() throws IOException {
1140
1141 TemporaryBuffer buf = new TemporaryBuffer.Heap(
1142 http.getPostBuffer());
1143 try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) {
1144 out.writeTo(gzip, null);
1145 if (out.length() < buf.length())
1146 buf = out;
1147 } catch (IOException err) {
1148
1149
1150 buf = out;
1151 }
1152
1153 HttpAuthMethod authenticator = null;
1154 Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
1155
1156
1157 int authAttempts = 1;
1158 int redirects = 0;
1159 for (;;) {
1160 try {
1161
1162
1163
1164
1165
1166 openStream();
1167 if (buf != out) {
1168 conn.setRequestProperty(HDR_CONTENT_ENCODING,
1169 ENCODING_GZIP);
1170 }
1171 conn.setFixedLengthStreamingMode((int) buf.length());
1172 try (OutputStream httpOut = conn.getOutputStream()) {
1173 buf.writeTo(httpOut, null);
1174 }
1175
1176 final int status = HttpSupport.response(conn);
1177 switch (status) {
1178 case HttpConnection.HTTP_OK:
1179
1180 return;
1181
1182 case HttpConnection.HTTP_NOT_FOUND:
1183 throw new NoRemoteRepositoryException(uri,
1184 MessageFormat.format(JGitText.get().uriNotFound,
1185 conn.getURL()));
1186
1187 case HttpConnection.HTTP_FORBIDDEN:
1188 throw new TransportException(uri,
1189 MessageFormat.format(
1190 JGitText.get().serviceNotPermitted,
1191 baseUrl, serviceName));
1192
1193 case HttpConnection.HTTP_MOVED_PERM:
1194 case HttpConnection.HTTP_MOVED_TEMP:
1195 case HttpConnection.HTTP_11_MOVED_TEMP:
1196
1197
1198
1199 if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
1200
1201 return;
1202 }
1203 currentUri = redirect(conn.getHeaderField(HDR_LOCATION),
1204 '/' + serviceName, redirects++);
1205 try {
1206 baseUrl = toURL(currentUri);
1207 } catch (MalformedURLException e) {
1208 throw new TransportException(uri,
1209 MessageFormat.format(
1210 JGitText.get().invalidRedirectLocation,
1211 baseUrl, currentUri),
1212 e);
1213 }
1214 continue;
1215
1216 case HttpConnection.HTTP_UNAUTHORIZED:
1217 HttpAuthMethod nextMethod = HttpAuthMethod
1218 .scanResponse(conn, ignoreTypes);
1219 switch (nextMethod.getType()) {
1220 case NONE:
1221 throw new TransportException(uri,
1222 MessageFormat.format(
1223 JGitText.get().authenticationNotSupported,
1224 conn.getURL()));
1225 case NEGOTIATE:
1226
1227
1228
1229
1230
1231
1232
1233 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1234 if (authenticator != null) {
1235 ignoreTypes.add(authenticator.getType());
1236 }
1237 authAttempts = 1;
1238
1239
1240 break;
1241 default:
1242
1243
1244
1245 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1246 if (authenticator == null || authenticator
1247 .getType() != nextMethod.getType()) {
1248 if (authenticator != null) {
1249 ignoreTypes.add(authenticator.getType());
1250 }
1251 authAttempts = 1;
1252 }
1253 break;
1254 }
1255 authMethod = nextMethod;
1256 authenticator = nextMethod;
1257 CredentialsProvider credentialsProvider = getCredentialsProvider();
1258 if (credentialsProvider == null) {
1259 throw new TransportException(uri,
1260 JGitText.get().noCredentialsProvider);
1261 }
1262 if (authAttempts > 1) {
1263 credentialsProvider.reset(currentUri);
1264 }
1265 if (3 < authAttempts || !authMethod
1266 .authorize(currentUri, credentialsProvider)) {
1267 throw new TransportException(uri,
1268 JGitText.get().notAuthorized);
1269 }
1270 authAttempts++;
1271 continue;
1272
1273 default:
1274
1275
1276 return;
1277 }
1278 } catch (SSLHandshakeException e) {
1279 handleSslFailure(e);
1280 continue;
1281 } catch (IOException e) {
1282 if (authenticator == null || authMethod
1283 .getType() != HttpAuthMethod.Type.NONE) {
1284
1285
1286
1287
1288
1289
1290
1291
1292 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
1293 ignoreTypes.add(authMethod.getType());
1294 }
1295
1296 authMethod = HttpAuthMethod.Type.NONE.method(null);
1297 authenticator = authMethod;
1298 authAttempts = 1;
1299 continue;
1300 }
1301 throw e;
1302 }
1303 }
1304 }
1305
1306 void openResponse() throws IOException {
1307 final int status = HttpSupport.response(conn);
1308 if (status != HttpConnection.HTTP_OK) {
1309 throw new TransportException(uri, status + " "
1310 + conn.getResponseMessage());
1311 }
1312
1313 final String contentType = conn.getContentType();
1314 if (!responseType.equals(contentType)) {
1315 conn.getInputStream().close();
1316 throw wrongContentType(responseType, contentType);
1317 }
1318 }
1319
1320 HttpOutputStream getOutputStream() {
1321 return out;
1322 }
1323
1324 InputStream getInputStream() {
1325 return in;
1326 }
1327
1328 abstract void execute() throws IOException;
1329
1330 class HttpExecuteStream extends InputStream {
1331 @Override
1332 public int read() throws IOException {
1333 execute();
1334 return -1;
1335 }
1336
1337 @Override
1338 public int read(byte[] b, int off, int len) throws IOException {
1339 execute();
1340 return -1;
1341 }
1342
1343 @Override
1344 public long skip(long n) throws IOException {
1345 execute();
1346 return 0;
1347 }
1348 }
1349
1350 class HttpOutputStream extends TemporaryBuffer {
1351 HttpOutputStream() {
1352 super(http.getPostBuffer());
1353 }
1354
1355 @Override
1356 protected OutputStream overflow() throws IOException {
1357 openStream();
1358 conn.setChunkedStreamingMode(0);
1359 return conn.getOutputStream();
1360 }
1361 }
1362 }
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384 class MultiRequestService extends Service {
1385 boolean finalRequest;
1386
1387 MultiRequestService(String serviceName) {
1388 super(serviceName);
1389 }
1390
1391
1392 @Override
1393 void execute() throws IOException {
1394 out.close();
1395
1396 if (conn == null) {
1397 if (out.length() == 0) {
1398
1399
1400
1401
1402
1403 if (finalRequest)
1404 return;
1405 throw new TransportException(uri,
1406 JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
1407 }
1408
1409 sendRequest();
1410 }
1411
1412 out.reset();
1413
1414 openResponse();
1415
1416 in.add(openInputStream(conn));
1417 if (!finalRequest)
1418 in.add(execute);
1419 conn = null;
1420 }
1421 }
1422
1423
1424 class LongPollService extends Service {
1425
1426
1427
1428 LongPollService(String serviceName) {
1429 super(serviceName);
1430 }
1431
1432
1433 @Override
1434 void execute() throws IOException {
1435 out.close();
1436 if (conn == null)
1437 sendRequest();
1438 openResponse();
1439 in.add(openInputStream(conn));
1440 }
1441 }
1442 }