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.FileNotFoundException;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.io.InterruptedIOException;
43 import java.io.OutputStream;
44 import java.io.UnsupportedEncodingException;
45 import java.net.HttpCookie;
46 import java.net.MalformedURLException;
47 import java.net.Proxy;
48 import java.net.ProxySelector;
49 import java.net.SocketException;
50 import java.net.URI;
51 import java.net.URISyntaxException;
52 import java.net.URL;
53 import java.net.URLDecoder;
54 import java.nio.charset.StandardCharsets;
55 import java.nio.file.InvalidPathException;
56 import java.nio.file.Path;
57 import java.nio.file.Paths;
58 import java.security.GeneralSecurityException;
59 import java.security.cert.CertPathBuilderException;
60 import java.security.cert.CertPathValidatorException;
61 import java.security.cert.CertificateException;
62 import java.text.MessageFormat;
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collection;
66 import java.util.Collections;
67 import java.util.EnumSet;
68 import java.util.HashSet;
69 import java.util.LinkedHashSet;
70 import java.util.LinkedList;
71 import java.util.List;
72 import java.util.Locale;
73 import java.util.Map;
74 import java.util.Set;
75 import java.util.TreeMap;
76 import java.util.zip.GZIPInputStream;
77 import java.util.zip.GZIPOutputStream;
78
79 import javax.net.ssl.SSLHandshakeException;
80
81 import org.eclipse.jgit.annotations.NonNull;
82 import org.eclipse.jgit.errors.ConfigInvalidException;
83 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
84 import org.eclipse.jgit.errors.NotSupportedException;
85 import org.eclipse.jgit.errors.PackProtocolException;
86 import org.eclipse.jgit.errors.TransportException;
87 import org.eclipse.jgit.internal.JGitText;
88 import org.eclipse.jgit.internal.storage.file.RefDirectory;
89 import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
90 import org.eclipse.jgit.internal.transport.http.NetscapeCookieFileCache;
91 import org.eclipse.jgit.lib.Constants;
92 import org.eclipse.jgit.lib.ObjectId;
93 import org.eclipse.jgit.lib.ObjectIdRef;
94 import org.eclipse.jgit.lib.ProgressMonitor;
95 import org.eclipse.jgit.lib.Ref;
96 import org.eclipse.jgit.lib.Repository;
97 import org.eclipse.jgit.lib.StoredConfig;
98 import org.eclipse.jgit.lib.SymbolicRef;
99 import org.eclipse.jgit.transport.HttpAuthMethod.Type;
100 import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
101 import org.eclipse.jgit.transport.http.HttpConnection;
102 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
103 import org.eclipse.jgit.transport.http.HttpConnectionFactory2;
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 static NetscapeCookieFile getCookieFileFromConfig(
1161 HttpConfig config) {
1162 if (!StringUtils.isEmptyOrNull(config.getCookieFile())) {
1163 try {
1164 Path cookieFilePath = Paths.get(config.getCookieFile());
1165 return NetscapeCookieFileCache.getInstance(config)
1166 .getEntry(cookieFilePath);
1167 } catch (InvalidPathException e) {
1168 LOG.warn(MessageFormat.format(
1169 JGitText.get().couldNotReadCookieFile,
1170 config.getCookieFile()), e);
1171 }
1172 }
1173 return null;
1174 }
1175
1176 private static Set<HttpCookie> filterCookies(NetscapeCookieFile cookieFile,
1177 URL url) {
1178 if (cookieFile != null) {
1179 return filterCookies(cookieFile.getCookies(true), url);
1180 }
1181 return Collections.emptySet();
1182 }
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194 private static Set<HttpCookie> filterCookies(Set<HttpCookie> allCookies,
1195 URL url) {
1196 Set<HttpCookie> filteredCookies = new HashSet<>();
1197 for (HttpCookie cookie : allCookies) {
1198 if (cookie.hasExpired()) {
1199 continue;
1200 }
1201 if (!matchesCookieDomain(url.getHost(), cookie.getDomain())) {
1202 continue;
1203 }
1204 if (!matchesCookiePath(url.getPath(), cookie.getPath())) {
1205 continue;
1206 }
1207 if (cookie.getSecure() && !"https".equals(url.getProtocol())) {
1208 continue;
1209 }
1210 filteredCookies.add(cookie);
1211 }
1212 return filteredCookies;
1213 }
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
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 static boolean matchesCookieDomain(String host, String cookieDomain) {
1258 cookieDomain = cookieDomain.toLowerCase(Locale.ROOT);
1259 host = host.toLowerCase(Locale.ROOT);
1260 if (host.equals(cookieDomain)) {
1261 return true;
1262 }
1263 if (!host.endsWith(cookieDomain)) {
1264 return false;
1265 }
1266 return host.charAt(host.length() - cookieDomain.length() - 1) == '.';
1267 }
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292 static boolean matchesCookiePath(String path, String cookiePath) {
1293 if (cookiePath.equals(path)) {
1294 return true;
1295 }
1296 if (!cookiePath.endsWith("/")) {
1297 cookiePath += "/";
1298 }
1299 return path.startsWith(cookiePath);
1300 }
1301
1302 private boolean isSmartHttp(HttpConnection c, String service) {
1303 final String expType = "application/x-" + service + "-advertisement";
1304 final String actType = c.getContentType();
1305 return expType.equals(actType);
1306 }
1307
1308 private boolean isGzipContent(HttpConnection c) {
1309 return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
1310 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
1311 }
1312
1313 private void readSmartHeaders(InputStream in, String service)
1314 throws IOException {
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324 final byte[] magic = new byte[14];
1325 if (!in.markSupported()) {
1326 throw new TransportException(uri,
1327 JGitText.get().inputStreamMustSupportMark);
1328 }
1329 in.mark(14);
1330 IO.readFully(in, magic, 0, magic.length);
1331
1332
1333 if (Arrays.equals(Arrays.copyOfRange(magic, 4, 11), VERSION)
1334 && magic[12] >= '1' && magic[12] <= '9') {
1335
1336
1337 in.reset();
1338 return;
1339 }
1340 if (magic[4] != '#') {
1341 throw new TransportException(uri, MessageFormat.format(
1342 JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
1343 }
1344 in.reset();
1345 final PacketLineInLineIn.html#PacketLineIn">PacketLineIn pckIn = new PacketLineIn(in);
1346 final String exp = "# service=" + service;
1347 final String act = pckIn.readString();
1348 if (!exp.equals(act)) {
1349 throw new TransportException(uri, MessageFormat.format(
1350 JGitText.get().expectedGot, exp, act));
1351 }
1352
1353 while (!PacketLineIn.isEnd(pckIn.readString())) {
1354
1355 }
1356 }
1357
1358 class HttpObjectDB extends WalkRemoteObjectDatabase {
1359 private final URL httpObjectsUrl;
1360
1361 HttpObjectDB(URL b) {
1362 httpObjectsUrl = b;
1363 }
1364
1365 @Override
1366 URIish getURI() {
1367 return new URIish(httpObjectsUrl);
1368 }
1369
1370 @Override
1371 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
1372 try {
1373 return readAlternates(INFO_HTTP_ALTERNATES);
1374 } catch (FileNotFoundException err) {
1375
1376 }
1377
1378 try {
1379 return readAlternates(INFO_ALTERNATES);
1380 } catch (FileNotFoundException err) {
1381
1382 }
1383
1384 return null;
1385 }
1386
1387 @Override
1388 WalkRemoteObjectDatabase openAlternate(String location)
1389 throws IOException {
1390 return new HttpObjectDB(new URL(httpObjectsUrl, location));
1391 }
1392
1393 @Override
1394 BufferedReader openReader(String path) throws IOException {
1395
1396
1397 InputStream is = open(path, AcceptEncoding.GZIP).in;
1398 return new BufferedReader(new InputStreamReader(is, UTF_8));
1399 }
1400
1401 @Override
1402 Collection<String> getPackNames() throws IOException {
1403 final Collection<String> packs = new ArrayList<>();
1404 try (BufferedReader br = openReader(INFO_PACKS)) {
1405 for (;;) {
1406 final String s = br.readLine();
1407 if (s == null || s.length() == 0)
1408 break;
1409 if (!s.startsWith("P pack-") || !s.endsWith(".pack"))
1410 throw invalidAdvertisement(s);
1411 packs.add(s.substring(2));
1412 }
1413 return packs;
1414 } catch (FileNotFoundException err) {
1415 return packs;
1416 }
1417 }
1418
1419 @Override
1420 FileStream open(String path) throws IOException {
1421 return open(path, AcceptEncoding.UNSPECIFIED);
1422 }
1423
1424 FileStream open(String path, AcceptEncoding acceptEncoding)
1425 throws IOException {
1426 final URL base = httpObjectsUrl;
1427 final URL u = new URL(base, path);
1428 final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
1429 switch (HttpSupport.response(c)) {
1430 case HttpConnection.HTTP_OK:
1431 final InputStream in = openInputStream(c);
1432
1433
1434
1435 if (!isGzipContent(c)) {
1436 final int len = c.getContentLength();
1437 return new FileStream(in, len);
1438 }
1439 return new FileStream(in);
1440 case HttpConnection.HTTP_NOT_FOUND:
1441 throw new FileNotFoundException(u.toString());
1442 default:
1443 throw new IOException(u.toString() + ": "
1444 + HttpSupport.response(c) + " "
1445 + c.getResponseMessage());
1446 }
1447 }
1448
1449 Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
1450 throws IOException, PackProtocolException {
1451 final TreeMap<String, Ref> avail = new TreeMap<>();
1452 for (;;) {
1453 String line = br.readLine();
1454 if (line == null)
1455 break;
1456
1457 final int tab = line.indexOf('\t');
1458 if (tab < 0)
1459 throw invalidAdvertisement(line);
1460
1461 String name;
1462 final ObjectId id;
1463
1464 name = line.substring(tab + 1);
1465 id = ObjectId.fromString(line.substring(0, tab));
1466 if (name.endsWith("^{}")) {
1467 name = name.substring(0, name.length() - 3);
1468 final Ref prior = avail.get(name);
1469 if (prior == null)
1470 throw outOfOrderAdvertisement(name);
1471
1472 if (prior.getPeeledObjectId() != null)
1473 throw duplicateAdvertisement(name + "^{}");
1474
1475 avail.put(name, new ObjectIdRef.PeeledTag(
1476 Ref.Storage.NETWORK, name,
1477 prior.getObjectId(), id));
1478 } else {
1479 Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
1480 Ref.Storage.NETWORK, name, id));
1481 if (prior != null)
1482 throw duplicateAdvertisement(name);
1483 }
1484 }
1485 return avail;
1486 }
1487
1488 private PackProtocolException outOfOrderAdvertisement(String n) {
1489 return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
1490 }
1491
1492 private PackProtocolException invalidAdvertisement(String n) {
1493 return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
1494 }
1495
1496 private PackProtocolException duplicateAdvertisement(String n) {
1497 return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
1498 }
1499
1500 @Override
1501 void close() {
1502
1503 }
1504 }
1505
1506 class SmartHttpFetchConnection extends BasePackFetchConnection {
1507 private MultiRequestService svc;
1508
1509 SmartHttpFetchConnection(InputStream advertisement)
1510 throws TransportException {
1511 this(advertisement, Collections.emptyList());
1512 }
1513
1514 SmartHttpFetchConnection(InputStream advertisement,
1515 Collection<RefSpec> refSpecs, String... additionalPatterns)
1516 throws TransportException {
1517 super(TransportHttp.this);
1518 statelessRPC = true;
1519
1520 init(advertisement, DisabledOutputStream.INSTANCE);
1521 outNeedsEnd = false;
1522 if (!readAdvertisedRefs()) {
1523
1524 LongPollService service = new LongPollService(SVC_UPLOAD_PACK,
1525 getProtocolVersion());
1526 init(service.getInputStream(), service.getOutputStream());
1527 lsRefs(refSpecs, additionalPatterns);
1528 }
1529 }
1530
1531 @Override
1532 protected void doFetch(final ProgressMonitor monitor,
1533 final Collection<Ref> want, final Set<ObjectId> have,
1534 final OutputStream outputStream) throws TransportException {
1535 try {
1536 svc = new MultiRequestService(SVC_UPLOAD_PACK,
1537 getProtocolVersion());
1538 init(svc.getInputStream(), svc.getOutputStream());
1539 super.doFetch(monitor, want, have, outputStream);
1540 } finally {
1541 svc = null;
1542 }
1543 }
1544
1545 @Override
1546 protected void onReceivePack() {
1547 svc.finalRequest = true;
1548 }
1549 }
1550
1551 class SmartHttpPushConnection extends BasePackPushConnection {
1552 SmartHttpPushConnection(InputStream advertisement)
1553 throws TransportException {
1554 super(TransportHttp.this);
1555 statelessRPC = true;
1556
1557 init(advertisement, DisabledOutputStream.INSTANCE);
1558 outNeedsEnd = false;
1559 readAdvertisedRefs();
1560 }
1561
1562 @Override
1563 protected void doPush(final ProgressMonitor monitor,
1564 final Map<String, RemoteRefUpdate> refUpdates,
1565 OutputStream outputStream) throws TransportException {
1566 final Service svc = new MultiRequestService(SVC_RECEIVE_PACK,
1567 getProtocolVersion());
1568 init(svc.getInputStream(), svc.getOutputStream());
1569 super.doPush(monitor, refUpdates, outputStream);
1570 }
1571 }
1572
1573
1574 abstract class Service {
1575 protected final String serviceName;
1576
1577 protected final String requestType;
1578
1579 protected final String responseType;
1580
1581 protected HttpConnection conn;
1582
1583 protected HttpOutputStream out;
1584
1585 protected final HttpExecuteStream execute;
1586
1587 protected final TransferConfig.ProtocolVersion protocolVersion;
1588
1589 final UnionInputStream in;
1590
1591 Service(String serviceName,
1592 TransferConfig.ProtocolVersion protocolVersion) {
1593 this.serviceName = serviceName;
1594 this.protocolVersion = protocolVersion;
1595 this.requestType = "application/x-" + serviceName + "-request";
1596 this.responseType = "application/x-" + serviceName + "-result";
1597
1598 this.out = new HttpOutputStream();
1599 this.execute = new HttpExecuteStream();
1600 this.in = new UnionInputStream(execute);
1601 }
1602
1603 void openStream() throws IOException {
1604 conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
1605 AcceptEncoding.GZIP);
1606 conn.setInstanceFollowRedirects(false);
1607 conn.setDoOutput(true);
1608 conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
1609 conn.setRequestProperty(HDR_ACCEPT, responseType);
1610 if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
1611 conn.setRequestProperty(GitProtocolConstants.PROTOCOL_HEADER,
1612 GitProtocolConstants.VERSION_2_REQUEST);
1613 }
1614 }
1615
1616 void sendRequest() throws IOException {
1617
1618 TemporaryBuffer buf = new TemporaryBuffer.Heap(
1619 http.getPostBuffer());
1620 try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) {
1621 out.writeTo(gzip, null);
1622 if (out.length() < buf.length())
1623 buf = out;
1624 } catch (IOException err) {
1625
1626
1627 buf = out;
1628 }
1629
1630 HttpAuthMethod authenticator = null;
1631 Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
1632
1633
1634 int authAttempts = 1;
1635 int redirects = 0;
1636 for (;;) {
1637 try {
1638
1639
1640
1641
1642
1643 openStream();
1644 if (buf != out) {
1645 conn.setRequestProperty(HDR_CONTENT_ENCODING,
1646 ENCODING_GZIP);
1647 }
1648 conn.setFixedLengthStreamingMode((int) buf.length());
1649 try (OutputStream httpOut = conn.getOutputStream()) {
1650 buf.writeTo(httpOut, null);
1651 }
1652
1653 final int status = HttpSupport.response(conn);
1654 switch (status) {
1655 case HttpConnection.HTTP_OK:
1656
1657 return;
1658
1659 case HttpConnection.HTTP_NOT_FOUND:
1660 throw createNotFoundException(uri, conn.getURL(),
1661 conn.getResponseMessage());
1662
1663 case HttpConnection.HTTP_FORBIDDEN:
1664 throw new TransportException(uri,
1665 MessageFormat.format(
1666 JGitText.get().serviceNotPermitted,
1667 baseUrl, serviceName));
1668
1669 case HttpConnection.HTTP_MOVED_PERM:
1670 case HttpConnection.HTTP_MOVED_TEMP:
1671 case HttpConnection.HTTP_11_MOVED_PERM:
1672 case HttpConnection.HTTP_11_MOVED_TEMP:
1673
1674
1675
1676 if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
1677
1678 return;
1679 }
1680 currentUri = redirect(conn.getURL(),
1681 conn.getHeaderField(HDR_LOCATION),
1682 '/' + serviceName, redirects++);
1683 try {
1684 baseUrl = toURL(currentUri);
1685 } catch (MalformedURLException e) {
1686 throw new TransportException(uri,
1687 MessageFormat.format(
1688 JGitText.get().invalidRedirectLocation,
1689 baseUrl, currentUri),
1690 e);
1691 }
1692 continue;
1693
1694 case HttpConnection.HTTP_UNAUTHORIZED:
1695 HttpAuthMethod nextMethod = HttpAuthMethod
1696 .scanResponse(conn, ignoreTypes);
1697 switch (nextMethod.getType()) {
1698 case NONE:
1699 throw new TransportException(uri,
1700 MessageFormat.format(
1701 JGitText.get().authenticationNotSupported,
1702 conn.getURL()));
1703 case NEGOTIATE:
1704
1705
1706
1707
1708
1709
1710
1711 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1712 if (authenticator != null) {
1713 ignoreTypes.add(authenticator.getType());
1714 }
1715 authAttempts = 1;
1716
1717
1718 break;
1719 default:
1720
1721
1722
1723 ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
1724 if (authenticator == null || authenticator
1725 .getType() != nextMethod.getType()) {
1726 if (authenticator != null) {
1727 ignoreTypes.add(authenticator.getType());
1728 }
1729 authAttempts = 1;
1730 }
1731 break;
1732 }
1733 authMethod = nextMethod;
1734 authenticator = nextMethod;
1735 CredentialsProvider credentialsProvider = getCredentialsProvider();
1736 if (credentialsProvider == null) {
1737 throw new TransportException(uri,
1738 JGitText.get().noCredentialsProvider);
1739 }
1740 if (authAttempts > 1) {
1741 credentialsProvider.reset(currentUri);
1742 }
1743 if (3 < authAttempts || !authMethod
1744 .authorize(currentUri, credentialsProvider)) {
1745 throw new TransportException(uri,
1746 JGitText.get().notAuthorized);
1747 }
1748 authAttempts++;
1749 continue;
1750
1751 default:
1752
1753
1754 return;
1755 }
1756 } catch (SSLHandshakeException e) {
1757 handleSslFailure(e);
1758 continue;
1759 } catch (SocketException | InterruptedIOException e) {
1760
1761
1762 throw e;
1763 } catch (IOException e) {
1764 if (authenticator == null || authMethod
1765 .getType() != HttpAuthMethod.Type.NONE) {
1766
1767
1768
1769
1770
1771
1772
1773
1774 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
1775 ignoreTypes.add(authMethod.getType());
1776 }
1777
1778 authMethod = HttpAuthMethod.Type.NONE.method(null);
1779 authenticator = authMethod;
1780 authAttempts = 1;
1781 continue;
1782 }
1783 throw e;
1784 }
1785 }
1786 }
1787
1788 void openResponse() throws IOException {
1789 final int status = HttpSupport.response(conn);
1790 if (status != HttpConnection.HTTP_OK) {
1791 throw new TransportException(uri, status + " "
1792 + conn.getResponseMessage());
1793 }
1794
1795 final String contentType = conn.getContentType();
1796 if (!responseType.equals(contentType)) {
1797 conn.getInputStream().close();
1798 throw wrongContentType(responseType, contentType);
1799 }
1800 }
1801
1802 HttpOutputStream getOutputStream() {
1803 return out;
1804 }
1805
1806 InputStream getInputStream() {
1807 return in;
1808 }
1809
1810 abstract void execute() throws IOException;
1811
1812 class HttpExecuteStream extends InputStream {
1813 @Override
1814 public int read() throws IOException {
1815 execute();
1816 return -1;
1817 }
1818
1819 @Override
1820 public int read(byte[] b, int off, int len) throws IOException {
1821 execute();
1822 return -1;
1823 }
1824
1825 @Override
1826 public long skip(long n) throws IOException {
1827 execute();
1828 return 0;
1829 }
1830 }
1831
1832 class HttpOutputStream extends TemporaryBuffer {
1833 HttpOutputStream() {
1834 super(http.getPostBuffer());
1835 }
1836
1837 @Override
1838 protected OutputStream overflow() throws IOException {
1839 openStream();
1840 conn.setChunkedStreamingMode(0);
1841 return conn.getOutputStream();
1842 }
1843 }
1844 }
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866 class MultiRequestService extends Service {
1867 boolean finalRequest;
1868
1869 MultiRequestService(String serviceName,
1870 TransferConfig.ProtocolVersion protocolVersion) {
1871 super(serviceName, protocolVersion);
1872 }
1873
1874
1875 @Override
1876 void execute() throws IOException {
1877 out.close();
1878
1879 if (conn == null) {
1880 if (out.length() == 0) {
1881
1882
1883
1884
1885
1886 if (finalRequest)
1887 return;
1888 throw new TransportException(uri,
1889 JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
1890 }
1891
1892 sendRequest();
1893 }
1894
1895 out.reset();
1896
1897 openResponse();
1898
1899 in.add(openInputStream(conn));
1900 if (!finalRequest)
1901 in.add(execute);
1902 conn = null;
1903 }
1904 }
1905
1906
1907 class LongPollService extends Service {
1908
1909 LongPollService(String serviceName,
1910 TransferConfig.ProtocolVersion protocolVersion) {
1911 super(serviceName, protocolVersion);
1912 }
1913
1914
1915 @Override
1916 void execute() throws IOException {
1917 out.close();
1918 if (conn == null)
1919 sendRequest();
1920 openResponse();
1921 in.add(openInputStream(conn));
1922 }
1923 }
1924 }