Skip to content

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

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

Closed
qstorey opened this issue Aug 24, 2022 · 3 comments · Fixed by #516
Closed

Comments

@qstorey
Copy link

qstorey commented Aug 24, 2022

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/git.colasdn.top-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.

@calavera
Copy link
Contributor

Thanks for opening this issue with such clear details. I think we probably need to encode the path before creating the URI for all the events. That should fix the issue, any other ideas are very welcome. Probably in these two places if you want to give it a try:

https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/lambda-http/src/request.rs#L351
https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/lambda-http/src/request.rs#L224

@github-actions
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for the maintainers of this repository to see.
If you need more assistance, please open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

@qstorey
Copy link
Author

qstorey commented Aug 29, 2022

@calavera thanks for fixing this. Rebuilt my project using the commit that landed on main and it works like a charm 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants