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