diff --git a/changelogs/current/new_features/lua__added-base64-decode.rst b/changelogs/current/new_features/lua__added-base64-decode.rst new file mode 100644 index 0000000000000..2088fbab4ddd8 --- /dev/null +++ b/changelogs/current/new_features/lua__added-base64-decode.rst @@ -0,0 +1,4 @@ +Added the :ref:`base64Decode() +` method to the HTTP Lua filter's stream +handle, the inverse of the existing ``base64Escape()``. It returns ``nil`` when the input is not +valid base64. diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 4788908540699..943f19c0540a2 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -667,6 +667,37 @@ which means the signature is verified; otherwise, the second element will store Encodes the input string as base64. This can be useful for escaping binary data. +.. _config_http_filters_lua_stream_handle_api_base64_decode: + +``base64Decode()`` +^^^^^^^^^^^^^^^^^^ + +.. code-block:: lua + + local decoded = handle:base64Decode("aW5wdXQgc3RyaW5n") + +Decodes a base64 encoded string, the inverse of :ref:`base64Escape() +`. Returns ``nil`` if the input is not +valid base64, so a value taken from a header or an upstream body can be checked rather than +having to be trusted: + +.. code-block:: lua + + function envoy_on_request(request_handle) + local claim = request_handle:headers():get("x-encoded-claim") + if claim ~= nil then + local decoded = request_handle:base64Decode(claim) + if decoded == nil then + request_handle:respond({[":status"] = "400"}, "malformed claim") + return + end + request_handle:headers():add("x-decoded-claim", decoded) + end + end + +The decoded value may contain NUL bytes, since base64 carries arbitrary binary data; Lua strings +are length-counted, so this is preserved. + ``timestamp()`` ^^^^^^^^^^^^^^^ diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index e3e2d73ebee6a..72ea2dea5c48e 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -810,6 +810,20 @@ int StreamHandleWrapper::luaBase64Escape(lua_State* state) { return 1; } +int StreamHandleWrapper::luaBase64Decode(lua_State* state) { + absl::string_view input = Filters::Common::Lua::getStringViewFromLuaString(state, 2); + std::string output; + if (!absl::Base64Unescape(input, &output)) { + // Returning nil rather than raising keeps a malformed value recoverable by the script, which + // is the common case when the input came from a header or an upstream response body. + lua_pushnil(state); + return 1; + } + lua_pushlstring(state, output.data(), output.size()); + + return 1; +} + int StreamHandleWrapper::luaTimestamp(lua_State* state) { auto now = time_source_.systemTime().time_since_epoch(); diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index 4f2dd7651177e..2ad6304d26674 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -224,6 +224,7 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObjectdecodeHeaders(request_headers, true))); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + EXPECT_LOG_CONTAINS_ALL_OF( + Envoy::ExpectedLogMessages({{"trace", "bad chars: nil"}, {"trace", "bad length: nil"}}), + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true))); +} + TEST_F(LuaHttpFilterTest, Timestamp_ReturnsFormatSet) { const std::string SCRIPT{R"EOF( function envoy_on_request(request_handle)