1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jgit.transport;
15
16 import static java.nio.charset.StandardCharsets.UTF_8;
17 import static org.eclipse.jgit.lib.Constants.HEAD;
18 import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES;
19 import static org.eclipse.jgit.lib.Constants.INFO_HTTP_ALTERNATES;
20 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
21 import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
22 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
23 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
24 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
25 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
26 import static org.eclipse.jgit.util.HttpSupport.HDR_COOKIE;
27 import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION;
28 import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
29 import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE;
30 import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE2;
31 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
32 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
33 import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
34 import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
35
36 import java.io.BufferedInputStream;
37 import java.io.BufferedReader;
38 import java.io.File;
39 import java.io.FileNotFoundException;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.InterruptedIOException;
44 import java.io.OutputStream;
45 import java.io.UnsupportedEncodingException;
46 import java.net.HttpCookie;
47 import java.net.MalformedURLException;
48 import java.net.Proxy;
49 import java.net.ProxySelector;
50 import java.net.SocketException;
51 import java.net.URI;
52 import java.net.URISyntaxException;
53 import java.net.URL;
54 import java.net.URLDecoder;
55 import java.nio.charset.StandardCharsets;
56 import java.nio.file.InvalidPathException;
57 import java.security.GeneralSecurityException;
58 import java.security.cert.CertPathBuilderException;
59 import java.security.cert.CertPathValidatorException;
60 import java.security.cert.CertificateException;
61 import java.text.MessageFormat;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collection;
65 import java.util.Collections;
66 import java.util.EnumSet;
67 import java.util.HashSet;
68 import java.util.LinkedHashSet;
69 import java.util.LinkedList;
70 import java.util.List;
71 import java.util.Locale;
72 import java.util.Map;
73 import java.util.Set;
74 import java.util.TreeMap;
75 import java.util.zip.GZIPInputStream;
76 import java.util.zip.GZIPOutputStream;
77
78 import javax.net.ssl.SSLHandshakeException;
79
80 import org.eclipse.jgit.annotations.NonNull;
81 import org.eclipse.jgit.errors.ConfigInvalidException;
82 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
83 import org.eclipse.jgit.errors.NotSupportedException;
84 import org.eclipse.jgit.errors.PackProtocolException;
85 import org.eclipse.jgit.errors.TransportException;
86 import org.eclipse.jgit.internal.JGitText;
87 import org.eclipse.jgit.internal.storage.file.RefDirectory;
88 import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
89 import org.eclipse.jgit.internal.transport.http.NetscapeCookieFileCache;
90 import org.eclipse.jgit.lib.Constants;
91 import org.eclipse.jgit.lib.ObjectId;
92 import org.eclipse.jgit.lib.ObjectIdRef;
93 import org.eclipse.jgit.lib.ProgressMonitor;
94 import org.eclipse.jgit.lib.Ref;
95 import org.eclipse.jgit.lib.Repository;
96 import org.eclipse.jgit.lib.StoredConfig;
97 import org.eclipse.jgit.lib.SymbolicRef;
98 import org.eclipse.jgit.transport.HttpAuthMethod.Type;
99 import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
100 import org.eclipse.jgit.transport.http.HttpConnection;
101 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
102 import org.eclipse.jgit.transport.http.HttpConnectionFactory2;
103 import org.eclipse.jgit.util.FS;
104 import org.eclipse.jgit.util.HttpSupport;
105 import org.eclipse.jgit.util.IO;
106 import org.eclipse.jgit.util.RawParseUtils;
107 import org.eclipse.jgit.util.StringUtils;
108 import org.eclipse.jgit.util.SystemReader;
109 import org.eclipse.jgit.util.TemporaryBuffer;
110 import org.eclipse.jgit.util.io.DisabledOutputStream;
111 import org.eclipse.jgit.util.io.UnionInputStream;
112 import org.slf4j.Logger;
113 import org.slf4j.LoggerFactory;
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 public class TransportHttp extends HttpTransport implements WalkTransport,
132 PackTransport {
133
134 private static final Logger LOG = LoggerFactory
135 .getLogger(TransportHttp.class);
136
137 private static final String SVC_UPLOAD_PACK = "git-upload-pack";
138
139 private static final String SVC_RECEIVE_PACK = "git-receive-pack";
140
141 private static final byte[] VERSION = "version"
142 .getBytes(StandardCharsets.US_ASCII);
143
144
145
146
147
148
149
150 public enum AcceptEncoding {
151
152
153
154
155 UNSPECIFIED,
156
157
158
159
160 GZIP
161 }
162
163 static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
164 private final String[] schemeNames = { "http", "https" };
165
166 private final Set<String> schemeSet = Collections
167 .unmodifiableSet(new LinkedHashSet<>(Arrays
168 .asList(schemeNames)));
169
170 @Override
171 public String getName() {
172 return JGitText.get().transportProtoHTTP;
173 }
174
175 @Override
176 public Set<String> getSchemes() {
177 return schemeSet;
178 }
179
180 @Override
181 public Set<URIishField> getRequiredFields() {
182 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
183 URIishField.PATH));
184 }
185
186 @Override
187 public Set<URIishField> getOptionalFields() {
188 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
189 URIishField.PASS, URIishField.PORT));
190 }
191
192 @Override
193 public int getDefaultPort() {
194 return 80;
195 }
196
197 @Override
198 public Transport open(URIish uri, Repository local, String remoteName)
199 throws NotSupportedException {
200 return new TransportHttp(local, uri);
201 }
202
203 @Override
204 public Transport open(URIish uri) throws NotSupportedException {
205 return new TransportHttp(uri);
206 }
207 };
208
209 static final TransportProtocol PROTO_FTP = new TransportProtocol() {
210 @Override
211 public String getName() {
212 return JGitText.get().transportProtoFTP;
213 }
214
215 @Override
216 public Set<String> getSchemes() {
217 return Collections.singleton("ftp");
218 }
219
220 @Override
221 public Set<URIishField> getRequiredFields() {
222 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
223 URIishField.PATH));
224 }
225
226 @Override
227 public Set<URIishField> getOptionalFields() {
228 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
229 URIishField.PASS, URIishField.PORT));
230 }
231
232 @Override
233 public int getDefaultPort() {
234 return 21;
235 }
236
237 @Override
238 public Transport open(URIish uri, Repository local, String remoteName)
239 throws NotSupportedException {
240 return new TransportHttp(local, uri);
241 }
242 };
243
244
245
246
247
248
249 private URIish currentUri;
250
251 private URL baseUrl;
252
253 private URL objectsUrl;
254
255 private final HttpConfig http;
256
257 private final ProxySelector proxySelector;
258
259 private boolean useSmartHttp = true;
260
261 private HttpAuthMethod authMethod = HttpAuthMethod.Type.NONE.method(null);
262
263 private Map<String, String> headers;
264
265 private boolean sslVerify;
266
267 private boolean sslFailure = false;
268
269 private HttpConnectionFactory factory;
270
271 private HttpConnectionFactory2.GitSession gitSession;
272
273 private boolean factoryUsed;
274
275
276
277
278 private final NetscapeCookieFile cookieFile;
279
280
281
282
283
284
285
286 private final Set<HttpCookie> relevantCookies;
287
288 TransportHttp(Repository local, URIish uri)
289 throws NotSupportedException {
290 super(local, uri);
291 setURI(uri);
292 http = new HttpConfig(local.getConfig(), uri);
293 proxySelector = ProxySelector.getDefault();
294 sslVerify = http.isSslVerify();
295 cookieFile = getCookieFileFromConfig(http);
296 relevantCookies = filterCookies(cookieFile, baseUrl);
297 factory = HttpTransport.getConnectionFactory();
298 }
299
300 private URL toURL(URIish urish) throws MalformedURLException {
301 String uriString = urish.toString();
302 if (!uriString.endsWith("/")) {
303 uriString += '/';
304 }
305 return new URL(uriString);
306 }
307
308
309
310
311
312
313
314
315
316 protected void setURI(URIish uri) throws NotSupportedException {
317 try {
318 currentUri = uri;
319 baseUrl = toURL(uri);
320 objectsUrl = new URL(baseUrl, "objects/");
321 } catch (MalformedURLException e) {
322 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
323 }
324 }
325
326
327
328
329
330
331
332 TransportHttp(URIish uri) throws NotSupportedException {
333 super(uri);
334 setURI(uri);
335 http = new HttpConfig(uri);
336 proxySelector = ProxySelector.getDefault();
337 sslVerify = http.isSslVerify();
338 cookieFile = getCookieFileFromConfig(http);
339 relevantCookies = filterCookies(cookieFile, baseUrl);
340 factory = HttpTransport.getConnectionFactory();
341 }
342
343
344
345
346
347
348
349
350
351
352
353 public void setUseSmartHttp(boolean on) {
354 useSmartHttp = on;
355 }
356
357 @SuppressWarnings("resource")
358 private FetchConnection getConnection(HttpConnection c, InputStream in,
359 String service, Collection<RefSpec> refSpecs,
360 String... additionalPatterns) throws IOException {
361 BaseConnection f;
362 if (isSmartHttp(c, service)) {
363 InputStream withMark = in.markSupported() ? in
364 : new BufferedInputStream(in);
365 readSmartHeaders(withMark, service);
366 f = new SmartHttpFetchConnection(withMark, refSpecs,
367 additionalPatterns);
368 } else {
369
370
371 f = newDumbConnection(in);
372 }
373 f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
374 return (FetchConnection) f;
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 public void setHttpConnectionFactory(
394 @NonNull HttpConnectionFactory customFactory) {
395 if (factoryUsed) {
396 throw new IllegalStateException(JGitText.get().httpFactoryInUse);
397 }
398 factory = customFactory;
399 }
400
401
402
403
404
405
406
407
408 @NonNull
409 public HttpConnectionFactory getHttpConnectionFactory() {
410 return factory;
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433 public void setPreemptiveBasicAuthentication(String username,
434 String password) {
435 if (factoryUsed) {
436 throw new IllegalStateException(JGitText.get().httpPreAuthTooLate);
437 }
438 if (StringUtils.isEmptyOrNull(username)
439 || StringUtils.isEmptyOrNull(password)) {
440 authMethod = authFromUri(currentUri);
441 } else {
442 HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null);
443 basic.authorize(username, password);
444 authMethod = basic;
445 }
446 }
447
448
449 @Override
450 public FetchConnection openFetch() throws TransportException,
451 NotSupportedException {
452 return openFetch(Collections.emptyList());
453 }
454
455 @Override
456 public FetchConnection openFetch(Collection<RefSpec> refSpecs,
457 String... additionalPatterns)
458 throws NotSupportedException, TransportException {
459 final String service = SVC_UPLOAD_PACK;
460 try {
461 TransferConfig.ProtocolVersion gitProtocol = protocol;
462 if (gitProtocol == null) {
463 gitProtocol = TransferConfig.ProtocolVersion.V2;
464 }
465 HttpConnection c = connect(service, gitProtocol);
466 try (InputStream in = openInputStream(c)) {
467 return getConnection(c, in, service, refSpecs,
468 additionalPatterns);
469 }
470 } catch (NotSupportedException | TransportException err) {
471 throw err;
472 } catch (IOException err) {
473 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
474 }
475 }
476
477 private WalkFetchConnection newDumbConnection(InputStream in)
478 throws IOException, PackProtocolException {
479 HttpObjectDB d = new HttpObjectDB(objectsUrl);
480 Map<String, Ref> refs;
481 try (BufferedReader br = toBufferedReader(in)) {
482 refs = d.readAdvertisedImpl(br);
483 }
484
485 if (!refs.containsKey(HEAD)) {
486
487
488
489
490 HttpConnection conn = httpOpen(
491 METHOD_GET,
492 new URL(baseUrl, HEAD),
493 AcceptEncoding.GZIP);
494 int status = HttpSupport.response(conn);
495 switch (status) {
496 case HttpConnection.HTTP_OK: {
497 try (BufferedReader br = toBufferedReader(
498 openInputStream(conn))) {
499 String line = br.readLine();
500 if (line != null && line.startsWith(RefDirectory.SYMREF)) {
501 String target = line.substring(RefDirectory.SYMREF.length());
502 Ref r = refs.get(target);
503 if (r == null)
504 r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
505 r = new SymbolicRef(HEAD, r);
506 refs.put(r.getName(), r);
507 } else if (line != null && ObjectId.isId(line)) {
508 Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
509 HEAD, ObjectId.fromString(line));
510 refs.put(r.getName(), r);
511 }
512 }
513 break;
514 }
515
516 case HttpConnection.HTTP_NOT_FOUND:
517 break;
518
519 default:
520 throw new TransportException(uri, MessageFormat.format(
521 JGitText.get().cannotReadHEAD, Integer.valueOf(status),
522 conn.getResponseMessage()));
523 }
524 }
525
526 WalkFetchConnection wfc = new WalkFetchConnection(this, d);
527 wfc.available(refs);
528 return wfc;
529 }
530
531 private BufferedReader toBufferedReader(InputStream in) {
532 return new BufferedReader(new InputStreamReader(in, UTF_8));
533 }
534
535
536 @Override
537 public PushConnection openPush() throws NotSupportedException,
538 TransportException {
539 final String service = SVC_RECEIVE_PACK;
540 try {
541 final HttpConnection c = connect(service);
542 try (InputStream in = openInputStream(c)) {
543 if (isSmartHttp(c, service)) {
544 return smartPush(service, c, in);
545 } else if (!useSmartHttp) {
546 final String msg = JGitText.get().smartHTTPPushDisabled;
547 throw new NotSupportedException(msg);
548
549 } else {
550 final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
551 throw new NotSupportedException(msg);
552 }
553 }
554 } catch (NotSupportedException | TransportException err) {
555 throw err;
556 } catch (IOException err) {
557 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
558 }
559 }
560
561 private PushConnection smartPush(String service, HttpConnection c,
562 InputStream in) throws IOException, TransportException {
563 BufferedInputStream inBuf = new BufferedInputStream(in);
564 readSmartHeaders(inBuf, service);
565 SmartHttpPushConnection p = new SmartHttpPushConnection(inBuf);
566 p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
567 return p;
568 }
569
570
571 @Override
572 public void close() {
573 if (gitSession != null) {
574 gitSession.close();
575 gitSession = null;
576 }
577 }
578
579
580
581
582
583
584
585
586
587 public void setAdditionalHeaders(Map<String, String> headers) {
588 this.headers = headers;
589 }
590
591 private NoRemoteRepositoryException createNotFoundException(URIish u,
592 URL url, String msg) {
593 String text;
594 if (msg != null && !msg.isEmpty()) {
595 text = MessageFormat.format(JGitText.get().uriNotFoundWithMessage,
596 url, msg);
597 } else {
598 text = MessageFormat.format(JGitText.get().uriNotFound, url);
599 }
600 return new NoRemoteRepositoryException(u, text);
601 }
602
603 private HttpAuthMethod authFromUri(URIish u) {
604 String user = u.getUser();
605 String pass = u.getPass();
606 if (user != null && pass != null) {
607 try {
608
609
610 user = URLDecoder.decode(user.replace("+", "%2B"),
611 StandardCharsets.UTF_8.name());
612 pass = URLDecoder.decode(pass.replace("+", "%2B"),
613 StandardCharsets.UTF_8.name());
614 HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null);
615 basic.authorize(user, pass);
616 return basic;
617 } catch (IllegalArgumentException
618 | UnsupportedEncodingException e) {
619 LOG.warn(JGitText.get().httpUserInfoDecodeError, u);
620 }
621 }
622 return HttpAuthMethod.Type.NONE.method(null);
623 }
624
625 private HttpConnection connect(String service)
626 throws TransportException, NotSupportedException {
627 return connect(service, null);
628 }
629
630 private HttpConnection connect(String service,
631 TransferConfig.ProtocolVersion protocolVersion)
632 throws TransportException, NotSupportedException {
633 URL u = getServiceURL(service);
634 if (HttpAuthMethod.Type.NONE.equals(authMethod.getType())) {
635 authMethod = authFromUri(currentUri);
636 }
637 int authAttempts = 1;
638 int redirects = 0;
639 Collection<Type> ignoreTypes = null;
640 for (;;) {
641 try {
642 final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
643 if (useSmartHttp) {
644 String exp = "application/x-" + service + "-advertisement";
645 conn.setRequestProperty(HDR_ACCEPT, exp + ", */*");
646 } else {
647 conn.setRequestProperty(HDR_ACCEPT, "*/*");
648 }
649 if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
650 conn.setRequestProperty(
651 GitProtocolConstants.PROTOCOL_HEADER,
652 GitProtocolConstants.VERSION_2_REQUEST);
653 }
654 final int status = HttpSupport.response(conn);
655 processResponseCookies(conn);
656 switch (status) {
657 case HttpConnection.HTTP_OK:
658
659
660
661
662 if (authMethod.getType() == HttpAuthMethod.Type.NONE
663 && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
664 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
665 return conn;
666
667 case HttpConnection.HTTP_NOT_FOUND:
668 throw createNotFoundException(uri, u,
669 conn.getResponseMessage());
670
671 case HttpConnection.HTTP_UNAUTHORIZED:
672 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
673 if (authMethod.getType() == HttpAuthMethod.Type.NONE)
674 throw new TransportException(uri, MessageFormat.format(
675 JGitText.get().authenticationNotSupported, uri));
676 CredentialsProvider credentialsProvider = getCredentialsProvider();
677 if (credentialsProvider == null)
678 throw new TransportException(uri,
679 JGitText.get().noCredentialsProvider);
680 if (authAttempts > 1)
681 credentialsProvider.reset(currentUri);
682 if (3 < authAttempts
683 || !authMethod.authorize(currentUri,
684 credentialsProvider)) {
685 throw new TransportException(uri,
686 JGitText.get().notAuthorized);
687 }
688 authAttempts++;
689 continue;
690
691 case HttpConnection.HTTP_FORBIDDEN:
692 throw new TransportException(uri, MessageFormat.format(
693 JGitText.get().serviceNotPermitted, baseUrl,
694 service));
695
696 case HttpConnection.HTTP_MOVED_PERM:
697 case HttpConnection.HTTP_MOVED_TEMP:
698 case HttpConnection.HTTP_SEE_OTHER:
699 case HttpConnection.HTTP_11_MOVED_PERM:
700 case HttpConnection.HTTP_11_MOVED_TEMP:
701
702
703
704 if (http.getFollowRedirects() == HttpRedirectMode.FALSE) {
705 throw new TransportException(uri,
706 MessageFormat.format(
707 JGitText.get().redirectsOff,
708 Integer.valueOf(status)));
709 }
710 URIish newUri = redirect(u,
711 conn.getHeaderField(HDR_LOCATION),
712 Constants.INFO_REFS, redirects++);
713 setURI(newUri);
714 u = getServiceURL(service);
715 authAttempts = 1;
716 break;
717 default:
718 String err = status + " " + conn.getResponseMessage();
719 throw new TransportException(uri, err);
720 }
721 } catch (NotSupportedException | TransportException e) {
722 throw e;
723 } catch (InterruptedIOException e) {
724
725 throw new TransportException(uri, MessageFormat.format(
726 JGitText.get().connectionTimeOut, u.getHost()), e);
727 } catch (SocketException e) {
728
729 throw new TransportException(uri,
730 JGitText.get().connectionFailed, e);
731 } catch (SSLHandshakeException e) {
732 handleSslFailure(e);
733 continue;
734 } catch (IOException e) {
735 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
736 if (ignoreTypes == null) {
737 ignoreTypes = new HashSet<>();
738 }
739
740 ignoreTypes.add(authMethod.getType());
741
742
743 authMethod = HttpAuthMethod.Type.NONE.method(null);
744 authAttempts = 1;
745
746 continue;
747 }
748
749 throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
750 }
751 }
752 }
753
754 void processResponseCookies(HttpConnection conn) {
755 if (cookieFile != null && http.getSaveCookies()) {
756 List<HttpCookie> foundCookies = new LinkedList<>();
757
758 List<String> cookieHeaderValues = conn
759 .getHeaderFields(HDR_SET_COOKIE);
760 if (!cookieHeaderValues.isEmpty()) {
761 foundCookies.addAll(
762 extractCookies(HDR_SET_COOKIE, cookieHeaderValues));
763 }
764 cookieHeaderValues = conn.getHeaderFields(HDR_SET_COOKIE2);
765 if (!cookieHeaderValues.isEmpty()) {
766 foundCookies.addAll(
767 extractCookies(HDR_SET_COOKIE2, cookieHeaderValues));
768 }
769 if (!foundCookies.isEmpty()) {
770 try {
771
772 Set<HttpCookie> cookies = cookieFile.getCookies(false);
773 cookies.addAll(foundCookies);
774 cookieFile.write(baseUrl);
775 relevantCookies.addAll(foundCookies);
776 } catch (IOException | IllegalArgumentException
777 | InterruptedException e) {
778 LOG.warn(MessageFormat.format(
779 JGitText.get().couldNotPersistCookies,
780 cookieFile.getPath()), e);
781 }
782 }
783 }
784 }
785
786 private List<HttpCookie> extractCookies(String headerKey,
787 List<String> headerValues) {
788 List<HttpCookie> foundCookies = new LinkedList<>();
789 for (String headerValue : headerValues) {
790 foundCookies
791 .addAll(HttpCookie.parse(headerKey + ':' + headerValue));
792 }
793
794
795
796 for (HttpCookie foundCookie : foundCookies) {
797 String domain = foundCookie.getDomain();
798 if (domain != null && domain.startsWith(".")) {
799 foundCookie.setDomain(domain.substring(1));
800 }
801 }
802 return foundCookies;
803 }
804
805 private static class CredentialItems {
806 CredentialItem.InformationalMessage message;
807
808
809 CredentialItem.YesNoType now;
810
811
812
813
814
815
816 CredentialItem.YesNoType forRepo;
817
818
819 CredentialItem.YesNoType always;
820
821 public CredentialItem[] items() {
822 if (forRepo == null) {
823 return new CredentialItem[] { message, now, always };
824 }
825 return new CredentialItem[] { message, now, forRepo, always };
826 }
827 }
828
829 private void handleSslFailure(Throwable e) throws TransportException {
830 if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
831 throw new TransportException(uri,
832 MessageFormat.format(
833 JGitText.get().sslFailureExceptionMessage,
834 currentUri.setPass(null)),
835 e);
836 }
837 sslFailure = true;
838 }
839
840 private boolean trustInsecureSslConnection(Throwable cause) {
841 if (cause instanceof CertificateException
842 || cause instanceof CertPathBuilderException
843 || cause instanceof CertPathValidatorException) {
844
845
846 CredentialsProvider provider = getCredentialsProvider();
847 if (provider != null) {
848 CredentialItems trust = constructSslTrustItems(cause);
849 CredentialItem[] items = trust.items();
850 if (provider.supports(items)) {
851 boolean answered = provider.get(uri, items);
852 if (answered) {
853
854 boolean trustNow = trust.now.getValue();
855 boolean trustLocal = trust.forRepo != null
856 && trust.forRepo.getValue();
857 boolean trustAlways = trust.always.getValue();
858 if (trustNow || trustLocal || trustAlways) {
859 sslVerify = false;
860 if (trustAlways) {
861 updateSslVerifyUser(false);
862 } else if (trustLocal) {
863 updateSslVerify(local.getConfig(), false);
864 }
865 return true;
866 }
867 }
868 }
869 }
870 }
871 return false;
872 }
873
874 private CredentialItems constructSslTrustItems(Throwable cause) {
875 CredentialItems items = new CredentialItems();
876 String info = MessageFormat.format(JGitText.get().sslFailureInfo,
877 currentUri.setPass(null));
878 String sslMessage = cause.getLocalizedMessage();
879 if (sslMessage == null) {
880 sslMessage = cause.toString();
881 }
882 sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
883 sslMessage);
884 items.message = new CredentialItem.InformationalMessage(info + '\n'
885 + sslMessage + '\n'
886 + JGitText.get().sslFailureTrustExplanation);
887 items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
888 if (local != null) {
889 items.forRepo = new CredentialItem.YesNoType(
890 MessageFormat.format(JGitText.get().sslTrustForRepo,
891 local.getDirectory()));
892 }
893 items.always = new CredentialItem.YesNoType(
894 JGitText.get().sslTrustAlways);
895 return items;
896 }
897
898 private void updateSslVerify(StoredConfig config, boolean value) {
899
900
901
902 String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
903 int port = uri.getPort();
904 if (port > 0) {
905 uriPattern += ":" + port;
906 }
907 config.setBoolean(HttpConfig.HTTP, uriPattern,
908 HttpConfig.SSL_VERIFY_KEY, value);
909 try {
910 config.save();
911 } catch (IOException e) {
912 LOG.error(JGitText.get().sslVerifyCannotSave, e);
913 }
914 }
915
916 private void updateSslVerifyUser(boolean value) {
917 StoredConfig userConfig = null;
918 try {
919 userConfig = SystemReader.getInstance().getUserConfig();
920 updateSslVerify(userConfig, value);
921 } catch (IOException | ConfigInvalidException e) {
922
923 LOG.error(e.getMessage(), e);
924 }
925 }
926
927 private URIish redirect(URL currentUrl, String location, String checkFor,
928 int redirects)
929 throws TransportException {
930 if (location == null || location.isEmpty()) {
931 throw new TransportException(uri,
932 MessageFormat.format(JGitText.get().redirectLocationMissing,
933 baseUrl));
934 }
935 if (redirects >= http.getMaxRedirects()) {
936 throw new TransportException(uri,
937 MessageFormat.format(JGitText.get().redirectLimitExceeded,
938 Integer.valueOf(http.getMaxRedirects()), baseUrl,
939 location));
940 }
941 try {
942 URI redirectTo = new URI(location);
943
944
945 boolean resetAuth = !StringUtils
946 .isEmptyOrNull(redirectTo.getUserInfo());
947 String currentHost = currentUrl.getHost();
948 redirectTo = currentUrl.toURI().resolve(redirectTo);
949 resetAuth = resetAuth || !currentHost.equals(redirectTo.getHost());
950 String redirected = redirectTo.toASCIIString();
951 if (!isValidRedirect(baseUrl, redirected, checkFor)) {
952 throw new TransportException(uri,
953 MessageFormat.format(JGitText.get().redirectBlocked,
954 baseUrl, redirected));
955 }
956 redirected = redirected.substring(0, redirected.indexOf(checkFor));
957 URIish result = new URIish(redirected);
958 if (resetAuth) {
959 authMethod = HttpAuthMethod.Type.NONE.method(null);
960 }
961 if (LOG.isInfoEnabled()) {
962 LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
963 uri.setPass(null),
964 Integer.valueOf(redirects), baseUrl, result));
965 }
966 return result;
967 } catch (URISyntaxException e) {
968 throw new TransportException(uri,
969 MessageFormat.format(JGitText.get().invalidRedirectLocation,
970 baseUrl, location),
971 e);
972 }
973 }
974
975 private boolean isValidRedirect(URL current, String next, String checkFor) {
976
977
978 String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT);
979 int schemeEnd = next.indexOf("://"); //$NON-NLS-1$
980 if (schemeEnd < 0) {
981 return false;
982 }
983 String newProtocol = next.substring(0, schemeEnd)
984 .toLowerCase(Locale.ROOT);
985 if (!oldProtocol.equals(newProtocol)) {
986 if (!"https".equals(newProtocol)) {
987 return false;
988 }
989 }
990
991
992 if (!next.contains(checkFor)) {
993 return false;
994 }
995
996
997
998 return true;
999 }
1000
1001 private URL getServiceURL(String service)
1002 throws NotSupportedException {
1003 try {
1004 final StringBuilder b = new StringBuilder();
1005 b.append(baseUrl);
1006
1007 if (b.charAt(b.length() - 1) != '/') {
1008 b.append('/');
1009 }
1010 b.append(Constants.INFO_REFS);
1011
1012 if (useSmartHttp) {
1013 b.append(b.indexOf("?") < 0 ? '?' : '&');
1014 b.append("service=");
1015 b.append(service);
1016 }
1017
1018 return new URL(b.toString());
1019 } catch (MalformedURLException e) {
1020 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
1021 }
1022 }
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034 protected HttpConnection httpOpen(String method, URL u,
1035 AcceptEncoding acceptEncoding) throws IOException {
1036 if (method == null || u == null || acceptEncoding == null) {
1037 throw new NullPointerException();
1038 }
1039
1040 final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
1041 factoryUsed = true;
1042 HttpConnection conn = factory.create(u, proxy);
1043
1044 if (gitSession == null && (factory instanceof HttpConnectionFactory2)) {
1045 gitSession = ((HttpConnectionFactory2) factory).newSession();
1046 }
1047 if (gitSession != null) {
1048 try {
1049 gitSession.configure(conn, sslVerify);
1050 } catch (GeneralSecurityException e) {
1051 throw new IOException(e.getMessage(), e);
1052 }
1053 } else if (!sslVerify && "https".equals(u.getProtocol())) {
1054
1055 HttpSupport.disableSslVerify(conn);
1056 }
1057
1058
1059
1060 conn.setInstanceFollowRedirects(false);
1061
1062 conn.setRequestMethod(method);
1063 conn.setUseCaches(false);
1064 if (acceptEncoding == AcceptEncoding.GZIP) {
1065 conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
1066 }
1067 conn.setRequestProperty(HDR_PRAGMA, "no-cache");
1068 if (http.getUserAgent() != null) {
1069 conn.setRequestProperty(HDR_USER_AGENT, http.getUserAgent());
1070 } else if (UserAgent.get() != null) {
1071 conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
1072 }
1073 int timeOut = getTimeout();
1074 if (timeOut != -1) {
1075 int effTimeOut = timeOut * 1000;
1076 conn.setConnectTimeout(effTimeOut);
1077 conn.setReadTimeout(effTimeOut);
1078 }
1079 addHeaders(conn, http.getExtraHeaders());
1080
1081 if (!relevantCookies.isEmpty()) {
1082 setCookieHeader(conn);
1083 }
1084
1085 if (this.headers != null && !this.headers.isEmpty()) {
1086 for (Map.Entry<String, String> entry : this.headers.entrySet()) {
1087 conn.setRequestProperty(entry.getKey(), entry.getValue());
1088 }
1089 }
1090 authMethod.configureRequest(conn);
1091 return conn;
1092 }
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105 static void addHeaders(HttpConnection conn, List<String> headersToAdd) {
1106 for (String header : headersToAdd) {
1107
1108
1109 int colon = header.indexOf(':');
1110 String key = null;
1111 if (colon > 0) {
1112 key = header.substring(0, colon).trim();
1113 }
1114 if (key == null || key.isEmpty()) {
1115 LOG.warn(MessageFormat.format(
1116 JGitText.get().invalidHeaderFormat, header));
1117 } else if (HttpSupport.scanToken(key, 0) != key.length()) {
1118 LOG.warn(MessageFormat.format(JGitText.get().invalidHeaderKey,
1119 header));
1120 } else {
1121 String value = header.substring(colon + 1).trim();
1122 if (!StandardCharsets.US_ASCII.newEncoder().canEncode(value)) {
1123 LOG.warn(MessageFormat
1124 .format(JGitText.get().invalidHeaderValue, header));
1125 } else {
1126 conn.setRequestProperty(key, value);
1127 }
1128 }
1129 }
1130 }
1131
1132 private void setCookieHeader(HttpConnection conn) {
1133 StringBuilder cookieHeaderValue = new StringBuilder();
1134 for (HttpCookie cookie : relevantCookies) {
1135 if (!cookie.hasExpired()) {
1136 if (cookieHeaderValue.length() > 0) {
1137 cookieHeaderValue.append(';');
1138 }
1139 cookieHeaderValue.append(cookie.toString());
1140 }
1141 }
1142 if (cookieHeaderValue.length() > 0) {
1143 conn.setRequestProperty(HDR_COOKIE, cookieHeaderValue.toString());
1144 }
1145 }
1146
1147 final InputStream openInputStream(HttpConnection conn)
1148 throws IOException {
1149 InputStream input = conn.getInputStream();
1150 if (isGzipContent(conn))
1151 input = new GZIPInputStream(input);
1152 return input;
1153 }
1154
1155 IOException wrongContentType(String expType, String actType) {
1156 final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
1157 return new TransportException(uri, why);
1158 }
1159
1160 private NetscapeCookieFile getCookieFileFromConfig(
1161 HttpConfig config) {
1162 String path = config.getCookieFile();
1163 if (!StringUtils.isEmptyOrNull(path)) {
1164 try {
1165 FS fs = local != null ? local.getFS() : FS.DETECTED;
1166 File f;
1167 if (path.startsWith("~/")) {
1168 f = fs.resolve(fs.userHome(), path.substring(2));
1169 } else {
1170 f = new File(path);
1171 if (!f.isAbsolute()) {
1172 f = fs.resolve(null, path);
1173 LOG.warn(MessageFormat.format(
1174 JGitText.get().cookieFilePathRelative, f));
1175 }
1176 }
1177 return NetscapeCookieFileCache.getInstance(config)
1178 .getEntry(f.toPath());
1179 } catch (InvalidPathException e) {
1180 LOG.warn(MessageFormat.format(
1181 JGitText.get().couldNotReadCookieFile, path), e);
1182 }
1183 }
1184 return null;
1185 }
1186
1187 private static Set<HttpCookie> filterCookies(NetscapeCookieFile cookieFile,
1188 URL url) {
1189 if (cookieFile != null) {
1190 return filterCookies(cookieFile.getCookies(true), url);
1191 }
1192 return Collections.emptySet();
1193 }
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205 private static Set<HttpCookie> filterCookies(Set<HttpCookie> allCookies,
1206 URL url) {
1207 Set<HttpCookie> filteredCookies = new HashSet<>();
1208 for (HttpCookie cookie : allCookies) {
1209 if (cookie.hasExpired()) {
1210 continue;
1211 }
1212 if (!matchesCookieDomain(url.getHost(), cookie.getDomain())) {
1213 continue;
1214 }
1215 if (!matchesCookiePath(url.getPath(), cookie.getPath())) {
1216 continue;
1217 }
1218 if (cookie.getSecure() && !"https".equals(url.getProtocol())) {
1219 continue;
1220 }
1221 filteredCookies.add(cookie);
1222 }
1223 return filteredCookies;
1224 }
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268 static boolean matchesCookieDomain(String host, String cookieDomain) {
1269 cookieDomain = cookieDomain.toLowerCase(Locale.ROOT);
1270 host = host.toLowerCase(Locale.ROOT);
1271 if (host.equals(cookieDomain)) {
1272 return true;
1273 }
1274 if (!host.endsWith(cookieDomain)) {
1275 return false;
1276 }
1277 return host.charAt(host.length() - cookieDomain.length() - 1) == '.';
1278 }
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303 static boolean matchesCookiePath(String path, String cookiePath) {
1304 if (cookiePath.equals(path)) {
1305 return true;
1306 }
1307 if (!cookiePath.endsWith("/")) {
1308 cookiePath += "/";
1309 }
1310 return path.startsWith(cookiePath);
1311 }
1312
1313 private boolean isSmartHttp(HttpConnection c, String service) {
1314 final String expType = "application/x-" + service + "-advertisement";
1315 final String actType = c.getContentType();
1316 return expType.equals(actType);
1317 }
1318
1319 private boolean isGzipContent(HttpConnection c) {
1320 return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
1321 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
1322 }
1323
1324 private void readSmartHeaders(InputStream in, String service)
1325 throws IOException {
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335 final byte[] magic = new byte[14];
1336 if (!in.markSupported()) {
1337 throw new TransportException(uri,
1338 JGitText.get().inputStreamMustSupportMark);
1339 }
1340 in.mark(14);
1341 IO.readFully(in, magic, 0, magic.length);
1342
1343
1344 if (Arrays.equals(Arrays.copyOfRange(magic, 4, 11), VERSION)
1345 && magic[12] >= '1' && magic[12] <= '9') {
1346
1347
1348 in.reset();
1349 return;
1350 }
1351 if (magic[4] != '#') {
1352 throw new TransportException(uri, MessageFormat.format(
1353 JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
1354 }
1355 in.reset();
1356 final PacketLineIn pckIn = new PacketLineIn(in);
1357 final String exp = "# service=" + service;
1358 final String act = pckIn.readString();
1359 if (!exp.equals(act)) {
1360 throw new TransportException(uri, MessageFormat.format(
1361 JGitText.get().expectedGot, exp, act));
1362 }
1363
1364 while (!PacketLineIn.isEnd(pckIn.readString())) {
1365
1366 }
1367 }
1368
1369 class HttpObjectDB extends WalkRemoteObjectDatabase {
1370 private final URL httpObjectsUrl;
1371
1372 HttpObjectDB(URL b) {
1373 httpObjectsUrl = b;
1374 }
1375
1376 @Override
1377 URIish getURI() {
1378 return new URIish(httpObjectsUrl);
1379 }
1380
1381 @Override
1382 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
1383 try {
1384 return readAlternates(INFO_HTTP_ALTERNATES);
1385 } catch (FileNotFoundException err) {
1386
1387 }
1388
1389 try {
1390 return readAlternates(INFO_ALTERNATES);
1391 } catch (FileNotFoundException err) {
1392
1393 }
1394
1395 return null;
1396 }
1397
1398 @Override
1399 WalkRemoteObjectDatabase openAlternate(String location)
1400 throws IOException {
1401 return new HttpObjectDB(new URL(httpObjectsUrl, location));
1402 }
1403
1404 @Override
1405 BufferedReader openReader(String path) throws IOException {
1406
1407
1408 InputStream is = open(path, AcceptEncoding.GZIP).in;
1409 return new BufferedReader(new InputStreamReader(is, UTF_8));
1410 }
1411
1412 @Override
1413 Collection<String> getPackNames() throws IOException {
1414 final Collection<String> packs = new ArrayList<>();
1415 try (BufferedReader br = openReader(INFO_PACKS)) {
1416 for (;;) {
1417 final String s = br.readLine();
1418 if (s == null || s.length() == 0)
1419 break;
1420 if (!s.startsWith("P pack-") || !s.endsWith(".pack"))
1421 throw invalidAdvertisement(s);
1422 packs.add(s.substring(2));
1423 }
1424 return packs;
1425 } catch (FileNotFoundException err) {
1426 return packs;
1427 }
1428 }
1429
1430 @Override
1431 FileStream open(String path) throws IOException {
1432 return open(path, AcceptEncoding.UNSPECIFIED);
1433 }
1434
1435 FileStream open(String path, AcceptEncoding acceptEncoding)
1436 throws IOException {
1437 final URL base = httpObjectsUrl;
1438 final URL u = new URL(base, path);
1439 final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
1440 switch (HttpSupport.response(c)) {
1441 case HttpConnection.HTTP_OK:
1442 final InputStream in = openInputStream(c);
1443
1444
1445
1446 if (!isGzipContent(c)) {
1447 final int len = c.getContentLength();
1448 return new FileStream(in, len);
1449 }
1450 return new FileStream(in);
1451 case HttpConnection.HTTP_NOT_FOUND:
1452 throw new FileNotFoundException(u.toString());
1453 default:
1454 throw new IOException(u.toString() + ": "
1455 + HttpSupport.response(c) + " "
1456 + c.getResponseMessage());
1457 }
1458 }
1459
1460 Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
1461 throws IOException, PackProtocolException {
1462 final TreeMap<String, Ref> avail = new TreeMap<>();
1463 for (;;) {
1464 String line = br.readLine();
1465 if (line == null)
1466 break;
1467
1468 final int tab = line.indexOf('\t');
1469 if (tab < 0)
1470 throw invalidAdvertisement(line);
1471
1472 String name;
1473 final ObjectId id;
1474
1475 name = line.substring(tab + 1);
1476 id = ObjectId.fromString(line.substring(0, tab));
1477 if (name.endsWith("^{}")) {
1478 name = name.substring(0, name.length() - 3);
1479 final Ref prior = avail.get(name);
1480 if (prior == null)
1481 throw outOfOrderAdvertisement(name);
1482
1483 if (prior.getPeeledObjectId() != null)
1484 throw duplicateAdvertisement(name + "^{}");
1485
1486 avail.put(name, new ObjectIdRef.PeeledTag(
1487 Ref.Storage.NETWORK, name,
1488 prior.getObjectId(), id));
1489 } else {
1490 Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
1491 Ref.Storage.NETWORK, name, id));
1492 if (prior != null)
1493 throw duplicateAdvertisement(name);
1494 }
1495 }
1496 return avail;
1497 }
1498
1499 private PackProtocolException outOfOrderAdvertisement(String n) {
1500 return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
1501 }
1502
1503 private PackProtocolException invalidAdvertisement(String n) {
1504 return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
1505 }
1506
1507 private PackProtocolException duplicateAdvertisement(String n) {
1508 return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
1509 }
1510
1511 @Override
1512 void close() {
1513
1514 }
1515 }
1516
1517 class SmartHttpFetchConnection extends BasePackFetchConnection {
1518 private MultiRequestService svc;
1519
1520 SmartHttpFetchConnection(InputStream advertisement)
1521 throws TransportException {
1522 this(advertisement, Collections.emptyList());
1523 }
1524
1525 SmartHttpFetchConnection(InputStream advertisement,
1526 Collection<RefSpec> refSpecs, String... additionalPatterns)
1527 throws TransportException {
1528 super(TransportHttp.this);
1529 statelessRPC = true;
1530
1531 init(advertisement, DisabledOutputStream.INSTANCE);
1532 outNeedsEnd = false;
1533 if (!readAdvertisedRefs()) {
1534
1535 LongPollService service = new LongPollService(SVC_UPLOAD_PACK,
1536 getProtocolVersion());
1537 init(service.getInputStream(), service.getOutputStream());
1538 lsRefs(refSpecs, additionalPatterns);
1539 }
1540 }
1541
1542 @Override
1543 protected void doFetch(ProgressMonitor monitor, Collection<Ref> want,
1544 Set<ObjectId> have, OutputStream outputStream)
1545 throws TransportException {
1546 svc = new MultiRequestService(SVC_UPLOAD_PACK,
1547 getProtocolVersion());
1548 try (InputStream svcIn = svc.getInputStream();
1549 OutputStream svcOut = svc.getOutputStream()) {
1550 init(svcIn, svcOut);
1551 super.doFetch(monitor, want, have, outputStream);
1552 } catch (TransportException e) {
1553 throw e;
1554 } catch (IOException e) {
1555 throw new TransportException(e.getMessage(), e);
1556 } finally {
1557 svc = null;
1558 }
1559 }
1560
1561 @Override
1562 protected void onReceivePack() {
1563 svc.finalRequest = true;
1564 }
1565 }
1566
1567 class SmartHttpPushConnection extends BasePackPushConnection {
1568 SmartHttpPushConnection(InputStream advertisement)
1569 throws TransportException {
1570 super(TransportHttp.this);
1571 statelessRPC = true;
1572
1573 init(advertisement, DisabledOutputStream.INSTANCE);
1574 outNeedsEnd = false;
1575 readAdvertisedRefs();
1576 }
1577
1578 @Override
1579 protected void doPush(ProgressMonitor monitor,
1580 Map<String, RemoteRefUpdate> refUpdates,
1581 OutputStream outputStream) throws TransportException {
1582 Service svc = new MultiRequestService(SVC_RECEIVE_PACK,
1583 getProtocolVersion());
1584 try (InputStream svcIn = svc.getInputStream();
1585 OutputStream svcOut = svc.getOutputStream()) {
1586 init(svcIn, svcOut);
1587 super.doPush(monitor, refUpdates, outputStream);
1588 } catch (TransportException e) {
1589 throw e;
1590 } catch (IOException e) {
1591 throw new TransportException(e.getMessage(), e);
1592 }
1593 }
1594 }
1595
1596
1597 abstract class Service {
1598 protected final String serviceName;
1599
1600 protected final String requestType;
1601
1602 protected final String responseType;
1603
1604 protected HttpConnection conn;
1605
1606 protected HttpOutputStream out;
1607
1608 protected final HttpExecuteStream execute;
1609
1610 protected final TransferConfig.ProtocolVersion protocolVersion;
1611
1612 final UnionInputStream in;
1613
1614 Service(String serviceName,
1615 TransferConfig.ProtocolVersion protocolVersion) {
1616 this.serviceName = serviceName;
1617 this.protocolVersion = protocolVersion;
1618 this.requestType = "application/x-" + serviceName + "-request";
1619 this.responseType = "application/x-" + serviceName + "-result";
1620
1621 this.out = new HttpOutputStream();
1622 this.execute = new HttpExecuteStream();
1623 this.in = new UnionInputStream(execute);
1624 }
1625
1626 void openStream() throws IOException {
1627 conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
1628 AcceptEncoding.GZIP);
1629 conn.setInstanceFollowRedirects(false);
1630 conn.setDoOutput(true);
1631 conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
1632 conn.setRequestProperty(HDR_ACCEPT, responseType);
1633 if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
1634 conn.setRequestProperty(GitProtocolConstants.PROTOCOL_HEADER,
1635 GitProtocolConstants.VERSION_2_REQUEST);
1636 }
1637 }
1638
1639 void sendRequest() throws IOException {
1640
1641 TemporaryBuffer buf = new TemporaryBuffer.Heap(
1642 http.getPostBuffer());
1643 try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) {
1644 out.writeTo(gzip, null);
1645 if (out.length() < buf.length())
1646 buf = out;
1647 } catch (IOException err) {
1648
1649
1650 buf = out;
1651 }
1652
1653 HttpAuthMethod authenticator = null;
1654 Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
1655
1656
1657 int authAttempts = 1;
1658 int redirects = 0;
1659 for (;;) {
1660 try {
1661
1662
1663
1664
1665
1666 openStream();
1667 if (buf != out) {
1668 conn.setRequestProperty(HDR_CONTENT_ENCODING,
1669 ENCODING_GZIP);
1670 }
1671 conn.setFixedLengthStreamingMode((int) buf.length());
1672 try (OutputStream httpOut = conn.getOutputStream()) {
1673 buf.writeTo(httpOut, null);
1674 }
1675
1676 final int status = HttpSupport.response(conn);
1677 switch (status) {
1678 case HttpConnection.HTTP_OK:
1679
1680 return;
1681
1682 case HttpConnection.HTTP_NOT_FOUND:
1683 throw createNotFoundException(uri, conn.getURL(),
1684 conn.getResponseMessage());
1685
1686 case HttpConnection.HTTP_FORBIDDEN:
1687 throw new TransportException(uri,
1688 MessageFormat.format(
1689 JGitText.get().serviceNotPermitted,
1690 baseUrl, serviceName));
1691
1692 case HttpConnection.HTTP_MOVED_PERM:
1693 case HttpConnection.HTTP_MOVED_TEMP:
1694 case HttpConnection.HTTP_11_MOVED_PERM:
1695 case HttpConnection.HTTP_11_MOVED_TEMP:
1696
1697
1698
1699 if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
1700
1701 return;
1702 }
1703 currentUri = redirect(conn.getURL(),
1704 conn.getHeaderField(HDR_LOCATION),
1705 '/' + serviceName, redirects++);
1706 try {
1707 baseUrl = toURL(currentUri);
1708 } catch (MalformedURLException e) {
1709 throw new TransportException(uri,
1710 MessageFormat.format(
1711 JGitText.get().invalidRedirectLocation,
1712 baseUrl, currentUri),
1713 e);
1714 }
1715 continue;
1716
1717 case HttpConnection.HTTP_UNAUTHORIZED:
1718 HttpAuthMethod nextMethod = HttpAuthMethod
1719 .scanResponse(conn, ignoreTypes);
1720 switch (nextMethod.getType()) {
1721 case NONE:
1722 throw new TransportException(uri,
1723 MessageFormat.format(
1724 JGitText.get().authenticationNotSupported,
1725 conn.getURL()));
1726 case NEGOTIATE:
1727
1728
1729
1730
1731
1732
1733
1734 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1735 if (authenticator != null) {
1736 ignoreTypes.add(authenticator.getType());
1737 }
1738 authAttempts = 1;
1739
1740
1741 break;
1742 default:
1743
1744
1745
1746 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1747 if (authenticator == null || authenticator
1748 .getType() != nextMethod.getType()) {
1749 if (authenticator != null) {
1750 ignoreTypes.add(authenticator.getType());
1751 }
1752 authAttempts = 1;
1753 }
1754 break;
1755 }
1756 authMethod = nextMethod;
1757 authenticator = nextMethod;
1758 CredentialsProvider credentialsProvider = getCredentialsProvider();
1759 if (credentialsProvider == null) {
1760 throw new TransportException(uri,
1761 JGitText.get().noCredentialsProvider);
1762 }
1763 if (authAttempts > 1) {
1764 credentialsProvider.reset(currentUri);
1765 }
1766 if (3 < authAttempts || !authMethod
1767 .authorize(currentUri, credentialsProvider)) {
1768 throw new TransportException(uri,
1769 JGitText.get().notAuthorized);
1770 }
1771 authAttempts++;
1772 continue;
1773
1774 default:
1775
1776
1777 return;
1778 }
1779 } catch (SSLHandshakeException e) {
1780 handleSslFailure(e);
1781 continue;
1782 } catch (SocketException | InterruptedIOException e) {
1783
1784
1785 throw e;
1786 } catch (IOException e) {
1787 if (authenticator == null || authMethod
1788 .getType() != HttpAuthMethod.Type.NONE) {
1789
1790
1791
1792
1793
1794
1795
1796
1797 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
1798 ignoreTypes.add(authMethod.getType());
1799 }
1800
1801 authMethod = HttpAuthMethod.Type.NONE.method(null);
1802 authenticator = authMethod;
1803 authAttempts = 1;
1804 continue;
1805 }
1806 throw e;
1807 }
1808 }
1809 }
1810
1811 void openResponse() throws IOException {
1812 final int status = HttpSupport.response(conn);
1813 if (status != HttpConnection.HTTP_OK) {
1814 throw new TransportException(uri, status + " "
1815 + conn.getResponseMessage());
1816 }
1817
1818 final String contentType = conn.getContentType();
1819 if (!responseType.equals(contentType)) {
1820 conn.getInputStream().close();
1821 throw wrongContentType(responseType, contentType);
1822 }
1823 }
1824
1825 HttpOutputStream getOutputStream() {
1826 return out;
1827 }
1828
1829 InputStream getInputStream() {
1830 return in;
1831 }
1832
1833 abstract void execute() throws IOException;
1834
1835 class HttpExecuteStream extends InputStream {
1836 @Override
1837 public int read() throws IOException {
1838 execute();
1839 return -1;
1840 }
1841
1842 @Override
1843 public int read(byte[] b, int off, int len) throws IOException {
1844 execute();
1845 return -1;
1846 }
1847
1848 @Override
1849 public long skip(long n) throws IOException {
1850 execute();
1851 return 0;
1852 }
1853 }
1854
1855 class HttpOutputStream extends TemporaryBuffer {
1856 HttpOutputStream() {
1857 super(http.getPostBuffer());
1858 }
1859
1860 @Override
1861 protected OutputStream overflow() throws IOException {
1862 openStream();
1863 conn.setChunkedStreamingMode(0);
1864 return conn.getOutputStream();
1865 }
1866 }
1867 }
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889 class MultiRequestService extends Service {
1890 boolean finalRequest;
1891
1892 MultiRequestService(String serviceName,
1893 TransferConfig.ProtocolVersion protocolVersion) {
1894 super(serviceName, protocolVersion);
1895 }
1896
1897
1898 @Override
1899 void execute() throws IOException {
1900 out.close();
1901
1902 if (conn == null) {
1903 if (out.length() == 0) {
1904
1905
1906
1907
1908
1909 if (finalRequest)
1910 return;
1911 throw new TransportException(uri,
1912 JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
1913 }
1914
1915 sendRequest();
1916 }
1917
1918 out.reset();
1919
1920 openResponse();
1921
1922 in.add(openInputStream(conn));
1923 if (!finalRequest)
1924 in.add(execute);
1925 conn = null;
1926 }
1927 }
1928
1929
1930 class LongPollService extends Service {
1931
1932 LongPollService(String serviceName,
1933 TransferConfig.ProtocolVersion protocolVersion) {
1934 super(serviceName, protocolVersion);
1935 }
1936
1937
1938 @Override
1939 void execute() throws IOException {
1940 out.close();
1941 if (conn == null)
1942 sendRequest();
1943 openResponse();
1944 in.add(openInputStream(conn));
1945 }
1946 }
1947 }