-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
api: make Headers and HeadersBuilder case-insensitive (#2400)
Description: An Android change that mimics iOS changes from #2383. Make the lookup of headers in HeadersBuilder and Headers case-insensitive and preserve the original casing of headers. Risk Level: Low Testing: Unit Docs Changes: Done Release Notes: Done Fixes: #2390 Signed-off-by: Rafal Augustyniak <[email protected]>
- Loading branch information
1 parent
8434e67
commit 52d6a4f
Showing
33 changed files
with
469 additions
and
134 deletions.
There are no files selected for viewing
This file contains 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 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 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 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 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
135 changes: 135 additions & 0 deletions
135
library/kotlin/io/envoyproxy/envoymobile/HeadersContainer.kt
This file contains 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 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 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 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 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.