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

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