Skip to content

Commit b9dcb08

Browse files
committed
chore: streamline http/common header merge, media-type guards, and ETag predicate
Behavior-preserving cleanups in the http.common package: - Headers.addAll now iterates the source's backing map directly instead of building a fully defensive entries() snapshot that is discarded after one pass. - MediaType.toString reuses the fullType getter rather than rebuilding "type/subtype" inline twice. - The wildcard-type guard in MediaType.of/parse is rewritten without the double negative, reading directly as the documented invariant. - The quoted-value unescape loop in MediaType.parse is lifted into a named unescapeQuotedValue helper, the inverse of formatParameterValue. - ETag.parse drops the redundant !startsWith("W/") term, which is always true once the strong-form test already requires a leading quote.
1 parent bab885b commit b9dcb08

3 files changed

Lines changed: 35 additions & 24 deletions

File tree

sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/common/ETag.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ public value class ETag private constructor(private val raw: String) {
8989
trimmed.length >= WEAK_FORM_MIN_LEN &&
9090
trimmed.startsWith("W/\"") &&
9191
trimmed.endsWith("\"")
92+
// A strong-form value must open with `"`, which a `W/`-prefixed weak form never
93+
// does, so no explicit weak-prefix exclusion is needed here.
9294
val isStrongForm =
9395
trimmed.length >= 2 &&
94-
!trimmed.startsWith("W/") && trimmed.startsWith("\"") && trimmed.endsWith("\"")
96+
trimmed.startsWith("\"") && trimmed.endsWith("\"")
9597
require(isWeakForm || isStrongForm) { "malformed ETag: $raw" }
9698
return ETag(trimmed)
9799
}

sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/common/Headers.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ public data class Headers private constructor(
277277
*/
278278
public fun addAll(headers: Headers): Builder =
279279
apply {
280-
headers.entries().forEach { (key, values) ->
280+
// Read the backing map directly (as the copy constructor above does) rather
281+
// than entries(), whose defensively-copied snapshot would be discarded after
282+
// this single iteration. Keys are already canonical, so merging is unchanged.
283+
headers.headersMap.forEach { (key, values) ->
281284
headersMap.computeIfAbsent(key) { mutableListOf() }.addAll(values)
282285
}
283286
}

sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/common/MediaType.kt

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public data class MediaType private constructor(
8989
parameters.entries.joinToString(separator = ";") { (key, value) ->
9090
"$key=${formatParameterValue(value)}"
9191
}
92-
return if (formattedParams.isEmpty()) "$type/$subtype" else "$type/$subtype;$formattedParams"
92+
return if (formattedParams.isEmpty()) fullType else "$fullType;$formattedParams"
9393
}
9494

9595
/**
@@ -142,7 +142,7 @@ public data class MediaType private constructor(
142142
): MediaType {
143143
require(type.isNotBlank()) { "Type must not be blank" }
144144
require(subtype.isNotBlank()) { "Subtype must not be blank" }
145-
require(!(type == "*" && subtype != "*")) {
145+
require(type != "*" || subtype == "*") {
146146
"Invalid media type format: type=$type, subtype=$subtype"
147147
}
148148

@@ -179,7 +179,7 @@ public data class MediaType private constructor(
179179
val type = mimeString.substring(0, slashIndex).trim().lowercase(Locale.US)
180180
val subtype = mimeString.substring(slashIndex + 1).trim().lowercase(Locale.US)
181181

182-
require(!(type == "*" && subtype != "*")) {
182+
require(type != "*" || subtype == "*") {
183183
"Invalid media type format: $mediaType"
184184
}
185185

@@ -201,27 +201,9 @@ public data class MediaType private constructor(
201201
}
202202
// RFC 7230 §3.2.6: strip surrounding double-quotes and unescape
203203
// quoted-pair sequences (`\"` → `"`, `\\` → `\`).
204-
val value =
205-
if (rawValue.startsWith("\"") && rawValue.endsWith("\"") && rawValue.length >= 2) {
206-
val inner = rawValue.substring(1, rawValue.length - 1)
207-
val sb = StringBuilder(inner.length)
208-
var i = 0
209-
while (i < inner.length) {
210-
if (inner[i] == '\\' && i + 1 < inner.length) {
211-
sb.append(inner[i + 1])
212-
i += 2
213-
} else {
214-
sb.append(inner[i])
215-
i++
216-
}
217-
}
218-
sb.toString()
219-
} else {
220-
rawValue
221-
}
222204
// Keys are lower-cased for case-insensitive lookup; values are preserved
223205
// because boundaries, base64 tokens, etc. are case-sensitive.
224-
key.lowercase(Locale.US) to value
206+
key.lowercase(Locale.US) to unescapeQuotedValue(rawValue)
225207
}
226208

227209
return MediaType(type, subtype, parametersMap)
@@ -265,5 +247,29 @@ public data class MediaType private constructor(
265247
result.add(current.toString())
266248
return result
267249
}
250+
251+
/**
252+
* Strips the surrounding double-quotes from a parameter [rawValue] and unescapes its
253+
* quoted-pair sequences (`\"` → `"`, `\\` → `\`), per RFC 7230 §3.2.6. A value that is
254+
* not a `quoted-string` is returned unchanged. Inverse of [formatParameterValue].
255+
*/
256+
private fun unescapeQuotedValue(rawValue: String): String {
257+
if (!(rawValue.startsWith("\"") && rawValue.endsWith("\"") && rawValue.length >= 2)) {
258+
return rawValue
259+
}
260+
val inner = rawValue.substring(1, rawValue.length - 1)
261+
val sb = StringBuilder(inner.length)
262+
var i = 0
263+
while (i < inner.length) {
264+
if (inner[i] == '\\' && i + 1 < inner.length) {
265+
sb.append(inner[i + 1])
266+
i += 2
267+
} else {
268+
sb.append(inner[i])
269+
i++
270+
}
271+
}
272+
return sb.toString()
273+
}
268274
}
269275
}

0 commit comments

Comments
 (0)