Skip to content

Commit 435638a

Browse files
committed
Swift: Port the JS tests to Swift.
1 parent 954b061 commit 435638a

File tree

5 files changed

+294
-241
lines changed

5 files changed

+294
-241
lines changed

swift/ql/test/query-tests/Security/CWE-020/MissingRegexAnchor.expected

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,57 @@
1+
| SemiAnchoredRegex.swift:50:16:50:16 | ^a\|b | Misleading operator precedence. The subexpression '^a' is anchored at the beginning, but the other parts of this regular expression are not |
2+
| SemiAnchoredRegex.swift:53:16:53:16 | ^a\|b\|c | Misleading operator precedence. The subexpression '^a' is anchored at the beginning, but the other parts of this regular expression are not |
3+
| SemiAnchoredRegex.swift:59:16:59:16 | ^a\|(b) | Misleading operator precedence. The subexpression '^a' is anchored at the beginning, but the other parts of this regular expression are not |
4+
| SemiAnchoredRegex.swift:61:16:61:16 | ^(a)\|(b) | Misleading operator precedence. The subexpression '^(a)' is anchored at the beginning, but the other parts of this regular expression are not |
5+
| SemiAnchoredRegex.swift:63:16:63:16 | a\|b$ | Misleading operator precedence. The subexpression 'b$' is anchored at the end, but the other parts of this regular expression are not |
6+
| SemiAnchoredRegex.swift:66:16:66:16 | a\|b\|c$ | Misleading operator precedence. The subexpression 'c$' is anchored at the end, but the other parts of this regular expression are not |
7+
| SemiAnchoredRegex.swift:72:16:72:16 | (a)\|b$ | Misleading operator precedence. The subexpression 'b$' is anchored at the end, but the other parts of this regular expression are not |
8+
| SemiAnchoredRegex.swift:74:16:74:16 | (a)\|(b)$ | Misleading operator precedence. The subexpression '(b)$' is anchored at the end, but the other parts of this regular expression are not |
9+
| SemiAnchoredRegex.swift:76:16:76:16 | ^good.com\|better.com | Misleading operator precedence. The subexpression '^good.com' is anchored at the beginning, but the other parts of this regular expression are not |
10+
| SemiAnchoredRegex.swift:76:16:76:16 | ^good.com\|better.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
11+
| SemiAnchoredRegex.swift:77:16:77:16 | ^good\\.com\|better\\.com | Misleading operator precedence. The subexpression '^good\\.com' is anchored at the beginning, but the other parts of this regular expression are not |
12+
| SemiAnchoredRegex.swift:77:16:77:16 | ^good\\.com\|better\\.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
13+
| SemiAnchoredRegex.swift:78:16:78:16 | ^good\\\\.com\|better\\\\.com | Misleading operator precedence. The subexpression '^good\\\\.com' is anchored at the beginning, but the other parts of this regular expression are not |
14+
| SemiAnchoredRegex.swift:79:16:79:16 | ^good\\\\\\.com\|better\\\\\\.com | Misleading operator precedence. The subexpression '^good\\\\\\.com' is anchored at the beginning, but the other parts of this regular expression are not |
15+
| SemiAnchoredRegex.swift:80:16:80:16 | ^good\\\\\\\\.com\|better\\\\\\\\.com | Misleading operator precedence. The subexpression '^good\\\\\\\\.com' is anchored at the beginning, but the other parts of this regular expression are not |
16+
| SemiAnchoredRegex.swift:82:16:82:16 | ^foo\|bar\|baz$ | Misleading operator precedence. The subexpression '^foo' is anchored at the beginning, but the other parts of this regular expression are not |
17+
| SemiAnchoredRegex.swift:82:16:82:16 | ^foo\|bar\|baz$ | Misleading operator precedence. The subexpression 'baz$' is anchored at the end, but the other parts of this regular expression are not |
18+
| SemiAnchoredRegex.swift:89:16:89:16 | (\\.xxx)\|(\\.yyy)\|(\\.zzz)$ | Misleading operator precedence. The subexpression '(\\.zzz)$' is anchored at the end, but the other parts of this regular expression are not |
19+
| SemiAnchoredRegex.swift:90:16:90:16 | (^left\|right\|center)\\sbottom$ | Misleading operator precedence. The subexpression '^left' is anchored at the beginning, but the other parts of this regular expression are not |
20+
| SemiAnchoredRegex.swift:91:16:91:16 | \\.xxx\|\\.yyy\|\\.zzz$ | Misleading operator precedence. The subexpression '\\.zzz$' is anchored at the end, but the other parts of this regular expression are not |
21+
| SemiAnchoredRegex.swift:92:16:92:16 | \\.xxx\|\\.yyy\|\\.zzz$ | Misleading operator precedence. The subexpression '\\.zzz$' is anchored at the end, but the other parts of this regular expression are not |
22+
| SemiAnchoredRegex.swift:93:16:93:16 | \\.xxx\|\\.yyy\|zzz$ | Misleading operator precedence. The subexpression 'zzz$' is anchored at the end, but the other parts of this regular expression are not |
23+
| SemiAnchoredRegex.swift:94:16:94:16 | ^([A-Z]\|xxx[XY]$) | Misleading operator precedence. The subexpression 'xxx[XY]$' is anchored at the end, but the other parts of this regular expression are not |
24+
| SemiAnchoredRegex.swift:95:16:95:16 | ^(xxx yyy zzz)\|(xxx yyy) | Misleading operator precedence. The subexpression '^(xxx yyy zzz)' is anchored at the beginning, but the other parts of this regular expression are not |
25+
| SemiAnchoredRegex.swift:96:16:96:16 | ^(xxx yyy zzz)\|(xxx yyy)\|(1st( xxx)? yyy)\|xxx\|1st | Misleading operator precedence. The subexpression '^(xxx yyy zzz)' is anchored at the beginning, but the other parts of this regular expression are not |
26+
| SemiAnchoredRegex.swift:97:16:97:16 | ^(xxx:)\|(yyy:)\|(zzz:) | Misleading operator precedence. The subexpression '^(xxx:)' is anchored at the beginning, but the other parts of this regular expression are not |
27+
| SemiAnchoredRegex.swift:98:16:98:16 | ^(xxx?:)\|(yyy:zzz\\/) | Misleading operator precedence. The subexpression '^(xxx?:)' is anchored at the beginning, but the other parts of this regular expression are not |
28+
| SemiAnchoredRegex.swift:99:16:99:16 | ^@media\|@page | Misleading operator precedence. The subexpression '^@media' is anchored at the beginning, but the other parts of this regular expression are not |
29+
| SemiAnchoredRegex.swift:100:16:100:16 | ^\\s*(xxx?\|yyy\|zzz):\|xxx:yyy | Misleading operator precedence. The subexpression '^\\s*(xxx?\|yyy\|zzz):' is anchored at the beginning, but the other parts of this regular expression are not |
30+
| SemiAnchoredRegex.swift:101:16:101:16 | ^click\|mouse\|touch | Misleading operator precedence. The subexpression '^click' is anchored at the beginning, but the other parts of this regular expression are not |
31+
| SemiAnchoredRegex.swift:102:16:102:16 | ^http://good\\.com\|http://better\\.com | Misleading operator precedence. The subexpression '^http://good\\.com' is anchored at the beginning, but the other parts of this regular expression are not |
32+
| SemiAnchoredRegex.swift:102:16:102:16 | ^http://good\\.com\|http://better\\.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
33+
| SemiAnchoredRegex.swift:103:16:103:16 | ^https?://good\\.com\|https?://better\\.com | Misleading operator precedence. The subexpression '^https?://good\\.com' is anchored at the beginning, but the other parts of this regular expression are not |
34+
| SemiAnchoredRegex.swift:103:16:103:16 | ^https?://good\\.com\|https?://better\\.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
35+
| SemiAnchoredRegex.swift:104:16:104:16 | ^mouse\|touch\|click\|contextmenu\|drop\|dragover\|dragend | Misleading operator precedence. The subexpression '^mouse' is anchored at the beginning, but the other parts of this regular expression are not |
36+
| SemiAnchoredRegex.swift:105:16:105:16 | ^xxx:\|yyy: | Misleading operator precedence. The subexpression '^xxx:' is anchored at the beginning, but the other parts of this regular expression are not |
37+
| SemiAnchoredRegex.swift:106:16:106:16 | _xxx\|_yyy\|_zzz$ | Misleading operator precedence. The subexpression '_zzz$' is anchored at the end, but the other parts of this regular expression are not |
38+
| SemiAnchoredRegex.swift:137:36:137:36 | ^a\|b/ | Misleading operator precedence. The subexpression '^a' is anchored at the beginning, but the other parts of this regular expression are not |
39+
| UnanchoredUrlRegex.swift:62:39:62:39 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
40+
| UnanchoredUrlRegex.swift:63:39:63:39 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
41+
| UnanchoredUrlRegex.swift:64:39:64:39 | ^https?://good.com | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
42+
| UnanchoredUrlRegex.swift:65:39:65:39 | (^https?://good1.com)\|(^https?://good2.com) | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
43+
| UnanchoredUrlRegex.swift:66:39:66:39 | (https?://good.com)\|(^https?://goodie.com) | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
44+
| UnanchoredUrlRegex.swift:66:39:66:39 | (https?://good.com)\|(^https?://goodie.com) | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
45+
| UnanchoredUrlRegex.swift:68:39:68:39 | https?:\\/\\/good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
46+
| UnanchoredUrlRegex.swift:69:39:69:39 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
47+
| UnanchoredUrlRegex.swift:71:46:71:46 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
48+
| UnanchoredUrlRegex.swift:78:39:78:39 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
49+
| UnanchoredUrlRegex.swift:79:39:79:39 | https?://good.com:8080 | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
50+
| UnanchoredUrlRegex.swift:82:3:82:3 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
51+
| UnanchoredUrlRegex.swift:83:3:83:3 | https?:\\/\\/good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
52+
| UnanchoredUrlRegex.swift:84:3:84:3 | ^https?://good.com | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
53+
| UnanchoredUrlRegex.swift:95:39:95:39 | https?:\\/\\/good.com\\/([0-9]+) | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
54+
| UnanchoredUrlRegex.swift:101:39:101:39 | example\\.com\|whatever | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
155
| test.swift:56:16:56:16 | ^http://example.com | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
256
| test.swift:59:16:59:16 | ^http://test\\.example.com | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |
357
| test.swift:69:16:69:16 | ^(.+\\.(?:example-a\|example-b)\\.com)/ | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
2+
// --- stubs ---
3+
4+
struct AnyRegexOutput {
5+
}
6+
7+
protocol RegexComponent<RegexOutput> {
8+
associatedtype RegexOutput
9+
}
10+
11+
struct Regex<Output> : RegexComponent {
12+
struct Match {
13+
}
14+
15+
init(_ pattern: String) throws where Output == AnyRegexOutput { }
16+
17+
func ignoresCase(_ ignoresCase: Bool = true) -> Regex<Regex<Output>.RegexOutput> { return self }
18+
19+
func firstMatch(in string: String) throws -> Regex<Output>.Match? { return nil }
20+
21+
typealias RegexOutput = Output
22+
}
23+
24+
extension StringProtocol {
25+
func replacingOccurrences<Target, Replacement>(of target: Target, with replacement: Replacement, options: String.CompareOptions = [], range searchRange: Range<Self.Index>? = nil) -> String where Target : StringProtocol, Replacement : StringProtocol { return "" }
26+
}
27+
28+
extension String : RegexComponent {
29+
typealias CompareOptions = NSString.CompareOptions
30+
typealias Output = Substring
31+
typealias RegexOutput = String.Output
32+
}
33+
34+
class NSObject {
35+
}
36+
37+
class NSString : NSObject {
38+
struct CompareOptions : OptionSet {
39+
var rawValue: UInt
40+
41+
static var regularExpression: NSString.CompareOptions { get { return CompareOptions(rawValue: 1) } }
42+
static var caseInsensitive: NSString.CompareOptions { get { return CompareOptions(rawValue: 2) } }
43+
}
44+
}
45+
46+
// --- tests ---
47+
48+
func tests(input: String) throws {
49+
_ = try Regex("^a|").firstMatch(in: input)
50+
_ = try Regex("^a|b").firstMatch(in: input) // BAD (missing anchor)
51+
_ = try Regex("a|^b").firstMatch(in: input)
52+
_ = try Regex("^a|^b").firstMatch(in: input)
53+
_ = try Regex("^a|b|c").firstMatch(in: input) // BAD (missing anchor)
54+
_ = try Regex("a|^b|c").firstMatch(in: input)
55+
_ = try Regex("a|b|^c").firstMatch(in: input)
56+
_ = try Regex("^a|^b|c").firstMatch(in: input)
57+
58+
_ = try Regex("(^a)|b").firstMatch(in: input)
59+
_ = try Regex("^a|(b)").firstMatch(in: input) // BAD (missing anchor)
60+
_ = try Regex("^a|(^b)").firstMatch(in: input)
61+
_ = try Regex("^(a)|(b)").firstMatch(in: input) // BAD (missing anchor)
62+
63+
_ = try Regex("a|b$").firstMatch(in: input) // BAD (missing anchor)
64+
_ = try Regex("a$|b").firstMatch(in: input)
65+
_ = try Regex("a$|b$").firstMatch(in: input)
66+
_ = try Regex("a|b|c$").firstMatch(in: input) // BAD (missing anchor)
67+
_ = try Regex("a|b$|c").firstMatch(in: input)
68+
_ = try Regex("a$|b|c").firstMatch(in: input)
69+
_ = try Regex("a|b$|c$").firstMatch(in: input)
70+
71+
_ = try Regex("a|(b$)").firstMatch(in: input)
72+
_ = try Regex("(a)|b$").firstMatch(in: input) // BAD (missing anchor)
73+
_ = try Regex("(a$)|b$").firstMatch(in: input)
74+
_ = try Regex("(a)|(b)$").firstMatch(in: input) // BAD (missing anchor)
75+
76+
_ = try Regex(#"^good.com|better.com"#).firstMatch(in: input) // BAD (missing anchor)
77+
_ = try Regex(#"^good\.com|better\.com"#).firstMatch(in: input) // BAD (missing anchor)
78+
_ = try Regex(#"^good\\.com|better\\.com"#).firstMatch(in: input) // BAD (missing anchor)
79+
_ = try Regex(#"^good\\\.com|better\\\.com"#).firstMatch(in: input) // BAD (missing anchor)
80+
_ = try Regex(#"^good\\\\.com|better\\\\.com"#).firstMatch(in: input) // BAD (missing anchor)
81+
82+
_ = try Regex("^foo|bar|baz$").firstMatch(in: input) // BAD (missing anchor)
83+
_ = try Regex("^foo|%").firstMatch(in: input)
84+
}
85+
86+
func realWorld(input: String) throws {
87+
// real-world examples that have been anonymized a bit
88+
// the following are bad:
89+
_ = try Regex(#"(\.xxx)|(\.yyy)|(\.zzz)$"#).firstMatch(in: input) // BAD (missing anchor)
90+
_ = try Regex(#"(^left|right|center)\sbottom$"#).firstMatch(in: input) // BAD (missing anchor)
91+
_ = try Regex(#"\.xxx|\.yyy|\.zzz$"#).ignoresCase().firstMatch(in: input) // BAD (missing anchor)
92+
_ = try Regex(#"\.xxx|\.yyy|\.zzz$"#).ignoresCase().firstMatch(in: input) // BAD (missing anchor)
93+
_ = try Regex(#"\.xxx|\.yyy|zzz$"#).firstMatch(in: input) // BAD (missing anchor)
94+
_ = try Regex(#"^([A-Z]|xxx[XY]$)"#).firstMatch(in: input) // BAD (missing anchor)
95+
_ = try Regex(#"^(xxx yyy zzz)|(xxx yyy)"#).ignoresCase().firstMatch(in: input) // BAD (missing anchor)
96+
_ = try Regex(#"^(xxx yyy zzz)|(xxx yyy)|(1st( xxx)? yyy)|xxx|1st"#).ignoresCase().firstMatch(in: input) // BAD (missing anchor)
97+
_ = try Regex(#"^(xxx:)|(yyy:)|(zzz:)"#).firstMatch(in: input) // BAD (missing anchor)
98+
_ = try Regex(#"^(xxx?:)|(yyy:zzz\/)"#).firstMatch(in: input) // BAD (missing anchor)
99+
_ = try Regex(#"^@media|@page"#).firstMatch(in: input) // BAD (missing anchor)
100+
_ = try Regex(#"^\s*(xxx?|yyy|zzz):|xxx:yyy"#).firstMatch(in: input) // BAD (missing anchor)
101+
_ = try Regex(#"^click|mouse|touch"#).firstMatch(in: input) // BAD (missing anchor)
102+
_ = try Regex(#"^http://good\.com|http://better\.com"#).firstMatch(in: input) // BAD (missing anchor)
103+
_ = try Regex(#"^https?://good\.com|https?://better\.com"#).firstMatch(in: input) // BAD (missing anchor)
104+
_ = try Regex(#"^mouse|touch|click|contextmenu|drop|dragover|dragend"#).firstMatch(in: input) // BAD (missing anchor)
105+
_ = try Regex(#"^xxx:|yyy:"#).ignoresCase().firstMatch(in: input) // BAD (missing anchor)
106+
_ = try Regex(#"_xxx|_yyy|_zzz$"#).firstMatch(in: input) // BAD (missing anchor)
107+
_ = try Regex(#"em|%$"#).firstMatch(in: input) // BAD (missing anchor) [NOT DETECTED] - not flagged at the moment due to the anchor not being for letters
108+
109+
// the following are MAYBE OK due to apparent complexity; not flagged
110+
_ = try Regex(#"(?:^[#?]?|&)([^=&]+)(?:=([^&]*))?"#).firstMatch(in: input)
111+
_ = try Regex(#"(?m)(^\s*|;\s*)\*.*;"#).firstMatch(in: input)
112+
_ = try Regex(#"(?m)(^\s*|\[)(?:xxx|yyy_(?:xxx|yyy)|xxx|yyy(?:xxx|yyy)?|xxx|yyy)\b"#).firstMatch(in: input)
113+
_ = try Regex(#"\s\S| \t|\t |\s$"#).firstMatch(in: input)
114+
_ = try Regex(#"\{[^}{]*\{|\}[^}{]*\}|\{[^}]*$"#).firstMatch(in: input)
115+
_ = try Regex(#"^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)"#).firstMatch(in: input)
116+
_ = try Regex(#"^(\/\/)|([a-z]+:(\/\/)?)"#).firstMatch(in: input)
117+
_ = try Regex(#"^[=?!#%@$]|!(?=[:}])"#).firstMatch(in: input)
118+
_ = try Regex(#"^[\[\]!:]|[<>]"#).firstMatch(in: input)
119+
_ = try Regex(#"^for\b|\b(?:xxx|yyy)\b/"#).ignoresCase().firstMatch(in: input)
120+
_ = try Regex(#"^if\b|\b(?:xxx|yyy|zzz)\b/"#).ignoresCase().firstMatch(in: input)
121+
122+
// the following are OK:
123+
_ = try Regex(#"$^|only-match"#).firstMatch(in: input)
124+
_ = try Regex(#"(#.+)|#$"#).firstMatch(in: input)
125+
_ = try Regex(#"(NaN| {2}|^$)"#).firstMatch(in: input)
126+
_ = try Regex(#"[^\n]*(?:\n|[^\n]$)"#).firstMatch(in: input)
127+
_ = try Regex(#"^$|\/(?:xxx|yyy)zzz"#).ignoresCase().firstMatch(in: input)
128+
_ = try Regex(#"^(\/|(xxx|yyy|zzz)$)"#).firstMatch(in: input)
129+
_ = try Regex(#"^9$|27"#).firstMatch(in: input)
130+
_ = try Regex(#"^\+|\s*"#).firstMatch(in: input)
131+
_ = try Regex(#"xxx_yyy=\w+|^$"#).firstMatch(in: input)
132+
_ = try Regex(#"^(?:mouse|contextmenu)|click"#).firstMatch(in: input)
133+
}
134+
135+
func replaceTest(x: String) -> String {
136+
// OK - possibly replacing too much, but not obviously a problem
137+
return x.replacingOccurrences(of: #"^a|b/"#, with: "", options: .regularExpression) // [FALSE POSITIVE]
138+
}

0 commit comments

Comments
 (0)