http-interchange 0.3.2.1 → 0.3.2.2
raw patch · 5 files changed
+156/−6 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +4/−0
- golden/response/003.input +19/−0
- golden/response/003.output +59/−0
- http-interchange.cabal +1/−1
- src/Http/Header.hs +73/−5
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for http-interchange +## 0.3.2.2 -- 2024-08-23++* Allow all RFC 7230 tchar in header name+ ## 0.3.2.1 -- 2024-02-07 * List test suite file dependencies in `extra-source-files` so that Hackage
+ golden/response/003.input view
@@ -0,0 +1,19 @@+HTTP/1.1 200 OK +Content-Length: 0 +Access-Control-Allow-Origin: * +Access-Control-Allow-Credentials: true +Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT +Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT +Access-Control-Allow-Headers: Authorization,Content-Type,Content-Encoding,Accept,X-Netflix.application.name,X-Netflix.application.version,X-Netflix.esn,X-Netflix.device.type,X-Netflix.certification.version,X-Netflix.request.uuid,X-Netflix.originating.request.uuid,X-Netflix.user.id,X-Netflix.oauth.consumer.key,X-Netflix.oauth.token,X-Netflix.ichnaea.request.type,X-Netflix.Request.Routing,X-NETFLIX-PREAPP-PARTNER-ID,X-NETFLIX-PREAPP-INTEGRITY-VALUE,X-Netflix.Request.Priority,X-Netflix.Retry.Client.Policy,X-Netflix.Client.Request.Name,X-Netflix.Request.Retry.Policy,X-Netflix.Request.Retry.Policy.Default,X-Netflix.request.client.user.guid,X-Netflix.Request.NonJson.Headers,X-Netflix.esnPrefix,X-Netflix.browserName,X-Netflix.browserVersion,X-Netflix.osName,X-Netflix.osVersion,X-Netflix.uiVersion,X-Netflix.clientType,X-NETFLIX-PERSONALIZATION-ID,X-NETFLIX-DET-TOKEN,X-NETFLIX-DET-PARTNER-PAI,X-NETFLIX-RESPONSE-OVERRIDDEN,X-NETFLIX-DET-DEPRECATION,X-Netflix.context.locales,X-Netflix.context.ui-flavor,X-Netflix.context.ale.token,X-Netflix.context.app-version,X-Netflix.context.schema-variant,X-Netflix.Request.toplevel.uuid,f9hi42t,x-session,X-Netflix.Request.Attempt +Access-Control-Expose-Headers: X-Netflix.Retry.Server.Policy,X-Netflix.Response.Tag,X-Netflix.Geo.Info,X-Netflix.request.inbound.identity.changed,Via,X-Netflix.Retry.Server.Policy.retryAfterSeconds,X-Netflix.Retry.Server.Policy.maxRetries,X-Ftl-Error,X-Netflix.uiVersion,X-Netflix.Playapi.Backoff +Via: 1.1 i-08d45052140120818 (us-east-2) +X-Xss-Protection: 1; mode=block; report=https://www.netflix.com/ichnaea/log/freeform/xssreport +X-Content-Type-Options: nosniff +Strict-Transport-Security: max-age=31536000; includeSubDomains +X-Originating-URL: http://www.netflix.com/ +Set-Cookie: nfvdid=BQFmAAEBEFU_lDUnKbaHRi5tl0Mhqc5ALJIuWadL1PlbBLQGl0n-1n-GCB41ux6moG0JMBj22dNaLsaXeB3DmLHrc6SUwOP43peCxZ-9EQNtFxz7gqTC0w%3D%3D; Domain=.netflix.com; Path=/; Max-Age=31536000 +Set-Cookie: SecureNetflixId=v%3D3%26mac%3DAQEAEQABABRXxJiCXjMlnNAESuzV2TyKuP3eS3p8YJc.%26dt%3D1724424807026; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Strict +Set-Cookie: NetflixId=v%3D3%26ct%3DBgjHlOvcAxK_AQ-lxAypPWxfZpzrloHvpi11GUHAmWl-qgv5VMtuoq9QRvkhHh1zrdLY_ALqrfLQh6YFvx1vjTj6Bwwaxa3_5dZMTkdMBJ9jBUR2nqz-uANNz8MLx210xS7_tyfEk8bTLNGPLXfUnE-XsqKT-Lt5x6A9VZzCrjsHtWOPObAA8ast0yC-FfMJT-1cxBnBDBd9LQeFysgRrRi3VjA6bb-KXqABXABbuXKS0iY1e8wgqs_hv4cvHBciaFkIuDO1GH4vGAYiDgoMorBJMaCVqFf0KRMy; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Lax +X-Netflix.nfstatus: 1_1 +X-Netflix.proxy.execution-time: 61 +
+ golden/response/003.output view
@@ -0,0 +1,59 @@+Response+ { statusLine =+ StatusLine { statusCode = 200 , statusReason = "OK" }+ , headers =+ [ Header { name = "Content-Length" , value = "0" }+ , Header { name = "Access-Control-Allow-Origin" , value = "*" }+ , Header+ { name = "Access-Control-Allow-Credentials" , value = "true" }+ , Header+ { name = "Allow"+ , value = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT"+ }+ , Header+ { name = "Access-Control-Allow-Methods"+ , value = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT"+ }+ , Header+ { name = "Access-Control-Allow-Headers"+ , value =+ "Authorization,Content-Type,Content-Encoding,Accept,X-Netflix.application.name,X-Netflix.application.version,X-Netflix.esn,X-Netflix.device.type,X-Netflix.certification.version,X-Netflix.request.uuid,X-Netflix.originating.request.uuid,X-Netflix.user.id,X-Netflix.oauth.consumer.key,X-Netflix.oauth.token,X-Netflix.ichnaea.request.type,X-Netflix.Request.Routing,X-NETFLIX-PREAPP-PARTNER-ID,X-NETFLIX-PREAPP-INTEGRITY-VALUE,X-Netflix.Request.Priority,X-Netflix.Retry.Client.Policy,X-Netflix.Client.Request.Name,X-Netflix.Request.Retry.Policy,X-Netflix.Request.Retry.Policy.Default,X-Netflix.request.client.user.guid,X-Netflix.Request.NonJson.Headers,X-Netflix.esnPrefix,X-Netflix.browserName,X-Netflix.browserVersion,X-Netflix.osName,X-Netflix.osVersion,X-Netflix.uiVersion,X-Netflix.clientType,X-NETFLIX-PERSONALIZATION-ID,X-NETFLIX-DET-TOKEN,X-NETFLIX-DET-PARTNER-PAI,X-NETFLIX-RESPONSE-OVERRIDDEN,X-NETFLIX-DET-DEPRECATION,X-Netflix.context.locales,X-Netflix.context.ui-flavor,X-Netflix.context.ale.token,X-Netflix.context.app-version,X-Netflix.context.schema-variant,X-Netflix.Request.toplevel.uuid,f9hi42t,x-session,X-Netflix.Request.Attempt"+ }+ , Header+ { name = "Access-Control-Expose-Headers"+ , value =+ "X-Netflix.Retry.Server.Policy,X-Netflix.Response.Tag,X-Netflix.Geo.Info,X-Netflix.request.inbound.identity.changed,Via,X-Netflix.Retry.Server.Policy.retryAfterSeconds,X-Netflix.Retry.Server.Policy.maxRetries,X-Ftl-Error,X-Netflix.uiVersion,X-Netflix.Playapi.Backoff"+ }+ , Header+ { name = "Via" , value = "1.1 i-08d45052140120818 (us-east-2)" }+ , Header+ { name = "X-Xss-Protection"+ , value =+ "1; mode=block; report=https://www.netflix.com/ichnaea/log/freeform/xssreport"+ }+ , Header { name = "X-Content-Type-Options" , value = "nosniff" }+ , Header+ { name = "Strict-Transport-Security"+ , value = "max-age=31536000; includeSubDomains"+ }+ , Header+ { name = "X-Originating-URL" , value = "http://www.netflix.com/" }+ , Header+ { name = "Set-Cookie"+ , value =+ "nfvdid=BQFmAAEBEFU_lDUnKbaHRi5tl0Mhqc5ALJIuWadL1PlbBLQGl0n-1n-GCB41ux6moG0JMBj22dNaLsaXeB3DmLHrc6SUwOP43peCxZ-9EQNtFxz7gqTC0w%3D%3D; Domain=.netflix.com; Path=/; Max-Age=31536000"+ }+ , Header+ { name = "Set-Cookie"+ , value =+ "SecureNetflixId=v%3D3%26mac%3DAQEAEQABABRXxJiCXjMlnNAESuzV2TyKuP3eS3p8YJc.%26dt%3D1724424807026; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Strict"+ }+ , Header+ { name = "Set-Cookie"+ , value =+ "NetflixId=v%3D3%26ct%3DBgjHlOvcAxK_AQ-lxAypPWxfZpzrloHvpi11GUHAmWl-qgv5VMtuoq9QRvkhHh1zrdLY_ALqrfLQh6YFvx1vjTj6Bwwaxa3_5dZMTkdMBJ9jBUR2nqz-uANNz8MLx210xS7_tyfEk8bTLNGPLXfUnE-XsqKT-Lt5x6A9VZzCrjsHtWOPObAA8ast0yC-FfMJT-1cxBnBDBd9LQeFysgRrRi3VjA6bb-KXqABXABbuXKS0iY1e8wgqs_hv4cvHBciaFkIuDO1GH4vGAYiDgoMorBJMaCVqFf0KRMy; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Lax"+ }+ , Header { name = "X-Netflix.nfstatus" , value = "1_1" }+ , Header { name = "X-Netflix.proxy.execution-time" , value = "61" }+ ]+ }
http-interchange.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: http-interchange-version: 0.3.2.1+version: 0.3.2.2 license: BSD-3-Clause license-file: LICENSE author: Andrew Martin
src/Http/Header.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PatternSynonyms #-} module Http.Header ( Header (..)@@ -15,6 +16,7 @@ import Data.Bytes.Types (Bytes (Bytes)) import Data.Primitive (ByteArray (ByteArray), SmallArray, SmallMutableArray) import Data.Text (Text)+import Data.Word (Word8) import Data.Bytes qualified as Bytes import Data.Bytes.Builder qualified as Builder@@ -74,9 +76,62 @@ parserHeaderStep (ix + 1) (n - 1) dst else Parser.fail () +pattern Bang :: Word8+pattern Bang = 0x21++pattern Pound :: Word8+pattern Pound = 0x23++pattern Dollar :: Word8+pattern Dollar = 0x24++pattern Percent :: Word8+pattern Percent = 0x25++pattern Ampersand :: Word8+pattern Ampersand = 0x26++pattern SingleQuote :: Word8+pattern SingleQuote = 0x27++pattern Asterisk :: Word8+pattern Asterisk = 0x2A++pattern Plus :: Word8+pattern Plus = 0x2B++pattern Hyphen :: Word8+pattern Hyphen = 0x2D++pattern Period :: Word8+pattern Period = 0x2E++pattern Caret :: Word8+pattern Caret = 0x5E++pattern Underscore :: Word8+pattern Underscore = 0x5F++pattern Backtick :: Word8+pattern Backtick = 0x60++pattern Pipe :: Word8+pattern Pipe = 0x7C++pattern Twiddle :: Word8+pattern Twiddle = 0x7E++pattern HorizontalTab :: Word8+pattern HorizontalTab = 0x09+ {- | Parse a single HTTP header including the trailing CRLF sequence. From RFC 7230: +> token = 1*tchar+> tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"+> / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +> / DIGIT / ALPHA+> > header-field = field-name ":" OWS field-value OWS > field-name = token > field-value = *( field-content / obs-fold )@@ -85,19 +140,32 @@ -} parser :: Parser () s Header parser = do- -- Header name may contain: a-z, A-Z, 0-9, underscore, hyphen+ -- Header name may contain: a-z, A-Z, 0-9, several different symbols !name <- Parser.takeWhile $ \c -> (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A) || (c >= 0x30 && c <= 0x39)- || c == 0x2D- || c == 0x5F+ || c == Bang+ || c == Pound+ || c == Dollar+ || c == Percent+ || c == Ampersand+ || c == SingleQuote+ || c == Asterisk+ || c == Plus+ || c == Hyphen+ || c == Period+ || c == Caret+ || c == Underscore+ || c == Backtick+ || c == Pipe+ || c == Twiddle Latin.char () ':' Latin.skipWhile (\c -> c == ' ' || c == '\t')- -- Header name allows vchar, space, and tab.+ -- Header value allows vchar, space, and tab. value0 <- Parser.takeWhile $ \c -> (c >= 0x20 && c <= 0x7e)- || (c == 0x09)+ || (c == HorizontalTab) Latin.char2 () '\r' '\n' -- We only need to trim the end because the leading spaces and tab -- were already skipped.