View Javadoc
1   /*
2    * Copyright (C) 2014, Alexey Kuznetsov <axet@me.com>
3    *
4    * This program and the accompanying materials are made available
5    * under the terms of the Eclipse Distribution License v1.0 which
6    * accompanies this distribution, is reproduced below, and is
7    * available at http://www.eclipse.org/org/documents/edl-v10.php
8    *
9    * All rights reserved.
10   *
11   * Redistribution and use in source and binary forms, with or
12   * without modification, are permitted provided that the following
13   * conditions are met:
14   *
15   * - Redistributions of source code must retain the above copyright
16   *   notice, this list of conditions and the following disclaimer.
17   *
18   * - Redistributions in binary form must reproduce the above
19   *   copyright notice, this list of conditions and the following
20   *   disclaimer in the documentation and/or other materials provided
21   *   with the distribution.
22   *
23   * - Neither the name of the Eclipse Foundation, Inc. nor the
24   *   names of its contributors may be used to endorse or promote
25   *   products derived from this software without specific prior
26   *   written permission.
27   *
28   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
30   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
31   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
33   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
35   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
36   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
40   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41   */
42  
43  package org.eclipse.jgit.transport;
44  
45  import java.io.BufferedReader;
46  import java.io.File;
47  import java.io.FileReader;
48  import java.io.IOException;
49  import java.util.Collection;
50  import java.util.HashMap;
51  import java.util.Locale;
52  import java.util.Map;
53  import java.util.TreeMap;
54  import java.util.regex.Matcher;
55  import java.util.regex.Pattern;
56  
57  import org.eclipse.jgit.util.FS;
58  
59  /**
60   * NetRC file parser.
61   *
62   * @since 3.5
63   */
64  public class NetRC {
65  	static final Pattern NETRC = Pattern.compile("(\\S+)"); //$NON-NLS-1$
66  
67  	/**
68  	 * 'default' netrc entry. This is the same as machine name except that
69  	 * default matches any name. There can be only one default token, and it
70  	 * must be after all machine tokens.
71  	 */
72  	static final String DEFAULT_ENTRY = "default"; //$NON-NLS-1$
73  
74  	/**
75  	 * .netrc file entry
76  	 */
77  	public static class NetRCEntry {
78  		/**
79  		 * login netrc entry
80  		 */
81  		public String login;
82  
83  		/**
84  		 * password netrc entry
85  		 */
86  		public char[] password;
87  
88  		/**
89  		 * machine netrc entry
90  		 */
91  		public String machine;
92  
93  		/**
94  		 * account netrc entry
95  		 */
96  		public String account;
97  
98  		/**
99  		 * macdef netrc entry. Defines a macro. This token functions like the
100 		 * ftp macdef command functions. A macro is defined with the specified
101 		 * name; its contents begins with the next .netrc line and continues
102 		 * until a null line (consecutive new-line characters) is encountered.
103 		 * If a macro named init is defined, it is automatically executed as the
104 		 * last step in the auto-login process.
105 		 */
106 		public String macdef;
107 
108 		/**
109 		 * macro script body of macdef entry.
110 		 */
111 		public String macbody;
112 
113 		/**
114 		 * Default constructor
115 		 */
116 		public NetRCEntry() {
117 		}
118 
119 		boolean complete() {
120 			return login != null && password != null && machine != null;
121 		}
122 	}
123 
124 	private File netrc;
125 
126 	private long lastModified;
127 
128 	private Map<String, NetRCEntry> hosts = new HashMap<>();
129 
130 	private static final TreeMap<String, State> STATE = new TreeMap<String, NetRC.State>() {
131 		private static final long serialVersionUID = -4285910831814853334L;
132 		{
133 			put("machine", State.MACHINE); //$NON-NLS-1$
134 			put("login", State.LOGIN); //$NON-NLS-1$
135 			put("password", State.PASSWORD); //$NON-NLS-1$
136 			put(DEFAULT_ENTRY, State.DEFAULT);
137 			put("account", State.ACCOUNT); //$NON-NLS-1$
138 			put("macdef", State.MACDEF); //$NON-NLS-1$
139 		}
140 	};
141 
142 	enum State {
143 		COMMAND, MACHINE, LOGIN, PASSWORD, DEFAULT, ACCOUNT, MACDEF
144 	}
145 
146 	/**
147 	 * <p>Constructor for NetRC.</p>
148 	 */
149 	public NetRC() {
150 		netrc = getDefaultFile();
151 		if (netrc != null)
152 			parse();
153 	}
154 
155 	/**
156 	 * <p>Constructor for NetRC.</p>
157 	 *
158 	 * @param netrc
159 	 *            the .netrc file
160 	 */
161 	public NetRC(File netrc) {
162 		this.netrc = netrc;
163 		parse();
164 	}
165 
166 	private static File getDefaultFile() {
167 		File home = FS.DETECTED.userHome();
168 		File netrc = new File(home, ".netrc"); //$NON-NLS-1$
169 		if (netrc.exists())
170 			return netrc;
171 
172 		netrc = new File(home, "_netrc"); //$NON-NLS-1$
173 		if (netrc.exists())
174 			return netrc;
175 
176 		return null;
177 	}
178 
179 	/**
180 	 * Get entry by host name
181 	 *
182 	 * @param host
183 	 *            the host name
184 	 * @return entry associated with host name or null
185 	 */
186 	public NetRCEntry getEntry(String host) {
187 		if (netrc == null)
188 			return null;
189 
190 		if (this.lastModified != this.netrc.lastModified())
191 			parse();
192 
193 		NetRCEntry entry = this.hosts.get(host);
194 
195 		if (entry == null)
196 			entry = this.hosts.get(DEFAULT_ENTRY);
197 
198 		return entry;
199 	}
200 
201 	/**
202 	 * Get all entries collected from .netrc file
203 	 *
204 	 * @return all entries collected from .netrc file
205 	 */
206 	public Collection<NetRCEntry> getEntries() {
207 		return hosts.values();
208 	}
209 
210 	private void parse() {
211 		this.hosts.clear();
212 		this.lastModified = this.netrc.lastModified();
213 
214 		try (BufferedReader r = new BufferedReader(new FileReader(netrc))) {
215 			String line = null;
216 
217 			NetRCEntry entry = new NetRCEntry();
218 
219 			State state = State.COMMAND;
220 
221 			String macbody = ""; //$NON-NLS-1$
222 
223 			Matcher matcher = NETRC.matcher(""); //$NON-NLS-1$
224 			while ((line = r.readLine()) != null) {
225 
226 				// reading macbody
227 				if (entry.macdef != null && entry.macbody == null) {
228 					if (line.length() == 0) {
229 						entry.macbody = macbody;
230 						macbody = ""; //$NON-NLS-1$
231 						continue;
232 					}
233 					macbody += line + "\n"; //$NON-NLS-1$;
234 					continue;
235 				}
236 
237 				matcher.reset(line);
238 				while (matcher.find()) {
239 					String command = matcher.group().toLowerCase(Locale.ROOT);
240 					if (command.startsWith("#")) { //$NON-NLS-1$
241 						matcher.reset(""); //$NON-NLS-1$
242 						continue;
243 					}
244 					state = STATE.get(command);
245 					if (state == null)
246 						state = State.COMMAND;
247 
248 					switch (state) {
249 					case COMMAND:
250 						break;
251 					case ACCOUNT:
252 						if (entry.account != null && entry.complete()) {
253 							hosts.put(entry.machine, entry);
254 							entry = new NetRCEntry();
255 						}
256 						if (matcher.find())
257 							entry.account = matcher.group();
258 						state = State.COMMAND;
259 						break;
260 					case LOGIN:
261 						if (entry.login != null && entry.complete()) {
262 							hosts.put(entry.machine, entry);
263 							entry = new NetRCEntry();
264 						}
265 						if (matcher.find())
266 							entry.login = matcher.group();
267 						state = State.COMMAND;
268 						break;
269 					case PASSWORD:
270 						if (entry.password != null && entry.complete()) {
271 							hosts.put(entry.machine, entry);
272 							entry = new NetRCEntry();
273 						}
274 						if (matcher.find())
275 							entry.password = matcher.group().toCharArray();
276 						state = State.COMMAND;
277 						break;
278 					case DEFAULT:
279 						if (entry.machine != null && entry.complete()) {
280 							hosts.put(entry.machine, entry);
281 							entry = new NetRCEntry();
282 						}
283 						entry.machine = DEFAULT_ENTRY;
284 						state = State.COMMAND;
285 						break;
286 					case MACDEF:
287 						if (entry.macdef != null && entry.complete()) {
288 							hosts.put(entry.machine, entry);
289 							entry = new NetRCEntry();
290 						}
291 						if (matcher.find())
292 							entry.macdef = matcher.group();
293 						state = State.COMMAND;
294 						break;
295 					case MACHINE:
296 						if (entry.machine != null && entry.complete()) {
297 							hosts.put(entry.machine, entry);
298 							entry = new NetRCEntry();
299 						}
300 						if (matcher.find())
301 							entry.machine = matcher.group();
302 						state = State.COMMAND;
303 						break;
304 					}
305 				}
306 			}
307 
308 			// reading macbody on EOF
309 			if (entry.macdef != null && entry.macbody == null)
310 				entry.macbody = macbody;
311 
312 			if (entry.complete())
313 				hosts.put(entry.machine, entry);
314 		} catch (IOException e) {
315 			throw new RuntimeException(e);
316 		}
317 	}
318 }