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 package org.eclipse.jgit.transport;
47
48 import static org.eclipse.jgit.lib.Constants.HEAD;
49 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
50 import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
51 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
52 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
53 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
54 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
55 import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION;
56 import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
57 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
58 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
59 import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
60 import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
61
62 import java.io.BufferedReader;
63 import java.io.ByteArrayInputStream;
64 import java.io.FileNotFoundException;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.io.InputStreamReader;
68 import java.io.OutputStream;
69 import java.net.MalformedURLException;
70 import java.net.Proxy;
71 import java.net.ProxySelector;
72 import java.net.URL;
73 import java.text.MessageFormat;
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.Collection;
77 import java.util.Collections;
78 import java.util.EnumSet;
79 import java.util.HashSet;
80 import java.util.LinkedHashSet;
81 import java.util.Map;
82 import java.util.Set;
83 import java.util.TreeMap;
84 import java.util.zip.GZIPInputStream;
85 import java.util.zip.GZIPOutputStream;
86
87 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
88 import org.eclipse.jgit.errors.NotSupportedException;
89 import org.eclipse.jgit.errors.PackProtocolException;
90 import org.eclipse.jgit.errors.TransportException;
91 import org.eclipse.jgit.internal.JGitText;
92 import org.eclipse.jgit.internal.storage.file.RefDirectory;
93 import org.eclipse.jgit.lib.Config;
94 import org.eclipse.jgit.lib.Config.SectionParser;
95 import org.eclipse.jgit.lib.Constants;
96 import org.eclipse.jgit.lib.ObjectId;
97 import org.eclipse.jgit.lib.ObjectIdRef;
98 import org.eclipse.jgit.lib.ProgressMonitor;
99 import org.eclipse.jgit.lib.Ref;
100 import org.eclipse.jgit.lib.Repository;
101 import org.eclipse.jgit.lib.SymbolicRef;
102 import org.eclipse.jgit.transport.HttpAuthMethod.Type;
103 import org.eclipse.jgit.transport.http.HttpConnection;
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.TemporaryBuffer;
108 import org.eclipse.jgit.util.io.DisabledOutputStream;
109 import org.eclipse.jgit.util.io.UnionInputStream;
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public class TransportHttp extends HttpTransport implements WalkTransport,
128 PackTransport {
129
130 private static final String SVC_UPLOAD_PACK = "git-upload-pack";
131
132 private static final String SVC_RECEIVE_PACK = "git-receive-pack";
133
134
135
136
137
138
139
140 public enum AcceptEncoding {
141
142
143
144
145 UNSPECIFIED,
146
147
148
149
150 GZIP
151 }
152
153 static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
154 private final String[] schemeNames = { "http", "https" };
155
156 private final Set<String> schemeSet = Collections
157 .unmodifiableSet(new LinkedHashSet<>(Arrays
158 .asList(schemeNames)));
159
160 @Override
161 public String getName() {
162 return JGitText.get().transportProtoHTTP;
163 }
164
165 @Override
166 public Set<String> getSchemes() {
167 return schemeSet;
168 }
169
170 @Override
171 public Set<URIishField> getRequiredFields() {
172 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
173 URIishField.PATH));
174 }
175
176 @Override
177 public Set<URIishField> getOptionalFields() {
178 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
179 URIishField.PASS, URIishField.PORT));
180 }
181
182 @Override
183 public int getDefaultPort() {
184 return 80;
185 }
186
187 @Override
188 public Transport open(URIish uri, Repository local, String remoteName)
189 throws NotSupportedException {
190 return new TransportHttp(local, uri);
191 }
192
193 @Override
194 public Transport open(URIish uri) throws NotSupportedException {
195 return new TransportHttp(uri);
196 }
197 };
198
199 static final TransportProtocol PROTO_FTP = new TransportProtocol() {
200 @Override
201 public String getName() {
202 return JGitText.get().transportProtoFTP;
203 }
204
205 @Override
206 public Set<String> getSchemes() {
207 return Collections.singleton("ftp");
208 }
209
210 @Override
211 public Set<URIishField> getRequiredFields() {
212 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
213 URIishField.PATH));
214 }
215
216 @Override
217 public Set<URIishField> getOptionalFields() {
218 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
219 URIishField.PASS, URIishField.PORT));
220 }
221
222 @Override
223 public int getDefaultPort() {
224 return 21;
225 }
226
227 @Override
228 public Transport open(URIish uri, Repository local, String remoteName)
229 throws NotSupportedException {
230 return new TransportHttp(local, uri);
231 }
232 };
233
234 private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() {
235 @Override
236 public HttpConfig parse(final Config cfg) {
237 return new HttpConfig(cfg);
238 }
239 };
240
241 private static class HttpConfig {
242 final int postBuffer;
243
244 final boolean sslVerify;
245
246 HttpConfig(final Config rc) {
247 postBuffer = rc.getInt("http", "postbuffer", 1 * 1024 * 1024);
248 sslVerify = rc.getBoolean("http", "sslVerify", true);
249 }
250
251 HttpConfig() {
252 this(new Config());
253 }
254 }
255
256 final URL baseUrl;
257
258 private final URL objectsUrl;
259
260 final HttpConfig http;
261
262 private final ProxySelector proxySelector;
263
264 private boolean useSmartHttp = true;
265
266 private HttpAuthMethod authMethod = HttpAuthMethod.Type.NONE.method(null);
267
268 private Map<String, String> headers;
269
270 TransportHttp(final Repository local, final URIish uri)
271 throws NotSupportedException {
272 super(local, uri);
273 try {
274 String uriString = uri.toString();
275 if (!uriString.endsWith("/"))
276 uriString += "/";
277 baseUrl = new URL(uriString);
278 objectsUrl = new URL(baseUrl, "objects/");
279 } catch (MalformedURLException e) {
280 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
281 }
282 http = local.getConfig().get(HTTP_KEY);
283 proxySelector = ProxySelector.getDefault();
284 }
285
286
287
288
289
290
291
292 TransportHttp(final URIish uri) throws NotSupportedException {
293 super(uri);
294 try {
295 String uriString = uri.toString();
296 if (!uriString.endsWith("/"))
297 uriString += "/";
298 baseUrl = new URL(uriString);
299 objectsUrl = new URL(baseUrl, "objects/");
300 } catch (MalformedURLException e) {
301 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
302 }
303 http = new HttpConfig();
304 proxySelector = ProxySelector.getDefault();
305 }
306
307
308
309
310
311
312
313
314
315
316
317 public void setUseSmartHttp(final boolean on) {
318 useSmartHttp = on;
319 }
320
321 @Override
322 public FetchConnection openFetch() throws TransportException,
323 NotSupportedException {
324 final String service = SVC_UPLOAD_PACK;
325 try {
326 final HttpConnection c = connect(service);
327 final InputStream in = openInputStream(c);
328 try {
329 BaseConnection f;
330 if (isSmartHttp(c, service)) {
331 readSmartHeaders(in, service);
332 f = new SmartHttpFetchConnection(in);
333 } else {
334
335
336 f = newDumbConnection(in);
337 }
338 f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
339 return (FetchConnection) f;
340 } finally {
341 in.close();
342 }
343 } catch (NotSupportedException err) {
344 throw err;
345 } catch (TransportException err) {
346 throw err;
347 } catch (IOException err) {
348 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
349 }
350 }
351
352 private WalkFetchConnection newDumbConnection(InputStream in)
353 throws IOException, PackProtocolException {
354 HttpObjectDB d = new HttpObjectDB(objectsUrl);
355 BufferedReader br = toBufferedReader(in);
356 Map<String, Ref> refs;
357 try {
358 refs = d.readAdvertisedImpl(br);
359 } finally {
360 br.close();
361 }
362
363 if (!refs.containsKey(HEAD)) {
364
365
366
367
368 HttpConnection conn = httpOpen(
369 METHOD_GET,
370 new URL(baseUrl, HEAD),
371 AcceptEncoding.GZIP);
372 int status = HttpSupport.response(conn);
373 switch (status) {
374 case HttpConnection.HTTP_OK: {
375 br = toBufferedReader(openInputStream(conn));
376 try {
377 String line = br.readLine();
378 if (line != null && line.startsWith(RefDirectory.SYMREF)) {
379 String target = line.substring(RefDirectory.SYMREF.length());
380 Ref r = refs.get(target);
381 if (r == null)
382 r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
383 r = new SymbolicRef(HEAD, r);
384 refs.put(r.getName(), r);
385 } else if (line != null && ObjectId.isId(line)) {
386 Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
387 HEAD, ObjectId.fromString(line));
388 refs.put(r.getName(), r);
389 }
390 } finally {
391 br.close();
392 }
393 break;
394 }
395
396 case HttpConnection.HTTP_NOT_FOUND:
397 break;
398
399 default:
400 throw new TransportException(uri, MessageFormat.format(
401 JGitText.get().cannotReadHEAD, Integer.valueOf(status),
402 conn.getResponseMessage()));
403 }
404 }
405
406 WalkFetchConnection wfc = new WalkFetchConnection(this, d);
407 wfc.available(refs);
408 return wfc;
409 }
410
411 private BufferedReader toBufferedReader(InputStream in) {
412 return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
413 }
414
415 @Override
416 public PushConnection openPush() throws NotSupportedException,
417 TransportException {
418 final String service = SVC_RECEIVE_PACK;
419 try {
420 final HttpConnection c = connect(service);
421 final InputStream in = openInputStream(c);
422 try {
423 if (isSmartHttp(c, service)) {
424 return smartPush(service, c, in);
425 } else if (!useSmartHttp) {
426 final String msg = JGitText.get().smartHTTPPushDisabled;
427 throw new NotSupportedException(msg);
428
429 } else {
430 final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
431 throw new NotSupportedException(msg);
432 }
433 } finally {
434 in.close();
435 }
436 } catch (NotSupportedException err) {
437 throw err;
438 } catch (TransportException err) {
439 throw err;
440 } catch (IOException err) {
441 throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
442 }
443 }
444
445 private PushConnection smartPush(String service, HttpConnection c,
446 InputStream in) throws IOException, TransportException {
447 readSmartHeaders(in, service);
448 SmartHttpPushConnection p = new SmartHttpPushConnection(in);
449 p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
450 return p;
451 }
452
453 @Override
454 public void close() {
455
456 }
457
458
459
460
461
462
463
464
465
466 public void setAdditionalHeaders(Map<String, String> headers) {
467 this.headers = headers;
468 }
469
470 private HttpConnection connect(final String service)
471 throws TransportException, NotSupportedException {
472 final URL u;
473 try {
474 final StringBuilder b = new StringBuilder();
475 b.append(baseUrl);
476
477 if (b.charAt(b.length() - 1) != '/')
478 b.append('/');
479 b.append(Constants.INFO_REFS);
480
481 if (useSmartHttp) {
482 b.append(b.indexOf("?") < 0 ? '?' : '&');
483 b.append("service=");
484 b.append(service);
485 }
486
487 u = new URL(b.toString());
488 } catch (MalformedURLException e) {
489 throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
490 }
491
492
493 int authAttempts = 1;
494 Collection<Type> ignoreTypes = null;
495 for (;;) {
496 try {
497 final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
498 if (useSmartHttp) {
499 String exp = "application/x-" + service + "-advertisement";
500 conn.setRequestProperty(HDR_ACCEPT, exp + ", */*");
501 } else {
502 conn.setRequestProperty(HDR_ACCEPT, "*/*");
503 }
504 final int status = HttpSupport.response(conn);
505 switch (status) {
506 case HttpConnection.HTTP_OK:
507
508
509
510
511 if (authMethod.getType() == HttpAuthMethod.Type.NONE
512 && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
513 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
514 return conn;
515
516 case HttpConnection.HTTP_NOT_FOUND:
517 throw new NoRemoteRepositoryException(uri,
518 MessageFormat.format(JGitText.get().uriNotFound, u));
519
520 case HttpConnection.HTTP_UNAUTHORIZED:
521 authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
522 if (authMethod.getType() == HttpAuthMethod.Type.NONE)
523 throw new TransportException(uri, MessageFormat.format(
524 JGitText.get().authenticationNotSupported, uri));
525 CredentialsProvider credentialsProvider = getCredentialsProvider();
526 if (credentialsProvider == null)
527 throw new TransportException(uri,
528 JGitText.get().noCredentialsProvider);
529 if (authAttempts > 1)
530 credentialsProvider.reset(uri);
531 if (3 < authAttempts
532 || !authMethod.authorize(uri, credentialsProvider)) {
533 throw new TransportException(uri,
534 JGitText.get().notAuthorized);
535 }
536 authAttempts++;
537 continue;
538
539 case HttpConnection.HTTP_FORBIDDEN:
540 throw new TransportException(uri, MessageFormat.format(
541 JGitText.get().serviceNotPermitted, service));
542
543 default:
544 String err = status + " " + conn.getResponseMessage();
545 throw new TransportException(uri, err);
546 }
547 } catch (NotSupportedException e) {
548 throw e;
549 } catch (TransportException e) {
550 throw e;
551 } catch (IOException e) {
552 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
553 if (ignoreTypes == null) {
554 ignoreTypes = new HashSet<>();
555 }
556
557 ignoreTypes.add(authMethod.getType());
558
559
560 authMethod = HttpAuthMethod.Type.NONE.method(null);
561 authAttempts = 1;
562
563 continue;
564 }
565
566 throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
567 }
568 }
569 }
570
571
572
573
574
575
576
577
578
579
580
581 @Deprecated
582 protected HttpConnection httpOpen(String method, URL u) throws IOException {
583 return httpOpen(method, u, AcceptEncoding.GZIP);
584 }
585
586
587
588
589
590
591
592
593
594
595
596 protected HttpConnection httpOpen(String method, URL u,
597 AcceptEncoding acceptEncoding) throws IOException {
598 if (method == null || u == null || acceptEncoding == null) {
599 throw new NullPointerException();
600 }
601
602 final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
603 HttpConnection conn = connectionFactory.create(u, proxy);
604
605 if (!http.sslVerify && "https".equals(u.getProtocol())) {
606 HttpSupport.disableSslVerify(conn);
607 }
608
609 conn.setRequestMethod(method);
610 conn.setUseCaches(false);
611 if (acceptEncoding == AcceptEncoding.GZIP) {
612 conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
613 }
614 conn.setRequestProperty(HDR_PRAGMA, "no-cache");
615 if (UserAgent.get() != null) {
616 conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
617 }
618 int timeOut = getTimeout();
619 if (timeOut != -1) {
620 int effTimeOut = timeOut * 1000;
621 conn.setConnectTimeout(effTimeOut);
622 conn.setReadTimeout(effTimeOut);
623 }
624 if (this.headers != null && !this.headers.isEmpty()) {
625 for (Map.Entry<String, String> entry : this.headers.entrySet())
626 conn.setRequestProperty(entry.getKey(), entry.getValue());
627 }
628 authMethod.configureRequest(conn);
629 return conn;
630 }
631
632 final InputStream openInputStream(HttpConnection conn)
633 throws IOException {
634 InputStream input = conn.getInputStream();
635 if (isGzipContent(conn))
636 input = new GZIPInputStream(input);
637 return input;
638 }
639
640 IOException wrongContentType(String expType, String actType) {
641 final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
642 return new TransportException(uri, why);
643 }
644
645 private boolean isSmartHttp(final HttpConnection c, final String service) {
646 final String expType = "application/x-" + service + "-advertisement";
647 final String actType = c.getContentType();
648 return expType.equals(actType);
649 }
650
651 private boolean isGzipContent(final HttpConnection c) {
652 return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
653 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
654 }
655
656 private void readSmartHeaders(final InputStream in, final String service)
657 throws IOException {
658
659
660
661
662
663 final byte[] magic = new byte[5];
664 IO.readFully(in, magic, 0, magic.length);
665 if (magic[4] != '#') {
666 throw new TransportException(uri, MessageFormat.format(
667 JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
668 }
669
670 final PacketLineIn pckIn = new PacketLineIn(new UnionInputStream(
671 new ByteArrayInputStream(magic), in));
672 final String exp = "# service=" + service;
673 final String act = pckIn.readString();
674 if (!exp.equals(act)) {
675 throw new TransportException(uri, MessageFormat.format(
676 JGitText.get().expectedGot, exp, act));
677 }
678
679 while (pckIn.readString() != PacketLineIn.END) {
680
681 }
682 }
683
684 class HttpObjectDB extends WalkRemoteObjectDatabase {
685 private final URL httpObjectsUrl;
686
687 HttpObjectDB(final URL b) {
688 httpObjectsUrl = b;
689 }
690
691 @Override
692 URIish getURI() {
693 return new URIish(httpObjectsUrl);
694 }
695
696 @Override
697 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
698 try {
699 return readAlternates(INFO_HTTP_ALTERNATES);
700 } catch (FileNotFoundException err) {
701
702 }
703
704 try {
705 return readAlternates(INFO_ALTERNATES);
706 } catch (FileNotFoundException err) {
707
708 }
709
710 return null;
711 }
712
713 @Override
714 WalkRemoteObjectDatabase openAlternate(final String location)
715 throws IOException {
716 return new HttpObjectDB(new URL(httpObjectsUrl, location));
717 }
718
719 @Override
720 BufferedReader openReader(String path) throws IOException {
721
722
723 InputStream is = open(path, AcceptEncoding.GZIP).in;
724 return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
725 }
726
727 @Override
728 Collection<String> getPackNames() throws IOException {
729 final Collection<String> packs = new ArrayList<>();
730 try {
731 final BufferedReader br = openReader(INFO_PACKS);
732 try {
733 for (;;) {
734 final String s = br.readLine();
735 if (s == null || s.length() == 0)
736 break;
737 if (!s.startsWith("P pack-") || !s.endsWith(".pack"))
738 throw invalidAdvertisement(s);
739 packs.add(s.substring(2));
740 }
741 return packs;
742 } finally {
743 br.close();
744 }
745 } catch (FileNotFoundException err) {
746 return packs;
747 }
748 }
749
750 @Override
751 FileStream open(final String path) throws IOException {
752 return open(path, AcceptEncoding.UNSPECIFIED);
753 }
754
755 FileStream open(String path, AcceptEncoding acceptEncoding)
756 throws IOException {
757 final URL base = httpObjectsUrl;
758 final URL u = new URL(base, path);
759 final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
760 switch (HttpSupport.response(c)) {
761 case HttpConnection.HTTP_OK:
762 final InputStream in = openInputStream(c);
763
764
765
766 if (!isGzipContent(c)) {
767 final int len = c.getContentLength();
768 return new FileStream(in, len);
769 }
770 return new FileStream(in);
771 case HttpConnection.HTTP_NOT_FOUND:
772 throw new FileNotFoundException(u.toString());
773 default:
774 throw new IOException(u.toString() + ": "
775 + HttpSupport.response(c) + " "
776 + c.getResponseMessage());
777 }
778 }
779
780 Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
781 throws IOException, PackProtocolException {
782 final TreeMap<String, Ref> avail = new TreeMap<>();
783 for (;;) {
784 String line = br.readLine();
785 if (line == null)
786 break;
787
788 final int tab = line.indexOf('\t');
789 if (tab < 0)
790 throw invalidAdvertisement(line);
791
792 String name;
793 final ObjectId id;
794
795 name = line.substring(tab + 1);
796 id = ObjectId.fromString(line.substring(0, tab));
797 if (name.endsWith("^{}")) {
798 name = name.substring(0, name.length() - 3);
799 final Ref prior = avail.get(name);
800 if (prior == null)
801 throw outOfOrderAdvertisement(name);
802
803 if (prior.getPeeledObjectId() != null)
804 throw duplicateAdvertisement(name + "^{}");
805
806 avail.put(name, new ObjectIdRef.PeeledTag(
807 Ref.Storage.NETWORK, name,
808 prior.getObjectId(), id));
809 } else {
810 Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
811 Ref.Storage.NETWORK, name, id));
812 if (prior != null)
813 throw duplicateAdvertisement(name);
814 }
815 }
816 return avail;
817 }
818
819 private PackProtocolException outOfOrderAdvertisement(final String n) {
820 return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
821 }
822
823 private PackProtocolException invalidAdvertisement(final String n) {
824 return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
825 }
826
827 private PackProtocolException duplicateAdvertisement(final String n) {
828 return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
829 }
830
831 @Override
832 void close() {
833
834 }
835 }
836
837 class SmartHttpFetchConnection extends BasePackFetchConnection {
838 private MultiRequestService svc;
839
840 SmartHttpFetchConnection(final InputStream advertisement)
841 throws TransportException {
842 super(TransportHttp.this);
843 statelessRPC = true;
844
845 init(advertisement, DisabledOutputStream.INSTANCE);
846 outNeedsEnd = false;
847 readAdvertisedRefs();
848 }
849
850 @Override
851 protected void doFetch(final ProgressMonitor monitor,
852 final Collection<Ref> want, final Set<ObjectId> have,
853 final OutputStream outputStream) throws TransportException {
854 try {
855 svc = new MultiRequestService(SVC_UPLOAD_PACK);
856 init(svc.getInputStream(), svc.getOutputStream());
857 super.doFetch(monitor, want, have, outputStream);
858 } finally {
859 svc = null;
860 }
861 }
862
863 @Override
864 protected void onReceivePack() {
865 svc.finalRequest = true;
866 }
867 }
868
869 class SmartHttpPushConnection extends BasePackPushConnection {
870 SmartHttpPushConnection(final InputStream advertisement)
871 throws TransportException {
872 super(TransportHttp.this);
873 statelessRPC = true;
874
875 init(advertisement, DisabledOutputStream.INSTANCE);
876 outNeedsEnd = false;
877 readAdvertisedRefs();
878 }
879
880 @Override
881 protected void doPush(final ProgressMonitor monitor,
882 final Map<String, RemoteRefUpdate> refUpdates,
883 OutputStream outputStream) throws TransportException {
884 final Service svc = new MultiRequestService(SVC_RECEIVE_PACK);
885 init(svc.getInputStream(), svc.getOutputStream());
886 super.doPush(monitor, refUpdates, outputStream);
887 }
888 }
889
890
891 abstract class Service {
892 protected final String serviceName;
893
894 protected final String requestType;
895
896 protected final String responseType;
897
898 protected HttpConnection conn;
899
900 protected HttpOutputStream out;
901
902 protected final HttpExecuteStream execute;
903
904 final UnionInputStream in;
905
906 Service(String serviceName) {
907 this.serviceName = serviceName;
908 this.requestType = "application/x-" + serviceName + "-request";
909 this.responseType = "application/x-" + serviceName + "-result";
910
911 this.out = new HttpOutputStream();
912 this.execute = new HttpExecuteStream();
913 this.in = new UnionInputStream(execute);
914 }
915
916 void openStream() throws IOException {
917 openStream(null);
918 }
919
920 void openStream(final String redirectUrl) throws IOException {
921 conn = httpOpen(
922 METHOD_POST,
923 redirectUrl == null ? new URL(baseUrl, serviceName) : new URL(redirectUrl),
924 AcceptEncoding.GZIP);
925 conn.setInstanceFollowRedirects(false);
926 conn.setDoOutput(true);
927 conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
928 conn.setRequestProperty(HDR_ACCEPT, responseType);
929 }
930
931 void sendRequest() throws IOException {
932 sendRequest(null);
933 }
934
935 void sendRequest(final String redirectUrl) throws IOException {
936
937 TemporaryBuffer buf = new TemporaryBuffer.Heap(http.postBuffer);
938 try {
939 GZIPOutputStream gzip = new GZIPOutputStream(buf);
940 out.writeTo(gzip, null);
941 gzip.close();
942 if (out.length() < buf.length())
943 buf = out;
944 } catch (IOException err) {
945
946
947 buf = out;
948 }
949
950 openStream(redirectUrl);
951 if (buf != out)
952 conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP);
953 conn.setFixedLengthStreamingMode((int) buf.length());
954 final OutputStream httpOut = conn.getOutputStream();
955 try {
956 buf.writeTo(httpOut, null);
957 } finally {
958 httpOut.close();
959 }
960
961 final int status = HttpSupport.response(conn);
962 if (status == HttpConnection.HTTP_MOVED_PERM) {
963 String locationHeader = HttpSupport.responseHeader(conn, HDR_LOCATION);
964 sendRequest(locationHeader);
965 }
966 }
967
968 void openResponse() throws IOException {
969 final int status = HttpSupport.response(conn);
970 if (status != HttpConnection.HTTP_OK) {
971 throw new TransportException(uri, status + " "
972 + conn.getResponseMessage());
973 }
974
975 final String contentType = conn.getContentType();
976 if (!responseType.equals(contentType)) {
977 conn.getInputStream().close();
978 throw wrongContentType(responseType, contentType);
979 }
980 }
981
982 HttpOutputStream getOutputStream() {
983 return out;
984 }
985
986 InputStream getInputStream() {
987 return in;
988 }
989
990 abstract void execute() throws IOException;
991
992 class HttpExecuteStream extends InputStream {
993 @Override
994 public int read() throws IOException {
995 execute();
996 return -1;
997 }
998
999 @Override
1000 public int read(byte[] b, int off, int len) throws IOException {
1001 execute();
1002 return -1;
1003 }
1004
1005 @Override
1006 public long skip(long n) throws IOException {
1007 execute();
1008 return 0;
1009 }
1010 }
1011
1012 class HttpOutputStream extends TemporaryBuffer {
1013 HttpOutputStream() {
1014 super(http.postBuffer);
1015 }
1016
1017 @Override
1018 protected OutputStream overflow() throws IOException {
1019 openStream();
1020 conn.setChunkedStreamingMode(0);
1021 return conn.getOutputStream();
1022 }
1023 }
1024 }
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046 class MultiRequestService extends Service {
1047 boolean finalRequest;
1048
1049 MultiRequestService(final String serviceName) {
1050 super(serviceName);
1051 }
1052
1053
1054 @Override
1055 void execute() throws IOException {
1056 out.close();
1057
1058 if (conn == null) {
1059 if (out.length() == 0) {
1060
1061
1062
1063
1064
1065 if (finalRequest)
1066 return;
1067 throw new TransportException(uri,
1068 JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
1069 }
1070
1071 sendRequest();
1072 }
1073
1074 out.reset();
1075
1076 openResponse();
1077
1078 in.add(openInputStream(conn));
1079 if (!finalRequest)
1080 in.add(execute);
1081 conn = null;
1082 }
1083 }
1084
1085
1086 class LongPollService extends Service {
1087
1088
1089
1090 LongPollService(String serviceName) {
1091 super(serviceName);
1092 }
1093
1094
1095 @Override
1096 void execute() throws IOException {
1097 out.close();
1098 if (conn == null)
1099 sendRequest();
1100 openResponse();
1101 in.add(openInputStream(conn));
1102 }
1103 }
1104 }