Skip to content

lambda_http fails to build request with spaces - http::Error(InvalidUri(InvalidUriChar)) #515

@qstorey

Description

@qstorey

Hi,

I'm running into an issue where URLs with spaces are causing my Lambda function to return server errors on API Gateway.

I'm no expert in URL encoding but I do realise that spaces are unsafe characters and should be avoided at all costs. Unfortunately the product I am building is acting as a sort of redirecting proxy and I am not in full control of the URL structure.

Here is the information needed to reproduce this error:

~ rustc --version
rustc 1.61.0 (fe5b13d68 2022-05-18)

Place the following files in the same directory:

# Cargo.toml
[package]
name = "http-basic-bug"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "example"
path = "main.rs"

[dependencies]
lambda_http = "0.6.0"
lambda_runtime = "0.6.0"
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
// main.rs
use lambda_http::{run, service_fn, Body, Error, Request, Response};

async fn function_handler(_event: Request) -> Result<Response<Body>, Error> {
    // Extract some useful information from the request

    // Return something that implements IntoResponse.
    // It will be serialized to the right response event automatically by the runtime
    let resp = Response::builder()
        .status(200)
        .header("content-type", "text/html")
        .body("Hello AWS Lambda HTTP request".into())
        .map_err(Box::new)?;
    Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        // disabling time is handy because CloudWatch will add the ingestion time.
        .without_time()
        .init();

    run(service_fn(function_handler)).await
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_no_space() {
        let input = include_str!("good-request.json");
        let request = lambda_http::request::from_str(input).expect("failed to create request");
        let response = function_handler(request)
            .await
            .expect("failed to handle request");
        assert_eq!(response.status().as_u16(), 200);
    }

    #[tokio::test]
    async fn test_with_space() {
        let input = include_str!("bad-request.json");
        let request = lambda_http::request::from_str(input).expect("failed to create request");
        let response = function_handler(request)
            .await
            .expect("failed to handle request");
        assert_eq!(response.status().as_u16(), 200);
    }
}

good-request.json

{
    "version": "2.0",
    "routeKey": "$default",
    "rawPath": "/my/path",
    "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
    "cookies": [ "cookie1=value1", "cookie2=value2" ],
    "headers": {
      "Header1": "value1",
      "Header2": "value2"
    },
    "queryStringParameters": { "parameter1": "value1,value2", "parameter2": "value" },
    "requestContext": {
      "accountId": "123456789012",
      "apiId": "api-id",
      "authorizer": { "jwt": {
          "claims": {"claim1": "value1", "claim2": "value2"},
          "scopes": ["scope1", "scope2"]
          }
      },
      "domainName": "id.execute-api.us-east-1.amazonaws.com",
      "domainPrefix": "id",
      "http": {
        "method": "POST",
        "path": "/my/path",
        "protocol": "HTTP/1.1",
        "sourceIp": "IP",
        "userAgent": "agent"
      },
      "requestId": "id",
      "routeKey": "$default",
      "stage": "$default",
      "time": "12/Mar/2020:19:03:58 +0000",
      "timeEpoch": 1583348638390
    },
    "body": "Hello from Lambda",
    "pathParameters": {"parameter1": "value1"},
    "isBase64Encoded": false,
    "stageVariables": {"stageVariable1": "value1", "stageVariable2": "value2"}
}

bad-request.json

{
    "version": "2.0",
    "routeKey": "$default",
    "rawPath": "/my/path-with space",
    "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
    "cookies": [ "cookie1=value1", "cookie2=value2" ],
    "headers": {
      "Header1": "value1",
      "Header2": "value2"
    },
    "queryStringParameters": { "parameter1": "value1,value2", "parameter2": "value" },
    "requestContext": {
      "accountId": "123456789012",
      "apiId": "api-id",
      "authorizer": { "jwt": {
          "claims": {"claim1": "value1", "claim2": "value2"},
          "scopes": ["scope1", "scope2"]
          }
      },
      "domainName": "id.execute-api.us-east-1.amazonaws.com",
      "domainPrefix": "id",
      "http": {
        "method": "POST",
        "path": "/my/path-with space",
        "protocol": "HTTP/1.1",
        "sourceIp": "IP",
        "userAgent": "agent"
      },
      "requestId": "id",
      "routeKey": "$default",
      "stage": "$default",
      "time": "12/Mar/2020:19:03:58 +0000",
      "timeEpoch": 1583348638390
    },
    "body": "Hello from Lambda",
    "pathParameters": {"parameter1": "value1"},
    "isBase64Encoded": false,
    "stageVariables": {"stageVariable1": "value1", "stageVariable2": "value2"}
}

Run the tests

~ cargo test
   Compiling http-basic-bug v0.1.0 (/private/tmp/lambda_http_bug)
    Finished test [unoptimized + debuginfo] target(s) in 0.74s
     Running unittests main.rs (target/debug/deps/example-4220d3d5bc4bf2cb)

running 2 tests
test tests::test_no_space ... ok
test tests::test_with_space ... FAILED

failures:

---- tests::test_with_space stdout ----
thread 'tests::test_with_space' panicked at 'failed to build request: http::Error(InvalidUri(InvalidUriChar))', /Users/<REDACTED>/.cargo/registry/src/github.com-1ecc6299db9ec823/lambda_http-0.6.0/src/request.rs:145:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::test_with_space

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass '--bin example'

If this is a case of using unsafe characters or you don't feel like this is an issue of lambda_http then please close this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions