Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-20684] Parser for .netrc file format
This path implements a parser for the .netrc file supporting various formats.
It is not restricted to having a machine definition on a single line but can
parse any valid format. The one exception is that it does *not* cope with
`macdef` definitions.

handle `macdef` definitons in the netrc file to the parser.
Anything following a `macdef` keyword will be ignored up to
the next empty line.
  • Loading branch information
flaix authored and ndeloof committed Nov 26, 2013
1 parent d988763 commit 3430d92
Show file tree
Hide file tree
Showing 6 changed files with 571 additions and 27 deletions.
28 changes: 1 addition & 27 deletions src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
Expand Up @@ -18,7 +18,6 @@
import hudson.remoting.Callable;
import hudson.slaves.SlaveComputer;
import hudson.util.ArgumentListBuilder;
import hudson.util.IOUtils;
import hudson.util.Secret;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
Expand Down Expand Up @@ -1405,7 +1404,7 @@ private String getURLWithCrendentials(URIish u, StandardCredentials cred) {
if (uri.getUser() != null && uri.getPass() != null) {
defaultcreds = new UsernamePasswordCredentials(uri.getUser(), uri.getPass());
} else {
defaultcreds = getCredentialsFromNetrc(uri.getHost());
defaultcreds = Netrc.getInstance().getCredentials(uri.getHost());
}
if (defaultcreds != null) {
client.getParams().setAuthenticationPreemptive(true);
Expand Down Expand Up @@ -1448,31 +1447,6 @@ private String getURLWithCrendentials(URIish u, StandardCredentials cred) {
return url;
}

public static final Pattern NETRC = Pattern.compile("machine (.*) login (.*) password (.*)");

private Credentials getCredentialsFromNetrc(String host) {
File home = new File(System.getProperty("user.home"));
File netrc = new File(home, ".netrc");
if (!netrc.exists()) netrc = new File("_netrc"); // windows variant
if (!netrc.exists()) return null;

BufferedReader r = null;
try {
r = new BufferedReader(new FileReader(netrc));
String line = null;
while ((line = r.readLine()) != null) {
Matcher matcher = NETRC.matcher(line);
if (!matcher.matches()) continue;
if (matcher.group(1).equals(host));
return new UsernamePasswordCredentials(matcher.group(2), matcher.group(3));
}
} catch (IOException e) {
throw new GitException("Invalid $HOME/.netrc file", e);
} finally {
IOUtils.closeQuietly(r);
}
return null;
}

private static class GetPrivateKeys implements Callable<List<String>, RuntimeException> {
private final SSHUserPrivateKey sshUser;
Expand Down
165 changes: 165 additions & 0 deletions src/main/java/org/jenkinsci/plugins/gitclient/Netrc.java
@@ -0,0 +1,165 @@
package org.jenkinsci.plugins.gitclient;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.plugins.git.GitException;
import hudson.util.IOUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;

class Netrc {
private static final Pattern NETRC_TOKEN = Pattern.compile("(\\S+)");

private enum ParseState {
START, REQ_KEY, REQ_VALUE, MACHINE, LOGIN, PASSWORD, MACDEF, END;
};


private File netrc;
private long lastModified;
private Map<String,UsernamePasswordCredentials> hosts = new HashMap<String,UsernamePasswordCredentials>();



public static Netrc getInstance() {
File netrc = getDefaultFile();
return netrc.exists() ? getInstance(netrc) : null;
}

public static Netrc getInstance(@NonNull String netrcPath) {
File netrc = new File(netrcPath);
return netrc.exists() ? getInstance(new File(netrcPath)) : null;
}

public static Netrc getInstance(File netrc) {
return new Netrc(netrc).parse();
}

private static File getDefaultFile() {
File home = new File(System.getProperty("user.home"));
File netrc = new File(home, ".netrc");
if (!netrc.exists()) netrc = new File(home, "_netrc"); // windows variant
return netrc;
}


public Credentials getCredentials(String host) {
if (!this.netrc.exists()) return null;
if (this.lastModified != this.netrc.lastModified()) parse();
return this.hosts.get(host);
}

private Netrc(File netrc) {
this.netrc = netrc;
}

synchronized private Netrc parse() {
if (!netrc.exists()) return null;

this.hosts.clear();
this.lastModified = this.netrc.lastModified();

BufferedReader r = null;
try {
r = new BufferedReader(new FileReader(netrc));
String line = null;
String machine = null;
String login = null;
String password = null;

ParseState state = ParseState.START;
Matcher matcher = NETRC_TOKEN.matcher("");
while ((line = r.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
if (state == ParseState.MACDEF) {
state = ParseState.REQ_KEY;
}
continue;
}

matcher.reset(line);
while (matcher.find()) {
String match = matcher.group();
switch (state) {
case START:
if ("machine".equals(match)) {
state = ParseState.MACHINE;
}
break;

case REQ_KEY:
if ("login".equals(match)) {
state = ParseState.LOGIN;
}
else if ("password".equals(match)) {
state = ParseState.PASSWORD;
}
else if ("macdef".equals(match)) {
state = ParseState.MACDEF;
}
else if ("machine".equals(match)) {
state = ParseState.MACHINE;
}
else {
state = ParseState.REQ_VALUE;
}
break;

case REQ_VALUE:
state = ParseState.REQ_KEY;
break;

case MACHINE:
if (machine != null) {
if (login != null && password != null) {
this.hosts.put(machine, new UsernamePasswordCredentials(login, password));
}
}
machine = match;
login = null;
password = null;
state = ParseState.REQ_KEY;
break;

case LOGIN:
login = match;
state = ParseState.REQ_KEY;
break;

case PASSWORD:
password = match;
state = ParseState.REQ_KEY;
break;

case MACDEF:
// Only way out is an empty line, handled before the find() loop.
break;
}
}
}
if (machine != null) {
if (login != null && password != null) {
this.hosts.put(machine, new UsernamePasswordCredentials(login, password));
}
}

} catch (IOException e) {
throw new GitException("Invalid netrc file: '" + this.netrc.getAbsolutePath() + "'", e);
} finally {
IOUtils.closeQuietly(r);
}

return this;
}

}

1 comment on commit 3430d92

@MarkEWaite
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Florian, I have enabled findbugs in #95 . One of the warnings from findbugs is that there is inconsistent synchronization in one area of the Netrc.java file. Is that inconsistent synchronization pattern intentional? If so, then it is trivial to add it as an exclusion to the exclusions file.

The findbugs report says:

Netrc.java:57, IS2_INCONSISTENT_SYNC, Priority: Normal
Inconsistent synchronization of org.jenkinsci.plugins.gitclient.Netrc.lastModified; locked 50% of time

The fields of this class appear to be accessed inconsistently with respect to synchronization. This bug report indicates that the bug pattern detector judged that

The class contains a mix of locked and unlocked accesses,
At least one locked access was performed by one of the class's own methods, and
The number of unsynchronized field accesses (reads and writes) was no more than one third of all accesses, with writes being weighed twice as high as reads
A typical bug matching this bug pattern is forgetting to synchronize one of the methods in a class that is intended to be thread-safe.

You can select the nodes labeled "Unsynchronized access" to show the code locations where the detector believed that a field was accessed without synchronization.

Note that there are various sources of inaccuracy in this detector; for example, the detector cannot statically detect all situations in which a lock is held. Also, even when the detector is accurate in distinguishing locked vs. unlocked accesses, the code in question may still be correct.

Please sign in to comment.