]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
netrc: scanner refactor
authorStefan Eissing <stefan@eissing.org>
Fri, 15 May 2026 09:45:49 +0000 (11:45 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 18 May 2026 13:18:09 +0000 (15:18 +0200)
Refactor the netrc scanner. Add test case for checking that the last
matched machine with unmatched login does not return the password as
success (unit1304).

Closes #21624

lib/netrc.c
lib/netrc.h
lib/url.c
tests/unit/unit1304.c

index eb67f2505ec9774da0d997fa89bfe0f10a021af4..48aaa76816176c85db4b4c53748e82c72db7a571 100644 (file)
 #endif
 
 #include "netrc.h"
+#include "urldata.h"
 #include "creds.h"
+#include "curl_trc.h"
 #include "strcase.h"
 #include "curl_get_line.h"
 #include "curlx/fopen.h"
 #include "curlx/strparse.h"
 
-/* Get user and password from .netrc when given a machine name */
 
-enum host_lookup_state {
-  NOTHING,
-  HOSTFOUND,    /* the 'machine' keyword was found */
-  HOSTVALID,    /* this is "our" machine! */
-  MACDEF
-};
-
-enum found_state {
-  NONE,
-  LOGIN,
-  PASSWORD
-};
-
-#define FOUND_LOGIN    1
-#define FOUND_PASSWORD 2
+/* .netrc is not really a standard. The GNU definition can be found here:
+ * https://www.gnu.org/software/inetutils/manual/\
+ *            html_node/The-_002enetrc-file.html
+ * This gives grammar like:
+ *
+ * LITERAL := \S+ | QUOTED
+ * QUOTED  := "(\\[rnt\]|[^"])*"
+ * ANYTHING := .
+ * EMPTY_LINE := \r*\n\r*\n
+ * MACHINE := machine         # case-insensitive
+ * LOGIN   := login           # case-insensitive
+ * PASSWD  := password        # case-insensitive
+ * ACCOUNT := account         # case-insensitive
+ * MACDEF  := macdef          # case-insensitive
+ * DEFAULT := default         # case-insensitive
+ *
+ * MACRO   := MACDEF ANYTHING* EMPTY_LINE
+ * JUNK    := LITERAL
+ * LKEY     := ( LOGIN | PASSWD | ACCOUNT ) LITERAL
+ * MENTRY   := MACHINE LITERAL LKEY*
+ * DENTRY   := DEFAULT LKEY*
+ * NETRC   := (MENTRY | DENTRY | MACRO | JUNK )* EOF
+ *
+ * Tokens are separated by whitespace or newlines. which have otherwise
+ * no special meaning, apart from the empty line ending a MACRO.
+ *
+ * Parsing is not strict, unmatched LITERALs are ignored
+ */
 
 #define MAX_NETRC_LINE  16384
 #define MAX_NETRC_FILE  (128 * 1024)
 #define MAX_NETRC_TOKEN 4096
 
+#define NETRC_DEBUG   0
+
 /* convert a dynbuf call CURLcode error to a NETRCcode error */
-#define curl2netrc(result)                     \
-  (((result) == CURLE_OUT_OF_MEMORY) ?         \
-   NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR)
+#define curl2netrc(r)                     \
+  ((!(r)) ? NETRC_OK : (((r) == CURLE_OUT_OF_MEMORY) ?         \
+   NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR))
+
+typedef enum {
+  NETRC_TOK_EOF,
+  NETRC_TOK_LITERAL,
+  NETRC_TOK_MACHINE,
+  NETRC_TOK_DEFAULT,
+  NETRC_TOK_ACCOUNT,
+  NETRC_TOK_LOGIN,
+  NETRC_TOK_PASSWD,
+  NETRC_TOK_MACDEF,
+  NETRC_TOK_JUNK
+} curl_netrc_token;
+
+struct netrc_lexer {
+  struct Curl_easy *data;
+  const char *content;
+  const char *pos;
+  struct dynbuf literal;
+  curl_netrc_token token;
+  bool pushed;
+};
 
-static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
+#if NETRC_DEBUG
+static const char *netrc_tokenstr(curl_netrc_token token)
 {
-  NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */
-  FILE *file = curlx_fopen(filename, FOPEN_READTEXT);
+  switch(token) {
+  case NETRC_TOK_EOF:
+    return "[EOF]";
+  case NETRC_TOK_LITERAL:
+    return "[LITERAL]";
+  case NETRC_TOK_MACHINE:
+    return "[MACHINE]";
+  case NETRC_TOK_DEFAULT:
+    return "[DEFAULT]";
+  case NETRC_TOK_ACCOUNT:
+    return "[ACCOUNT]";
+  case NETRC_TOK_LOGIN:
+    return "[LOGIN]";
+  case NETRC_TOK_PASSWD:
+    return "[PASSWORD]";
+  case NETRC_TOK_MACDEF:
+    return "[MACDEF]";
+  case NETRC_TOK_JUNK:
+    return "[JUNK]";
+  default:
+    return "[???]";
+  }
+}
 
-  if(file) {
-    curlx_struct_stat stat;
-    if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) {
-      CURLcode result = CURLE_OK;
-      bool eof;
-      struct dynbuf linebuf;
-      curlx_dyn_init(&linebuf, MAX_NETRC_LINE);
-      ret = NETRC_OK;
-      do {
-        const char *line;
-        /* Curl_get_line always returns lines ending with a newline */
-        result = Curl_get_line(&linebuf, file, &eof);
-        if(!result) {
-          line = curlx_dyn_ptr(&linebuf);
-          /* skip comments on load */
-          curlx_str_passblanks(&line);
-          if(*line == '#')
-            continue;
-          result = curlx_dyn_add(filebuf, line);
-        }
-        if(result) {
-          curlx_dyn_free(filebuf);
-          ret = curl2netrc(result);
-          break;
-        }
-      } while(!eof);
-      curlx_dyn_free(&linebuf);
+#endif
+
+static void netrc_lexer_init(struct netrc_lexer *lexer,
+                             struct Curl_easy *data,
+                             const char *content)
+{
+  curlx_dyn_init(&lexer->literal, MAX_NETRC_TOKEN);
+  lexer->data = data;
+  lexer->content = lexer->pos = content;
+}
+
+static void netrc_lexer_cleanup(struct netrc_lexer *lexer)
+{
+  lexer->content = lexer->pos = NULL;
+  lexer->data = NULL;
+  curlx_dyn_free(&lexer->literal);
+}
+
+static void netrc_skip_blanks(struct netrc_lexer *lexer)
+{
+  const char *s = lexer->pos;
+  while(*s) {
+    curlx_str_passblanks(&s);
+    while(*s == '\r')
+      ++s;
+    if(*s == '\n') {
+      ++s;
     }
-    curlx_fclose(file);
+    else
+      break;
   }
-  return ret;
+  lexer->pos = s;
 }
 
-/* bundled parser state to keep function signatures compact */
-struct netrc_state {
-  struct Curl_creds *existing;
-  char *login;
-  char *password;
-  enum host_lookup_state state;
-  enum found_state keyword;
-  NETRCcode retcode;
-  unsigned char found; /* FOUND_LOGIN | FOUND_PASSWORD bits */
-  bool our_login;
-  bool done;
-};
+static void netrc_skip_to_empty_line(struct netrc_lexer *lexer)
+{
+  const char *s = lexer->pos;
+  while(*s) {
+    if(*s == '\r')
+      ++s;
+    else if(*s == '\n') {
+      ++s;
+      while(*s == '\r')
+        ++s;
+      if(*s == '\n')
+        goto out;
+    }
+    else
+      ++s;
+  }
+out:
+  lexer->pos = s;
+}
 
 /*
  * Parse a quoted token starting after the opening '"'. Handles \n, \r, \t
@@ -126,374 +193,370 @@ struct netrc_state {
  *
  * Returns NETRC_OK or error.
  */
-static NETRCcode netrc_quoted_token(const char **tok_endp,
-                                    struct dynbuf *token)
+static NETRCcode netrc_lexer_quoted(struct netrc_lexer *lexer)
 {
-  bool escape = FALSE;
   NETRCcode rc = NETRC_SYNTAX_ERROR;
-  const char *tok_end = *tok_endp;
-  tok_end++; /* pass the leading quote */
-  while(*tok_end) {
-    CURLcode result;
-    char s = *tok_end;
+  const char *s = lexer->pos;
+  bool escape = FALSE;
+  CURLcode result;
+
+  DEBUGASSERT(*s == '\"');
+  ++s; /* pass the leading quote */
+  while(*s) {
+    char c = *s;
     if(escape) {
       escape = FALSE;
-      switch(s) {
+      switch(c) {
       case 'n':
-        s = '\n';
+        c = '\n';
         break;
       case 'r':
-        s = '\r';
+        c = '\r';
         break;
       case 't':
-        s = '\t';
+        c = '\t';
         break;
       }
     }
-    else if(s == '\\') {
+    else if(c == '\\') {
       escape = TRUE;
-      tok_end++;
+      ++s;
       continue;
     }
-    else if(s == '\"') {
-      tok_end++; /* pass the ending quote */
+    else if(c == '\"') {
+      ++s; /* pass the ending quote */
       rc = NETRC_OK;
-      break;
+      goto out;
     }
-    result = curlx_dyn_addn(token, &s, 1);
+    result = curlx_dyn_addn(&lexer->literal, &c, 1);
     if(result) {
-      *tok_endp = tok_end;
-      return curl2netrc(result);
+      rc = curl2netrc(result);
+      goto out;
     }
-    tok_end++;
+    ++s;
   }
-  *tok_endp = tok_end;
+out:
+  lexer->pos = s;
   return rc;
 }
 
-/*
- * Gets the next token from the netrc buffer at *tokp. Writes the token into
- * the 'token' dynbuf. Advances *tok_endp past the consumed token in the input
- * buffer. Updates *statep for MACDEF newline handling. Sets *lineend = TRUE
- * when the line is exhausted.
- *
- * Returns NETRC_OK or an error code.
- */
-static NETRCcode netrc_get_token(const char **tokp,
-                                 const char **tok_endp,
-                                 struct dynbuf *token,
-                                 enum host_lookup_state *statep,
-                                 bool *lineend)
+static void netrc_lexer_push(struct netrc_lexer *lexer)
 {
-  const char *tok = *tokp;
-  const char *tok_end;
-
-  *lineend = FALSE;
-  curlx_dyn_reset(token);
-  curlx_str_passblanks(&tok);
-
-  /* tok is first non-space letter */
-  if(*statep == MACDEF) {
-    if((*tok == '\n') || (*tok == '\r'))
-      *statep = NOTHING; /* end of macro definition */
-    *lineend = TRUE;
-    *tokp = tok;
-    return NETRC_OK;
-  }
+  lexer->pushed = TRUE;
+}
 
-  if(!*tok || (*tok == '\n')) {
-    /* end of line */
-    *lineend = TRUE;
-    *tokp = tok;
-    return NETRC_OK;
+static NETRCcode netrc_lexer_next(struct netrc_lexer *lexer,
+                                  bool want_literal)
+{
+  const char *s = lexer->pos, *start;
+  NETRCcode rc = NETRC_OK;
+  size_t slen;
+  CURLcode result;
+
+  if(lexer->pushed) {
+    lexer->pushed = FALSE;
+    goto out;
   }
 
-  tok_end = tok;
-  if(*tok == '\"') {
-    /* quoted string */
-    NETRCcode ret = netrc_quoted_token(&tok_end, token);
-    if(ret)
-      return ret;
-  }
-  else {
+  curlx_dyn_reset(&lexer->literal);
+  netrc_skip_blanks(lexer);
+  s = lexer->pos;
+
+  switch(*s) {
+  case 0:
+    lexer->token = NETRC_TOK_EOF;
+    break;
+  case '\"':
+    rc = netrc_lexer_quoted(lexer);
+    lexer->token = NETRC_TOK_LITERAL;
+    s = lexer->pos;
+    break;
+  default:
     /* unquoted token */
-    size_t len = 0;
-    CURLcode result;
-    while(*tok_end > ' ') {
-      tok_end++;
-      len++;
+    start = s;
+    while(*s && !ISBLANK(*s) && !ISNEWLINE(*s))
+      ++s;
+    slen = s - start;
+    if(!slen) {
+      rc = NETRC_SYNTAX_ERROR;
+    }
+    if(want_literal) {
+      lexer->token = NETRC_TOK_LITERAL;
+      result = curlx_dyn_addn(&lexer->literal, start, slen);
+      rc = curl2netrc(result);
+    }
+    else if((slen == 7) && curl_strnequal(start, "machine", slen)) {
+      lexer->token = NETRC_TOK_MACHINE;
+    }
+    else if((slen == 7) && curl_strnequal(start, "default", slen)) {
+      lexer->token = NETRC_TOK_DEFAULT;
     }
-    if(!len)
-      return NETRC_SYNTAX_ERROR;
-    result = curlx_dyn_addn(token, tok, len);
-    if(result)
-      return curl2netrc(result);
+    else if((slen == 7) && curl_strnequal(start, "account", slen)) {
+      lexer->token = NETRC_TOK_ACCOUNT;
+    }
+    else if((slen == 5) && curl_strnequal(start, "login", slen)) {
+      lexer->token = NETRC_TOK_LOGIN;
+    }
+    else if((slen == 8) && curl_strnequal(start, "password", slen)) {
+      lexer->token = NETRC_TOK_PASSWD;
+    }
+    else if((slen == 6) && curl_strnequal(start, "macdef", slen)) {
+      lexer->token = NETRC_TOK_MACDEF;
+    }
+    else {
+      lexer->token = NETRC_TOK_JUNK;
+    }
+    break;
   }
 
-  *tok_endp = tok_end;
+out:
+#if NETRC_DEBUG
+  CURL_TRC_M(lexer->data, "[NETRC] token %s '%s', rc=%d",
+             netrc_tokenstr(lexer->token),
+             curlx_dyn_ptr(&lexer->literal), rc);
+#endif
+  lexer->pos = s;
+  return rc;
+}
 
-  if(curlx_dyn_len(token))
-    *tokp = curlx_dyn_ptr(token);
-  else
-    /* set it to blank to avoid NULL */
-    *tokp = "";
+struct netrc_scanner {
+  struct netrc_lexer lexer;
+  const char *hostname; /* non-NULL, machine to scan for */
+  const char *user; /* maybe NULL, login to scan for */
+  char *login;
+  char *passwd;
+  struct Curl_creds *creds;
+  bool matches_host;
+  bool found;
+};
 
-  return NETRC_OK;
+static void netrc_scan_reset(struct netrc_scanner *sc)
+{
+  curlx_safefree(sc->login);
+  curlx_safefree(sc->passwd);
+  sc->matches_host = FALSE;
 }
 
-/*
- * Reset parser for a new machine entry. Frees password and optionally login
- * if it was not user-specified.
- */
-static void netrc_new_machine(struct netrc_state *ns)
+static void netrc_scan_init(struct netrc_scanner *sc,
+                            struct Curl_easy *data,
+                            const char *content,
+                            const char *hostname,
+                            const char *user)
 {
-  ns->keyword = NONE;
-  ns->found = 0;
-  ns->our_login = FALSE;
-  curlx_safefree(ns->password);
-  curlx_safefree(ns->login);
+  memset(sc, 0, sizeof(*sc));
+  netrc_lexer_init(&sc->lexer, data, content);
+  sc->hostname = hostname;
+  sc->user = (user && user[0]) ? user : NULL;
+  netrc_scan_reset(sc);
 }
 
-/*
- * Process a parsed token through the HOSTVALID state machine branch. This
- * handles login/password values and keyword transitions for the matched host.
- *
- * Returns NETRC_OK or an error code.
- */
-static NETRCcode netrc_hostvalid(struct netrc_state *ns, const char *tok)
+static void netrc_scan_cleanup(struct netrc_scanner *sc)
 {
-  if(ns->keyword == LOGIN) {
-    if(Curl_creds_has_user(ns->existing))
-      ns->our_login = !Curl_timestrcmp(ns->existing->user, tok);
-    else {
-      ns->our_login = TRUE;
-      curlx_free(ns->login);
-      ns->login = curlx_strdup(tok);
-      if(!ns->login)
-        return NETRC_OUT_OF_MEMORY;
-    }
-    ns->found |= FOUND_LOGIN;
-    ns->keyword = NONE;
-  }
-  else if(ns->keyword == PASSWORD) {
-    curlx_free(ns->password);
-    ns->password = curlx_strdup(tok);
-    if(!ns->password)
-      return NETRC_OUT_OF_MEMORY;
-    ns->found |= FOUND_PASSWORD;
-    ns->keyword = NONE;
-  }
-  else if(curl_strequal("login", tok))
-    ns->keyword = LOGIN;
-  else if(curl_strequal("password", tok))
-    ns->keyword = PASSWORD;
-  else if(curl_strequal("machine", tok)) {
-    /* a new machine here */
-    bool specific_login = Curl_creds_has_user(ns->existing);
-
-    if((ns->found & FOUND_PASSWORD) &&
-      /* a password was provided for this host */
-       (!specific_login || ns->our_login ||
-        /* and found a login that is suitable
-           (either matched specific one or simply present) */
-        (specific_login && !(ns->found & FOUND_LOGIN)))) {
-      /* or we look for a specific login, but no login was not specified */
-
-      ns->done = TRUE;
-      return NETRC_OK;
-    }
-
-    ns->state = HOSTFOUND;
-    netrc_new_machine(ns);
-  }
-  else if(curl_strequal("default", tok)) {
-    ns->state = HOSTVALID;
-    ns->retcode = NETRC_OK;
-    netrc_new_machine(ns);
-  }
-  if((ns->found == (FOUND_PASSWORD | FOUND_LOGIN)) && ns->our_login)
-    ns->done = TRUE;
-  return NETRC_OK;
+  netrc_scan_reset(sc);
+  sc->hostname = NULL;
+  sc->user = NULL;
+  Curl_creds_unlink(&sc->creds);
+  netrc_lexer_cleanup(&sc->lexer);
 }
 
-/*
- * Process one parsed token through the netrc state
- * machine. Updates the parser state in *ns.
- * Returns NETRC_OK or an error code.
- */
-static NETRCcode netrc_handle_token(struct netrc_state *ns,
-                                    const char *tok,
-                                    const char *host)
+static NETRCcode netrc_scan_literal(struct netrc_scanner *sc,
+                                    char **pdest)
 {
-  switch(ns->state) {
-  case NOTHING:
-    if(curl_strequal("macdef", tok))
-      ns->state = MACDEF;
-    else if(curl_strequal("machine", tok)) {
-      ns->state = HOSTFOUND;
-      netrc_new_machine(ns);
-    }
-    else if(curl_strequal("default", tok)) {
-      ns->state = HOSTVALID;
-      ns->retcode = NETRC_OK;
-    }
-    break;
-  case MACDEF:
-    if(!*tok)
-      ns->state = NOTHING;
-    break;
-  case HOSTFOUND:
-    if(curl_strequal(host, tok)) {
-      ns->state = HOSTVALID;
-      ns->retcode = NETRC_OK;
+  NETRCcode rc = netrc_lexer_next(&sc->lexer, TRUE);
+  if(!rc) {
+    if(sc->lexer.token == NETRC_TOK_LITERAL) {
+      if(pdest && sc->matches_host) {
+        curlx_free(*pdest);
+        *pdest = curlx_strdup(curlx_dyn_ptr(&sc->lexer.literal));
+        if(!*pdest)
+          rc = NETRC_OUT_OF_MEMORY;
+      }
     }
     else
-      ns->state = NOTHING;
-    break;
-  case HOSTVALID:
-    return netrc_hostvalid(ns, tok);
+      netrc_lexer_push(&sc->lexer);
   }
-  return NETRC_OK;
+  return rc;
 }
 
-/*
- * Finalize the parse result: fill in defaults and free
- * resources on error.
- */
-static NETRCcode netrc_finalize(struct netrc_state *ns,
-                                struct store_netrc *store,
-                                struct Curl_creds **pcreds)
+static NETRCcode netrc_scan_end_entry(struct netrc_scanner *sc)
 {
-  NETRCcode retcode = ns->retcode;
-  if(!retcode) {
-    if(!ns->password && ns->our_login) {
-      /* success without a password, set a blank one */
-      ns->password = curlx_strdup("");
-      if(!ns->password) {
-        retcode = NETRC_OUT_OF_MEMORY;
-        goto out;
+  NETRCcode rc = NETRC_OK;
+#if NETRC_DEBUG
+  CURL_TRC_M(sc->lexer.data,
+             "[NETRC] entry matches_host=%d, login='%s', passwd='%s'",
+             sc->matches_host, sc->login, sc->passwd);
+#endif
+  if(sc->matches_host) {
+    if(sc->login) {
+      if(sc->user) {
+        if(Curl_timestrcmp(sc->user, sc->login))
+          goto out;
+        /* We look for a specific user,
+         * entry is only interesting with password */
+        sc->found = !!sc->passwd;
+      }
+      else {
+        sc->found = TRUE;
       }
     }
-    else if(!ns->login && !ns->password) {
-      /* a default with no credentials */
-      retcode = NETRC_NO_MATCH;
-      goto out;
+    else if(sc->passwd) {
+      /* found a passwd that applies to any user */
+      sc->found = TRUE;
     }
-  }
-
-  if(!retcode) {
-    /* success
-       netrc_finalize() can return a password even when specific_login is set
-       but our_login is false (e.g., host matched but the requested login
-       never matched). See test 685. */
-    const char *login = Curl_creds_has_user(ns->existing) ?
-      ns->existing->user : ns->login;
-    /* success without a password, set a blank one */
-    const char *passwd = ns->password ? ns->password : "";
-
-    if(Curl_creds_create(login, passwd, NULL, NULL, NULL, CREDS_NETRC,
-                         pcreds)) {
-      retcode = NETRC_OUT_OF_MEMORY;
-      goto out;
+    else {
+      /* entry has nothing interesting */
+    }
+    if(sc->found) {
+#if NETRC_DEBUG
+      CURL_TRC_M(sc->lexer.data, "[NETRC] entry match found");
+#endif
+      if(Curl_creds_create(sc->user ? sc->user : sc->login, sc->passwd,
+                           NULL, NULL, NULL, CREDS_NETRC, &sc->creds))
+        rc = NETRC_OUT_OF_MEMORY;
     }
   }
-
 out:
-  curlx_free(ns->login);
-  curlx_free(ns->password);
-  if(retcode) {
-    curlx_dyn_free(&store->filebuf);
-    store->loaded = FALSE;
-  }
-  return retcode;
+  netrc_scan_reset(sc);
+  return rc;
 }
 
-/*
- * Returns zero on success.
- */
-static NETRCcode parsenetrc(struct store_netrc *store,
-                            const char *host,
-                            struct Curl_creds *existing,
-                            const char *netrcfile,
+static NETRCcode netrc_scan(struct Curl_easy *data,
+                            const char *content,
+                            const char *hostname,
+                            const char *user,
                             struct Curl_creds **pcreds)
 {
-  const char *netrcbuffer;
-  struct dynbuf token;
-  struct dynbuf *filebuf = &store->filebuf;
-  struct netrc_state ns;
-
-  DEBUGASSERT(!existing || !Curl_creds_has_passwd(existing));
-  memset(&ns, 0, sizeof(ns));
-  ns.retcode = NETRC_NO_MATCH;
-  ns.existing = existing;
-
-  curlx_dyn_init(&token, MAX_NETRC_TOKEN);
-
-  if(!store->loaded) {
-    NETRCcode ret = file2memory(netrcfile, filebuf);
-    if(ret)
-      return ret;
-    store->loaded = TRUE;
-  }
-
-  netrcbuffer = curlx_dyn_ptr(filebuf);
+  struct netrc_scanner sc;
+  NETRCcode rc = NETRC_OK;
 
-  while(!ns.done) {
-    const char *tok = netrcbuffer;
-    while(tok && !ns.done) {
-      const char *tok_end;
-      bool lineend;
-      NETRCcode ret;
-
-      ret = netrc_get_token(&tok, &tok_end, &token, &ns.state, &lineend);
-      if(ret) {
-        ns.retcode = ret;
-        goto out;
-      }
-      if(lineend)
+  Curl_creds_unlink(pcreds);
+  netrc_scan_init(&sc, data, content, hostname, user);
+
+  while(!rc && !sc.found) {
+    rc = netrc_lexer_next(&sc.lexer, FALSE);
+    if(!rc) {
+      /* Does this token end any previous entry? */
+      switch(sc.lexer.token) {
+      case NETRC_TOK_EOF:
+      case NETRC_TOK_MACHINE:
+      case NETRC_TOK_DEFAULT:
+      case NETRC_TOK_MACDEF:
+        rc = netrc_scan_end_entry(&sc);
+        if(rc || sc.found)
+          goto out;
         break;
+      default:
+        break;
+      }
 
-      ret = netrc_handle_token(&ns, tok, host);
-      if(ret) {
-        ns.retcode = ret;
+      switch(sc.lexer.token) {
+      case NETRC_TOK_EOF:
         goto out;
-      }
-      /* tok_end cannot point to a null byte here since lines are always
-         newline terminated */
-      DEBUGASSERT(*tok_end);
-      tok = ++tok_end;
-    }
-    if(!ns.done) {
-      const char *nl = NULL;
-      if(tok)
-        nl = strchr(tok, '\n');
-      if(!nl)
+      case NETRC_TOK_MACHINE:
+        rc = netrc_lexer_next(&sc.lexer, TRUE);
+        if(!rc) {
+          if(sc.lexer.token == NETRC_TOK_LITERAL) {
+            sc.matches_host = curl_strequal(
+              sc.hostname, curlx_dyn_ptr(&sc.lexer.literal));
+          }
+          else {
+            sc.matches_host = FALSE;
+            netrc_lexer_push(&sc.lexer);
+          }
+        }
+        break;
+      case NETRC_TOK_DEFAULT:
+        sc.matches_host = TRUE;
+        break;
+      case NETRC_TOK_ACCOUNT:
+        rc = netrc_scan_literal(&sc, NULL); /* ignore, not used */
+        break;
+      case NETRC_TOK_LOGIN:
+        rc = netrc_scan_literal(&sc, &sc.login);
         break;
-      /* point to next line */
-      netrcbuffer = &nl[1];
+      case NETRC_TOK_PASSWD:
+        rc = netrc_scan_literal(&sc, &sc.passwd);
+        break;
+      case NETRC_TOK_MACDEF:
+        netrc_skip_to_empty_line(&sc.lexer);
+        break;
+      case NETRC_TOK_LITERAL:
+      case NETRC_TOK_JUNK:
+      default:
+        /* skip this */
+        break;
+      }
     }
-  } /* while !done */
+  }
 
 out:
-  curlx_dyn_free(&token);
-  return netrc_finalize(&ns, store, pcreds);
+  if(!rc) {
+    if(sc.creds)
+      Curl_creds_link(pcreds, sc.creds);
+    else
+      rc = NETRC_NO_MATCH;
+  }
+  netrc_scan_cleanup(&sc);
+  return rc;
 }
 
-const char *Curl_netrc_strerror(NETRCcode ret)
+static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
 {
-  switch(ret) {
-  default:
-    return ""; /* not a legit error */
-  case NETRC_FILE_MISSING:
-    return "no such file";
-  case NETRC_NO_MATCH:
-    return "no matching entry";
-  case NETRC_OUT_OF_MEMORY:
-    return "out of memory";
-  case NETRC_SYNTAX_ERROR:
-    return "syntax error";
+  NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */
+  FILE *file = curlx_fopen(filename, FOPEN_READTEXT);
+
+  if(file) {
+    curlx_struct_stat stat;
+    if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) {
+      CURLcode result = CURLE_OK;
+      bool eof;
+      struct dynbuf linebuf;
+      curlx_dyn_init(&linebuf, MAX_NETRC_LINE);
+      ret = NETRC_OK;
+      do {
+        const char *line;
+        /* Curl_get_line always returns lines ending with a newline */
+        result = Curl_get_line(&linebuf, file, &eof);
+        if(!result) {
+          line = curlx_dyn_ptr(&linebuf);
+          /* skip comments on load */
+          curlx_str_passblanks(&line);
+          if(*line == '#')
+            continue;
+          result = curlx_dyn_add(filebuf, line);
+        }
+        if(result) {
+          curlx_dyn_free(filebuf);
+          ret = curl2netrc(result);
+          break;
+        }
+      } while(!eof);
+      curlx_dyn_free(&linebuf);
+    }
+    curlx_fclose(file);
   }
-  /* never reached */
+  return ret;
+}
+
+static NETRCcode netrc_scan_file(struct Curl_easy *data,
+                                 struct store_netrc *store,
+                                 const char *hostname,
+                                 const char *user,
+                                 const char *netrcfile,
+                                 struct Curl_creds **pcreds)
+{
+  struct dynbuf *filebuf = &store->filebuf;
+
+  if(!store->loaded) {
+    NETRCcode ret = file2memory(netrcfile, filebuf);
+    if(ret) {
+      CURL_TRC_M(data, "[NETRC] could not load '%s'", netrcfile);
+      return ret;
+    }
+    store->loaded = TRUE;
+  }
+
+  return netrc_scan(data, curlx_dyn_ptr(filebuf), hostname, user, pcreds);
 }
 
 /*
@@ -502,14 +565,18 @@ const char *Curl_netrc_strerror(NETRCcode ret)
  * *loginp and *passwordp MUST be allocated if they are not NULL when passed
  * in.
  */
-NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host,
-                          struct Curl_creds *existing,
+NETRCcode Curl_netrc_scan(struct Curl_easy *data,
+                          struct store_netrc *store,
+                          const char *hostname,
+                          const char *user,
                           const char *netrcfile,
                           struct Curl_creds **pcreds)
 {
   NETRCcode retcode = NETRC_OK;
   char *filealloc = NULL;
 
+  CURL_TRC_M(data, "[NETRC] scanning '%s' for host '%s' user '%s'",
+             netrcfile, hostname, user);
   Curl_creds_unlink(pcreds);
   if(!netrcfile) {
     char *home = NULL;
@@ -559,7 +626,8 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host,
         goto out;
       }
     }
-    retcode = parsenetrc(store, host, existing, filealloc, pcreds);
+    retcode = netrc_scan_file(
+      data, store, hostname, user, filealloc, pcreds);
     curlx_free(filealloc);
 #ifdef _WIN32
     if(retcode == NETRC_FILE_MISSING) {
@@ -569,14 +637,17 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host,
         curlx_free(homea);
         return NETRC_OUT_OF_MEMORY;
       }
-      retcode = parsenetrc(store, host, existing, filealloc, pcreds);
+      retcode = netrc_scan_file(
+        data, store, hostname, user, filealloc, pcreds);
       curlx_free(filealloc);
     }
 #endif
     curlx_free(homea);
   }
   else
-    retcode = parsenetrc(store, host, existing, netrcfile, pcreds);
+    retcode = netrc_scan_file(
+      data, store, hostname, user, netrcfile, pcreds);
+
 out:
   if(retcode)
     Curl_creds_unlink(pcreds);
@@ -593,4 +664,22 @@ void Curl_netrc_cleanup(struct store_netrc *store)
   curlx_dyn_free(&store->filebuf);
   store->loaded = FALSE;
 }
-#endif
+
+const char *Curl_netrc_strerror(NETRCcode ret)
+{
+  switch(ret) {
+  default:
+    return ""; /* not a legit error */
+  case NETRC_FILE_MISSING:
+    return "no such file";
+  case NETRC_NO_MATCH:
+    return "no matching entry";
+  case NETRC_OUT_OF_MEMORY:
+    return "out of memory";
+  case NETRC_SYNTAX_ERROR:
+    return "syntax error";
+  }
+  /* never reached */
+}
+
+#endif /* !CURL_DISABLE_NETRC */
index 92dd4d47c9e65a3969b6d48da287b9b322a726d3..6be9b83316213e669f5822b719de562492118c07 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "curlx/dynbuf.h"
 
+struct Curl_easy;
 struct Curl_creds;
 
 struct store_netrc {
@@ -50,15 +51,14 @@ const char *Curl_netrc_strerror(NETRCcode ret);
 void Curl_netrc_init(struct store_netrc *store);
 void Curl_netrc_cleanup(struct store_netrc *store);
 
-NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host,
-                          struct Curl_creds *existing,
+/* Scan a netrc file for credentials matching hostname
+ * and optional user. */
+NETRCcode Curl_netrc_scan(struct Curl_easy *data,
+                          struct store_netrc *store,
+                          const char *hostname,
+                          const char *user,
                           const char *netrcfile,
                           struct Curl_creds **pcreds);
-/* Assume: (*passwordp)[0]=0, host[0] != 0.
- * If (*loginp)[0] = 0, search for login and password within a machine
- * section in the netrc.
- * If (*loginp)[0] != 0, search for password within machine and login.
- */
 #else
 /* disabled */
 #define Curl_netrc_init(x)
index ec4fb8b1b0f71d7690bdacd5877ce60a7e6bc085..a569c3e4bc103017db6ccdb736736de8d1e95858 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -2161,14 +2161,11 @@ static bool str_has_ctrl(const char *input)
 static CURLcode override_login(struct Curl_easy *data,
                                struct connectdata *conn)
 {
-  CURLUcode uc;
   char **optionsp = &conn->options;
 #ifndef CURL_DISABLE_NETRC
-  struct Curl_creds *ncreds_in = NULL;
   struct Curl_creds *ncreds_out = NULL;
 #endif
   CURLcode result = CURLE_OK;
-  bool creds_changed = FALSE;
 
   if(data->set.str[STRING_OPTIONS]) {
     curlx_free(*optionsp);
@@ -2180,107 +2177,97 @@ static CURLcode override_login(struct Curl_easy *data,
   }
 
 #ifndef CURL_DISABLE_NETRC
-  if(data->set.use_netrc) {
-    /* Determine how to react on already existing credentials */
-    if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
-      Curl_creds_unlink(&conn->creds);
-    }
+  if(data->set.use_netrc) { /* not CURL_NETRC_IGNORED */
+    struct Curl_creds *ncreds_in = NULL;
+    bool scan_netrc = TRUE;
+    NETRCcode ret;
+    CURLUcode uc;
 
     if(data->state.creds) {
       switch(data->state.creds->source) {
       case CREDS_OPTION:
-        /* we never override credentials set via CURLOPT_* */
-        goto out;
-      case CREDS_URL:
+        /* we never override credentials set via CURLOPT_*, leave. */
+        scan_netrc = FALSE;
+        break;
+      case CREDS_URL: /* only apply when netrc is not required */
         if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
-          /* use the URL user to search netrc */
-          result = Curl_creds_create(
-            data->state.creds->user, NULL, NULL, NULL, NULL, CREDS_URL,
-            &ncreds_in);
-          if(result)
-            goto out;
+          /* We ignore password from URL */
+          ncreds_in = data->state.creds;
+        }
+        else if(!Curl_creds_has_user(data->state.creds) ||
+                !Curl_creds_has_passwd(data->state.creds)) {
+          /* We use netrc to complete what is missing */
+          ncreds_in = data->state.creds;
         }
         else
-          /* only search when something is still missing */
-          Curl_creds_link(&ncreds_in, data->state.creds);
+          scan_netrc = FALSE;
         break;
-      default:
-        /* ignore credentials from other sources */
+      default: /* ignore credentials from other sources */
         break;
       }
     }
 
-    /* Only search in netrc when the creds are not already complete */
-    if(!Curl_creds_has_passwd(ncreds_in)) {
-      NETRCcode ret;
-
-      CURL_TRC_M(data, "netrc: find credentials for %s, user %s",
-                 conn->origin->hostname,
-                 Curl_creds_has_user(ncreds_in) ? ncreds_in->user : "*");
-      ret = Curl_parsenetrc(&data->state.netrc,
-                            conn->origin->hostname,
-                            ncreds_in,
-                            data->set.str[STRING_NETRC_FILE],
-                            &ncreds_out);
-      DEBUGASSERT(!ret || !ncreds_out);
-      if(ret == NETRC_OUT_OF_MEMORY) {
-        result = CURLE_OUT_OF_MEMORY;
-        goto out;
-      }
-      else if(ret && ((ret == NETRC_NO_MATCH) ||
-                      (data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
-        infof(data, "Could not find host %s in the %s file; using defaults",
-              conn->origin->hostname,
-              (data->set.str[STRING_NETRC_FILE] ?
-               data->set.str[STRING_NETRC_FILE] : ".netrc"));
-      }
-      else if(ret) {
-        const char *m = Curl_netrc_strerror(ret);
-        failf(data, ".netrc error: %s", m);
-        result = CURLE_READ_ERROR;
-        goto out;
-      }
-      else if(ncreds_out) {
-        if(!(conn->scheme->flags & PROTOPT_USERPWDCTRL)) {
-          /* if the protocol cannot handle control codes in credentials, make
-             sure there are none */
-          if(str_has_ctrl(ncreds_out->user) ||
-             str_has_ctrl(ncreds_out->passwd)) {
-            failf(data, "control code detected in .netrc credentials");
-            result = CURLE_READ_ERROR;
-            goto out;
-          }
-        }
-        CURL_TRC_M(data, "netrc: using credentials for %s as %s",
-                   conn->origin->hostname, ncreds_out->user);
-        result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
-                                  data->state.creds, CREDS_NETRC,
-                                  &data->state.creds);
-        if(result)
+    if(!scan_netrc)
+      goto out;
+
+    ret = Curl_netrc_scan(data, &data->state.netrc,
+                          conn->origin->hostname,
+                          Curl_creds_user(ncreds_in),
+                          data->set.str[STRING_NETRC_FILE],
+                          &ncreds_out);
+    DEBUGASSERT(!ret || !ncreds_out);
+    if(ret == NETRC_OUT_OF_MEMORY) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
+    }
+    else if(ret && ((ret == NETRC_NO_MATCH) ||
+                    (data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
+      infof(data, "Could not find host %s in the %s file; using defaults",
+            conn->origin->hostname,
+            (data->set.str[STRING_NETRC_FILE] ?
+             data->set.str[STRING_NETRC_FILE] : ".netrc"));
+    }
+    else if(ret) {
+      const char *m = Curl_netrc_strerror(ret);
+      failf(data, ".netrc error: %s", m);
+      result = CURLE_READ_ERROR;
+      goto out;
+    }
+    else if(ncreds_out) {
+      if(!(conn->scheme->flags & PROTOPT_USERPWDCTRL)) {
+        /* if the protocol cannot handle control codes in credentials, make
+           sure there are none */
+        if(str_has_ctrl(ncreds_out->user) ||
+           str_has_ctrl(ncreds_out->passwd)) {
+          failf(data, "control code detected in .netrc credentials");
+          result = CURLE_READ_ERROR;
           goto out;
-        creds_changed = TRUE;
+        }
       }
-      else
-        DEBUGASSERT(0);
+      CURL_TRC_M(data, "netrc: using credentials for %s as %s",
+                 conn->origin->hostname, ncreds_out->user);
+      result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
+                                data->state.creds, CREDS_NETRC,
+                                &data->state.creds);
+      if(result)
+        goto out;
+      /* for updated strings, we update them in the URL */
+      uc = curl_url_set(data->state.uh, CURLUPART_USER,
+                        Curl_creds_user(data->state.creds), CURLU_URLENCODE);
+      if(!uc)
+        uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
+                          Curl_creds_passwd(data->state.creds),
+                          CURLU_URLENCODE);
+      if(uc)
+        result = Curl_uc_to_curlcode(uc);
     }
+    else
+      DEBUGASSERT(0);
   }
-
 #endif
 
-  if(creds_changed) {
-    /* for updated strings, we update them in the URL */
-    uc = curl_url_set(data->state.uh, CURLUPART_USER,
-                      Curl_creds_user(data->state.creds), CURLU_URLENCODE);
-    if(!uc)
-      uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
-                        Curl_creds_passwd(data->state.creds), CURLU_URLENCODE);
-    if(uc)
-      result = Curl_uc_to_curlcode(uc);
-  }
-
 out:
 #ifndef CURL_DISABLE_NETRC
-  Curl_creds_unlink(&ncreds_in);
   Curl_creds_unlink(&ncreds_out);
 #endif
   return result;
index 099f39dd916c321d1863b580da624c84c8c4edc0..18bc9d4215930e1d9dc39463d1b445abb3383afe 100644 (file)
 #include "netrc.h"
 #include "creds.h"
 
-static void t1304_stop(struct Curl_creds **pc1, struct Curl_creds **pc2)
+static CURLcode t1304_setup(struct Curl_easy **easy)
 {
-  Curl_creds_unlink(pc1);
-  Curl_creds_unlink(pc2);
+  CURLcode result = CURLE_OK;
+
+  global_init(CURL_GLOBAL_ALL);
+  *easy = curl_easy_init();
+  if(!*easy) {
+    curl_global_cleanup();
+    return CURLE_OUT_OF_MEMORY;
+  }
+  return result;
 }
 
-static bool t1304_set_creds(const char *user, const char *passwd,
-                           struct Curl_creds **pcreds)
+static void t1304_stop(struct Curl_easy *easy)
 {
-  Curl_creds_unlink(pcreds);
-  if(user || passwd)
-    return !Curl_creds_create(user, passwd, NULL, NULL, NULL, CREDS_NONE,
-                              pcreds);
-  else
-    return TRUE;
+  curl_easy_cleanup(easy);
+  curl_global_cleanup();
 }
 
 static bool t1304_no_user(struct Curl_creds *creds)
@@ -56,130 +58,105 @@ static bool t1304_no_passwd(struct Curl_creds *creds)
 
 static CURLcode test_unit1304(const char *arg)
 {
-  struct Curl_creds *cr_out = NULL, *cr_in = NULL;
-
-  UNITTEST_BEGIN_SIMPLE
-
+  struct Curl_creds *cr_out = NULL;
+  struct Curl_easy *data;
   int result;
   struct store_netrc store;
 
+  UNITTEST_BEGIN(t1304_setup(&data))
+
   /*
    * Test a non existent host in our netrc file.
    */
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "test.example.com", NULL, arg, &cr_out);
+  result = Curl_netrc_scan(
+    data, &store, "test.example.com", NULL, arg, &cr_out);
   fail_unless(result == 1, "expected no match");
-  abort_unless(cr_out == NULL, "creds did not return NULL!");
+  fail_unless(cr_out == NULL, "creds did not return NULL!");
   Curl_netrc_cleanup(&store);
 
   /*
    * Test a non existent login in our netrc file.
    */
-  fail_unless(t1304_set_creds("me", NULL, &cr_in), "err set creds");
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+  result = Curl_netrc_scan(data, &store, "example.com", "me", arg, &cr_out);
   fail_unless(result == 1, "expected no match");
-  abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+  fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
   Curl_netrc_cleanup(&store);
 
   /*
    * Test a non existent login and host in our netrc file.
    */
-  fail_unless(t1304_set_creds("me", NULL, &cr_in), "err set creds");
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "test.example.com", cr_in, arg, &cr_out);
+  result = Curl_netrc_scan(
+    data, &store, "test.example.com", "me", arg, &cr_out);
   fail_unless(result == 1, "expected no match");
-  abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+  fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
   Curl_netrc_cleanup(&store);
 
   /*
    * Test a non existent login (substring of an existing one) in our
    * netrc file.
    */
-  fail_unless(t1304_set_creds(
-    "admi", NULL, &cr_in), "err set creds"); /* spellchecker:disable-line */
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+  result = Curl_netrc_scan(
+    data, &store, "example.com", "a", arg, &cr_out);
   fail_unless(result == 1, "expected no match");
-  abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+  fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
   Curl_netrc_cleanup(&store);
 
   /*
    * Test a non existent login (superstring of an existing one)
    * in our netrc file.
    */
-  fail_unless(t1304_set_creds("adminn", NULL, &cr_in), "err set creds");
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+  result = Curl_netrc_scan(
+    data, &store, "example.com", "administrator", arg, &cr_out);
   fail_unless(result == 1, "expected no match");
-  abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+  fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
   Curl_netrc_cleanup(&store);
 
   /*
-   * Test for the first existing host in our netrc file
-   * with login[0] = 0.
+   * Test for the first existing host in our netrc file with no user
    */
-  Curl_creds_unlink(&cr_in);
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+  result = Curl_netrc_scan(data, &store, "example.com", NULL, arg, &cr_out);
   fail_unless(result == 0, "Host should have been found");
-  abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
   fail_unless(strncmp(Curl_creds_passwd(cr_out), "passwd", 6) == 0,
               "password should be 'passwd'");
-  abort_unless(!t1304_no_user(cr_out), "returned NULL!");
+  fail_unless(!t1304_no_user(cr_out), "returned NULL!");
   fail_unless(strncmp(Curl_creds_user(cr_out), "admin", 5) == 0,
               "login should be 'admin'");
   Curl_netrc_cleanup(&store);
 
   /*
-   * Test for the first existing host in our netrc file
-   * with login[0] != 0.
+   * Test for the second existing host in our netrc file with no user
    */
-  Curl_creds_unlink(&cr_in);
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+  result = Curl_netrc_scan(
+    data, &store, "curl.example.com", NULL, arg, &cr_out);
   fail_unless(result == 0, "Host should have been found");
-  abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
-  fail_unless(strncmp(Curl_creds_passwd(cr_out), "passwd", 6) == 0,
-              "password should be 'passwd'");
-  abort_unless(!t1304_no_user(cr_out), "returned NULL!");
-  fail_unless(strncmp(Curl_creds_user(cr_out), "admin", 5) == 0,
-              "login should be 'admin'");
-  Curl_netrc_cleanup(&store);
-
-  /*
-   * Test for the second existing host in our netrc file
-   * with login[0] = 0.
-   */
-  Curl_creds_unlink(&cr_in);
-  Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "curl.example.com", cr_in, arg, &cr_out);
-  fail_unless(result == 0, "Host should have been found");
-  abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
   fail_unless(strncmp(Curl_creds_passwd(cr_out), "none", 4) == 0,
                       "password should be 'none'");
-  abort_unless(!t1304_no_user(cr_out), "returned NULL!");
+  fail_unless(!t1304_no_user(cr_out), "returned NULL!");
   fail_unless(strncmp(Curl_creds_user(cr_out), "none", 4) == 0,
               "login should be 'none'");
   Curl_netrc_cleanup(&store);
 
   /*
-   * Test for the second existing host in our netrc file
-   * with login[0] != 0.
+   * Test for the last host where we do not want to see the password
+   * if the login does not match.
    */
-  Curl_creds_unlink(&cr_in);
   Curl_netrc_init(&store);
-  result = Curl_parsenetrc(&store, "curl.example.com", cr_in, arg, &cr_out);
-  fail_unless(result == 0, "Host should have been found");
-  abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
-  fail_unless(strncmp(Curl_creds_passwd(cr_out), "none", 4) == 0,
-                      "password should be 'none'");
-  abort_unless(!t1304_no_user(cr_out), "returned NULL!");
-  fail_unless(strncmp(Curl_creds_user(cr_out), "none", 4) == 0,
-                      "login should be 'none'");
+  result = Curl_netrc_scan(
+    data, &store, "curl.example.com", "hilarious", arg, &cr_out);
+  fail_unless(result == 1, "expect no match");
+  fail_unless(!Curl_creds_has_passwd(cr_out), "password must be NULL");
   Curl_netrc_cleanup(&store);
 
-  UNITTEST_END(t1304_stop(&cr_in, &cr_out))
+  Curl_creds_unlink(&cr_out);
+
+  UNITTEST_END(t1304_stop(data))
 }
 
 #else