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 33KB

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