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.

HttpSupport.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * Copyright (C) 2010, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.util;
  12. import static java.nio.charset.StandardCharsets.UTF_8;
  13. import java.io.IOException;
  14. import java.io.UnsupportedEncodingException;
  15. import java.net.ConnectException;
  16. import java.net.Proxy;
  17. import java.net.ProxySelector;
  18. import java.net.URI;
  19. import java.net.URISyntaxException;
  20. import java.net.URL;
  21. import java.net.URLEncoder;
  22. import java.security.KeyManagementException;
  23. import java.security.NoSuchAlgorithmException;
  24. import java.text.MessageFormat;
  25. import java.util.Arrays;
  26. import java.util.Collections;
  27. import java.util.LinkedHashSet;
  28. import java.util.Set;
  29. import javax.net.ssl.SSLSocket;
  30. import javax.net.ssl.TrustManager;
  31. import org.eclipse.jgit.internal.JGitText;
  32. import org.eclipse.jgit.transport.http.HttpConnection;
  33. import org.eclipse.jgit.transport.http.NoCheckX509TrustManager;
  34. import org.slf4j.Logger;
  35. import org.slf4j.LoggerFactory;
  36. /**
  37. * Extra utilities to support usage of HTTP.
  38. */
  39. public class HttpSupport {
  40. private final static Logger LOG = LoggerFactory
  41. .getLogger(HttpSupport.class);
  42. /** The {@code GET} HTTP method. */
  43. public static final String METHOD_GET = "GET"; //$NON-NLS-1$
  44. /** The {@code HEAD} HTTP method.
  45. * @since 4.3 */
  46. public static final String METHOD_HEAD = "HEAD"; //$NON-NLS-1$
  47. /** The {@code POST} HTTP method.
  48. * @since 4.3 */
  49. public static final String METHOD_PUT = "PUT"; //$NON-NLS-1$
  50. /** The {@code POST} HTTP method. */
  51. public static final String METHOD_POST = "POST"; //$NON-NLS-1$
  52. /** The {@code Cache-Control} header. */
  53. public static final String HDR_CACHE_CONTROL = "Cache-Control"; //$NON-NLS-1$
  54. /** The {@code Pragma} header. */
  55. public static final String HDR_PRAGMA = "Pragma"; //$NON-NLS-1$
  56. /** The {@code User-Agent} header. */
  57. public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$
  58. /**
  59. * The {@code Server} header.
  60. * @since 4.0
  61. */
  62. public static final String HDR_SERVER = "Server"; //$NON-NLS-1$
  63. /** The {@code Date} header. */
  64. public static final String HDR_DATE = "Date"; //$NON-NLS-1$
  65. /** The {@code Expires} header. */
  66. public static final String HDR_EXPIRES = "Expires"; //$NON-NLS-1$
  67. /** The {@code ETag} header. */
  68. public static final String HDR_ETAG = "ETag"; //$NON-NLS-1$
  69. /** The {@code If-None-Match} header. */
  70. public static final String HDR_IF_NONE_MATCH = "If-None-Match"; //$NON-NLS-1$
  71. /** The {@code Last-Modified} header. */
  72. public static final String HDR_LAST_MODIFIED = "Last-Modified"; //$NON-NLS-1$
  73. /** The {@code If-Modified-Since} header. */
  74. public static final String HDR_IF_MODIFIED_SINCE = "If-Modified-Since"; //$NON-NLS-1$
  75. /** The {@code Accept} header. */
  76. public static final String HDR_ACCEPT = "Accept"; //$NON-NLS-1$
  77. /** The {@code Content-Type} header. */
  78. public static final String HDR_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$
  79. /** The {@code Content-Length} header. */
  80. public static final String HDR_CONTENT_LENGTH = "Content-Length"; //$NON-NLS-1$
  81. /** The {@code Content-Encoding} header. */
  82. public static final String HDR_CONTENT_ENCODING = "Content-Encoding"; //$NON-NLS-1$
  83. /** The {@code Content-Range} header. */
  84. public static final String HDR_CONTENT_RANGE = "Content-Range"; //$NON-NLS-1$
  85. /** The {@code Accept-Ranges} header. */
  86. public static final String HDR_ACCEPT_RANGES = "Accept-Ranges"; //$NON-NLS-1$
  87. /** The {@code If-Range} header. */
  88. public static final String HDR_IF_RANGE = "If-Range"; //$NON-NLS-1$
  89. /** The {@code Range} header. */
  90. public static final String HDR_RANGE = "Range"; //$NON-NLS-1$
  91. /** The {@code Accept-Encoding} header. */
  92. public static final String HDR_ACCEPT_ENCODING = "Accept-Encoding"; //$NON-NLS-1$
  93. /**
  94. * The {@code Location} header.
  95. * @since 4.7
  96. */
  97. public static final String HDR_LOCATION = "Location"; //$NON-NLS-1$
  98. /** The {@code gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. */
  99. public static final String ENCODING_GZIP = "gzip"; //$NON-NLS-1$
  100. /**
  101. * The {@code x-gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}.
  102. * @since 4.6
  103. */
  104. public static final String ENCODING_X_GZIP = "x-gzip"; //$NON-NLS-1$
  105. /** The standard {@code text/plain} MIME type. */
  106. public static final String TEXT_PLAIN = "text/plain"; //$NON-NLS-1$
  107. /** The {@code Authorization} header. */
  108. public static final String HDR_AUTHORIZATION = "Authorization"; //$NON-NLS-1$
  109. /** The {@code WWW-Authenticate} header. */
  110. public static final String HDR_WWW_AUTHENTICATE = "WWW-Authenticate"; //$NON-NLS-1$
  111. /**
  112. * The {@code Cookie} header.
  113. *
  114. * @since 5.4
  115. */
  116. public static final String HDR_COOKIE = "Cookie"; //$NON-NLS-1$
  117. /**
  118. * The {@code Set-Cookie} header.
  119. *
  120. * @since 5.4
  121. */
  122. public static final String HDR_SET_COOKIE = "Set-Cookie"; //$NON-NLS-1$
  123. /**
  124. * The {@code Set-Cookie2} header.
  125. *
  126. * @since 5.4
  127. */
  128. public static final String HDR_SET_COOKIE2 = "Set-Cookie2"; //$NON-NLS-1$
  129. private static Set<String> configuredHttpsProtocols;
  130. /**
  131. * URL encode a value string into an output buffer.
  132. *
  133. * @param urlstr
  134. * the output buffer.
  135. * @param key
  136. * value which must be encoded to protected special characters.
  137. */
  138. public static void encode(StringBuilder urlstr, String key) {
  139. if (key == null || key.length() == 0)
  140. return;
  141. try {
  142. urlstr.append(URLEncoder.encode(key, UTF_8.name()));
  143. } catch (UnsupportedEncodingException e) {
  144. throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e);
  145. }
  146. }
  147. /**
  148. * Get the HTTP response code from the request.
  149. * <p>
  150. * Roughly the same as <code>c.getResponseCode()</code> but the
  151. * ConnectException is translated to be more understandable.
  152. *
  153. * @param c
  154. * connection the code should be obtained from.
  155. * @return r HTTP status code, usually 200 to indicate success. See
  156. * {@link org.eclipse.jgit.transport.http.HttpConnection} for other
  157. * defined constants.
  158. * @throws java.io.IOException
  159. * communications error prevented obtaining the response code.
  160. * @since 3.3
  161. */
  162. public static int response(HttpConnection c) throws IOException {
  163. try {
  164. return c.getResponseCode();
  165. } catch (ConnectException ce) {
  166. final URL url = c.getURL();
  167. final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$
  168. // The standard J2SE error message is not very useful.
  169. //
  170. if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$
  171. throw new ConnectException(MessageFormat.format(JGitText.get().connectionTimeOut, host));
  172. throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$
  173. }
  174. }
  175. /**
  176. * Get the HTTP response code from the request.
  177. * <p>
  178. * Roughly the same as <code>c.getResponseCode()</code> but the
  179. * ConnectException is translated to be more understandable.
  180. *
  181. * @param c
  182. * connection the code should be obtained from.
  183. * @return r HTTP status code, usually 200 to indicate success. See
  184. * {@link org.eclipse.jgit.transport.http.HttpConnection} for other
  185. * defined constants.
  186. * @throws java.io.IOException
  187. * communications error prevented obtaining the response code.
  188. */
  189. public static int response(java.net.HttpURLConnection c)
  190. throws IOException {
  191. try {
  192. return c.getResponseCode();
  193. } catch (ConnectException ce) {
  194. final URL url = c.getURL();
  195. final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$
  196. // The standard J2SE error message is not very useful.
  197. //
  198. if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$
  199. throw new ConnectException(MessageFormat.format(
  200. JGitText.get().connectionTimeOut, host));
  201. throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$
  202. }
  203. }
  204. /**
  205. * Extract a HTTP header from the response.
  206. *
  207. * @param c
  208. * connection the header should be obtained from.
  209. * @param headerName
  210. * the header name
  211. * @return the header value
  212. * @throws java.io.IOException
  213. * communications error prevented obtaining the header.
  214. * @since 4.7
  215. */
  216. public static String responseHeader(final HttpConnection c,
  217. final String headerName) throws IOException {
  218. return c.getHeaderField(headerName);
  219. }
  220. /**
  221. * Determine the proxy server (if any) needed to obtain a URL.
  222. *
  223. * @param proxySelector
  224. * proxy support for the caller.
  225. * @param u
  226. * location of the server caller wants to talk to.
  227. * @return proxy to communicate with the supplied URL.
  228. * @throws java.net.ConnectException
  229. * the proxy could not be computed as the supplied URL could not
  230. * be read. This failure should never occur.
  231. */
  232. public static Proxy proxyFor(ProxySelector proxySelector, URL u)
  233. throws ConnectException {
  234. try {
  235. URI uri = new URI(u.getProtocol(), null, u.getHost(), u.getPort(),
  236. null, null, null);
  237. return proxySelector.select(uri).get(0);
  238. } catch (URISyntaxException e) {
  239. final ConnectException err;
  240. err = new ConnectException(MessageFormat.format(JGitText.get().cannotDetermineProxyFor, u));
  241. err.initCause(e);
  242. throw err;
  243. }
  244. }
  245. /**
  246. * Disable SSL and hostname verification for given HTTP connection
  247. *
  248. * @param conn
  249. * a {@link org.eclipse.jgit.transport.http.HttpConnection}
  250. * object.
  251. * @throws java.io.IOException
  252. * @since 4.3
  253. */
  254. public static void disableSslVerify(HttpConnection conn)
  255. throws IOException {
  256. TrustManager[] trustAllCerts = {
  257. new NoCheckX509TrustManager() };
  258. try {
  259. conn.configure(null, trustAllCerts, null);
  260. conn.setHostnameVerifier((name, session) -> true);
  261. } catch (KeyManagementException | NoSuchAlgorithmException e) {
  262. throw new IOException(e.getMessage(), e);
  263. }
  264. }
  265. /**
  266. * Enables all supported TLS protocol versions on the socket given. If
  267. * system property "https.protocols" is set, only protocols specified there
  268. * are enabled.
  269. * <p>
  270. * This is primarily a mechanism to deal with using TLS on IBM JDK. IBM JDK
  271. * returns sockets that support all TLS protocol versions but have only the
  272. * one specified in the context enabled. Oracle or OpenJDK return sockets
  273. * that have all available protocols enabled already, up to the one
  274. * specified.
  275. * <p>
  276. * <table>
  277. * <tr>
  278. * <td>SSLContext.getInstance()</td>
  279. * <td>OpenJDK</td>
  280. * <td>IDM JDK</td>
  281. * </tr>
  282. * <tr>
  283. * <td>"TLS"</td>
  284. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br />
  285. * Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)</td>
  286. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
  287. * Enabled: TLSv1</td>
  288. * </tr>
  289. * <tr>
  290. * <td>"TLSv1.2"</td>
  291. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
  292. * Enabled: TLSv1, TLSV1.1, TLSv1.2</td>
  293. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
  294. * Enabled: TLSv1.2</td>
  295. * </tr>
  296. * </table>
  297. *
  298. * @param socket
  299. * to configure
  300. * @see <a href=
  301. * "https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html">Behavior
  302. * of SSLContext.getInstance("TLS") on IBM JDK</a>
  303. * @see <a href=
  304. * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization">Customizing
  305. * JSSE about https.protocols</a>
  306. * @since 5.7
  307. */
  308. public static void configureTLS(SSLSocket socket) {
  309. // 1. Enable all available TLS protocol versions
  310. Set<String> enabled = new LinkedHashSet<>(
  311. Arrays.asList(socket.getEnabledProtocols()));
  312. for (String s : socket.getSupportedProtocols()) {
  313. if (s.startsWith("TLS")) { //$NON-NLS-1$
  314. enabled.add(s);
  315. }
  316. }
  317. // 2. Respect the https.protocols system property
  318. Set<String> configured = getConfiguredProtocols();
  319. if (!configured.isEmpty()) {
  320. enabled.retainAll(configured);
  321. }
  322. if (!enabled.isEmpty()) {
  323. socket.setEnabledProtocols(enabled.toArray(new String[0]));
  324. }
  325. }
  326. private static Set<String> getConfiguredProtocols() {
  327. Set<String> result = configuredHttpsProtocols;
  328. if (result == null) {
  329. String configured = getProperty("https.protocols"); //$NON-NLS-1$
  330. if (StringUtils.isEmptyOrNull(configured)) {
  331. result = Collections.emptySet();
  332. } else {
  333. result = new LinkedHashSet<>(
  334. Arrays.asList(configured.split("\\s*,\\s*"))); //$NON-NLS-1$
  335. }
  336. configuredHttpsProtocols = result;
  337. }
  338. return result;
  339. }
  340. private static String getProperty(String property) {
  341. try {
  342. return SystemReader.getInstance().getProperty(property);
  343. } catch (SecurityException e) {
  344. LOG.warn(JGitText.get().failedReadHttpsProtocols, e);
  345. return null;
  346. }
  347. }
  348. /**
  349. * Scan a RFC 7230 token as it appears in HTTP headers.
  350. *
  351. * @param header
  352. * to scan in
  353. * @param from
  354. * index in {@code header} to start scanning at
  355. * @return the index after the token, that is, on the first non-token
  356. * character or {@code header.length}
  357. * @throws IndexOutOfBoundsException
  358. * if {@code from < 0} or {@code from > header.length()}
  359. *
  360. * @see <a href="https://tools.ietf.org/html/rfc7230#appendix-B">RFC 7230,
  361. * Appendix B: Collected Grammar; "token" production</a>
  362. * @since 5.10
  363. */
  364. public static int scanToken(String header, int from) {
  365. int length = header.length();
  366. int i = from;
  367. if (i < 0 || i > length) {
  368. throw new IndexOutOfBoundsException();
  369. }
  370. while (i < length) {
  371. char c = header.charAt(i);
  372. switch (c) {
  373. case '!':
  374. case '#':
  375. case '$':
  376. case '%':
  377. case '&':
  378. case '\'':
  379. case '*':
  380. case '+':
  381. case '-':
  382. case '.':
  383. case '^':
  384. case '_':
  385. case '`':
  386. case '|':
  387. case '~':
  388. case '0':
  389. case '1':
  390. case '2':
  391. case '3':
  392. case '4':
  393. case '5':
  394. case '6':
  395. case '7':
  396. case '8':
  397. case '9':
  398. i++;
  399. break;
  400. default:
  401. if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
  402. i++;
  403. break;
  404. }
  405. return i;
  406. }
  407. }
  408. return i;
  409. }
  410. private HttpSupport() {
  411. // Utility class only.
  412. }
  413. }