diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java index 42064581c..47a84354f 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java @@ -153,7 +153,12 @@ public class SmartClientSmartServerSslTest extends HttpTestCase { FileBasedConfig userConfig = SystemReader.getInstance() .openUserConfig(null, FS.DETECTED); - userConfig.setBoolean("http", null, "sslVerify", false); + userConfig.setBoolean("http", + "https://" + secureURI.getHost() + ':' + server.getSecurePort(), + "sslVerify", false); + userConfig.setBoolean("http", + "http://" + remoteURI.getHost() + ':' + server.getPort(), + "sslVerify", false); userConfig.save(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java new file mode 100644 index 000000000..c6b016a4c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.lib.Config; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for correctly resolving URIs when reading http.* values from a + * {@link Config}. + */ +public class HttpConfigTest { + + private static final String DEFAULT = "[http]\n" + "\tpostBuffer = 1\n" + + "\tsslVerify= true\n" + "\tfollowRedirects = true\n" + + "\tmaxRedirects = 5\n\n"; + + private Config config; + + @Before + public void setUp() { + config = new Config(); + } + + @Test + public void testDefault() throws Exception { + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024 * 1024, http.getPostBuffer()); + assertTrue(http.isSslVerify()); + assertEquals(HttpConfig.HttpRedirectMode.INITIAL, + http.getFollowRedirects()); + } + + @Test + public void testMatchSuccess() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("https://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.org/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com:80/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com:8080/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchWithOnlySchemeInConfig() throws Exception { + config.fromText( + DEFAULT + "[http \"http://\"]\n" + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchWithPrefixUriInConfig() throws Exception { + config.fromText(DEFAULT + "[http \"http://example\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchCaseSensitivity() throws Exception { + config.fromText(DEFAULT + "[http \"http://exAMPle.com\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + } + + @Test + public void testMatchWithInvalidUriInConfig() throws Exception { + config.fromText( + DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + } + + @Test + public void testMatchWithInvalidAndValidUriInConfig() throws Exception { + config.fromText(DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n" + + "[http \"http://example.com\"]\n" + "\tpostBuffer = 2048\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(2048, http.getPostBuffer()); + } + + @Test + public void testMatchWithHostEndingInSlash() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com/\"]\n" + + "\tpostBuffer = 1024\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + } + + @Test + public void testMatchWithUser() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n" + + "\tpostBuffer = 1024\n" + + "[http \"http://example.com/path/repo\"]\n" + + "\tpostBuffer = 2048\n" + + "[http \"http://user@example.com/path\"]\n" + + "\tpostBuffer = 4096\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path/repo.git")); + assertEquals(4096, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path/repo/foo.git")); + assertEquals(2048, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path/foo.git")); + assertEquals(4096, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com/path/foo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://User@example.com/path/repo/foo.git")); + assertEquals(2048, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://User@example.com/path/foo.git")); + assertEquals(1024, http.getPostBuffer()); + } + + @Test + public void testMatchLonger() throws Exception { + config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n" + + "\tpostBuffer = 1024\n" + + "[http \"http://example.com/path/repo\"]\n" + + "\tpostBuffer = 2048\n"); + HttpConfig http = new HttpConfig(config, + new URIish("http://example.com/path/repo.git")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com/foo/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("https://example.com/path/repo.git")); + assertEquals(1, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://example.com/path/repo/.git")); + assertEquals(2048, http.getPostBuffer()); + http = new HttpConfig(config, new URIish("http://example.com/path")); + assertEquals(1024, http.getPostBuffer()); + http = new HttpConfig(config, + new URIish("http://user@example.com/path")); + assertEquals(1024, http.getPostBuffer()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java new file mode 100644 index 000000000..94de2f211 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2017, Thomas Wolf + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +/** + * Basic URI path prefix match tests for {@link HttpConfig}. + */ +public class HttpConfigUriPathTest { + + @Test + public void testNormalizationEmptyPaths() { + assertEquals("/", HttpConfig.normalize("")); + assertEquals("/", HttpConfig.normalize("/")); + } + + @Test + public void testNormalization() { + assertEquals("/f", HttpConfig.normalize("f")); + assertEquals("/f", HttpConfig.normalize("/f")); + assertEquals("/f/", HttpConfig.normalize("/f/")); + assertEquals("/foo", HttpConfig.normalize("foo")); + assertEquals("/foo", HttpConfig.normalize("/foo")); + assertEquals("/foo/", HttpConfig.normalize("/foo/")); + assertEquals("/foo/bar", HttpConfig.normalize("foo/bar")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/")); + } + + @Test + public void testNormalizationWithDot() { + assertEquals("/", HttpConfig.normalize(".")); + assertEquals("/", HttpConfig.normalize("/.")); + assertEquals("/", HttpConfig.normalize("/./")); + assertEquals("/foo", HttpConfig.normalize("foo/.")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/./bar")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/.")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/./././bar")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/./././bar/")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/././.")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./././")); + assertEquals("/foo/bar/.baz/bam", + HttpConfig.normalize("/foo/bar/.baz/bam")); + assertEquals("/foo/bar/.baz/bam/", + HttpConfig.normalize("/foo/bar/.baz/bam/")); + } + + @Test + public void testNormalizationWithDotDot() { + assertEquals("/", HttpConfig.normalize("foo/..")); + assertEquals("/", HttpConfig.normalize("/foo/..")); + assertEquals("/", HttpConfig.normalize("/foo/../bar/..")); + assertEquals("/", HttpConfig.normalize("/foo/.././bar/..")); + assertEquals("/bar", HttpConfig.normalize("foo/../bar")); + assertEquals("/bar", HttpConfig.normalize("/foo/../bar")); + assertEquals("/bar", HttpConfig.normalize("/foo/./.././bar")); + assertEquals("/bar/", HttpConfig.normalize("/foo/../bar/")); + assertEquals("/bar/", HttpConfig.normalize("/foo/./.././bar/")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/baz/..")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/baz/../")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../..")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../..")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/.././..")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../././..")); + assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz")); + assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/")); + assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz/.")); + assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/./")); + assertEquals("/foo", HttpConfig.normalize("/foo/bar/../baz/..")); + assertEquals("/foo/", HttpConfig.normalize("/foo/bar/../baz/../")); + assertEquals("/baz", HttpConfig.normalize("/foo/bar/../../baz")); + assertEquals("/baz/", HttpConfig.normalize("/foo/bar/../../baz/")); + assertEquals("/foo/.b/bar", HttpConfig.normalize("/foo/.b/bar")); + assertEquals("/.f/foo/.b/bar/", HttpConfig.normalize(".f/foo/.b/bar/")); + assertEquals("/foo/bar/..baz/bam", + HttpConfig.normalize("/foo/bar/..baz/bam")); + assertEquals("/foo/bar/..baz/bam/", + HttpConfig.normalize("/foo/bar/..baz/bam/")); + assertEquals("/foo/bar/.../baz/bam", + HttpConfig.normalize("/foo/bar/.../baz/bam")); + assertEquals("/foo/bar/.../baz/bam/", + HttpConfig.normalize("/foo/bar/.../baz/bam/")); + } + + @Test + public void testNormalizationWithDoubleSlash() { + assertEquals("/", HttpConfig.normalize("//")); + assertEquals("/foo/", HttpConfig.normalize("///foo//")); + assertEquals("/foo", HttpConfig.normalize("///foo//.")); + assertEquals("/foo/", HttpConfig.normalize("///foo//.////")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar")); + assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar//.")); + assertEquals("/foo/bar/", HttpConfig.normalize("/foo//bar//./")); + } + + @Test + public void testNormalizationWithDotDotFailing() { + assertNull(HttpConfig.normalize("..")); + assertNull(HttpConfig.normalize("/..")); + assertNull(HttpConfig.normalize("/../")); + assertNull(HttpConfig.normalize("/../foo")); + assertNull(HttpConfig.normalize("./../foo")); + assertNull(HttpConfig.normalize("/./../foo")); + assertNull(HttpConfig.normalize("/foo/./.././..")); + assertNull(HttpConfig.normalize("/foo/../bar/../..")); + assertNull(HttpConfig.normalize("/foo/../bar/../../baz")); + } + + @Test + public void testSegmentCompare() { + // 2nd parameter is the match, will be normalized + assertSuccess("/foo", ""); + assertSuccess("/foo", "/"); + assertSuccess("/foo", "//"); + assertSuccess("/foo", "foo"); + assertSuccess("/foo", "/foo"); + assertSuccess("/foo/", "foo"); + assertSuccess("/foo/", "/foo"); + assertSuccess("/foo/", "foo/"); + assertSuccess("/foo/", "/foo/"); + assertSuccess("/foo/bar", "foo"); + assertSuccess("/foo/bar", "foo/"); + assertSuccess("/foo/bar", "foo/bar"); + assertSuccess("/foo/bar/", "foo/bar"); + assertSuccess("/foo/bar/", "foo/bar/"); + assertSuccess("/foo/bar", "/foo/bar"); + assertSuccess("/foo/bar/", "/foo/bar"); + assertSuccess("/foo/bar/", "/foo/bar/"); + assertSuccess("/foo/bar", "/foo/bar/.."); + assertSuccess("/foo/bar/", "/foo/bar/.."); + assertSuccess("/foo/bar/", "/foo/bar/../"); + assertSuccess("/foo/bar", "/foo/./bar"); + assertSuccess("/foo/bar/", "/foo/./bar/"); + assertSuccess("/some/repo/.git", "/some/repo"); + assertSuccess("/some/repo/bare.git", "/some/repo"); + assertSuccess("/some/repo/.git", "/some/repo/.git"); + assertSuccess("/some/repo/bare.git", "/some/repo/bare.git"); + } + + @Test + public void testSegmentCompareFailing() { + // 2nd parameter is the match, will be normalized + assertEquals(-1, HttpConfig.segmentCompare("/foo", "foo/")); + assertEquals(-1, HttpConfig.segmentCompare("/foo", "/foo/")); + assertEquals(-1, HttpConfig.segmentCompare("/foobar", "foo")); + assertEquals(-1, HttpConfig.segmentCompare("/foobar", "/foo")); + assertEquals(-1, + HttpConfig.segmentCompare("/foo/barbar/baz", "foo/bar")); + assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar", "/foo/bar")); + assertEquals(-1, + HttpConfig.segmentCompare("/some/repo.git", "/some/repo")); + assertEquals(-1, + HttpConfig.segmentCompare("/some/repo.git", "/some/repo.g")); + assertEquals(-1, HttpConfig.segmentCompare("/some/repo/bare.git", + "/some/repo/bar")); + assertSuccess("/some/repo/bare.git", "/some/repo"); + // Just to make sure we don't use the PathMatchers... + assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar/baz", "**")); + assertEquals(-1, + HttpConfig.segmentCompare("/foo/barbar/baz", "**/foo")); + assertEquals(-1, + HttpConfig.segmentCompare("/foo/barbar/baz", "/*/barbar/**")); + assertEquals(-1, HttpConfig.segmentCompare("/foo", "/*")); + assertEquals(-1, HttpConfig.segmentCompare("/foo", "/???")); + assertEquals(-1, HttpConfig.segmentCompare("/foo/bar/baz", "bar")); + // Failing to normalize + assertEquals(-1, + HttpConfig.segmentCompare("/foo/bar/baz", "bar/../..")); + } + + private void assertSuccess(String uri, String match) { + String normalized = HttpConfig.normalize(match); + assertEquals(normalized.length(), + HttpConfig.segmentCompare(uri, match)); + } +} diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 8d3931493..66699be35 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -307,6 +307,8 @@ gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. gitmodulesNotFound=.gitmodules not found in tree. headRequiredToStash=HEAD required to stash local changes hoursAgo={0} hours ago +httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments +httpConfigInvalidURL=Cannot parse URL from subsection http.{0} in git config; ignored. hugeIndexesAreNotSupportedByJgitYet=Huge indexes are not supported by jgit, yet hunkBelongsToAnotherFile=Hunk belongs to another file hunkDisconnectedFromFile=Hunk disconnected from file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 07666eb93..0f1c8e984 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -366,6 +366,8 @@ public class JGitText extends TranslationBundle { /***/ public String gitmodulesNotFound; /***/ public String headRequiredToStash; /***/ public String hoursAgo; + /***/ public String httpConfigCannotNormalizeURL; + /***/ public String httpConfigInvalidURL; /***/ public String hugeIndexesAreNotSupportedByJgitYet; /***/ public String hunkBelongsToAnotherFile; /***/ public String hunkDisconnectedFromFile; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java new file mode 100644 index 000000000..73f596db7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2008, 2010, Google Inc. + * Copyright (C) 2017, Thomas Wolf + * 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.net.URISyntaxException; +import java.text.MessageFormat; +import java.util.Set; +import java.util.function.Supplier; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config; +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 Config}. git + * provides for setting values for specific URLs through "http..* + * 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$ + + private static final String HTTP = "http"; //$NON-NLS-1$ + + private static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$ + + private static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$ + + private static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$ + + private 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() { + + @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 final int postBuffer; + + private final boolean sslVerify; + + private final HttpRedirectMode followRedirects; + + private final int maxRedirects; + + /** + * @return the value of the "http.postBuffer" setting + */ + public int getPostBuffer() { + return postBuffer; + } + + /** + * @return the value of the "http.sslVerify" setting + */ + public boolean isSslVerify() { + return sslVerify; + } + + /** + * @return the value of the "http.followRedirects" setting + */ + public HttpRedirectMode getFollowRedirects() { + return followRedirects; + } + + /** + * @return the value of the "http.maxRedirects" setting + */ + public int getMaxRedirects() { + return maxRedirects; + } + + /** + * Creates a new {@link HttpConfig} tailored to the given {@link URIish}. + * + * @param config + * to read the {@link HttpConfig} from + * @param uri + * to get the configuration values for + */ + public HttpConfig(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; + } + + /** + * Creates a {@link HttpConfig} that reads values solely from the user + * config. + * + * @param uri + * to get the configuration values for + */ + public HttpConfig(URIish uri) { + this(SystemReader.getInstance().openUserConfig(null, FS.DETECTED), uri); + } + + /** + * 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 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(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 26612120c..937c36885 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -84,7 +84,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import java.util.function.Supplier; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -94,7 +93,6 @@ import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.RefDirectory; -import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; @@ -103,11 +101,11 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.transport.HttpAuthMethod.Type; +import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode; import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; -import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.io.DisabledOutputStream; import org.eclipse.jgit.util.io.UnionInputStream; @@ -140,30 +138,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport, private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$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() { - - @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(); - /** * Accept-Encoding header in the HTTP request * (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). @@ -264,65 +238,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport, } }; - /** - * Config values for http.followRedirect - */ - private static 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 static class HttpConfig { - final int postBuffer; - - final boolean sslVerify; - - final HttpRedirectMode followRedirects; - - final int maxRedirects; - - HttpConfig(final Config rc) { - postBuffer = rc.getInt("http", "postbuffer", 1 * 1024 * 1024); //$NON-NLS-1$ //$NON-NLS-2$ - sslVerify = rc.getBoolean("http", "sslVerify", true); //$NON-NLS-1$ //$NON-NLS-2$ - followRedirects = rc.getEnum(HttpRedirectMode.values(), "http", //$NON-NLS-1$ - null, "followRedirects", HttpRedirectMode.INITIAL); //$NON-NLS-1$ - int redirectLimit = rc.getInt("http", "maxRedirects", //$NON-NLS-1$ //$NON-NLS-2$ - MAX_REDIRECTS); - if (redirectLimit < 0) { - redirectLimit = MAX_REDIRECTS; - } - maxRedirects = redirectLimit; - } - - HttpConfig() { - this(new Config()); - } - } - /** * The current URI we're talking to. The inherited (final) field * {@link #uri} stores the original URI; {@code currentUri} may be different @@ -348,7 +263,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, throws NotSupportedException { super(local, uri); setURI(uri); - http = local.getConfig().get(HttpConfig::new); + http = new HttpConfig(local.getConfig(), uri); proxySelector = ProxySelector.getDefault(); } @@ -384,7 +299,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, TransportHttp(final URIish uri) throws NotSupportedException { super(uri); setURI(uri); - http = new HttpConfig(); + http = new HttpConfig(uri); proxySelector = ProxySelector.getDefault(); } @@ -614,7 +529,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, // SEE_OTHER should actually never be sent by a git server, // and in general should occur only on POST requests. But it // doesn't hurt to accept it here as a redirect. - if (http.followRedirects == HttpRedirectMode.FALSE) { + if (http.getFollowRedirects() == HttpRedirectMode.FALSE) { throw new TransportException(uri, MessageFormat.format( JGitText.get().redirectsOff, @@ -661,10 +576,11 @@ public class TransportHttp extends HttpTransport implements WalkTransport, MessageFormat.format(JGitText.get().redirectLocationMissing, baseUrl)); } - if (redirects >= http.maxRedirects) { + if (redirects >= http.getMaxRedirects()) { throw new TransportException(uri, MessageFormat.format(JGitText.get().redirectLimitExceeded, - Integer.valueOf(http.maxRedirects), baseUrl, location)); + Integer.valueOf(http.getMaxRedirects()), baseUrl, + location)); } try { if (!isValidRedirect(baseUrl, location, checkFor)) { @@ -771,7 +687,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, final Proxy proxy = HttpSupport.proxyFor(proxySelector, u); HttpConnection conn = connectionFactory.create(u, proxy); - if (!http.sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$ + if (!http.isSslVerify() && "https".equals(u.getProtocol())) { //$NON-NLS-1$ HttpSupport.disableSslVerify(conn); } @@ -1097,7 +1013,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport, void sendRequest() throws IOException { // Try to compress the content, but only if that is smaller. - TemporaryBuffer buf = new TemporaryBuffer.Heap(http.postBuffer); + TemporaryBuffer buf = new TemporaryBuffer.Heap( + http.getPostBuffer()); try { GZIPOutputStream gzip = new GZIPOutputStream(buf); out.writeTo(gzip, null); @@ -1152,7 +1069,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, // SEE_OTHER after a POST doesn't make sense for a git // server, so we don't handle it here and thus we'll // report an error in openResponse() later on. - if (http.followRedirects != HttpRedirectMode.TRUE) { + if (http.getFollowRedirects() != HttpRedirectMode.TRUE) { // Let openResponse() issue an error return; } @@ -1284,7 +1201,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, class HttpOutputStream extends TemporaryBuffer { HttpOutputStream() { - super(http.postBuffer); + super(http.getPostBuffer()); } @Override