diff --git a/Sources/Basics/Netrc.swift b/Sources/Basics/Netrc.swift index fdfe4db25e0..0d946d02854 100644 --- a/Sources/Basics/Netrc.swift +++ b/Sources/Basics/Netrc.swift @@ -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 } @@ -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))"# static let accountOptional: String = #"(?:\s*account\s+\S++)?"# static let loginPassword: String = @@ -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+))"# } } diff --git a/Tests/BasicsTests/NetrcTests.swift b/Tests/BasicsTests/NetrcTests.swift index d9cde729ce2..72e69bcbd29 100644 --- a/Tests/BasicsTests/NetrcTests.swift +++ b/Tests/BasicsTests/NetrcTests.swift @@ -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") + } +}