1#[cfg(not(feature = "std"))]
2extern crate alloc;
3#[cfg(feature = "std")]
4extern crate std;
5
6#[cfg(not(feature = "std"))]
7use alloc::string::{String, ToString};
8#[cfg(not(feature = "std"))]
9use alloc::vec::Vec;
10use core::fmt;
11use core::fmt::{Display, Formatter};
12use core::result;
13use core::str::Chars;
14
15#[derive(Debug, Default, Eq, PartialEq, Clone)]
17pub struct Machine {
18 pub name: Option<String>,
20 pub login: Option<String>,
22 pub password: Option<String>,
24 pub account: Option<String>,
26}
27
28impl Display for Machine {
29 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
30 macro_rules! write_key {
31 ($key:expr, $fmt:expr, $default:expr) => {
32 match &$key {
33 None => write!(f, $default),
34 Some(val) => write!(f, $fmt, val),
35 }
36 };
37 }
38
39 write_key!(self.name, "machine {}", "default")?;
40 write_key!(self.login, " login {}", "")?;
41 write_key!(self.password, " password {}", "")?;
42 write_key!(self.account, " account {}", "")?;
43
44 Ok(())
45 }
46}
47
48#[derive(Debug, Default)]
50pub struct Netrc {
51 pub machines: Vec<Machine>,
53 pub macdefs: Vec<(String, Vec<String>)>,
55 pub unknown_entries: Vec<String>,
57}
58
59#[derive(Debug, Copy, Clone)]
61pub struct Position(pub usize, pub usize);
62
63#[derive(Debug)]
65pub enum Error {
66 EOF,
68 IllegalFormat(Position, String),
70}
71
72impl Display for Error {
73 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74 match self {
75 Error::EOF => write!(f, "End of data: EOF"),
76 Error::IllegalFormat(pos, s) => write!(f, "Illegal format in {} {}", pos, s.as_str()),
77 }
78 }
79}
80
81pub type Result<T> = result::Result<T, Error>;
82
83impl Netrc {
84 pub fn parse<T: AsRef<str>>(buf: T, unknown_entries: bool) -> Result<Netrc> {
103 Self::parse_borrow(&buf, unknown_entries)
104 }
105
106 pub fn parse_borrow<T: AsRef<str>>(buf: &T, unknown_entries: bool) -> Result<Netrc> {
126 let mut netrc = Netrc::default();
127 let mut lexer = Lexer::new::<T>(buf);
128 let mut count = MachineCount::default();
129 loop {
130 match lexer.next_token() {
131 Err(Error::EOF) => break,
132 Err(err) => return Err(err),
133 Ok(tok) => {
134 netrc.parse_entry::<T>(&mut lexer, &tok, &mut count, unknown_entries)?;
135 }
136 }
137 }
138 Ok(netrc)
139 }
140
141 fn parse_entry<T: AsRef<str>>(
142 &mut self,
143 lexer: &mut Lexer,
144 item: &Token,
145 count: &mut MachineCount,
146 unknown_entries: bool,
147 ) -> Result<()> {
148 match item {
149 Token::Machine => {
150 let host_name = lexer.next_token()?;
151 self.machines.push(Default::default());
152 self.machines[count.machine].name = Some(host_name.to_string());
153 count.machine += 1;
154 Ok(())
155 }
156
157 Token::Default => {
158 self.machines.push(Default::default());
159 count.machine += 1;
160 Ok(())
161 }
162
163 Token::Login => {
164 let name = lexer.next_token()?.to_string();
165 count.login += 1;
166 if count.login > count.machine {
167 return Err(Error::IllegalFormat(
168 lexer.tokens.position(),
169 "login must follow machine".to_string(),
170 ));
171 } else {
172 let last = self.machines.len() - 1;
173 self.machines[last].login = Some(name)
174 }
175 Ok(())
176 }
177
178 Token::Password => {
179 let name = lexer.next_token()?.to_string();
180 count.password += 1;
181 if count.password > count.machine {
182 return Err(Error::IllegalFormat(
183 lexer.tokens.position(),
184 "password must follow machine".to_string(),
185 ));
186 } else {
187 let last = self.machines.len() - 1;
188 self.machines[last].password = Some(name)
189 }
190 Ok(())
191 }
192
193 Token::Account => {
194 let name = lexer.next_token()?.to_string();
195 count.account += 1;
196 if count.account > count.machine {
197 return Err(Error::IllegalFormat(
198 lexer.tokens.position(),
199 "account must follow machine".to_string(),
200 ));
201 } else {
202 let last = self.machines.len() - 1;
203 self.machines[last].account = Some(name)
204 }
205 Ok(())
206 }
207
208 Token::MacDef => {
210 let name = lexer.next_token()?.to_string();
211 let cmds = lexer.next_commands();
212
213 self.macdefs.push((name, cmds));
214 Ok(())
215 }
216
217 Token::Str(s) if unknown_entries => {
218 self.unknown_entries.push(s.to_string());
219 Ok(())
220 }
221
222 Token::Str(s) => Err(Error::IllegalFormat(
223 lexer.tokens.position(),
224 "token: ".to_string() + s,
225 )),
226 }
227 }
228}
229
230#[derive(Debug, Default)]
231struct MachineCount {
232 machine: usize,
233 login: usize,
234 password: usize,
235 account: usize,
236}
237
238#[derive(Debug)]
239enum Token {
240 Machine,
241 Default,
242 Login,
243 Password,
244 Account,
245 MacDef,
246 Str(String),
247}
248
249impl Display for Token {
250 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
251 use Token::*;
252
253 let s = match self {
254 Machine => "machine",
255 Default => "default",
256 Login => "login",
257 Password => "password",
258 Account => "account",
259 MacDef => "macdef",
260 Str(s) => s,
261 };
262 write!(f, "{}", s)
263 }
264}
265
266impl Token {
267 fn new(s: String) -> Self {
268 use Token::*;
269
270 match s.as_str() {
271 "machine" => Machine,
272 "default" => Default,
273 "login" => Login,
274 "password" => Password,
275 "account" => Account,
276 "macdef" => MacDef,
277
278 _ => Str(s),
279 }
280 }
281}
282
283struct Tokens<'a> {
284 buf: Chars<'a>,
285 pos: Position,
286}
287
288impl Display for Position {
289 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
290 write!(f, "{}:{}", self.0, self.1)
291 }
292}
293
294impl<'a> Tokens<'a> {
295 fn new<T: AsRef<str>>(buf: &'a T) -> Self {
296 Self {
297 buf: buf.as_ref().chars(),
298 pos: Position(1, 1),
299 }
300 }
301
302 fn update_position(&mut self, ch: char) {
303 if ch == '\n' {
304 self.pos.0 += 1;
305 self.pos.1 = 1;
306 } else {
307 self.pos.1 += 1;
308 }
309 }
310
311 fn position(&self) -> Position {
312 self.pos
313 }
314
315 fn skip_whitespace(&mut self) {
316 for ch in self.buf.clone() {
317 if !ch.is_whitespace() {
318 break;
319 }
320
321 self.update_position(ch);
322 self.buf.next();
323 }
324 }
325
326 fn next_token(&mut self) -> Option<Token> {
327 self.skip_whitespace();
328 if self.buf.clone().next().is_some() {
329 let mut s = String::new();
330 for ch in self.buf.clone() {
331 if ch.is_whitespace() {
332 break;
333 }
334
335 self.update_position(ch);
336 self.buf.next();
337 s.push(ch);
338 }
339
340 if s.is_empty() {
341 None
342 } else {
343 Some(Token::new(s))
344 }
345 } else {
346 None
347 }
348 }
349
350 fn next_commands(&mut self) -> Vec<String> {
351 self.skip_whitespace();
352 let mut cmds = Vec::new();
353 for line in self.buf.clone().as_str().lines() {
354 for _ in 0..=line.len() {
355 self.buf.next();
356 }
357 if line.is_empty() || line == "\n" {
358 break;
359 }
360 cmds.push(line.trim().to_string());
361 }
362 self.pos.0 += cmds.len();
363 self.pos.1 = 1;
364 cmds
365 }
366}
367
368struct Lexer<'a> {
369 tokens: Tokens<'a>,
370}
371
372impl<'a> Lexer<'a> {
373 fn new<T: AsRef<str>>(buf: &'a T) -> Self {
374 Self {
375 tokens: Tokens::new(buf),
376 }
377 }
378
379 fn next_token(&mut self) -> Result<Token> {
380 self.tokens.next_token().ok_or(Error::EOF)
381 }
382
383 fn next_commands(&mut self) -> Vec<String> {
384 self.tokens.next_commands()
385 }
386}
387
388#[cfg(test)]
389mod test {
390 use super::*;
391
392 #[test]
393 fn test_token() {
394 let input = r#"
395machine host1.com login login1
396macdef test
397cd /pub/tests
398bin
399put filename.tar.gz
400quit
401machine host2.com login login2"#
402 .to_string();
403 let mut tokens = Tokens::new(&input);
404 let strs: Vec<&str> = input.split_whitespace().collect();
405 let mut count = 0;
406 loop {
407 match tokens.next_token() {
408 Some(tok) => {
409 assert_eq!(tok.to_string().as_str(), strs[count]);
410 count += 1;
411 }
412 None => break,
413 }
414 }
415 }
416
417 #[test]
418 fn parse_simple() {
419 let input = "machine 中文.com login test password p@ssw0rd".to_string();
420 let netrc = Netrc::parse(input, false).unwrap();
421 assert_eq!(netrc.machines.len(), 1);
422 assert!(netrc.macdefs.is_empty());
423 let machine = netrc.machines[0].clone();
424 assert_eq!(machine.name, Some("中文.com".into()));
425 assert_eq!(machine.login, Some("test".into()));
426 assert_eq!(machine.password.as_ref().unwrap(), "p@ssw0rd");
427 assert_eq!(machine.account, None);
428 }
429
430 #[test]
431 fn parse_unknown() {
432 let input = "machine example.com login test my_entry1 password foo my_entry2".to_string();
433 let netrc = Netrc::parse(input, true).unwrap();
434 assert_eq!(netrc.machines.len(), 1);
435 assert!(netrc.macdefs.is_empty());
436 assert_eq!(
437 netrc.unknown_entries,
438 vec!["my_entry1".to_string(), "my_entry2".to_string()]
439 );
440
441 let machine = netrc.machines[0].clone();
442 assert_eq!(machine.name, Some("example.com".into()));
443 assert_eq!(machine.login, Some("test".into()));
444 assert_eq!(machine.password.as_ref().unwrap(), "foo");
445 }
446
447 #[test]
448 fn parse_macdef() {
449 let input = r#"machine host0.com login login0
450 macdef uploadtest
451 cd /pub/tests
452 bin
453 put filename.tar.gz
454 echo 中文测试
455 quit
456
457 machine host1.com login login1"#;
458 let netrc = Netrc::parse(input, false).unwrap();
459 assert_eq!(netrc.machines.len(), 2);
460 for (i, machine) in netrc.machines.iter().enumerate() {
461 assert_eq!(machine.name, Some(format!("host{}.com", i)));
462 assert_eq!(machine.login, Some(format!("login{}", i)));
463 }
464 assert_eq!(netrc.macdefs.len(), 1);
465 let (ref name, ref cmds) = netrc.macdefs[0];
466 assert_eq!(name, "uploadtest");
467 assert_eq!(
468 *cmds,
469 vec![
470 "cd /pub/tests",
471 "bin",
472 "put filename.tar.gz",
473 "echo 中文测试",
474 "quit"
475 ]
476 .iter()
477 .map(|s| s.to_string())
478 .collect::<Vec<String>>()
479 )
480 }
481
482 #[test]
483 fn parse_default() {
484 let input = r#"machine example.com login test
485 default login def"#;
486 let netrc = Netrc::parse(input, false).unwrap();
487 assert_eq!(netrc.machines.len(), 2);
488
489 let machine = netrc.machines[0].clone();
490 assert_eq!(machine.name, Some("example.com".into()));
491 assert_eq!(machine.login, Some("test".into()));
492
493 let machine = netrc.machines[1].clone();
494 assert_eq!(machine.name, None);
495 assert_eq!(machine.login, Some("def".into()));
496 }
497
498 #[test]
499 fn parse_error_unknown_entry() {
500 let input = "machine foobar.com foo";
501 match Netrc::parse(input, false).unwrap_err() {
502 Error::IllegalFormat(_pos, _s) => {}
503 e => panic!("Error type: {:?}", e),
504 }
505 }
506
507 #[test]
508 fn parse_error_eof() {
509 let input = "machine foobar.com password melody login";
510 match Netrc::parse(input, false).unwrap_err() {
511 Error::EOF => {}
512 e => panic!("Error type: {}", e),
513 }
514 }
515
516 #[test]
517 fn parse_error_illegal_format() {
518 let input = "password bar login foo";
519 match Netrc::parse(input, false).unwrap_err() {
520 Error::IllegalFormat(_pos, _s) => {}
521 e => panic!("Error type: {}", e),
522 }
523 }
524}