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