123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- /*
- * Copyright (C) 2008, 2010, Google Inc.
- * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
- package org.eclipse.jgit.transport;
-
- import java.io.IOException;
- import java.net.URISyntaxException;
- import java.text.MessageFormat;
- import java.util.Set;
- import java.util.function.Supplier;
-
- import org.eclipse.jgit.errors.ConfigInvalidException;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.lib.Config;
- import org.eclipse.jgit.storage.file.FileBasedConfig;
- import org.eclipse.jgit.util.FS;
- import org.eclipse.jgit.util.StringUtils;
- import org.eclipse.jgit.util.SystemReader;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- /**
- * A representation of the "http.*" config values in a git
- * {@link org.eclipse.jgit.lib.Config}. git provides for setting values for
- * specific URLs through "http.<url>.*" subsections. git always considers
- * only the initial original URL for such settings, not any redirected URL.
- *
- * @since 4.9
- */
- public class HttpConfig {
-
- private static final Logger LOG = LoggerFactory.getLogger(HttpConfig.class);
-
- private static final String FTP = "ftp"; //$NON-NLS-1$
-
- /** git config section key for http settings. */
- public static final String HTTP = "http"; //$NON-NLS-1$
-
- /** git config key for the "followRedirects" setting. */
- public static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$
-
- /** git config key for the "maxRedirects" setting. */
- public static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$
-
- /** git config key for the "postBuffer" setting. */
- public static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$
-
- /** git config key for the "sslVerify" setting. */
- public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
-
- private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$
-
- private static final int DEFAULT_MAX_REDIRECTS = 5;
-
- private static final int MAX_REDIRECTS = (new Supplier<Integer>() {
-
- @Override
- public Integer get() {
- String rawValue = SystemReader.getInstance()
- .getProperty(MAX_REDIRECT_SYSTEM_PROPERTY);
- Integer value = Integer.valueOf(DEFAULT_MAX_REDIRECTS);
- if (rawValue != null) {
- try {
- value = Integer.valueOf(Integer.parseUnsignedInt(rawValue));
- } catch (NumberFormatException e) {
- LOG.warn(MessageFormat.format(
- JGitText.get().invalidSystemProperty,
- MAX_REDIRECT_SYSTEM_PROPERTY, rawValue, value));
- }
- }
- return value;
- }
- }).get().intValue();
-
- /**
- * Config values for http.followRedirect.
- */
- public enum HttpRedirectMode implements Config.ConfigEnum {
-
- /** Always follow redirects (up to the http.maxRedirects limit). */
- TRUE("true"), //$NON-NLS-1$
- /**
- * Only follow redirects on the initial GET request. This is the
- * default.
- */
- INITIAL("initial"), //$NON-NLS-1$
- /** Never follow redirects. */
- FALSE("false"); //$NON-NLS-1$
-
- private final String configValue;
-
- private HttpRedirectMode(String configValue) {
- this.configValue = configValue;
- }
-
- @Override
- public String toConfigValue() {
- return configValue;
- }
-
- @Override
- public boolean matchConfigValue(String s) {
- return configValue.equals(s);
- }
- }
-
- private int postBuffer;
-
- private boolean sslVerify;
-
- private HttpRedirectMode followRedirects;
-
- private int maxRedirects;
-
- /**
- * Get the "http.postBuffer" setting
- *
- * @return the value of the "http.postBuffer" setting
- */
- public int getPostBuffer() {
- return postBuffer;
- }
-
- /**
- * Get the "http.sslVerify" setting
- *
- * @return the value of the "http.sslVerify" setting
- */
- public boolean isSslVerify() {
- return sslVerify;
- }
-
- /**
- * Get the "http.followRedirects" setting
- *
- * @return the value of the "http.followRedirects" setting
- */
- public HttpRedirectMode getFollowRedirects() {
- return followRedirects;
- }
-
- /**
- * Get the "http.maxRedirects" setting
- *
- * @return the value of the "http.maxRedirects" setting
- */
- public int getMaxRedirects() {
- return maxRedirects;
- }
-
- /**
- * Creates a new {@link org.eclipse.jgit.transport.HttpConfig} tailored to
- * the given {@link org.eclipse.jgit.transport.URIish}.
- *
- * @param config
- * to read the {@link org.eclipse.jgit.transport.HttpConfig} from
- * @param uri
- * to get the configuration values for
- */
- public HttpConfig(Config config, URIish uri) {
- init(config, uri);
- }
-
- /**
- * Creates a {@link org.eclipse.jgit.transport.HttpConfig} that reads values
- * solely from the user config.
- *
- * @param uri
- * to get the configuration values for
- */
- public HttpConfig(URIish uri) {
- FileBasedConfig userConfig = SystemReader.getInstance()
- .openUserConfig(null, FS.DETECTED);
- try {
- userConfig.load();
- } catch (IOException | ConfigInvalidException e) {
- // Log it and then work with default values.
- LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
- userConfig.getFile().getAbsolutePath(), e));
- init(new Config(), uri);
- return;
- }
- init(userConfig, uri);
- }
-
- private void init(Config config, URIish uri) {
- // Set defaults from the section first
- int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY,
- 1 * 1024 * 1024);
- boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true);
- HttpRedirectMode followRedirectsMode = config.getEnum(
- HttpRedirectMode.values(), HTTP, null,
- FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL);
- int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY,
- MAX_REDIRECTS);
- if (redirectLimit < 0) {
- redirectLimit = MAX_REDIRECTS;
- }
- String match = findMatch(config.getSubsections(HTTP), uri);
- if (match != null) {
- // Override with more specific items
- postBufferSize = config.getInt(HTTP, match, POST_BUFFER_KEY,
- postBufferSize);
- sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY,
- sslVerifyFlag);
- followRedirectsMode = config.getEnum(HttpRedirectMode.values(),
- HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode);
- int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY,
- redirectLimit);
- if (newMaxRedirects >= 0) {
- redirectLimit = newMaxRedirects;
- }
- }
- postBuffer = postBufferSize;
- sslVerify = sslVerifyFlag;
- followRedirects = followRedirectsMode;
- maxRedirects = redirectLimit;
- }
-
- /**
- * Determines the best match from a set of subsection names (representing
- * prefix URLs) for the given {@link URIish}.
- *
- * @param names
- * to match against the {@code uri}
- * @param uri
- * to find a match for
- * @return the best matching subsection name, or {@code null} if no
- * subsection matches
- */
- private String findMatch(Set<String> names, URIish uri) {
- String bestMatch = null;
- int bestMatchLength = -1;
- boolean withUser = false;
- String uPath = uri.getPath();
- boolean hasPath = !StringUtils.isEmptyOrNull(uPath);
- if (hasPath) {
- uPath = normalize(uPath);
- if (uPath == null) {
- // Normalization failed; warning was logged.
- return null;
- }
- }
- for (String s : names) {
- try {
- URIish candidate = new URIish(s);
- // Scheme and host must match case-insensitively
- if (!compare(uri.getScheme(), candidate.getScheme())
- || !compare(uri.getHost(), candidate.getHost())) {
- continue;
- }
- // Ports must match after default ports have been substituted
- if (defaultedPort(uri.getPort(),
- uri.getScheme()) != defaultedPort(candidate.getPort(),
- candidate.getScheme())) {
- continue;
- }
- // User: if present in candidate, must match
- boolean hasUser = false;
- if (candidate.getUser() != null) {
- if (!candidate.getUser().equals(uri.getUser())) {
- continue;
- }
- hasUser = true;
- }
- // Path: prefix match, longer is better
- String cPath = candidate.getPath();
- int matchLength = -1;
- if (StringUtils.isEmptyOrNull(cPath)) {
- matchLength = 0;
- } else {
- if (!hasPath) {
- continue;
- }
- // Paths can match only on segments
- matchLength = segmentCompare(uPath, cPath);
- if (matchLength < 0) {
- continue;
- }
- }
- // A longer path match is always preferred even over a user
- // match. If the path matches are equal, a match with user wins
- // over a match without user.
- if (matchLength > bestMatchLength || !withUser && hasUser
- && matchLength >= 0 && matchLength == bestMatchLength) {
- bestMatch = s;
- bestMatchLength = matchLength;
- withUser = hasUser;
- }
- } catch (URISyntaxException e) {
- LOG.warn(MessageFormat
- .format(JGitText.get().httpConfigInvalidURL, s));
- }
- }
- return bestMatch;
- }
-
- private boolean compare(String a, String b) {
- if (a == null) {
- return b == null;
- }
- return a.equalsIgnoreCase(b);
- }
-
- private int defaultedPort(int port, String scheme) {
- if (port >= 0) {
- return port;
- }
- if (FTP.equalsIgnoreCase(scheme)) {
- return 21;
- } else if (HTTP.equalsIgnoreCase(scheme)) {
- return 80;
- } else {
- return 443; // https
- }
- }
-
- static int segmentCompare(String uriPath, String m) {
- // Precondition: !uriPath.isEmpty() && !m.isEmpty(),and u must already
- // be normalized
- String matchPath = normalize(m);
- if (matchPath == null || !uriPath.startsWith(matchPath)) {
- return -1;
- }
- // We can match only on a segment boundary: either both paths are equal,
- // or if matchPath does not end in '/', there is a '/' in uriPath right
- // after the match.
- int uLength = uriPath.length();
- int mLength = matchPath.length();
- if (mLength == uLength || matchPath.charAt(mLength - 1) == '/'
- || mLength < uLength && uriPath.charAt(mLength) == '/') {
- return mLength;
- }
- return -1;
- }
-
- static String normalize(String path) {
- // C-git resolves . and .. segments
- int i = 0;
- int length = path.length();
- StringBuilder builder = new StringBuilder(length);
- builder.append('/');
- if (length > 0 && path.charAt(0) == '/') {
- i = 1;
- }
- while (i < length) {
- int slash = path.indexOf('/', i);
- if (slash < 0) {
- slash = length;
- }
- if (slash == i || slash == i + 1 && path.charAt(i) == '.') {
- // Skip /. or also double slashes
- } else if (slash == i + 2 && path.charAt(i) == '.'
- && path.charAt(i + 1) == '.') {
- // Remove previous segment if we have "/.."
- int l = builder.length() - 2; // Skip terminating slash.
- while (l >= 0 && builder.charAt(l) != '/') {
- l--;
- }
- if (l < 0) {
- LOG.warn(MessageFormat.format(
- JGitText.get().httpConfigCannotNormalizeURL, path));
- return null;
- }
- builder.setLength(l + 1);
- } else {
- // Include the slash, if any
- builder.append(path, i, Math.min(length, slash + 1));
- }
- i = slash + 1;
- }
- if (builder.length() > 1 && builder.charAt(builder.length() - 1) == '/'
- && length > 0 && path.charAt(length - 1) != '/') {
- // . or .. normalization left a trailing slash when the original
- // path had none at the end
- builder.setLength(builder.length() - 1);
- }
- return builder.toString();
- }
- }
|