Skip to content

Commit e8ea9f6

Browse files
committed
Add strict HTTP header validation to EvaluatorSettings
1 parent fc46610 commit e8ea9f6

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

pkl-core/src/main/java/org/pkl/core/evaluatorSettings/PklEvaluatorSettings.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,12 @@ public record Http(
167167
var pairs = entry.getValue();
168168
for (var pair : pairs) {
169169
if (!HEADER_NAME_REGEX.matcher(pair.getFirst()).matches()) {
170-
throw new PklException(
171-
ErrorMessages.create("invalidHeaderName", pair.getFirst()));
170+
throw new PklException(ErrorMessages.create("invalidHeaderName", pair.getFirst()));
172171
}
173172
if (!HEADER_VALUE_REGEX.matcher(pair.getSecond()).matches()) {
174173
throw new PklException(
175-
ErrorMessages.create("invalidHeaderValue", pair.getSecond()));
176-
}
174+
ErrorMessages.create("invalidHeaderValue", pair.getSecond()));
175+
}
177176
}
178177
try {
179178
parsedHeaders.put(new URI(uri), pairs);

stdlib/EvaluatorSettings.pkl

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class Http {
175175

176176
/// HTTP headers to add to outbound requests targeting specified URLs.
177177
@Since { version = "0.30.0" }
178-
headers: Mapping<HttpPrefix, Listing<Pair<String, String>>>?
178+
headers: Mapping<HttpPrefix, Mapping<HttpHeaderName, Listing<HttpHeaderValue>>>?
179179
}
180180

181181
/// Settings that control how Pkl talks to HTTP proxies.
@@ -242,3 +242,55 @@ class ExternalReader {
242242
/// Additional command line arguments passed to the external reader process.
243243
arguments: Listing<String>?
244244
}
245+
246+
typealias ReservedHttpHeaderName =
247+
"accept-charset"
248+
| "accept-encoding"
249+
| "access-control-request-headers"
250+
| "access-control-request-method"
251+
| "connection"
252+
| "content-length"
253+
| "cookie"
254+
| "date"
255+
| "dnt"
256+
| "expect"
257+
| "host"
258+
| "keep-alive"
259+
| "origin"
260+
| "permissions-policy"
261+
| "referer"
262+
| "te"
263+
| "trailer"
264+
| "transfer-encoding"
265+
| "upgrade"
266+
| "via"
267+
268+
const local ReservedHttpHeaderPrefix = new Listing {
269+
"proxy-"
270+
"sec-"
271+
"access-control-"
272+
}
273+
274+
const local hasReservedHttpHeaderPrefix = (header: String) ->
275+
ReservedHttpHeaderPrefix.any((it) -> header.startsWith(it))
276+
277+
const local httpHeaderNameRegex = Regex("^[a-zA-Z0-9!#\\$%&'*+-.^_`|~]+$")
278+
const local hasValidHttpHeaderName = (header: String) ->
279+
httpHeaderNameRegex.findMatchesIn(header)
280+
281+
@Since {version = "0.30.0" }
282+
typealias HttpHeaderName = String(
283+
this == toLowerCase(),
284+
!(this is ReservedHttpHeaderName),
285+
!hasReservedHttpHeaderPrefix.apply(this),
286+
hasValidHttpHeaderName
287+
)
288+
289+
const local httpHeaderValueRegex = Regex("^[\\t\\u0020-\\u007E\\u0080-\\u00FF]*$")
290+
const local hasValidHttpHeaderValue = (value : String) ->
291+
httpHeaderValueRegex.findMatchesIn(value)
292+
293+
@Since {version = "0.30.0"}
294+
typealias HttpHeaderValue = String(
295+
hasValidHttpHeaderValue
296+
)

0 commit comments

Comments
 (0)