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