// -*- mode: cpp; mode: fold -*-
// SPDX-License-Identifier: GPL-2.0+
// Description								/*{{{*/
/* ######################################################################

   netrc file parser - returns the login and password of a give host in
                       a specified netrc-type file

   This file had this historic note, but now includes further changes
   under the GPL-2.0+:

   Originally written by Daniel Stenberg, <daniel@haxx.se>, et al. and
   placed into the Public Domain, do with it what you will.

   ##################################################################### */
									/*}}}*/
#include <config.h>
#include <apti18n.h>

#include <apt-pkg/configuration.h>
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/strutl.h>

#include <iostream>

#include "netrc.h"

/* Get user and password from .netrc when given a machine name */
bool MaybeAddAuth(FileFd &NetRCFile, URI &Uri)
{
   if (Uri.User.empty() == false || Uri.Password.empty() == false)
      return true;
   if (NetRCFile.IsOpen() == false || NetRCFile.Failed())
      return false;
   auto const Debug = _config->FindB("Debug::Acquire::netrc", false);

   std::string lookfor;
   if (Uri.Port != 0)
      strprintf(lookfor, "%s:%i%s", Uri.Host.c_str(), Uri.Port, Uri.Path.c_str());
   else
      lookfor.append(Uri.Host).append(Uri.Path);

   enum
   {
      NO,
      MACHINE,
      GOOD_MACHINE,
      LOGIN,
      PASSWORD
   } active_token = NO;
   std::string line;
   while (NetRCFile.Eof() == false || line.empty() == false)
   {
      bool protocolSpecified = false;

      if (line.empty())
      {
	 if (NetRCFile.ReadLine(line) == false)
	    break;
	 else if (line.empty())
	    continue;
      }
      auto tokenend = line.find_first_of("\t ");
      std::string token;
      if (tokenend != std::string::npos)
      {
	 token = line.substr(0, tokenend);
	 line.erase(0, tokenend + 1);
      }
      else
	 std::swap(line, token);
      if (token.empty())
	 continue;
      switch (active_token)
      {
      case NO:
	 if (token == "machine")
	    active_token = MACHINE;
	 break;
      case MACHINE:
	 // If token contains a protocol: Check it first, and strip it away if
	 // it matches. If it does not match, ignore this stanza.
	 // If there is no protocol, only allow https protocols.
	 protocolSpecified = token.find("://") != std::string::npos;
	 if (protocolSpecified)
	 {
	    if (not APT::String::Startswith(token, Uri.Access + "://"))
	    {
	       active_token = NO;
	       break;
	    }
	    token.erase(0, Uri.Access.length() + 3);
	 }

	 if (token.find('/') == std::string::npos)
	 {
	    if (Uri.Port != 0 && Uri.Host == token)
	       active_token = GOOD_MACHINE;
	    else if (lookfor.compare(0, lookfor.length() - Uri.Path.length(), token) == 0)
	       active_token = GOOD_MACHINE;
	    else
	       active_token = NO;
	 }
	 else
	 {
	    if (APT::String::Startswith(lookfor, token))
	       active_token = GOOD_MACHINE;
	    else
	       active_token = NO;
	 }

	 if (active_token == GOOD_MACHINE && not protocolSpecified)
	 {
	    if (Uri.Access != "https" && Uri.Access != "tor+https")
	    {
	       _error->Warning(_("%s: Credentials for %s match, but the protocol is not encrypted. Annotate with %s:// to use."), NetRCFile.Name().c_str(), token.c_str(), Uri.Access.c_str());
	       active_token = NO;
	    }
	 }
	 break;
      case GOOD_MACHINE:
	 if (token == "login")
	    active_token = LOGIN;
	 else if (token == "password")
	    active_token = PASSWORD;
	 else if (token == "machine")
	 {
	    if (Debug)
	       std::clog << "MaybeAddAuth: Found matching host adding '" << Uri.User << "' and '" << Uri.Password << "' for "
			 << (std::string)Uri << " from " << NetRCFile.Name() << std::endl;
	    return true;
	 }
	 break;
      case LOGIN:
	 std::swap(Uri.User, token);
	 active_token = GOOD_MACHINE;
	 break;
      case PASSWORD:
	 std::swap(Uri.Password, token);
	 active_token = GOOD_MACHINE;
	 break;
      }
   }
   if (active_token == GOOD_MACHINE)
   {
      if (Debug)
	 std::clog << "MaybeAddAuth: Found matching host adding '" << Uri.User << "' and '" << Uri.Password << "' for "
		   << (std::string)Uri << " from " << NetRCFile.Name() << std::endl;
      return true;
   }
   else if (active_token == NO)
   {
      if (Debug)
	 std::clog << "MaybeAddAuth: Found no matching host for "
		   << (std::string)Uri << " from " << NetRCFile.Name() << std::endl;
      return true;
   }
   else if (Debug)
   {
      std::clog << "MaybeAddAuth: Found no matching host (syntax error: token:";
      switch (active_token)
      {
	 case NO: std::clog << "NO"; break;
	 case MACHINE: std::clog << "MACHINE"; break;
	 case GOOD_MACHINE: std::clog << "GOOD_MACHINE"; break;
	 case LOGIN: std::clog << "LOGIN"; break;
	 case PASSWORD: std::clog << "PASSWORD"; break;
      }
      std::clog << ") for " << (std::string)Uri << " from " << NetRCFile.Name() << std::endl;
   }
   return false;
}
