You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TransportHttp.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. /*
  2. * Copyright (C) 2008-2010, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4. * Copyright (C) 2013, Matthias Sohn <matthias.sohn@sap.com>
  5. * and other copyright owners as documented in the project's IP log.
  6. *
  7. * This program and the accompanying materials are made available
  8. * under the terms of the Eclipse Distribution License v1.0 which
  9. * accompanies this distribution, is reproduced below, and is
  10. * available at http://www.eclipse.org/org/documents/edl-v10.php
  11. *
  12. * All rights reserved.
  13. *
  14. * Redistribution and use in source and binary forms, with or
  15. * without modification, are permitted provided that the following
  16. * conditions are met:
  17. *
  18. * - Redistributions of source code must retain the above copyright
  19. * notice, this list of conditions and the following disclaimer.
  20. *
  21. * - Redistributions in binary form must reproduce the above
  22. * copyright notice, this list of conditions and the following
  23. * disclaimer in the documentation and/or other materials provided
  24. * with the distribution.
  25. *
  26. * - Neither the name of the Eclipse Foundation, Inc. nor the
  27. * names of its contributors may be used to endorse or promote
  28. * products derived from this software without specific prior
  29. * written permission.
  30. *
  31. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  32. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  33. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  34. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  35. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  36. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  38. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  39. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  40. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  41. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  42. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  43. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. */
  45. package org.eclipse.jgit.transport;
  46. import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
  47. import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
  48. import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
  49. import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
  50. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
  51. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
  52. import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
  53. import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
  54. import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
  55. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  56. import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
  57. import java.io.BufferedReader;
  58. import java.io.ByteArrayInputStream;
  59. import java.io.FileNotFoundException;
  60. import java.io.IOException;
  61. import java.io.InputStream;
  62. import java.io.InputStreamReader;
  63. import java.io.OutputStream;
  64. import java.net.MalformedURLException;
  65. import java.net.Proxy;
  66. import java.net.ProxySelector;
  67. import java.net.URL;
  68. import java.text.MessageFormat;
  69. import java.util.ArrayList;
  70. import java.util.Arrays;
  71. import java.util.Collection;
  72. import java.util.Collections;
  73. import java.util.EnumSet;
  74. import java.util.HashSet;
  75. import java.util.LinkedHashSet;
  76. import java.util.Map;
  77. import java.util.Set;
  78. import java.util.TreeMap;
  79. import java.util.zip.GZIPInputStream;
  80. import java.util.zip.GZIPOutputStream;
  81. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  82. import org.eclipse.jgit.errors.NotSupportedException;
  83. import org.eclipse.jgit.errors.PackProtocolException;
  84. import org.eclipse.jgit.errors.TransportException;
  85. import org.eclipse.jgit.internal.JGitText;
  86. import org.eclipse.jgit.internal.storage.file.RefDirectory;
  87. import org.eclipse.jgit.lib.Config;
  88. import org.eclipse.jgit.lib.Config.SectionParser;
  89. import org.eclipse.jgit.lib.Constants;
  90. import org.eclipse.jgit.lib.ObjectId;
  91. import org.eclipse.jgit.lib.ObjectIdRef;
  92. import org.eclipse.jgit.lib.ProgressMonitor;
  93. import org.eclipse.jgit.lib.Ref;
  94. import org.eclipse.jgit.lib.Repository;
  95. import org.eclipse.jgit.lib.SymbolicRef;
  96. import org.eclipse.jgit.transport.HttpAuthMethod.Type;
  97. import org.eclipse.jgit.transport.http.HttpConnection;
  98. import org.eclipse.jgit.util.HttpSupport;
  99. import org.eclipse.jgit.util.IO;
  100. import org.eclipse.jgit.util.RawParseUtils;
  101. import org.eclipse.jgit.util.TemporaryBuffer;
  102. import org.eclipse.jgit.util.io.DisabledOutputStream;
  103. import org.eclipse.jgit.util.io.UnionInputStream;
  104. /**
  105. * Transport over HTTP and FTP protocols.
  106. * <p>
  107. * If the transport is using HTTP and the remote HTTP service is Git-aware
  108. * (speaks the "smart-http protocol") this client will automatically take
  109. * advantage of the additional Git-specific HTTP extensions. If the remote
  110. * service does not support these extensions, the client will degrade to direct
  111. * file fetching.
  112. * <p>
  113. * If the remote (server side) repository does not have the specialized Git
  114. * support, object files are retrieved directly through standard HTTP GET (or
  115. * binary FTP GET) requests. This make it easy to serve a Git repository through
  116. * a standard web host provider that does not offer specific support for Git.
  117. *
  118. * @see WalkFetchConnection
  119. */
  120. public class TransportHttp extends HttpTransport implements WalkTransport,
  121. PackTransport {
  122. private static final String SVC_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$
  123. private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$
  124. static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
  125. private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$
  126. private final Set<String> schemeSet = Collections
  127. .unmodifiableSet(new LinkedHashSet<String>(Arrays
  128. .asList(schemeNames)));
  129. public String getName() {
  130. return JGitText.get().transportProtoHTTP;
  131. }
  132. public Set<String> getSchemes() {
  133. return schemeSet;
  134. }
  135. public Set<URIishField> getRequiredFields() {
  136. return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
  137. URIishField.PATH));
  138. }
  139. public Set<URIishField> getOptionalFields() {
  140. return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
  141. URIishField.PASS, URIishField.PORT));
  142. }
  143. public int getDefaultPort() {
  144. return 80;
  145. }
  146. public Transport open(URIish uri, Repository local, String remoteName)
  147. throws NotSupportedException {
  148. return new TransportHttp(local, uri);
  149. }
  150. public Transport open(URIish uri) throws NotSupportedException {
  151. return new TransportHttp(uri);
  152. }
  153. };
  154. static final TransportProtocol PROTO_FTP = new TransportProtocol() {
  155. public String getName() {
  156. return JGitText.get().transportProtoFTP;
  157. }
  158. public Set<String> getSchemes() {
  159. return Collections.singleton("ftp"); //$NON-NLS-1$
  160. }
  161. public Set<URIishField> getRequiredFields() {
  162. return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
  163. URIishField.PATH));
  164. }
  165. public Set<URIishField> getOptionalFields() {
  166. return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
  167. URIishField.PASS, URIishField.PORT));
  168. }
  169. public int getDefaultPort() {
  170. return 21;
  171. }
  172. public Transport open(URIish uri, Repository local, String remoteName)
  173. throws NotSupportedException {
  174. return new TransportHttp(local, uri);
  175. }
  176. };
  177. private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() {
  178. public HttpConfig parse(final Config cfg) {
  179. return new HttpConfig(cfg);
  180. }
  181. };
  182. private static class HttpConfig {
  183. final int postBuffer;
  184. final boolean sslVerify;
  185. HttpConfig(final Config rc) {
  186. postBuffer = rc.getInt("http", "postbuffer", 1 * 1024 * 1024); //$NON-NLS-1$ //$NON-NLS-2$
  187. sslVerify = rc.getBoolean("http", "sslVerify", true); //$NON-NLS-1$ //$NON-NLS-2$
  188. }
  189. HttpConfig() {
  190. this(new Config());
  191. }
  192. }
  193. final URL baseUrl;
  194. private final URL objectsUrl;
  195. final HttpConfig http;
  196. private final ProxySelector proxySelector;
  197. private boolean useSmartHttp = true;
  198. private HttpAuthMethod authMethod = HttpAuthMethod.Type.NONE.method(null);
  199. private Map<String, String> headers;
  200. TransportHttp(final Repository local, final URIish uri)
  201. throws NotSupportedException {
  202. super(local, uri);
  203. try {
  204. String uriString = uri.toString();
  205. if (!uriString.endsWith("/")) //$NON-NLS-1$
  206. uriString += "/"; //$NON-NLS-1$
  207. baseUrl = new URL(uriString);
  208. objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
  209. } catch (MalformedURLException e) {
  210. throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  211. }
  212. http = local.getConfig().get(HTTP_KEY);
  213. proxySelector = ProxySelector.getDefault();
  214. }
  215. /**
  216. * Create a minimal HTTP transport with default configuration values.
  217. *
  218. * @param uri
  219. * @throws NotSupportedException
  220. */
  221. TransportHttp(final URIish uri) throws NotSupportedException {
  222. super(uri);
  223. try {
  224. String uriString = uri.toString();
  225. if (!uriString.endsWith("/")) //$NON-NLS-1$
  226. uriString += "/"; //$NON-NLS-1$
  227. baseUrl = new URL(uriString);
  228. objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
  229. } catch (MalformedURLException e) {
  230. throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  231. }
  232. http = new HttpConfig();
  233. proxySelector = ProxySelector.getDefault();
  234. }
  235. /**
  236. * Toggle whether or not smart HTTP transport should be used.
  237. * <p>
  238. * This flag exists primarily to support backwards compatibility testing
  239. * within a testing framework, there is no need to modify it in most
  240. * applications.
  241. *
  242. * @param on
  243. * if {@code true} (default), smart HTTP is enabled.
  244. */
  245. public void setUseSmartHttp(final boolean on) {
  246. useSmartHttp = on;
  247. }
  248. @Override
  249. public FetchConnection openFetch() throws TransportException,
  250. NotSupportedException {
  251. final String service = SVC_UPLOAD_PACK;
  252. try {
  253. final HttpConnection c = connect(service);
  254. final InputStream in = openInputStream(c);
  255. try {
  256. BaseConnection f;
  257. if (isSmartHttp(c, service)) {
  258. readSmartHeaders(in, service);
  259. f = new SmartHttpFetchConnection(in);
  260. } else {
  261. // Assume this server doesn't support smart HTTP fetch
  262. // and fall back on dumb object walking.
  263. f = newDumbConnection(in);
  264. }
  265. f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
  266. return (FetchConnection) f;
  267. } finally {
  268. in.close();
  269. }
  270. } catch (NotSupportedException err) {
  271. throw err;
  272. } catch (TransportException err) {
  273. throw err;
  274. } catch (IOException err) {
  275. throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
  276. }
  277. }
  278. private WalkFetchConnection newDumbConnection(InputStream in)
  279. throws IOException, PackProtocolException {
  280. HttpObjectDB d = new HttpObjectDB(objectsUrl);
  281. BufferedReader br = toBufferedReader(in);
  282. Map<String, Ref> refs;
  283. try {
  284. refs = d.readAdvertisedImpl(br);
  285. } finally {
  286. br.close();
  287. }
  288. if (!refs.containsKey(Constants.HEAD)) {
  289. // If HEAD was not published in the info/refs file (it usually
  290. // is not there) download HEAD by itself as a loose file and do
  291. // the resolution by hand.
  292. //
  293. HttpConnection conn = httpOpen(new URL(baseUrl, Constants.HEAD));
  294. int status = HttpSupport.response(conn);
  295. switch (status) {
  296. case HttpConnection.HTTP_OK: {
  297. br = toBufferedReader(openInputStream(conn));
  298. try {
  299. String line = br.readLine();
  300. if (line != null && line.startsWith(RefDirectory.SYMREF)) {
  301. String target = line.substring(RefDirectory.SYMREF.length());
  302. Ref r = refs.get(target);
  303. if (r == null)
  304. r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
  305. r = new SymbolicRef(Constants.HEAD, r);
  306. refs.put(r.getName(), r);
  307. } else if (line != null && ObjectId.isId(line)) {
  308. Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
  309. Constants.HEAD, ObjectId.fromString(line));
  310. refs.put(r.getName(), r);
  311. }
  312. } finally {
  313. br.close();
  314. }
  315. break;
  316. }
  317. case HttpConnection.HTTP_NOT_FOUND:
  318. break;
  319. default:
  320. throw new TransportException(uri, MessageFormat.format(
  321. JGitText.get().cannotReadHEAD, Integer.valueOf(status),
  322. conn.getResponseMessage()));
  323. }
  324. }
  325. WalkFetchConnection wfc = new WalkFetchConnection(this, d);
  326. wfc.available(refs);
  327. return wfc;
  328. }
  329. private BufferedReader toBufferedReader(InputStream in) {
  330. return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
  331. }
  332. @Override
  333. public PushConnection openPush() throws NotSupportedException,
  334. TransportException {
  335. final String service = SVC_RECEIVE_PACK;
  336. try {
  337. final HttpConnection c = connect(service);
  338. final InputStream in = openInputStream(c);
  339. try {
  340. if (isSmartHttp(c, service)) {
  341. return smartPush(service, c, in);
  342. } else if (!useSmartHttp) {
  343. final String msg = JGitText.get().smartHTTPPushDisabled;
  344. throw new NotSupportedException(msg);
  345. } else {
  346. final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
  347. throw new NotSupportedException(msg);
  348. }
  349. } finally {
  350. in.close();
  351. }
  352. } catch (NotSupportedException err) {
  353. throw err;
  354. } catch (TransportException err) {
  355. throw err;
  356. } catch (IOException err) {
  357. throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
  358. }
  359. }
  360. private PushConnection smartPush(String service, HttpConnection c,
  361. InputStream in) throws IOException, TransportException {
  362. readSmartHeaders(in, service);
  363. SmartHttpPushConnection p = new SmartHttpPushConnection(in);
  364. p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
  365. return p;
  366. }
  367. @Override
  368. public void close() {
  369. // No explicit connections are maintained.
  370. }
  371. /**
  372. * Set additional headers on the HTTP connection
  373. *
  374. * @param headers
  375. * a map of name:values that are to be set as headers on the HTTP
  376. * connection
  377. * @since 3.4
  378. */
  379. public void setAdditionalHeaders(Map<String, String> headers) {
  380. this.headers = headers;
  381. }
  382. private HttpConnection connect(final String service)
  383. throws TransportException, NotSupportedException {
  384. final URL u;
  385. try {
  386. final StringBuilder b = new StringBuilder();
  387. b.append(baseUrl);
  388. if (b.charAt(b.length() - 1) != '/')
  389. b.append('/');
  390. b.append(Constants.INFO_REFS);
  391. if (useSmartHttp) {
  392. b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$
  393. b.append("service="); //$NON-NLS-1$
  394. b.append(service);
  395. }
  396. u = new URL(b.toString());
  397. } catch (MalformedURLException e) {
  398. throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  399. }
  400. int authAttempts = 1;
  401. Collection<Type> ignoreTypes = null;
  402. for (;;) {
  403. try {
  404. final HttpConnection conn = httpOpen(u);
  405. if (useSmartHttp) {
  406. String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
  407. conn.setRequestProperty(HDR_ACCEPT, exp + ", */*"); //$NON-NLS-1$
  408. } else {
  409. conn.setRequestProperty(HDR_ACCEPT, "*/*"); //$NON-NLS-1$
  410. }
  411. final int status = HttpSupport.response(conn);
  412. switch (status) {
  413. case HttpConnection.HTTP_OK:
  414. // Check if HttpConnection did some authentication in the
  415. // background (e.g Kerberos/SPNEGO).
  416. // That may not work for streaming requests and jgit
  417. // explicit authentication would be required
  418. if (authMethod.getType() == HttpAuthMethod.Type.NONE
  419. && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
  420. authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
  421. return conn;
  422. case HttpConnection.HTTP_NOT_FOUND:
  423. throw new NoRemoteRepositoryException(uri,
  424. MessageFormat.format(JGitText.get().uriNotFound, u));
  425. case HttpConnection.HTTP_UNAUTHORIZED:
  426. authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
  427. if (authMethod.getType() == HttpAuthMethod.Type.NONE)
  428. throw new TransportException(uri, MessageFormat.format(
  429. JGitText.get().authenticationNotSupported, uri));
  430. CredentialsProvider credentialsProvider = getCredentialsProvider();
  431. if (credentialsProvider == null)
  432. throw new TransportException(uri,
  433. JGitText.get().noCredentialsProvider);
  434. if (authAttempts > 1)
  435. credentialsProvider.reset(uri);
  436. if (3 < authAttempts
  437. || !authMethod.authorize(uri, credentialsProvider)) {
  438. throw new TransportException(uri,
  439. JGitText.get().notAuthorized);
  440. }
  441. authAttempts++;
  442. continue;
  443. case HttpConnection.HTTP_FORBIDDEN:
  444. throw new TransportException(uri, MessageFormat.format(
  445. JGitText.get().serviceNotPermitted, service));
  446. default:
  447. String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$
  448. throw new TransportException(uri, err);
  449. }
  450. } catch (NotSupportedException e) {
  451. throw e;
  452. } catch (TransportException e) {
  453. throw e;
  454. } catch (IOException e) {
  455. if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
  456. if (ignoreTypes == null) {
  457. ignoreTypes = new HashSet<Type>();
  458. }
  459. ignoreTypes.add(authMethod.getType());
  460. // reset auth method & attempts for next authentication type
  461. authMethod = HttpAuthMethod.Type.NONE.method(null);
  462. authAttempts = 1;
  463. continue;
  464. }
  465. throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
  466. }
  467. }
  468. }
  469. final HttpConnection httpOpen(URL u) throws IOException {
  470. return httpOpen(METHOD_GET, u);
  471. }
  472. /**
  473. * Open an HTTP connection.
  474. *
  475. * @param method
  476. * @param u
  477. * @return the connection
  478. * @throws IOException
  479. * @since 3.3
  480. */
  481. protected HttpConnection httpOpen(String method, URL u)
  482. throws IOException {
  483. final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
  484. HttpConnection conn = connectionFactory.create(u, proxy);
  485. if (!http.sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$
  486. HttpSupport.disableSslVerify(conn);
  487. }
  488. conn.setRequestMethod(method);
  489. conn.setUseCaches(false);
  490. conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
  491. conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$
  492. if (UserAgent.get() != null) {
  493. conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
  494. }
  495. int timeOut = getTimeout();
  496. if (timeOut != -1) {
  497. int effTimeOut = timeOut * 1000;
  498. conn.setConnectTimeout(effTimeOut);
  499. conn.setReadTimeout(effTimeOut);
  500. }
  501. if (this.headers != null && !this.headers.isEmpty()) {
  502. for (Map.Entry<String, String> entry : this.headers.entrySet())
  503. conn.setRequestProperty(entry.getKey(), entry.getValue());
  504. }
  505. authMethod.configureRequest(conn);
  506. return conn;
  507. }
  508. final InputStream openInputStream(HttpConnection conn)
  509. throws IOException {
  510. InputStream input = conn.getInputStream();
  511. if (isGzipContent(conn))
  512. input = new GZIPInputStream(input);
  513. return input;
  514. }
  515. IOException wrongContentType(String expType, String actType) {
  516. final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
  517. return new TransportException(uri, why);
  518. }
  519. private boolean isSmartHttp(final HttpConnection c, final String service) {
  520. final String expType = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
  521. final String actType = c.getContentType();
  522. return expType.equals(actType);
  523. }
  524. private boolean isGzipContent(final HttpConnection c) {
  525. return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
  526. || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
  527. }
  528. private void readSmartHeaders(final InputStream in, final String service)
  529. throws IOException {
  530. // A smart reply will have a '#' after the first 4 bytes, but
  531. // a dumb reply cannot contain a '#' until after byte 41. Do a
  532. // quick check to make sure its a smart reply before we parse
  533. // as a pkt-line stream.
  534. //
  535. final byte[] magic = new byte[5];
  536. IO.readFully(in, magic, 0, magic.length);
  537. if (magic[4] != '#') {
  538. throw new TransportException(uri, MessageFormat.format(
  539. JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
  540. }
  541. final PacketLineIn pckIn = new PacketLineIn(new UnionInputStream(
  542. new ByteArrayInputStream(magic), in));
  543. final String exp = "# service=" + service; //$NON-NLS-1$
  544. final String act = pckIn.readString();
  545. if (!exp.equals(act)) {
  546. throw new TransportException(uri, MessageFormat.format(
  547. JGitText.get().expectedGot, exp, act));
  548. }
  549. while (pckIn.readString() != PacketLineIn.END) {
  550. // for now, ignore the remaining header lines
  551. }
  552. }
  553. class HttpObjectDB extends WalkRemoteObjectDatabase {
  554. private final URL httpObjectsUrl;
  555. HttpObjectDB(final URL b) {
  556. httpObjectsUrl = b;
  557. }
  558. @Override
  559. URIish getURI() {
  560. return new URIish(httpObjectsUrl);
  561. }
  562. @Override
  563. Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
  564. try {
  565. return readAlternates(INFO_HTTP_ALTERNATES);
  566. } catch (FileNotFoundException err) {
  567. // Fall through.
  568. }
  569. try {
  570. return readAlternates(INFO_ALTERNATES);
  571. } catch (FileNotFoundException err) {
  572. // Fall through.
  573. }
  574. return null;
  575. }
  576. @Override
  577. WalkRemoteObjectDatabase openAlternate(final String location)
  578. throws IOException {
  579. return new HttpObjectDB(new URL(httpObjectsUrl, location));
  580. }
  581. @Override
  582. Collection<String> getPackNames() throws IOException {
  583. final Collection<String> packs = new ArrayList<String>();
  584. try {
  585. final BufferedReader br = openReader(INFO_PACKS);
  586. try {
  587. for (;;) {
  588. final String s = br.readLine();
  589. if (s == null || s.length() == 0)
  590. break;
  591. if (!s.startsWith("P pack-") || !s.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
  592. throw invalidAdvertisement(s);
  593. packs.add(s.substring(2));
  594. }
  595. return packs;
  596. } finally {
  597. br.close();
  598. }
  599. } catch (FileNotFoundException err) {
  600. return packs;
  601. }
  602. }
  603. @Override
  604. FileStream open(final String path) throws IOException {
  605. final URL base = httpObjectsUrl;
  606. final URL u = new URL(base, path);
  607. final HttpConnection c = httpOpen(u);
  608. switch (HttpSupport.response(c)) {
  609. case HttpConnection.HTTP_OK:
  610. final InputStream in = openInputStream(c);
  611. // If content is being gzipped and then transferred, the content
  612. // length in the header is the zipped content length, not the
  613. // actual content length.
  614. if (!isGzipContent(c)) {
  615. final int len = c.getContentLength();
  616. return new FileStream(in, len);
  617. }
  618. return new FileStream(in);
  619. case HttpConnection.HTTP_NOT_FOUND:
  620. throw new FileNotFoundException(u.toString());
  621. default:
  622. throw new IOException(u.toString() + ": " //$NON-NLS-1$
  623. + HttpSupport.response(c) + " " //$NON-NLS-1$
  624. + c.getResponseMessage());
  625. }
  626. }
  627. Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
  628. throws IOException, PackProtocolException {
  629. final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
  630. for (;;) {
  631. String line = br.readLine();
  632. if (line == null)
  633. break;
  634. final int tab = line.indexOf('\t');
  635. if (tab < 0)
  636. throw invalidAdvertisement(line);
  637. String name;
  638. final ObjectId id;
  639. name = line.substring(tab + 1);
  640. id = ObjectId.fromString(line.substring(0, tab));
  641. if (name.endsWith("^{}")) { //$NON-NLS-1$
  642. name = name.substring(0, name.length() - 3);
  643. final Ref prior = avail.get(name);
  644. if (prior == null)
  645. throw outOfOrderAdvertisement(name);
  646. if (prior.getPeeledObjectId() != null)
  647. throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$
  648. avail.put(name, new ObjectIdRef.PeeledTag(
  649. Ref.Storage.NETWORK, name,
  650. prior.getObjectId(), id));
  651. } else {
  652. Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
  653. Ref.Storage.NETWORK, name, id));
  654. if (prior != null)
  655. throw duplicateAdvertisement(name);
  656. }
  657. }
  658. return avail;
  659. }
  660. private PackProtocolException outOfOrderAdvertisement(final String n) {
  661. return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
  662. }
  663. private PackProtocolException invalidAdvertisement(final String n) {
  664. return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
  665. }
  666. private PackProtocolException duplicateAdvertisement(final String n) {
  667. return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
  668. }
  669. @Override
  670. void close() {
  671. // We do not maintain persistent connections.
  672. }
  673. }
  674. class SmartHttpFetchConnection extends BasePackFetchConnection {
  675. private MultiRequestService svc;
  676. SmartHttpFetchConnection(final InputStream advertisement)
  677. throws TransportException {
  678. super(TransportHttp.this);
  679. statelessRPC = true;
  680. init(advertisement, DisabledOutputStream.INSTANCE);
  681. outNeedsEnd = false;
  682. readAdvertisedRefs();
  683. }
  684. @Override
  685. protected void doFetch(final ProgressMonitor monitor,
  686. final Collection<Ref> want, final Set<ObjectId> have,
  687. final OutputStream outputStream) throws TransportException {
  688. try {
  689. svc = new MultiRequestService(SVC_UPLOAD_PACK);
  690. init(svc.getInputStream(), svc.getOutputStream());
  691. super.doFetch(monitor, want, have, outputStream);
  692. } finally {
  693. svc = null;
  694. }
  695. }
  696. @Override
  697. protected void onReceivePack() {
  698. svc.finalRequest = true;
  699. }
  700. }
  701. class SmartHttpPushConnection extends BasePackPushConnection {
  702. SmartHttpPushConnection(final InputStream advertisement)
  703. throws TransportException {
  704. super(TransportHttp.this);
  705. statelessRPC = true;
  706. init(advertisement, DisabledOutputStream.INSTANCE);
  707. outNeedsEnd = false;
  708. readAdvertisedRefs();
  709. }
  710. protected void doPush(final ProgressMonitor monitor,
  711. final Map<String, RemoteRefUpdate> refUpdates,
  712. OutputStream outputStream) throws TransportException {
  713. final Service svc = new MultiRequestService(SVC_RECEIVE_PACK);
  714. init(svc.getInputStream(), svc.getOutputStream());
  715. super.doPush(monitor, refUpdates, outputStream);
  716. }
  717. }
  718. /** Basic service for sending and receiving HTTP requests. */
  719. abstract class Service {
  720. protected final String serviceName;
  721. protected final String requestType;
  722. protected final String responseType;
  723. protected HttpConnection conn;
  724. protected HttpOutputStream out;
  725. protected final HttpExecuteStream execute;
  726. final UnionInputStream in;
  727. Service(String serviceName) {
  728. this.serviceName = serviceName;
  729. this.requestType = "application/x-" + serviceName + "-request"; //$NON-NLS-1$ //$NON-NLS-2$
  730. this.responseType = "application/x-" + serviceName + "-result"; //$NON-NLS-1$ //$NON-NLS-2$
  731. this.out = new HttpOutputStream();
  732. this.execute = new HttpExecuteStream();
  733. this.in = new UnionInputStream(execute);
  734. }
  735. void openStream() throws IOException {
  736. conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName));
  737. conn.setInstanceFollowRedirects(false);
  738. conn.setDoOutput(true);
  739. conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
  740. conn.setRequestProperty(HDR_ACCEPT, responseType);
  741. }
  742. void sendRequest() throws IOException {
  743. // Try to compress the content, but only if that is smaller.
  744. TemporaryBuffer buf = new TemporaryBuffer.Heap(http.postBuffer);
  745. try {
  746. GZIPOutputStream gzip = new GZIPOutputStream(buf);
  747. out.writeTo(gzip, null);
  748. gzip.close();
  749. if (out.length() < buf.length())
  750. buf = out;
  751. } catch (IOException err) {
  752. // Most likely caused by overflowing the buffer, meaning
  753. // its larger if it were compressed. Don't compress.
  754. buf = out;
  755. }
  756. openStream();
  757. if (buf != out)
  758. conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP);
  759. conn.setFixedLengthStreamingMode((int) buf.length());
  760. final OutputStream httpOut = conn.getOutputStream();
  761. try {
  762. buf.writeTo(httpOut, null);
  763. } finally {
  764. httpOut.close();
  765. }
  766. }
  767. void openResponse() throws IOException {
  768. final int status = HttpSupport.response(conn);
  769. if (status != HttpConnection.HTTP_OK) {
  770. throw new TransportException(uri, status + " " //$NON-NLS-1$
  771. + conn.getResponseMessage());
  772. }
  773. final String contentType = conn.getContentType();
  774. if (!responseType.equals(contentType)) {
  775. conn.getInputStream().close();
  776. throw wrongContentType(responseType, contentType);
  777. }
  778. }
  779. HttpOutputStream getOutputStream() {
  780. return out;
  781. }
  782. InputStream getInputStream() {
  783. return in;
  784. }
  785. abstract void execute() throws IOException;
  786. class HttpExecuteStream extends InputStream {
  787. public int read() throws IOException {
  788. execute();
  789. return -1;
  790. }
  791. public int read(byte[] b, int off, int len) throws IOException {
  792. execute();
  793. return -1;
  794. }
  795. public long skip(long n) throws IOException {
  796. execute();
  797. return 0;
  798. }
  799. }
  800. class HttpOutputStream extends TemporaryBuffer {
  801. HttpOutputStream() {
  802. super(http.postBuffer);
  803. }
  804. @Override
  805. protected OutputStream overflow() throws IOException {
  806. openStream();
  807. conn.setChunkedStreamingMode(0);
  808. return conn.getOutputStream();
  809. }
  810. }
  811. }
  812. /**
  813. * State required to speak multiple HTTP requests with the remote.
  814. * <p>
  815. * A service wrapper provides a normal looking InputStream and OutputStream
  816. * pair which are connected via HTTP to the named remote service. Writing to
  817. * the OutputStream is buffered until either the buffer overflows, or
  818. * reading from the InputStream occurs. If overflow occurs HTTP/1.1 and its
  819. * chunked transfer encoding is used to stream the request data to the
  820. * remote service. If the entire request fits in the memory buffer, the
  821. * older HTTP/1.0 standard and a fixed content length is used instead.
  822. * <p>
  823. * It is an error to attempt to read without there being outstanding data
  824. * ready for transmission on the OutputStream.
  825. * <p>
  826. * No state is preserved between write-read request pairs. The caller is
  827. * responsible for replaying state vector information as part of the request
  828. * data written to the OutputStream. Any session HTTP cookies may or may not
  829. * be preserved between requests, it is left up to the JVM's implementation
  830. * of the HTTP client.
  831. */
  832. class MultiRequestService extends Service {
  833. boolean finalRequest;
  834. MultiRequestService(final String serviceName) {
  835. super(serviceName);
  836. }
  837. /** Keep opening send-receive pairs to the given URI. */
  838. @Override
  839. void execute() throws IOException {
  840. out.close();
  841. if (conn == null) {
  842. if (out.length() == 0) {
  843. // Request output hasn't started yet, but more data is being
  844. // requested. If there is no request data buffered and the
  845. // final request was already sent, do nothing to ensure the
  846. // caller is shown EOF on the InputStream; otherwise an
  847. // programming error has occurred within this module.
  848. if (finalRequest)
  849. return;
  850. throw new TransportException(uri,
  851. JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
  852. }
  853. sendRequest();
  854. }
  855. out.reset();
  856. openResponse();
  857. in.add(openInputStream(conn));
  858. if (!finalRequest)
  859. in.add(execute);
  860. conn = null;
  861. }
  862. }
  863. /** Service for maintaining a single long-poll connection. */
  864. class LongPollService extends Service {
  865. /**
  866. * @param serviceName
  867. */
  868. LongPollService(String serviceName) {
  869. super(serviceName);
  870. }
  871. /** Only open one send-receive request. */
  872. @Override
  873. void execute() throws IOException {
  874. out.close();
  875. if (conn == null)
  876. sendRequest();
  877. openResponse();
  878. in.add(openInputStream(conn));
  879. }
  880. }
  881. }