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(final Repository local, final 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(final 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(final 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(final boolean on) {
336 useSmartHttp = on;
337 }
338
339
340 @Override
341 public FetchConnection openFetch() throws TransportException,
342 NotSupportedException {
343 final String service = SVC_UPLOAD_PACK;
344 try {
345 final HttpConnection c = connect(service);
346 final InputStream in = openInputStream(c);
347 try {
348 BaseConnection f;
349 if (isSmartHttp(c, service)) {
350 readSmartHeaders(in, service);
351 f = new SmartHttpFetchConnection(in);
352 } else {
353
354
355 f = newDumbConnection(in);
356 }
357 f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
358 return (FetchConnection) f;
359 } finally {
360 in.close();
361 }
362 } catch (NotSupportedException err) {
363 throw err;
364 } catch (TransportException err) {
365 throw err;
366 } catch (IOException err) {
367 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
368 }
369 }
370
371 private WalkFetchConnection newDumbConnection(InputStream in)
372 throws IOException, PackProtocolException {
373 HttpObjectDB d = new HttpObjectDB(objectsUrl);
374 BufferedReader br = toBufferedReader(in);
375 Map<String, Ref> refs;
376 try {
377 refs = d.readAdvertisedImpl(br);
378 } finally {
379 br.close();
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 br = toBufferedReader(openInputStream(conn));
395 try {
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 } finally {
410 br.close();
411 }
412 break;
413 }
414
415 case HttpConnection.HTTP_NOT_FOUND:
416 break;
417
418 default:
419 throw new TransportException(uri, MessageFormat.format(
420 JGitText.get().cannotReadHEAD, Integer.valueOf(status),
421 conn.getResponseMessage()));
422 }
423 }
424
425 WalkFetchConnection wfc = new WalkFetchConnection(this, d);
426 wfc.available(refs);
427 return wfc;
428 }
429
430 private BufferedReader toBufferedReader(InputStream in) {
431 return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
432 }
433
434
435 @Override
436 public PushConnection openPush() throws NotSupportedException,
437 TransportException {
438 final String service = SVC_RECEIVE_PACK;
439 try {
440 final HttpConnection c = connect(service);
441 final InputStream in = openInputStream(c);
442 try {
443 if (isSmartHttp(c, service)) {
444 return smartPush(service, c, in);
445 } else if (!useSmartHttp) {
446 final String msg = JGitText.get().smartHTTPPushDisabled;
447 throw new NotSupportedException(msg);
448
449 } else {
450 final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
451 throw new NotSupportedException(msg);
452 }
453 } finally {
454 in.close();
455 }
456 } catch (NotSupportedException err) {
457 throw err;
458 } catch (TransportException err) {
459 throw err;
460 } catch (IOException err) {
461 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
462 }
463 }
464
465 private PushConnection smartPush(String service, HttpConnection c,
466 InputStream in) throws IOException, TransportException {
467 readSmartHeaders(in, service);
468 SmartHttpPushConnection p = new SmartHttpPushConnection(in);
469 p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
470 return p;
471 }
472
473
474 @Override
475 public void close() {
476
477 }
478
479
480
481
482
483
484
485
486
487 public void setAdditionalHeaders(Map<String, String> headers) {
488 this.headers = headers;
489 }
490
491 private HttpConnection connect(final String service)
492 throws TransportException, NotSupportedException {
493 URL u = getServiceURL(service);
494 int authAttempts = 1;
495 int redirects = 0;
496 Collection<Type> ignoreTypes = null;
497 for (;;) {
498 try {
499 final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
500 if (useSmartHttp) {
501 String exp = "application/x-" + service + "-advertisement";
502 conn.setRequestProperty(HDR_ACCEPT, exp + ", */*");
503 } else {
504 conn.setRequestProperty(HDR_ACCEPT, "*/*");
505 }
506 final int status = HttpSupport.response(conn);
507 switch (status) {
508 case HttpConnection.HTTP_OK:
509
510
511
512
513 if (authMethod.getType() == HttpAuthMethod.Type.NONE
514 && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
515 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
516 return conn;
517
518 case HttpConnection.HTTP_NOT_FOUND:
519 throw new NoRemoteRepositoryException(uri,
520 MessageFormat.format(JGitText.get().uriNotFound, u));
521
522 case HttpConnection.HTTP_UNAUTHORIZED:
523 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
524 if (authMethod.getType() == HttpAuthMethod.Type.NONE)
525 throw new TransportException(uri, MessageFormat.format(
526 JGitText.get().authenticationNotSupported, uri));
527 CredentialsProvider credentialsProvider = getCredentialsProvider();
528 if (credentialsProvider == null)
529 throw new TransportException(uri,
530 JGitText.get().noCredentialsProvider);
531 if (authAttempts > 1)
532 credentialsProvider.reset(currentUri);
533 if (3 < authAttempts
534 || !authMethod.authorize(currentUri,
535 credentialsProvider)) {
536 throw new TransportException(uri,
537 JGitText.get().notAuthorized);
538 }
539 authAttempts++;
540 continue;
541
542 case HttpConnection.HTTP_FORBIDDEN:
543 throw new TransportException(uri, MessageFormat.format(
544 JGitText.get().serviceNotPermitted, baseUrl,
545 service));
546
547 case HttpConnection.HTTP_MOVED_PERM:
548 case HttpConnection.HTTP_MOVED_TEMP:
549 case HttpConnection.HTTP_SEE_OTHER:
550 case HttpConnection.HTTP_11_MOVED_TEMP:
551
552
553
554 if (http.getFollowRedirects() == HttpRedirectMode.FALSE) {
555 throw new TransportException(uri,
556 MessageFormat.format(
557 JGitText.get().redirectsOff,
558 Integer.valueOf(status)));
559 }
560 URIish newUri = redirect(conn.getHeaderField(HDR_LOCATION),
561 Constants.INFO_REFS, redirects++);
562 setURI(newUri);
563 u = getServiceURL(service);
564 authAttempts = 1;
565 break;
566 default:
567 String err = status + " " + conn.getResponseMessage();
568 throw new TransportException(uri, err);
569 }
570 } catch (NotSupportedException e) {
571 throw e;
572 } catch (TransportException e) {
573 throw e;
574 } catch (SSLHandshakeException e) {
575 handleSslFailure(e);
576 continue;
577 } catch (IOException e) {
578 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
579 if (ignoreTypes == null) {
580 ignoreTypes = new HashSet<>();
581 }
582
583 ignoreTypes.add(authMethod.getType());
584
585
586 authMethod = HttpAuthMethod.Type.NONE.method(null);
587 authAttempts = 1;
588
589 continue;
590 }
591
592 throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
593 }
594 }
595 }
596
597 private static class CredentialItems {
598 CredentialItem.InformationalMessage message;
599
600
601 CredentialItem.YesNoType now;
602
603
604
605
606
607
608 CredentialItem.YesNoType forRepo;
609
610
611 CredentialItem.YesNoType always;
612
613 public CredentialItem[] items() {
614 if (forRepo == null) {
615 return new CredentialItem[] { message, now, always };
616 } else {
617 return new CredentialItem[] { message, now, forRepo, always };
618 }
619 }
620 }
621
622 private void handleSslFailure(Throwable e) throws TransportException {
623 if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
624 throw new TransportException(uri,
625 MessageFormat.format(
626 JGitText.get().sslFailureExceptionMessage,
627 currentUri.setPass(null)),
628 e);
629 }
630 sslFailure = true;
631 }
632
633 private boolean trustInsecureSslConnection(Throwable cause) {
634 if (cause instanceof CertificateException
635 || cause instanceof CertPathBuilderException
636 || cause instanceof CertPathValidatorException) {
637
638
639 CredentialsProvider provider = getCredentialsProvider();
640 if (provider != null) {
641 CredentialItems trust = constructSslTrustItems(cause);
642 CredentialItem[] items = trust.items();
643 if (provider.supports(items)) {
644 boolean answered = provider.get(uri, items);
645 if (answered) {
646
647 boolean trustNow = trust.now.getValue();
648 boolean trustLocal = trust.forRepo != null
649 && trust.forRepo.getValue();
650 boolean trustAlways = trust.always.getValue();
651 if (trustNow || trustLocal || trustAlways) {
652 sslVerify = false;
653 if (trustAlways) {
654 updateSslVerifyUser(false);
655 } else if (trustLocal) {
656 updateSslVerify(local.getConfig(), false);
657 }
658 return true;
659 }
660 }
661 }
662 }
663 }
664 return false;
665 }
666
667 private CredentialItems constructSslTrustItems(Throwable cause) {
668 CredentialItems items = new CredentialItems();
669 String info = MessageFormat.format(JGitText.get().sslFailureInfo,
670 currentUri.setPass(null));
671 String sslMessage = cause.getLocalizedMessage();
672 if (sslMessage == null) {
673 sslMessage = cause.toString();
674 }
675 sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
676 sslMessage);
677 items.message = new CredentialItem.InformationalMessage(info + '\n'
678 + sslMessage + '\n'
679 + JGitText.get().sslFailureTrustExplanation);
680 items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
681 if (local != null) {
682 items.forRepo = new CredentialItem.YesNoType(
683 MessageFormat.format(JGitText.get().sslTrustForRepo,
684 local.getDirectory()));
685 }
686 items.always = new CredentialItem.YesNoType(
687 JGitText.get().sslTrustAlways);
688 return items;
689 }
690
691 private void updateSslVerify(StoredConfig config, boolean value) {
692
693
694
695 String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
696 int port = uri.getPort();
697 if (port > 0) {
698 uriPattern += ":" + port;
699 }
700 config.setBoolean(HttpConfig.HTTP, uriPattern,
701 HttpConfig.SSL_VERIFY_KEY, value);
702 try {
703 config.save();
704 } catch (IOException e) {
705 LOG.error(JGitText.get().sslVerifyCannotSave, e);
706 }
707 }
708
709 private void updateSslVerifyUser(boolean value) {
710 FileBasedConfig userConfig = SystemReader.getInstance()
711 .openUserConfig(null, FS.DETECTED);
712 try {
713 userConfig.load();
714 updateSslVerify(userConfig, value);
715 } catch (IOException | ConfigInvalidException e) {
716
717 LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
718 userConfig.getFile().getAbsolutePath(), e));
719 }
720 }
721
722 private URIish redirect(String location, String checkFor, int redirects)
723 throws TransportException {
724 if (location == null || location.isEmpty()) {
725 throw new TransportException(uri,
726 MessageFormat.format(JGitText.get().redirectLocationMissing,
727 baseUrl));
728 }
729 if (redirects >= http.getMaxRedirects()) {
730 throw new TransportException(uri,
731 MessageFormat.format(JGitText.get().redirectLimitExceeded,
732 Integer.valueOf(http.getMaxRedirects()), baseUrl,
733 location));
734 }
735 try {
736 if (!isValidRedirect(baseUrl, location, checkFor)) {
737 throw new TransportException(uri,
738 MessageFormat.format(JGitText.get().redirectBlocked,
739 baseUrl, location));
740 }
741 location = location.substring(0, location.indexOf(checkFor));
742 URIish result = new URIish(location);
743 if (LOG.isInfoEnabled()) {
744 LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
745 uri.setPass(null),
746 Integer.valueOf(redirects), baseUrl, result));
747 }
748 return result;
749 } catch (URISyntaxException e) {
750 throw new TransportException(uri,
751 MessageFormat.format(JGitText.get().invalidRedirectLocation,
752 baseUrl, location),
753 e);
754 }
755 }
756
757 private boolean isValidRedirect(URL current, String next, String checkFor) {
758
759
760 String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT);
761 int schemeEnd = next.indexOf("://"); //$NON-NLS-1$
762 if (schemeEnd < 0) {
763 return false;
764 }
765 String newProtocol = next.substring(0, schemeEnd)
766 .toLowerCase(Locale.ROOT);
767 if (!oldProtocol.equals(newProtocol)) {
768 if (!"https".equals(newProtocol)) {
769 return false;
770 }
771 }
772
773
774 if (next.indexOf(checkFor) < 0) {
775 return false;
776 }
777
778
779
780 return true;
781 }
782
783 private URL getServiceURL(final String service)
784 throws NotSupportedException {
785 try {
786 final StringBuilder b = new StringBuilder();
787 b.append(baseUrl);
788
789 if (b.charAt(b.length() - 1) != '/') {
790 b.append('/');
791 }
792 b.append(Constants.INFO_REFS);
793
794 if (useSmartHttp) {
795 b.append(b.indexOf("?") < 0 ? '?' : '&');
796 b.append("service=");
797 b.append(service);
798 }
799
800 return new URL(b.toString());
801 } catch (MalformedURLException e) {
802 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
803 }
804 }
805
806
807
808
809
810
811
812
813
814
815
816 @Deprecated
817 protected HttpConnection httpOpen(String method, URL u) throws IOException {
818 return httpOpen(method, u, AcceptEncoding.GZIP);
819 }
820
821
822
823
824
825
826
827
828
829
830
831 protected HttpConnection httpOpen(String method, URL u,
832 AcceptEncoding acceptEncoding) throws IOException {
833 if (method == null || u == null || acceptEncoding == null) {
834 throw new NullPointerException();
835 }
836
837 final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
838 HttpConnection conn = connectionFactory.create(u, proxy);
839
840 if (!sslVerify && "https".equals(u.getProtocol())) {
841 HttpSupport.disableSslVerify(conn);
842 }
843
844
845
846 conn.setInstanceFollowRedirects(false);
847
848 conn.setRequestMethod(method);
849 conn.setUseCaches(false);
850 if (acceptEncoding == AcceptEncoding.GZIP) {
851 conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
852 }
853 conn.setRequestProperty(HDR_PRAGMA, "no-cache");
854 if (UserAgent.get() != null) {
855 conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
856 }
857 int timeOut = getTimeout();
858 if (timeOut != -1) {
859 int effTimeOut = timeOut * 1000;
860 conn.setConnectTimeout(effTimeOut);
861 conn.setReadTimeout(effTimeOut);
862 }
863 if (this.headers != null && !this.headers.isEmpty()) {
864 for (Map.Entry<String, String> entry : this.headers.entrySet())
865 conn.setRequestProperty(entry.getKey(), entry.getValue());
866 }
867 authMethod.configureRequest(conn);
868 return conn;
869 }
870
871 final InputStream openInputStream(HttpConnection conn)
872 throws IOException {
873 InputStream input = conn.getInputStream();
874 if (isGzipContent(conn))
875 input = new GZIPInputStream(input);
876 return input;
877 }
878
879 IOException wrongContentType(String expType, String actType) {
880 final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
881 return new TransportException(uri, why);
882 }
883
884 private boolean isSmartHttp(final HttpConnection c, final String service) {
885 final String expType = "application/x-" + service + "-advertisement";
886 final String actType = c.getContentType();
887 return expType.equals(actType);
888 }
889
890 private boolean isGzipContent(final HttpConnection c) {
891 return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
892 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
893 }
894
895 private void readSmartHeaders(final InputStream in, final String service)
896 throws IOException {
897
898
899
900
901
902 final byte[] magic = new byte[5];
903 IO.readFully(in, magic, 0, magic.length);
904 if (magic[4] != '#') {
905 throw new TransportException(uri, MessageFormat.format(
906 JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
907 }
908
909 final PacketLineIn pckIn = new PacketLineIn(new UnionInputStream(
910 new ByteArrayInputStream(magic), in));
911 final String exp = "# service=" + service;
912 final String act = pckIn.readString();
913 if (!exp.equals(act)) {
914 throw new TransportException(uri, MessageFormat.format(
915 JGitText.get().expectedGot, exp, act));
916 }
917
918 while (pckIn.readString() != PacketLineIn.END) {
919
920 }
921 }
922
923 class HttpObjectDB extends WalkRemoteObjectDatabase {
924 private final URL httpObjectsUrl;
925
926 HttpObjectDB(final URL b) {
927 httpObjectsUrl = b;
928 }
929
930 @Override
931 URIish getURI() {
932 return new URIish(httpObjectsUrl);
933 }
934
935 @Override
936 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
937 try {
938 return readAlternates(INFO_HTTP_ALTERNATES);
939 } catch (FileNotFoundException err) {
940
941 }
942
943 try {
944 return readAlternates(INFO_ALTERNATES);
945 } catch (FileNotFoundException err) {
946
947 }
948
949 return null;
950 }
951
952 @Override
953 WalkRemoteObjectDatabase openAlternate(final String location)
954 throws IOException {
955 return new HttpObjectDB(new URL(httpObjectsUrl, location));
956 }
957
958 @Override
959 BufferedReader openReader(String path) throws IOException {
960
961
962 InputStream is = open(path, AcceptEncoding.GZIP).in;
963 return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
964 }
965
966 @Override
967 Collection<String> getPackNames() throws IOException {
968 final Collection<String> packs = new ArrayList<>();
969 try {
970 final BufferedReader br = openReader(INFO_PACKS);
971 try {
972 for (;;) {
973 final String s = br.readLine();
974 if (s == null || s.length() == 0)
975 break;
976 if (!s.startsWith("P pack-") || !s.endsWith(".pack"))
977 throw invalidAdvertisement(s);
978 packs.add(s.substring(2));
979 }
980 return packs;
981 } finally {
982 br.close();
983 }
984 } catch (FileNotFoundException err) {
985 return packs;
986 }
987 }
988
989 @Override
990 FileStream open(final String path) throws IOException {
991 return open(path, AcceptEncoding.UNSPECIFIED);
992 }
993
994 FileStream open(String path, AcceptEncoding acceptEncoding)
995 throws IOException {
996 final URL base = httpObjectsUrl;
997 final URL u = new URL(base, path);
998 final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
999 switch (HttpSupport.response(c)) {
1000 case HttpConnection.HTTP_OK:
1001 final InputStream in = openInputStream(c);
1002
1003
1004
1005 if (!isGzipContent(c)) {
1006 final int len = c.getContentLength();
1007 return new FileStream(in, len);
1008 }
1009 return new FileStream(in);
1010 case HttpConnection.HTTP_NOT_FOUND:
1011 throw new FileNotFoundException(u.toString());
1012 default:
1013 throw new IOException(u.toString() + ": "
1014 + HttpSupport.response(c) + " "
1015 + c.getResponseMessage());
1016 }
1017 }
1018
1019 Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
1020 throws IOException, PackProtocolException {
1021 final TreeMap<String, Ref> avail = new TreeMap<>();
1022 for (;;) {
1023 String line = br.readLine();
1024 if (line == null)
1025 break;
1026
1027 final int tab = line.indexOf('\t');
1028 if (tab < 0)
1029 throw invalidAdvertisement(line);
1030
1031 String name;
1032 final ObjectId id;
1033
1034 name = line.substring(tab + 1);
1035 id = ObjectId.fromString(line.substring(0, tab));
1036 if (name.endsWith("^{}")) {
1037 name = name.substring(0, name.length() - 3);
1038 final Ref prior = avail.get(name);
1039 if (prior == null)
1040 throw outOfOrderAdvertisement(name);
1041
1042 if (prior.getPeeledObjectId() != null)
1043 throw duplicateAdvertisement(name + "^{}");
1044
1045 avail.put(name, new ObjectIdRef.PeeledTag(
1046 Ref.Storage.NETWORK, name,
1047 prior.getObjectId(), id));
1048 } else {
1049 Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
1050 Ref.Storage.NETWORK, name, id));
1051 if (prior != null)
1052 throw duplicateAdvertisement(name);
1053 }
1054 }
1055 return avail;
1056 }
1057
1058 private PackProtocolException outOfOrderAdvertisement(final String n) {
1059 return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
1060 }
1061
1062 private PackProtocolException invalidAdvertisement(final String n) {
1063 return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
1064 }
1065
1066 private PackProtocolException duplicateAdvertisement(final String n) {
1067 return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
1068 }
1069
1070 @Override
1071 void close() {
1072
1073 }
1074 }
1075
1076 class SmartHttpFetchConnection extends BasePackFetchConnection {
1077 private MultiRequestService svc;
1078
1079 SmartHttpFetchConnection(final InputStream advertisement)
1080 throws TransportException {
1081 super(TransportHttp.this);
1082 statelessRPC = true;
1083
1084 init(advertisement, DisabledOutputStream.INSTANCE);
1085 outNeedsEnd = false;
1086 readAdvertisedRefs();
1087 }
1088
1089 @Override
1090 protected void doFetch(final ProgressMonitor monitor,
1091 final Collection<Ref> want, final Set<ObjectId> have,
1092 final OutputStream outputStream) throws TransportException {
1093 try {
1094 svc = new MultiRequestService(SVC_UPLOAD_PACK);
1095 init(svc.getInputStream(), svc.getOutputStream());
1096 super.doFetch(monitor, want, have, outputStream);
1097 } finally {
1098 svc = null;
1099 }
1100 }
1101
1102 @Override
1103 protected void onReceivePack() {
1104 svc.finalRequest = true;
1105 }
1106 }
1107
1108 class SmartHttpPushConnection extends BasePackPushConnection {
1109 SmartHttpPushConnection(final InputStream advertisement)
1110 throws TransportException {
1111 super(TransportHttp.this);
1112 statelessRPC = true;
1113
1114 init(advertisement, DisabledOutputStream.INSTANCE);
1115 outNeedsEnd = false;
1116 readAdvertisedRefs();
1117 }
1118
1119 @Override
1120 protected void doPush(final ProgressMonitor monitor,
1121 final Map<String, RemoteRefUpdate> refUpdates,
1122 OutputStream outputStream) throws TransportException {
1123 final Service svc = new MultiRequestService(SVC_RECEIVE_PACK);
1124 init(svc.getInputStream(), svc.getOutputStream());
1125 super.doPush(monitor, refUpdates, outputStream);
1126 }
1127 }
1128
1129
1130 abstract class Service {
1131 protected final String serviceName;
1132
1133 protected final String requestType;
1134
1135 protected final String responseType;
1136
1137 protected HttpConnection conn;
1138
1139 protected HttpOutputStream out;
1140
1141 protected final HttpExecuteStream execute;
1142
1143 final UnionInputStream in;
1144
1145 Service(String serviceName) {
1146 this.serviceName = serviceName;
1147 this.requestType = "application/x-" + serviceName + "-request";
1148 this.responseType = "application/x-" + serviceName + "-result";
1149
1150 this.out = new HttpOutputStream();
1151 this.execute = new HttpExecuteStream();
1152 this.in = new UnionInputStream(execute);
1153 }
1154
1155 void openStream() throws IOException {
1156 conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
1157 AcceptEncoding.GZIP);
1158 conn.setInstanceFollowRedirects(false);
1159 conn.setDoOutput(true);
1160 conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
1161 conn.setRequestProperty(HDR_ACCEPT, responseType);
1162 }
1163
1164 void sendRequest() throws IOException {
1165
1166 TemporaryBuffer buf = new TemporaryBuffer.Heap(
1167 http.getPostBuffer());
1168 try {
1169 GZIPOutputStream gzip = new GZIPOutputStream(buf);
1170 out.writeTo(gzip, null);
1171 gzip.close();
1172 if (out.length() < buf.length())
1173 buf = out;
1174 } catch (IOException err) {
1175
1176
1177 buf = out;
1178 }
1179
1180 HttpAuthMethod authenticator = null;
1181 Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
1182
1183
1184 int authAttempts = 1;
1185 int redirects = 0;
1186 for (;;) {
1187 try {
1188
1189
1190
1191
1192
1193 openStream();
1194 if (buf != out) {
1195 conn.setRequestProperty(HDR_CONTENT_ENCODING,
1196 ENCODING_GZIP);
1197 }
1198 conn.setFixedLengthStreamingMode((int) buf.length());
1199 try (OutputStream httpOut = conn.getOutputStream()) {
1200 buf.writeTo(httpOut, null);
1201 }
1202
1203 final int status = HttpSupport.response(conn);
1204 switch (status) {
1205 case HttpConnection.HTTP_OK:
1206
1207 return;
1208
1209 case HttpConnection.HTTP_NOT_FOUND:
1210 throw new NoRemoteRepositoryException(uri,
1211 MessageFormat.format(JGitText.get().uriNotFound,
1212 conn.getURL()));
1213
1214 case HttpConnection.HTTP_FORBIDDEN:
1215 throw new TransportException(uri,
1216 MessageFormat.format(
1217 JGitText.get().serviceNotPermitted,
1218 baseUrl, serviceName));
1219
1220 case HttpConnection.HTTP_MOVED_PERM:
1221 case HttpConnection.HTTP_MOVED_TEMP:
1222 case HttpConnection.HTTP_11_MOVED_TEMP:
1223
1224
1225
1226 if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
1227
1228 return;
1229 }
1230 currentUri = redirect(conn.getHeaderField(HDR_LOCATION),
1231 '/' + serviceName, redirects++);
1232 try {
1233 baseUrl = toURL(currentUri);
1234 } catch (MalformedURLException e) {
1235 throw new TransportException(uri,
1236 MessageFormat.format(
1237 JGitText.get().invalidRedirectLocation,
1238 baseUrl, currentUri),
1239 e);
1240 }
1241 continue;
1242
1243 case HttpConnection.HTTP_UNAUTHORIZED:
1244 HttpAuthMethod nextMethod = HttpAuthMethod
1245 .scanResponse(conn, ignoreTypes);
1246 switch (nextMethod.getType()) {
1247 case NONE:
1248 throw new TransportException(uri,
1249 MessageFormat.format(
1250 JGitText.get().authenticationNotSupported,
1251 conn.getURL()));
1252 case NEGOTIATE:
1253
1254
1255
1256
1257
1258
1259
1260 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1261 if (authenticator != null) {
1262 ignoreTypes.add(authenticator.getType());
1263 }
1264 authAttempts = 1;
1265
1266
1267 break;
1268 default:
1269
1270
1271
1272 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1273 if (authenticator == null || authenticator
1274 .getType() != nextMethod.getType()) {
1275 if (authenticator != null) {
1276 ignoreTypes.add(authenticator.getType());
1277 }
1278 authAttempts = 1;
1279 }
1280 break;
1281 }
1282 authMethod = nextMethod;
1283 authenticator = nextMethod;
1284 CredentialsProvider credentialsProvider = getCredentialsProvider();
1285 if (credentialsProvider == null) {
1286 throw new TransportException(uri,
1287 JGitText.get().noCredentialsProvider);
1288 }
1289 if (authAttempts > 1) {
1290 credentialsProvider.reset(currentUri);
1291 }
1292 if (3 < authAttempts || !authMethod
1293 .authorize(currentUri, credentialsProvider)) {
1294 throw new TransportException(uri,
1295 JGitText.get().notAuthorized);
1296 }
1297 authAttempts++;
1298 continue;
1299
1300 default:
1301
1302
1303 return;
1304 }
1305 } catch (SSLHandshakeException e) {
1306 handleSslFailure(e);
1307 continue;
1308 } catch (IOException e) {
1309 if (authenticator == null || authMethod
1310 .getType() != HttpAuthMethod.Type.NONE) {
1311
1312
1313
1314
1315
1316
1317
1318
1319 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
1320 ignoreTypes.add(authMethod.getType());
1321 }
1322
1323 authMethod = HttpAuthMethod.Type.NONE.method(null);
1324 authenticator = authMethod;
1325 authAttempts = 1;
1326 continue;
1327 }
1328 throw e;
1329 }
1330 }
1331 }
1332
1333 void openResponse() throws IOException {
1334 final int status = HttpSupport.response(conn);
1335 if (status != HttpConnection.HTTP_OK) {
1336 throw new TransportException(uri, status + " "
1337 + conn.getResponseMessage());
1338 }
1339
1340 final String contentType = conn.getContentType();
1341 if (!responseType.equals(contentType)) {
1342 conn.getInputStream().close();
1343 throw wrongContentType(responseType, contentType);
1344 }
1345 }
1346
1347 HttpOutputStream getOutputStream() {
1348 return out;
1349 }
1350
1351 InputStream getInputStream() {
1352 return in;
1353 }
1354
1355 abstract void execute() throws IOException;
1356
1357 class HttpExecuteStream extends InputStream {
1358 @Override
1359 public int read() throws IOException {
1360 execute();
1361 return -1;
1362 }
1363
1364 @Override
1365 public int read(byte[] b, int off, int len) throws IOException {
1366 execute();
1367 return -1;
1368 }
1369
1370 @Override
1371 public long skip(long n) throws IOException {
1372 execute();
1373 return 0;
1374 }
1375 }
1376
1377 class HttpOutputStream extends TemporaryBuffer {
1378 HttpOutputStream() {
1379 super(http.getPostBuffer());
1380 }
1381
1382 @Override
1383 protected OutputStream overflow() throws IOException {
1384 openStream();
1385 conn.setChunkedStreamingMode(0);
1386 return conn.getOutputStream();
1387 }
1388 }
1389 }
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411 class MultiRequestService extends Service {
1412 boolean finalRequest;
1413
1414 MultiRequestService(final String serviceName) {
1415 super(serviceName);
1416 }
1417
1418
1419 @Override
1420 void execute() throws IOException {
1421 out.close();
1422
1423 if (conn == null) {
1424 if (out.length() == 0) {
1425
1426
1427
1428
1429
1430 if (finalRequest)
1431 return;
1432 throw new TransportException(uri,
1433 JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
1434 }
1435
1436 sendRequest();
1437 }
1438
1439 out.reset();
1440
1441 openResponse();
1442
1443 in.add(openInputStream(conn));
1444 if (!finalRequest)
1445 in.add(execute);
1446 conn = null;
1447 }
1448 }
1449
1450
1451 class LongPollService extends Service {
1452
1453
1454
1455 LongPollService(String serviceName) {
1456 super(serviceName);
1457 }
1458
1459
1460 @Override
1461 void execute() throws IOException {
1462 out.close();
1463 if (conn == null)
1464 sendRequest();
1465 openResponse();
1466 in.add(openInputStream(conn));
1467 }
1468 }
1469 }