Skip to content

Commit 41942f2

Browse files
Prevent accessing unicode scalar element with incorrect index.
This change prevents a potential DocC crash by making sure that it does not access an array element using an index that is outside of the bonds of the array. `.index(after:` can crash in some cases if the passed index belongs to the last element of the collection. with this change we ensure that the passed index is not the last index.
1 parent 94eaaa3 commit 41942f2

File tree

2 files changed

+14
-2
lines changed

2 files changed

+14
-2
lines changed

Sources/SwiftDocC/Utility/ValidatedURL.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,13 @@ private extension StringProtocol {
178178
/// Returns a percent encoded version of the string or the original string if it is already percent encoded.
179179
func addingPercentEncodingIfNeeded(withAllowedCharacters allowedCharacters: CharacterSet) -> String? {
180180
var needsPercentEncoding: Bool {
181-
for (index, character) in unicodeScalars.indexed() where !allowedCharacters.contains(character) {
181+
let unicodeScalars = unicodeScalars.filter { !allowedCharacters.contains($0) }
182+
for (index, character) in unicodeScalars.indexed() {
182183
if character == "%" {
184+
// There's not two characters after the "%". This "%" can't represent a percent encoded character.
185+
guard index != unicodeScalars.endIndex || index != unicodeScalars.index(before: index) else {
186+
return true
187+
}
183188
// % isn't allowed in a URL fragment but it is also the escape character for percent encoding.
184189
let firstFollowingIndex = unicodeScalars.index(after: index)
185190
let secondFollowingIndex = unicodeScalars.index(after: firstFollowingIndex)
@@ -188,7 +193,7 @@ private extension StringProtocol {
188193
// There's not two characters after the "%". This "%" can't represent a percent encoded character.
189194
return true
190195
}
191-
// If either of the two following characters aren't hex digits, the "%" doesn't represent a
196+
// If either of the two following characters aren't hex digits, the "%" doesn't represent a percent encoded character.
192197
return !Character(unicodeScalars[firstFollowingIndex]).isHexDigit
193198
|| !Character(unicodeScalars[secondFollowingIndex]).isHexDigit
194199

Tests/SwiftDocCTests/Utility/ValidatedURLTests.swift

+7
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ class ValidatedURLTests: XCTestCase {
120120
XCTAssertEqual(validatedWithHeading.components.path, expectedPath)
121121
XCTAssertEqual(validatedWithHeading.components.fragment, "Heading-Name")
122122
}
123+
124+
for linkText in [
125+
"url://com.example.test/new/docc=Whats%20New&version=DocC&Title=[Update]"
126+
] {
127+
let validated = try XCTUnwrap(ValidatedURL(parsingAuthoredLink: linkText), "Failed to parse \(linkText.singleQuoted) as authored link")
128+
XCTAssertEqual(validated.components.path.split(separator: "/").last!, "docc=Whats%20New&version=DocC&Title=[Update]")
129+
}
123130
}
124131

125132
func testEscapedFragment() throws {

0 commit comments

Comments
 (0)