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 TransportProtocoll.html#TransportProtocol">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 TransportProtocolol.html#TransportProtocol">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 PacketLineInLineIn.html#PacketLineIn">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(final ProgressMonitor monitor,
1544 final Collection<Ref> want, final Set<ObjectId> have,
1545 final OutputStream outputStream) throws TransportException {
1546 try {
1547 svc = new MultiRequestService(SVC_UPLOAD_PACK,
1548 getProtocolVersion());
1549 init(svc.getInputStream(), svc.getOutputStream());
1550 super.doFetch(monitor, want, have, outputStream);
1551 } finally {
1552 svc = null;
1553 }
1554 }
1555
1556 @Override
1557 protected void onReceivePack() {
1558 svc.finalRequest = true;
1559 }
1560 }
1561
1562 class SmartHttpPushConnection extends BasePackPushConnection {
1563 SmartHttpPushConnection(InputStream advertisement)
1564 throws TransportException {
1565 super(TransportHttp.this);
1566 statelessRPC = true;
1567
1568 init(advertisement, DisabledOutputStream.INSTANCE);
1569 outNeedsEnd = false;
1570 readAdvertisedRefs();
1571 }
1572
1573 @Override
1574 protected void doPush(final ProgressMonitor monitor,
1575 final Map<String, RemoteRefUpdate> refUpdates,
1576 OutputStream outputStream) throws TransportException {
1577 final Service svc = new MultiRequestService(SVC_RECEIVE_PACK,
1578 getProtocolVersion());
1579 init(svc.getInputStream(), svc.getOutputStream());
1580 super.doPush(monitor, refUpdates, outputStream);
1581 }
1582 }
1583
1584
1585 abstract class Service {
1586 protected final String serviceName;
1587
1588 protected final String requestType;
1589
1590 protected final String responseType;
1591
1592 protected HttpConnection conn;
1593
1594 protected HttpOutputStream out;
1595
1596 protected final HttpExecuteStream execute;
1597
1598 protected final TransferConfig.ProtocolVersion protocolVersion;
1599
1600 final UnionInputStream in;
1601
1602 Service(String serviceName,
1603 TransferConfig.ProtocolVersion protocolVersion) {
1604 this.serviceName = serviceName;
1605 this.protocolVersion = protocolVersion;
1606 this.requestType = "application/x-" + serviceName + "-request";
1607 this.responseType = "application/x-" + serviceName + "-result";
1608
1609 this.out = new HttpOutputStream();
1610 this.execute = new HttpExecuteStream();
1611 this.in = new UnionInputStream(execute);
1612 }
1613
1614 void openStream() throws IOException {
1615 conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
1616 AcceptEncoding.GZIP);
1617 conn.setInstanceFollowRedirects(false);
1618 conn.setDoOutput(true);
1619 conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
1620 conn.setRequestProperty(HDR_ACCEPT, responseType);
1621 if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
1622 conn.setRequestProperty(GitProtocolConstants.PROTOCOL_HEADER,
1623 GitProtocolConstants.VERSION_2_REQUEST);
1624 }
1625 }
1626
1627 void sendRequest() throws IOException {
1628
1629 TemporaryBuffer buf = new TemporaryBuffer.Heap(
1630 http.getPostBuffer());
1631 try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) {
1632 out.writeTo(gzip, null);
1633 if (out.length() < buf.length())
1634 buf = out;
1635 } catch (IOException err) {
1636
1637
1638 buf = out;
1639 }
1640
1641 HttpAuthMethod authenticator = null;
1642 Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
1643
1644
1645 int authAttempts = 1;
1646 int redirects = 0;
1647 for (;;) {
1648 try {
1649
1650
1651
1652
1653
1654 openStream();
1655 if (buf != out) {
1656 conn.setRequestProperty(HDR_CONTENT_ENCODING,
1657 ENCODING_GZIP);
1658 }
1659 conn.setFixedLengthStreamingMode((int) buf.length());
1660 try (OutputStream httpOut = conn.getOutputStream()) {
1661 buf.writeTo(httpOut, null);
1662 }
1663
1664 final int status = HttpSupport.response(conn);
1665 switch (status) {
1666 case HttpConnection.HTTP_OK:
1667
1668 return;
1669
1670 case HttpConnection.HTTP_NOT_FOUND:
1671 throw createNotFoundException(uri, conn.getURL(),
1672 conn.getResponseMessage());
1673
1674 case HttpConnection.HTTP_FORBIDDEN:
1675 throw new TransportException(uri,
1676 MessageFormat.format(
1677 JGitText.get().serviceNotPermitted,
1678 baseUrl, serviceName));
1679
1680 case HttpConnection.HTTP_MOVED_PERM:
1681 case HttpConnection.HTTP_MOVED_TEMP:
1682 case HttpConnection.HTTP_11_MOVED_PERM:
1683 case HttpConnection.HTTP_11_MOVED_TEMP:
1684
1685
1686
1687 if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
1688
1689 return;
1690 }
1691 currentUri = redirect(conn.getURL(),
1692 conn.getHeaderField(HDR_LOCATION),
1693 '/' + serviceName, redirects++);
1694 try {
1695 baseUrl = toURL(currentUri);
1696 } catch (MalformedURLException e) {
1697 throw new TransportException(uri,
1698 MessageFormat.format(
1699 JGitText.get().invalidRedirectLocation,
1700 baseUrl, currentUri),
1701 e);
1702 }
1703 continue;
1704
1705 case HttpConnection.HTTP_UNAUTHORIZED:
1706 HttpAuthMethod nextMethod = HttpAuthMethod
1707 .scanResponse(conn, ignoreTypes);
1708 switch (nextMethod.getType()) {
1709 case NONE:
1710 throw new TransportException(uri,
1711 MessageFormat.format(
1712 JGitText.get().authenticationNotSupported,
1713 conn.getURL()));
1714 case NEGOTIATE:
1715
1716
1717
1718
1719
1720
1721
1722 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1723 if (authenticator != null) {
1724 ignoreTypes.add(authenticator.getType());
1725 }
1726 authAttempts = 1;
1727
1728
1729 break;
1730 default:
1731
1732
1733
1734 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1735 if (authenticator == null || authenticator
1736 .getType() != nextMethod.getType()) {
1737 if (authenticator != null) {
1738 ignoreTypes.add(authenticator.getType());
1739 }
1740 authAttempts = 1;
1741 }
1742 break;
1743 }
1744 authMethod = nextMethod;
1745 authenticator = nextMethod;
1746 CredentialsProvider credentialsProvider = getCredentialsProvider();
1747 if (credentialsProvider == null) {
1748 throw new TransportException(uri,
1749 JGitText.get().noCredentialsProvider);
1750 }
1751 if (authAttempts > 1) {
1752 credentialsProvider.reset(currentUri);
1753 }
1754 if (3 < authAttempts || !authMethod
1755 .authorize(currentUri, credentialsProvider)) {
1756 throw new TransportException(uri,
1757 JGitText.get().notAuthorized);
1758 }
1759 authAttempts++;
1760 continue;
1761
1762 default:
1763
1764
1765 return;
1766 }
1767 } catch (SSLHandshakeException e) {
1768 handleSslFailure(e);
1769 continue;
1770 } catch (SocketException | InterruptedIOException e) {
1771
1772
1773 throw e;
1774 } catch (IOException e) {
1775 if (authenticator == null || authMethod
1776 .getType() != HttpAuthMethod.Type.NONE) {
1777
1778
1779
1780
1781
1782
1783
1784
1785 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
1786 ignoreTypes.add(authMethod.getType());
1787 }
1788
1789 authMethod = HttpAuthMethod.Type.NONE.method(null);
1790 authenticator = authMethod;
1791 authAttempts = 1;
1792 continue;
1793 }
1794 throw e;
1795 }
1796 }
1797 }
1798
1799 void openResponse() throws IOException {
1800 final int status = HttpSupport.response(conn);
1801 if (status != HttpConnection.HTTP_OK) {
1802 throw new TransportException(uri, status + " "
1803 + conn.getResponseMessage());
1804 }
1805
1806 final String contentType = conn.getContentType();
1807 if (!responseType.equals(contentType)) {
1808 conn.getInputStream().close();
1809 throw wrongContentType(responseType, contentType);
1810 }
1811 }
1812
1813 HttpOutputStream getOutputStream() {
1814 return out;
1815 }
1816
1817 InputStream getInputStream() {
1818 return in;
1819 }
1820
1821 abstract void execute() throws IOException;
1822
1823 class HttpExecuteStream extends InputStream {
1824 @Override
1825 public int read() throws IOException {
1826 execute();
1827 return -1;
1828 }
1829
1830 @Override
1831 public int read(byte[] b, int off, int len) throws IOException {
1832 execute();
1833 return -1;
1834 }
1835
1836 @Override
1837 public long skip(long n) throws IOException {
1838 execute();
1839 return 0;
1840 }
1841 }
1842
1843 class HttpOutputStream extends TemporaryBuffer {
1844 HttpOutputStream() {
1845 super(http.getPostBuffer());
1846 }
1847
1848 @Override
1849 protected OutputStream overflow() throws IOException {
1850 openStream();
1851 conn.setChunkedStreamingMode(0);
1852 return conn.getOutputStream();
1853 }
1854 }
1855 }
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877 class MultiRequestService extends Service {
1878 boolean finalRequest;
1879
1880 MultiRequestService(String serviceName,
1881 TransferConfig.ProtocolVersion protocolVersion) {
1882 super(serviceName, protocolVersion);
1883 }
1884
1885
1886 @Override
1887 void execute() throws IOException {
1888 out.close();
1889
1890 if (conn == null) {
1891 if (out.length() == 0) {
1892
1893
1894
1895
1896
1897 if (finalRequest)
1898 return;
1899 throw new TransportException(uri,
1900 JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
1901 }
1902
1903 sendRequest();
1904 }
1905
1906 out.reset();
1907
1908 openResponse();
1909
1910 in.add(openInputStream(conn));
1911 if (!finalRequest)
1912 in.add(execute);
1913 conn = null;
1914 }
1915 }
1916
1917
1918 class LongPollService extends Service {
1919
1920 LongPollService(String serviceName,
1921 TransferConfig.ProtocolVersion protocolVersion) {
1922 super(serviceName, protocolVersion);
1923 }
1924
1925
1926 @Override
1927 void execute() throws IOException {
1928 out.close();
1929 if (conn == null)
1930 sendRequest();
1931 openResponse();
1932 in.add(openInputStream(conn));
1933 }
1934 }
1935 }