--- /dev/null
+// netrc -- parse .netrc file
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// netrc library parses .netrc file. It is copy-paste of
+// golang.org/x/tools/cmd/auth/netrcauth.
+//
+// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
+// for documentation on the .netrc format.
+package netrc
+
+import (
+ "os"
+ "path"
+ "strings"
+)
+
+type Line struct {
+ Machine string
+ Login string
+ Password string
+}
+
+func Parse(data string) []Line {
+ var nrc []Line
+ var l Line
+ inMacro := false
+ for _, line := range strings.Split(data, "\n") {
+ if inMacro {
+ if line == "" {
+ inMacro = false
+ }
+ continue
+ }
+ f := strings.Fields(line)
+ i := 0
+ for ; i < len(f)-1; i += 2 {
+ // Reset at each "machine" token.
+ // "The auto-login process searches the .netrc file for a machine token
+ // that matches […]. Once a match is made, the subsequent .netrc tokens
+ // are processed, stopping when the end of file is reached or another
+ // machine or a default token is encountered."
+ switch f[i] {
+ case "machine":
+ l = Line{Machine: f[i+1]}
+ case "default":
+ break
+ case "login":
+ l.Login = f[i+1]
+ case "password":
+ l.Password = f[i+1]
+ case "macdef":
+ // "A macro is defined with the specified name; its contents begin with
+ // the next .netrc line and continue until a null line (consecutive
+ // new-line characters) is encountered."
+ inMacro = true
+ }
+ if l.Machine != "" && l.Login != "" && l.Password != "" {
+ nrc = append(nrc, l)
+ l = Line{}
+ }
+ }
+ if i < len(f) && f[i] == "default" {
+ // "There can be only one default token, and it must be after all machine tokens."
+ break
+ }
+ }
+ return nrc
+}
+
+func Path() (netrc string) {
+ p, ok := os.LookupEnv("NETRC")
+ if ok {
+ return p
+ }
+ homeDir, _ := os.UserHomeDir()
+ return path.Join(homeDir, ".netrc")
+}
+
+func Find(host string) (login string, password string) {
+ data, err := os.ReadFile(Path())
+ if err != nil {
+ return "", ""
+ }
+ for _, l := range Parse(string(data)) {
+ if l.Machine == host {
+ return l.Login, l.Password
+ }
+ }
+ return "", ""
+}
--- /dev/null
+package netrc
+
+import (
+ "reflect"
+ "testing"
+)
+
+var testNetrc = `
+machine incomplete
+password none
+
+machine api.github.com
+ login user
+ password pwd
+
+machine incomlete.host
+ login justlogin
+
+machine test.host
+login user2
+password pwd2
+
+machine oneline login user3 password pwd3
+
+machine ignore.host macdef ignore
+ login nobody
+ password nothing
+
+machine hasmacro.too macdef ignore-next-lines login user4 password pwd4
+ login nobody
+ password nothing
+
+default
+login anonymous
+password gopher@golang.org
+
+machine after.default
+login oops
+password too-late-in-file
+`
+
+func TestParse(t *testing.T) {
+ lines := Parse(testNetrc)
+ want := []Line{
+ {"api.github.com", "user", "pwd"},
+ {"test.host", "user2", "pwd2"},
+ {"oneline", "user3", "pwd3"},
+ {"hasmacro.too", "user4", "pwd4"},
+ }
+ if !reflect.DeepEqual(lines, want) {
+ t.Errorf("have %q\nwant %q", lines, want)
+ }
+}