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