-
Notifications
You must be signed in to change notification settings - Fork 84
api: make Headers and HeadersBuilder case-insensitive #2400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Augustyniak
merged 24 commits into
main
from
andr-make-headers-and-headersbuilder-case-insensitive
Jul 6, 2022
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
1f54fb3
Add HeadersContainer and start using it
Augustyniak 3cfe57b
lint fix
Augustyniak b31d80c
Update Swift names to caseSensitiveHeaders
Augustyniak d370991
make checks for restricted headers case-insensitive
Augustyniak 586eea6
Add tests and documentation
Augustyniak ba0c112
Lint fixes
Augustyniak 89e0de6
Fix example apps
Augustyniak 611cedf
fix java example app
Augustyniak beeeaa7
Merge branch 'main' into andr-make-headers-and-headersbuilder-case-in…
Augustyniak 508cf05
lint fix
Augustyniak 2b6eb7f
fix
Augustyniak 844b83b
fix
Augustyniak dc5e4b8
fix
Augustyniak 032c66d
lint fix
Augustyniak 69af126
lint fix
Augustyniak cab1d9d
lint fix
Augustyniak d7f69d8
add missing doc string
Augustyniak 49c78e9
add missing doc string
Augustyniak 24da302
Add missing @Test annotation
Augustyniak 9e2cbbb
fix removal operation
Augustyniak 363fa4f
add more tests
Augustyniak 0e3f113
add labels
Augustyniak b35c5c8
doc improvements
Augustyniak 592a434
lint fix
Augustyniak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,15 @@ package io.envoyproxy.envoymobile | |
| * See `{Request|Response}HeadersBuilder` for usage. | ||
| */ | ||
| open class HeadersBuilder { | ||
| protected val headers: MutableMap<String, MutableList<String>> | ||
| protected val container: HeadersContainer | ||
|
|
||
| /** | ||
| * Instantiate a new builder, only used by child classes. | ||
| * | ||
| * @param headers: The headers to start with. | ||
| * @param container: The headers container to start with. | ||
| */ | ||
| protected constructor(headers: MutableMap<String, MutableList<String>>) { | ||
| this.headers = headers | ||
| internal constructor(container: HeadersContainer) { | ||
| this.container = container | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -28,7 +28,7 @@ open class HeadersBuilder { | |
| if (isRestrictedHeader(name)) { | ||
| return this | ||
| } | ||
| headers.getOrPut(name) { mutableListOf<String>() }.add(value) | ||
| container.add(name, value) | ||
| return this | ||
| } | ||
|
|
||
|
|
@@ -44,7 +44,7 @@ open class HeadersBuilder { | |
| if (isRestrictedHeader(name)) { | ||
| return this | ||
| } | ||
| headers[name] = value | ||
| container.set(name, value) | ||
| return this | ||
| } | ||
|
|
||
|
|
@@ -59,7 +59,7 @@ open class HeadersBuilder { | |
| if (isRestrictedHeader(name)) { | ||
| return this | ||
| } | ||
| headers.remove(name) | ||
| container.remove(name) | ||
| return this | ||
| } | ||
|
|
||
|
|
@@ -72,10 +72,10 @@ open class HeadersBuilder { | |
| * @return HeadersBuilder, This builder. | ||
| */ | ||
| internal open fun internalSet(name: String, value: MutableList<String>): HeadersBuilder { | ||
| headers[name] = value | ||
| container.set(name, value) | ||
| return this | ||
| } | ||
|
|
||
| private fun isRestrictedHeader(name: String) = name.startsWith(":") || | ||
| name.startsWith("x-envoy-mobile") || name == "host" | ||
| name.startsWith("x-envoy-mobile", ignoreCase = true) || name.equals("host", ignoreCase = true) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, the parameter label helps a lot here. |
||
| } | ||
135 changes: 135 additions & 0 deletions
135
library/kotlin/io/envoyproxy/envoymobile/HeadersContainer.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| package io.envoyproxy.envoymobile | ||
|
|
||
| /** | ||
| * The container that manages the underlying headers map. | ||
| * It maintains the original casing of passed header names. | ||
| * It treats headers names as case-insensitive for the purpose | ||
| * of header lookups and header name conflict resolutions. | ||
| */ | ||
| open class HeadersContainer { | ||
| protected val headers: MutableMap<String, Header> | ||
|
|
||
| /** | ||
| * Represents a header name together with all of its values. | ||
| * It preserves the original casing of the header name. | ||
| */ | ||
| data class Header(val name: String, var value: MutableList<String>) { | ||
| constructor(name: String) : this(name, mutableListOf()) | ||
|
|
||
| fun add(value: List<String>) { | ||
| this.value.addAll(value) | ||
| } | ||
|
|
||
| fun add(value: String) { | ||
| this.value.add(value) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Instantiate a new instance of the receiver using the provided headers map | ||
| * | ||
| * @param headers: The headers to start with. | ||
| */ | ||
| internal constructor(headers: Map<String, MutableList<String>>) { | ||
| var underlyingHeaders = mutableMapOf<String, Header>() | ||
| /** | ||
| * Dictionaries are unordered collections. Process headers with names | ||
| * that are the same when lowercased in an alphabetical order to avoid a situation | ||
| * in which the result of the initialization is non-derministic i.e., we want | ||
| * mapOf("A" to listOf("1"), "a" to listOf("2")) headers to be always converted to | ||
| * mapOf("A" to listOf("1", "2")) and never to mapOf("a" to listOf("2", "1")). | ||
| * | ||
| * If a given header name already exists in the processed headers map, check | ||
| * if the currently processed header name is before the existing header name as | ||
| * determined by an alphabetical order. | ||
| */ | ||
| headers.forEach { | ||
| val lowercased = it.key.lowercase() | ||
| val existing = underlyingHeaders[lowercased] | ||
|
|
||
| if (existing == null) { | ||
| underlyingHeaders[lowercased] = Header(it.key, it.value) | ||
| } else if (existing.name > it.key) { | ||
| underlyingHeaders[lowercased] = Header(it.key, (it.value + existing.value).toMutableList()) | ||
| } else { | ||
| underlyingHeaders[lowercased]?.add(it.value) | ||
| } | ||
| } | ||
|
|
||
| this.headers = underlyingHeaders | ||
| } | ||
|
|
||
| companion object { | ||
| /** | ||
| * Create a new instance of the receiver using a provider headers map. | ||
| * Not implemented as a constructor due to conflicting JVM signatures with | ||
| * other constructors. | ||
| * | ||
| * @param headers: The headers to create the container with. | ||
| */ | ||
| fun create(headers: Map<String, List<String>>) : HeadersContainer { | ||
| return HeadersContainer(headers.mapValues { it.value.toMutableList() }) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Add a value to a header with a given name. | ||
| * | ||
| * @param name The name of the header. For the purpose of headers lookup | ||
| * and header name conflict resolution, the name of the header | ||
| * is considered to be case-insensitive. | ||
| * @param value The value to add. | ||
| */ | ||
| fun add(name: String, value: String) { | ||
| val lowercased = name.lowercase() | ||
| headers[lowercased]?.let { it.add(value) } ?: run { | ||
| headers.put(lowercased, Header(name, mutableListOf(value))) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Set the value of a given header. | ||
| * | ||
| * @param name The name of the header. | ||
| * @param value The value to set the header value to. | ||
| */ | ||
| fun set(name: String, value: List<String>) { | ||
| headers[name.lowercase()] = Header(name, value.toMutableList()) | ||
| } | ||
|
|
||
| /** | ||
| * Remove a given header. | ||
| * | ||
| * @param name The name of the header to remove. | ||
| */ | ||
| fun remove(name: String) { | ||
| headers.remove(name.lowercase()) | ||
| } | ||
|
|
||
| /** | ||
| * Get the value for the provided header name. | ||
| * | ||
| * @param name The case-insensitive header name for which to | ||
| * get the current value. | ||
| * @return The value associated with a given header. | ||
| */ | ||
| fun value(name: String): List<String>? { | ||
| return headers[name.lowercase()]?.value | ||
| } | ||
|
|
||
| /** | ||
| * Accessor for all underlying case-sensitive headers. When possible, | ||
| * use case-insensitive accessors instead. | ||
| * | ||
| * @return The underlying headers. | ||
| */ | ||
| fun caseSensitiveHeaders(): Map<String, List<String>> { | ||
| var caseSensitiveHeaders = mutableMapOf<String, List<String>>() | ||
| headers.forEach { | ||
| caseSensitiveHeaders.put(it.value.name, it.value.value) | ||
| } | ||
|
|
||
| return caseSensitiveHeaders | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.