From de23e7c2d37ba2ca1345e81ca636d7b72d543922 Mon Sep 17 00:00:00 2001 From: Thomas Keppler Date: Wed, 21 Dec 2022 16:12:16 +0100 Subject: [PATCH] pro: Allow passing Basic Auth credentials for HTTP URLs Since our WebServer can already react to Basic Auth, now there is a way to use that in SerenityOS :^) This commit intentionally omits any Digest authentication. NOTE: We specifically allow for empty credentials (just ':'), since standard RFC7617 doesn't explicitly prohibit this. --- Userland/Utilities/pro.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Userland/Utilities/pro.cpp b/Userland/Utilities/pro.cpp index 62f20c39c3..0691aeb911 100644 --- a/Userland/Utilities/pro.cpp +++ b/Userland/Utilities/pro.cpp @@ -5,10 +5,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include +#include #include #include #include @@ -159,6 +161,7 @@ ErrorOr serenity_main(Main::Arguments arguments) DeprecatedString method = "GET"; StringView method_override; HashMap request_headers; + String credentials; Core::ArgsParser args_parser; args_parser.set_general_help( @@ -182,6 +185,26 @@ ErrorOr serenity_main(Main::Arguments arguments) request_headers.set(header.substring_view(0, split.value()), header.substring_view(split.value() + 1)); return true; } }); + args_parser.add_option(Core::ArgsParser::Option { + .argument_mode = Core::ArgsParser::OptionArgumentMode::Required, + .help_string = "(HTTP only) Provide basic authentication credentials", + .long_name = "auth", + .short_name = 'u', + .value_name = "username:password", + .accept_value = [&](auto* s) { + StringView input { s, strlen(s) }; + if (!input.contains(':')) + return false; + + // NOTE: Input is explicitly not trimmed, but instad taken in raw; + // Space prepended usernames and appended passwords might be legal in the user's context. + auto maybe_credentials = String::from_utf8(input); + if (maybe_credentials.is_error()) + return false; + + credentials = maybe_credentials.release_value(); + return true; + } }); args_parser.add_option(proxy_spec, "Specify a proxy server to use for this request (proto://ip:port)", "proxy", 'p', "proxy"); args_parser.add_option(verbose_output, "(HTTP only) Log request and response metadata", "verbose", 'v'); args_parser.add_positional_argument(url_str, "URL to download from", "url"); @@ -222,6 +245,17 @@ ErrorOr serenity_main(Main::Arguments arguments) auto protocol_client = TRY(Protocol::RequestClient::try_create()); auto output_stream = ConditionalOutputFileStream { [&] { return should_save_stream_data; }, stdout }; + // https://httpwg.org/specs/rfc9110.html#authentication + if (!credentials.is_empty() && is_http_url) { + // 11.2. Authentication Parameters + // The authentication scheme is followed by additional information necessary for achieving authentication via + // that scheme as (...) or a single sequence of characters capable of holding base64-encoded information. + // FIXME: Prevent overriding manually provided Authorization header + auto const encoded_credentials = TRY(encode_base64(credentials.bytes())); + auto const authorization = TRY(String::formatted("Basic {}", encoded_credentials)); + request_headers.set("Authorization", authorization.to_deprecated_string()); + } + Function setup_request = [&] { if (!request) { warnln("Failed to start request for '{}'", url_str);