Skip to content

Commit

Permalink
Handle hash characters (#) in netrc fields (#8109)
Browse files Browse the repository at this point in the history
Hash symbols were always treated as the beginning of a comment even if
they were in the middle of a username or password. To address this
require some whitespace before a hash character before a comment is
parsed.

That patch also implements support for quoted fields so that passwords
can contain the sequence "foo #bar" without dropping "bar" as a comment.

Issue: #8090
  • Loading branch information
plemarquand authored Nov 14, 2024
1 parent 42aaad6 commit bdd4cfc
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 7 deletions.
21 changes: 15 additions & 6 deletions Sources/Basics/Netrc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ public struct NetrcParser {
let matches = regex.matches(in: text, range: range)
var trimmedCommentsText = text
matches.forEach {
trimmedCommentsText = trimmedCommentsText
.replacingOccurrences(of: nsString.substring(with: $0.range), with: "")
let matchedString = nsString.substring(with: $0.range)
if !matchedString.starts(with: "\"") {
trimmedCommentsText = trimmedCommentsText
.replacingOccurrences(of: matchedString, with: "")
}
}
return trimmedCommentsText
}
Expand All @@ -151,12 +154,18 @@ private enum RegexUtil {
case machine, login, password, account, macdef, `default`

func capture(prefix: String = "", in match: NSTextCheckingResult, string: String) -> String? {
guard let range = Range(match.range(withName: prefix + rawValue), in: string) else { return nil }
return String(string[range])
if let quotedRange = Range(match.range(withName: prefix + rawValue + quotedIdentifier), in: string) {
return String(string[quotedRange])
} else if let range = Range(match.range(withName: prefix + rawValue), in: string) {
return String(string[range])
} else {
return nil
}
}
}

static let comments: String = "\\#[\\s\\S]*?.*$"
private static let quotedIdentifier = "quoted"
static let comments: String = "(\"[^\"]*\"|\\s#.*$)"
static let `default`: String = #"(?:\s*(?<default>default))"#
static let accountOptional: String = #"(?:\s*account\s+\S++)?"#
static let loginPassword: String =
Expand All @@ -171,6 +180,6 @@ private enum RegexUtil {
}

static func namedTrailingCapture(_ string: String, prefix: String = "") -> String {
#"\s*\#(string)\s+(?<\#(prefix + string)>\S++)"#
#"\s*\#(string)\s+(?:"(?<\#(prefix + string + quotedIdentifier)>[^"]*)"|(?<\#(prefix + string)>\S+))"#
}
}
81 changes: 80 additions & 1 deletion Tests/BasicsTests/NetrcTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -431,5 +431,84 @@ class NetrcTests: XCTestCase {
XCTAssertEqual(netrc.machines[1].login, "fred")
XCTAssertEqual(netrc.machines[1].password, "sunshine4ever")
}
}

func testComments() throws {
let content = """
# A comment at the beginning of the line
machine example.com # Another comment
login anonymous # Another comment
password qw#erty # Another comment
"""

let netrc = try NetrcParser.parse(content)

let machine = netrc.machines.first
XCTAssertEqual(machine?.name, "example.com")
XCTAssertEqual(machine?.login, "anonymous")
XCTAssertEqual(machine?.password, "qw#erty")
}

// TODO: These permutation tests would be excellent swift-testing parameterized tests.
func testAllHashQuotingPermutations() throws {
let cases = [
("qwerty", "qwerty"),
("qwe#rty", "qwe#rty"),
("\"qwe#rty\"", "qwe#rty"),
("\"qwe #rty\"", "qwe #rty"),
("\"qwe# rty\"", "qwe# rty"),
]

for (testCase, expected) in cases {
let content = """
machine example.com
login \(testCase)
password \(testCase)
"""
let netrc = try NetrcParser.parse(content)

let machine = netrc.machines.first
XCTAssertEqual(machine?.name, "example.com")
XCTAssertEqual(machine?.login, expected, "Expected login \(testCase) to parse as \(expected)")
XCTAssertEqual(machine?.password, expected, "Expected \(testCase) to parse as \(expected)")
}
}

func testAllCommentPermutations() throws {
let cases = [
("qwerty # a comment", "qwerty"),
("qwe#rty # a comment", "qwe#rty"),
("\"qwe#rty\" # a comment", "qwe#rty"),
("\"qwe #rty\" # a comment", "qwe #rty"),
("\"qwe# rty\" # a comment", "qwe# rty"),
]

for (testCase, expected) in cases {
let content = """
machine example.com
login \(testCase)
password \(testCase)
"""
let netrc = try NetrcParser.parse(content)

let machine = netrc.machines.first
XCTAssertEqual(machine?.name, "example.com")
XCTAssertEqual(machine?.login, expected, "Expected login \(testCase) to parse as \(expected)")
XCTAssertEqual(machine?.password, expected, "Expected password \(testCase) to parse as \(expected)")
}
}

func testQuotedMachine() throws {
let content = """
machine "example.com"
login anonymous
password qwerty
"""

let netrc = try NetrcParser.parse(content)

let machine = netrc.machines.first
XCTAssertEqual(machine?.name, "example.com")
XCTAssertEqual(machine?.login, "anonymous")
XCTAssertEqual(machine?.password, "qwerty")
}
}

0 comments on commit bdd4cfc

Please sign in to comment.