Fix HTTP/REST clients HTTP Content-Type header parsers. (#4139)

* Fix client HTTP content-type parsers.

* Fix tests.

* Address review comment and apply wildcard checks for generic decodeBytes.
This commit is contained in:
Eugene Kabanov 2022-09-19 12:17:29 +03:00 committed by GitHub
parent 9999362b11
commit ca871a5435
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 109 deletions

View File

@ -70,6 +70,7 @@ const
ApplicationJsonMediaType* = MediaType.init("application/json") ApplicationJsonMediaType* = MediaType.init("application/json")
TextPlainMediaType* = MediaType.init("text/plain") TextPlainMediaType* = MediaType.init("text/plain")
OctetStreamMediaType* = MediaType.init("application/octet-stream")
UrlEncodedMediaType* = MediaType.init("application/x-www-form-urlencoded") UrlEncodedMediaType* = MediaType.init("application/x-www-form-urlencoded")
type type
@ -2376,10 +2377,21 @@ proc encodeBytes*[T: EncodeArrays](value: T,
else: else:
err("Content-Type not supported") err("Content-Type not supported")
proc decodeBytes*[T: DecodeTypes](t: typedesc[T], value: openArray[byte], proc decodeBytes*[T: DecodeTypes](
contentType: string): RestResult[T] = t: typedesc[T],
case contentType value: openArray[byte],
of "application/json": contentType: Opt[ContentTypeData]
): RestResult[T] =
let mediaType =
if contentType.isNone():
ApplicationJsonMediaType
else:
if isWildCard(contentType.get().mediaType):
return err("Incorrect Content-Type")
contentType.get().mediaType
if mediaType == ApplicationJsonMediaType:
try: try:
ok RestJson.decode(value, T, ok RestJson.decode(value, T,
requireAllFields = true, requireAllFields = true,
@ -2392,10 +2404,19 @@ proc decodeBytes*[T: DecodeTypes](t: typedesc[T], value: openArray[byte],
else: else:
err("Content-Type not supported") err("Content-Type not supported")
proc decodeBytes*[T: SszDecodeTypes](t: typedesc[T], value: openArray[byte], proc decodeBytes*[T: SszDecodeTypes](
contentType: string, updateRoot = true): RestResult[T] = t: typedesc[T],
case contentType value: openArray[byte],
of "application/octet-stream": contentType: Opt[ContentTypeData],
updateRoot = true
): RestResult[T] =
if contentType.isNone() or
isWildCard(contentType.get().mediaType):
return err("Missing or incorrect Content-Type")
let mediaType = contentType.get().mediaType
if mediaType == OctetStreamMediaType:
try: try:
var v: RestResult[T] var v: RestResult[T]
v.ok(T()) # This optimistically avoids an expensive genericAssign v.ok(T()) # This optimistically avoids an expensive genericAssign

View File

@ -132,27 +132,23 @@ proc getBlock*(client: RestClientRef, block_id: BlockIdent,
let data = let data =
case resp.status case resp.status
of 200: of 200:
case resp.contentType if resp.contentType.isNone() or
of "application/json": isWildCard(resp.contentType.get().mediaType):
let blck = raise newException(RestError, "Missing or incorrect Content-Type")
block: else:
let res = decodeBytes(GetBlockResponse, resp.data, let mediaType = resp.contentType.get().mediaType
resp.contentType) if mediaType == ApplicationJsonMediaType:
if res.isErr(): let blck = decodeBytes(GetBlockResponse, resp.data,
raise newException(RestError, $res.error()) resp.contentType).valueOr:
res.get() raise newException(RestError, $error)
ForkedSignedBeaconBlock.init(blck.data) ForkedSignedBeaconBlock.init(blck.data)
of "application/octet-stream": elif mediaType == OctetStreamMediaType:
let blck = let blck = decodeBytes(GetPhase0BlockSszResponse, resp.data,
block: resp.contentType).valueOr:
let res = decodeBytes(GetPhase0BlockSszResponse, resp.data, raise newException(RestError, $error)
resp.contentType)
if res.isErr():
raise newException(RestError, $res.error())
res.get()
ForkedSignedBeaconBlock.init(blck) ForkedSignedBeaconBlock.init(blck)
else: else:
raise newException(RestError, "Unsupported content-type") raise newException(RestError, "Unsupported Content-Type")
of 400, 404, 500: of 400, 404, 500:
raiseGenericError(resp) raiseGenericError(resp)
else: else:
@ -180,35 +176,31 @@ proc getBlockV2*(client: RestClientRef, block_id: BlockIdent,
return return
case resp.status case resp.status
of 200: of 200:
case resp.contentType if resp.contentType.isNone() or
of "application/json": isWildCard(resp.contentType.get().mediaType):
let blck = raise newException(RestError, "Missing or incorrect Content-Type")
block: else:
let res = decodeBytes(GetBlockV2Response, resp.data, let mediaType = resp.contentType.get().mediaType
resp.contentType) if mediaType == ApplicationJsonMediaType:
if res.isErr(): let blck = decodeBytes(GetBlockV2Response, resp.data,
raise newException(RestError, $res.error()) resp.contentType).valueOr:
newClone(res.get()) raise newException(RestError, $error)
some blck some(newClone(blck))
of "application/octet-stream": elif mediaType == OctetStreamMediaType:
try: try:
some newClone(readSszForkedSignedBeaconBlock(cfg, resp.data)) some newClone(readSszForkedSignedBeaconBlock(cfg, resp.data))
except CatchableError as exc: except CatchableError as exc:
raise newException(RestError, exc.msg) raise newException(RestError, exc.msg)
else: else:
raise newException(RestError, "Unsupported content-type") raise newException(RestError, "Unsupported Content-Type")
of 404: of 404:
none(ref ForkedSignedBeaconBlock) none(ref ForkedSignedBeaconBlock)
of 400, 500: of 400, 500:
let error = let error = decodeBytes(RestGenericError, resp.data,
block: resp.contentType).valueOr:
let res = decodeBytes(RestGenericError, resp.data, resp.contentType)
if res.isErr():
let msg = "Incorrect response error format (" & $resp.status & let msg = "Incorrect response error format (" & $resp.status &
") [" & $res.error() & "]" ") [" & $error & "]"
raise newException(RestError, msg) raise newException(RestError, msg)
res.get()
let msg = "Error response (" & $resp.status & ") [" & error.message & "]" let msg = "Error response (" & $resp.status & ") [" & error.message & "]"
raise newException(RestError, msg) raise newException(RestError, msg)
else: else:

View File

@ -32,24 +32,20 @@ proc getState*(client: RestClientRef, state_id: StateIdent,
let data = let data =
case resp.status case resp.status
of 200: of 200:
case resp.contentType if resp.contentType.isNone() or
of "application/json": isWildCard(resp.contentType.get().mediaType):
let state = raise newException(RestError, "Missing or incorrect Content-Type")
block: else:
let res = decodeBytes(GetStateResponse, resp.data, let mediaType = resp.contentType.get().mediaType
resp.contentType) if mediaType == ApplicationJsonMediaType:
if res.isErr(): let state = decodeBytes(GetStateResponse, resp.data,
raise newException(RestError, $res.error()) resp.contentType).valueOr:
res.get() raise newException(RestError, $error)
state.data state.data
of "application/octet-stream": elif mediaType == OctetStreamMediaType:
let state = let state = decodeBytes(GetPhase0StateSszResponse, resp.data,
block: resp.contentType).valueOr:
let res = decodeBytes(GetPhase0StateSszResponse, resp.data, raise newException(RestError, $error)
resp.contentType)
if res.isErr():
raise newException(RestError, $res.error())
res.get()
state state
else: else:
raise newException(RestError, "Unsupported content-type") raise newException(RestError, "Unsupported content-type")
@ -93,8 +89,12 @@ proc getStateV2*(client: RestClientRef, state_id: StateIdent,
let data = let data =
case resp.status case resp.status
of 200: of 200:
case resp.contentType if resp.contentType.isNone() or
of "application/json": isWildCard(resp.contentType.get().mediaType):
raise newException(RestError, "Missing or incorrect Content-Type")
else:
let mediaType = resp.contentType.get().mediaType
if mediaType == ApplicationJsonMediaType:
let state = let state =
block: block:
let res = newClone(decodeBytes(GetStateV2Response, resp.data, let res = newClone(decodeBytes(GetStateV2Response, resp.data,
@ -103,7 +103,7 @@ proc getStateV2*(client: RestClientRef, state_id: StateIdent,
raise newException(RestError, $res[].error()) raise newException(RestError, $res[].error())
newClone(res[].get()) newClone(res[].get())
state state
of "application/octet-stream": elif mediaType == OctetStreamMediaType:
try: try:
newClone(readSszForkedHashedBeaconState(cfg, resp.data)) newClone(readSszForkedHashedBeaconState(cfg, resp.data))
except CatchableError as exc: except CatchableError as exc:

View File

@ -105,17 +105,26 @@ proc signData*(client: RestClientRef, identifier: ValidatorPubKey,
case response.status case response.status
of 200: of 200:
inc(nbc_remote_signer_200_responses) inc(nbc_remote_signer_200_responses)
let sig = if response.contentType.contains("text/plain"): let sig =
if response.contentType.isNone() or
isWildCard(response.contentType.get().mediaType):
return Web3SignerDataResponse.err(
"Unable to decode signature from missing or incorrect content")
else:
let mediaType = response.contentType.get().mediaType
if mediaType == TextPlainMediaType:
let asStr = fromBytes(string, response.data) let asStr = fromBytes(string, response.data)
let sigFromText = fromHex(ValidatorSig, asStr) let sigFromText = fromHex(ValidatorSig, asStr)
if sigFromText.isErr: if sigFromText.isErr:
return Web3SignerDataResponse.err("Unable to decode signature from plain text") return Web3SignerDataResponse.err(
"Unable to decode signature from plain text")
sigFromText.get.load sigFromText.get.load
else: else:
let res = decodeBytes(Web3SignerSignatureResponse, response.data, let res = decodeBytes(Web3SignerSignatureResponse, response.data,
response.contentType) response.contentType)
if res.isErr: if res.isErr:
let msg = "Unable to decode remote signer response [" & $res.error() & "]" let msg = "Unable to decode remote signer response [" &
$res.error() & "]"
inc(nbc_remote_signer_failures) inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(msg) return Web3SignerDataResponse.err(msg)
res.get.signature.load res.get.signature.load

View File

@ -650,19 +650,19 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
let let
r1 = decodeBytes(KeystoresAndSlashingProtection, r1 = decodeBytes(KeystoresAndSlashingProtection,
Vector1.toOpenArrayByte(0, len(Vector1) - 1), Vector1.toOpenArrayByte(0, len(Vector1) - 1),
"application/json") Opt.some(getContentType("application/json").get()))
r2 = decodeBytes(KeystoresAndSlashingProtection, r2 = decodeBytes(KeystoresAndSlashingProtection,
Vector2.toOpenArrayByte(0, len(Vector2) - 1), Vector2.toOpenArrayByte(0, len(Vector2) - 1),
"application/json") Opt.some(getContentType("application/json").get()))
r3 = decodeBytes(KeystoresAndSlashingProtection, r3 = decodeBytes(KeystoresAndSlashingProtection,
Vector3.toOpenArrayByte(0, len(Vector3) - 1), Vector3.toOpenArrayByte(0, len(Vector3) - 1),
"application/json") Opt.some(getContentType("application/json").get()))
r4 = decodeBytes(KeystoresAndSlashingProtection, r4 = decodeBytes(KeystoresAndSlashingProtection,
Vector4.toOpenArrayByte(0, len(Vector4) - 1), Vector4.toOpenArrayByte(0, len(Vector4) - 1),
"application/json") Opt.some(getContentType("application/json").get()))
r5 = decodeBytes(KeystoresAndSlashingProtection, r5 = decodeBytes(KeystoresAndSlashingProtection,
Vector5.toOpenArrayByte(0, len(Vector5) - 1), Vector5.toOpenArrayByte(0, len(Vector5) - 1),
"application/json") Opt.some(getContentType("application/json").get()))
check: check:
r1.isOk() == true r1.isOk() == true

2
vendor/nim-presto vendored

@ -1 +1 @@
Subproject commit 3984431dc0fc829eb668e12e57e90542b041d298 Subproject commit 8d5a2512f0fbbb44d265f179c5c5769f4bc351ea