diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java new file mode 100644 index 000000000..ba449508f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2014, Alexey Kuznetsov + * + * 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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Before; +import org.junit.Test; + +public class NetRCTest extends RepositoryTestCase { + private File home; + + private NetRC netrc; + + private File configFile; + + @Before + public void setUp() throws Exception { + super.setUp(); + + home = new File(trash, "home"); + FileUtils.mkdir(home); + + configFile = new File(home, ".netrc"); + } + + private void config(final String data) throws IOException { + final OutputStreamWriter fw = new OutputStreamWriter( + new FileOutputStream(configFile), "UTF-8"); + fw.write(data); + fw.close(); + } + + @Test + public void testNetRCFile() throws Exception { + config("machine my.host login test password 2222" + + "\n" + + + "#machine ignore.host.example login ignoreuser password 5555" + + "\n" + + + "machine twolinehost.example #login kuznetsov.alexey password 5555" + + "\n" + + + "login twologin password 6666" + + "\n" + + + "machine afterlinehost.example login test1 password 8888" + + "\n" + + + "machine macdef.example login test7 password 7777 macdef mac1" + + "\n" + + + "init +apc 1" + "\n" + + + "init2 +apc 2" + "\n"); + + netrc = new NetRC(configFile); + + assertNull(netrc.getEntry("ignore.host.example")); + + assertNull(netrc.getEntry("cignore.host.example")); + + assertEquals("twologin", netrc.getEntry("twolinehost.example").login); + assertEquals("6666", new String( + netrc.getEntry("twolinehost.example").password)); + + assertEquals("test1", netrc.getEntry("afterlinehost.example").login); + assertEquals("8888", new String( + netrc.getEntry("afterlinehost.example").password)); + + // macdef test + assertEquals("test7", netrc.getEntry("macdef.example").login); + assertEquals("7777", new String( + netrc.getEntry("macdef.example").password)); + assertEquals("init +apc 1" + "\n" + "init2 +apc 2" + "\n", + netrc.getEntry("macdef.example").macbody); + } + + @Test + public void testNetRCDefault() throws Exception { + config("machine my.host login test password 2222" + + "\n" + + + "#machine ignore.host.example login ignoreuser password 5555" + + "\n" + + + "machine twolinehost.example #login kuznetsov.alexey password 5555" + + "\n" + + + "login twologin password 6666" + + "\n" + + + "machine afterlinehost.example login test1 password 8888" + + "\n" + + + "machine macdef.example login test7 password 7777 macdef mac1" + + "\n" + + + "init +apc 1" + "\n" + + + "init2 +apc 2" + "\n" + + + "\n" + + + "default login test5 password 3333" + "\n" + + ); + + netrc = new NetRC(configFile); + + // default test + assertEquals("test5", netrc.getEntry("ignore.host.example").login); + assertEquals("3333", new String( + netrc.getEntry("ignore.host.example").password)); + + // default test + assertEquals("test5", new String( + netrc.getEntry("ignore.host.example").login)); + assertEquals("3333", new String( + netrc.getEntry("ignore.host.example").password)); + + // default test + assertEquals("test5", netrc.getEntry("cignore.host.example").login); + assertEquals("3333", new String( + netrc.getEntry("cignore.host.example").password)); + + assertEquals("twologin", netrc.getEntry("twolinehost.example").login); + assertEquals("6666", new String( + netrc.getEntry("twolinehost.example").password)); + + assertEquals("test1", netrc.getEntry("afterlinehost.example").login); + assertEquals("8888", new String( + netrc.getEntry("afterlinehost.example").password)); + + // macdef test + assertEquals("test7", netrc.getEntry("macdef.example").login); + assertEquals("7777", new String( + netrc.getEntry("macdef.example").password)); + assertEquals("init +apc 1" + "\n" + "init2 +apc 2" + "\n", + netrc.getEntry("macdef.example").macbody); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java new file mode 100644 index 000000000..bacab7e21 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2014, Alexey Kuznetsov + * + * 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.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jgit.util.FS; + +/** + * NetRC file parser. + * + * @since 3.5 + */ +public class NetRC { + static final Pattern NETRC = Pattern.compile("(\\S+)"); //$NON-NLS-1$ + + /** + * 'default' netrc entry. This is the same as machine name except that + * default matches any name. There can be only one default token, and it + * must be after all machine tokens. + */ + static final String DEFAULT_ENTRY = "default"; //$NON-NLS-1$ + + /** + * .netrc file entry + */ + public static class NetRCEntry { + /** + * login netrc entry + */ + public String login; + + /** + * password netrc entry + */ + public char[] password; + + /** + * machine netrc entry + */ + public String machine; + + /** + * account netrc entry + */ + public String account; + + /** + * macdef netrc entry. Defines a macro. This token functions like the + * ftp macdef command functions. A macro is defined with the specified + * name; its contents begins with the next .netrc line and continues + * until a null line (consecutive new-line characters) is encountered. + * If a macro named init is defined, it is automatically executed as the + * last step in the auto-login process. + */ + public String macdef; + + /** + * macro script body of macdef entry. + */ + public String macbody; + + /** + * Default constructor + */ + public NetRCEntry() { + } + + boolean complete() { + return login != null && password != null && machine != null; + } + } + + private File netrc; + + private long lastModified; + + private Map hosts = new HashMap(); + + private static final TreeMap STATE = new TreeMap() { + private static final long serialVersionUID = -4285910831814853334L; + { + put("machine", State.MACHINE); //$NON-NLS-1$ + put("login", State.LOGIN); //$NON-NLS-1$ + put("password", State.PASSWORD); //$NON-NLS-1$ + put(DEFAULT_ENTRY, State.DEFAULT); + put("account", State.ACCOUNT); //$NON-NLS-1$ + put("macdef", State.MACDEF); //$NON-NLS-1$ + } + }; + + enum State { + COMMAND, MACHINE, LOGIN, PASSWORD, DEFAULT, ACCOUNT, MACDEF + } + + /** */ + public NetRC() { + netrc = getDefaultFile(); + if (netrc != null) + parse(); + } + + /** + * @param netrc + * the .netrc file + */ + public NetRC(File netrc) { + this.netrc = netrc; + parse(); + } + + private static File getDefaultFile() { + File home = FS.DETECTED.userHome(); + File netrc = new File(home, ".netrc"); //$NON-NLS-1$ + if (netrc.exists()) + return netrc; + + netrc = new File(home, "_netrc"); //$NON-NLS-1$ + if (netrc.exists()) + return netrc; + + return null; + } + + /** + * Get entry by host name + * + * @param host + * @return entry associated with host name or null + */ + public NetRCEntry getEntry(String host) { + if (netrc == null) + return null; + + if (this.lastModified != this.netrc.lastModified()) + parse(); + + NetRCEntry entry = this.hosts.get(host); + + if (entry == null) + entry = this.hosts.get(DEFAULT_ENTRY); + + return entry; + } + + /** + * @return all entries collected from .netrc file + */ + public Collection getEntries() { + return hosts.values(); + } + + private void parse() { + this.hosts.clear(); + this.lastModified = this.netrc.lastModified(); + + BufferedReader r = null; + try { + r = new BufferedReader(new FileReader(netrc)); + String line = null; + + NetRCEntry entry = new NetRCEntry(); + + State state = State.COMMAND; + + String macbody = ""; //$NON-NLS-1$ + + Matcher matcher = NETRC.matcher(""); //$NON-NLS-1$ + while ((line = r.readLine()) != null) { + + // reading macbody + if (entry.macdef != null && entry.macbody == null) { + if (line.length() == 0) { + entry.macbody = macbody; + macbody = ""; //$NON-NLS-1$ + continue; + } + macbody += line + "\n"; //$NON-NLS-1$; + continue; + } + + matcher.reset(line); + while (matcher.find()) { + String command = matcher.group().toLowerCase(); + if (command.startsWith("#")) { //$NON-NLS-1$ + matcher.reset(""); //$NON-NLS-1$ + continue; + } + state = STATE.get(command); + if (state == null) + state = State.COMMAND; + + switch (state) { + case COMMAND: + break; + case ACCOUNT: + if (entry.account != null && entry.complete()) { + hosts.put(entry.machine, entry); + entry = new NetRCEntry(); + } + if (matcher.find()) + entry.account = matcher.group(); + state = State.COMMAND; + break; + case LOGIN: + if (entry.login != null && entry.complete()) { + hosts.put(entry.machine, entry); + entry = new NetRCEntry(); + } + if (matcher.find()) + entry.login = matcher.group(); + state = State.COMMAND; + break; + case PASSWORD: + if (entry.password != null && entry.complete()) { + hosts.put(entry.machine, entry); + entry = new NetRCEntry(); + } + if (matcher.find()) + entry.password = matcher.group().toCharArray(); + state = State.COMMAND; + break; + case DEFAULT: + if (entry.machine != null && entry.complete()) { + hosts.put(entry.machine, entry); + entry = new NetRCEntry(); + } + entry.machine = DEFAULT_ENTRY; + state = State.COMMAND; + break; + case MACDEF: + if (entry.macdef != null && entry.complete()) { + hosts.put(entry.machine, entry); + entry = new NetRCEntry(); + } + if (matcher.find()) + entry.macdef = matcher.group(); + state = State.COMMAND; + break; + case MACHINE: + if (entry.machine != null && entry.complete()) { + hosts.put(entry.machine, entry); + entry = new NetRCEntry(); + } + if (matcher.find()) + entry.machine = matcher.group(); + state = State.COMMAND; + break; + } + } + } + + // reading macbody on EOF + if (entry.macdef != null && entry.macbody == null) + entry.macbody = macbody; + + if (entry.complete()) + hosts.put(entry.machine, entry); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + if (r != null) + r.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java new file mode 100644 index 000000000..74909998c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014, Alexey Kuznetsov + * + * 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 org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.NetRC.NetRCEntry; + +/** + * Simple .netrc credentials provider. It can lookup the first machine entry + * from your .netrc file. + * + * @since 3.5 + */ +public class NetRCCredentialsProvider extends CredentialsProvider { + + NetRC netrc = new NetRC(); + + /** */ + public NetRCCredentialsProvider() { + } + + /** + * Install default provider for the .netrc parser. + */ + public static void install() { + CredentialsProvider.setDefault(new NetRCCredentialsProvider()); + } + + @Override + public boolean supports(CredentialItem... items) { + for (CredentialItem i : items) { + if (i instanceof CredentialItem.Username) + continue; + else if (i instanceof CredentialItem.Password) + continue; + else + return false; + } + return true; + } + + @Override + public boolean get(URIish uri, CredentialItem... items) + throws UnsupportedCredentialItem { + NetRCEntry cc = netrc.getEntry(uri.getHost()); + + if (cc == null) + return false; + + for (CredentialItem i : items) { + if (i instanceof CredentialItem.Username) { + ((CredentialItem.Username) i).setValue(cc.login); + continue; + } + if (i instanceof CredentialItem.Password) { + ((CredentialItem.Password) i).setValue(cc.password); + continue; + } + if (i instanceof CredentialItem.StringType) { + if (i.getPromptText().equals("Password: ")) { //$NON-NLS-1$ + ((CredentialItem.StringType) i).setValue(new String( + cc.password)); + continue; + } + } + throw new UnsupportedCredentialItem(uri, i.getClass().getName() + + ":" + i.getPromptText()); //$NON-NLS-1$ + } + return true; + } + + @Override + public boolean isInteractive() { + return false; + } + +} 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 e13bfd300..c4a6bd966 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -264,6 +264,9 @@ private HttpConfig() { } http = local.getConfig().get(HTTP_KEY); proxySelector = ProxySelector.getDefault(); + + if (getCredentialsProvider() == null) + setCredentialsProvider(new NetRCCredentialsProvider()); } /**