Skip to content
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

net: Http basic auth #1253

Merged
merged 3 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions apps/utilities/httpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ typedef struct {
String host;
String uri; // Optional.
String outputPath; // Optional.
NetHttpAuth auth;
} HttpuContext;

static NetHttpFlags httpu_flags(const HttpuContext* ctx) {
Expand Down Expand Up @@ -96,7 +97,7 @@ static i32 httpu_head(const HttpuContext* ctx) {
res = 1;
goto Done;
}
if (net_http_head_sync(client, ctx->uri) != NetResult_Success) {
if (net_http_head_sync(client, ctx->uri, &ctx->auth) != NetResult_Success) {
res = 1;
goto Done;
}
Expand All @@ -117,7 +118,7 @@ static i32 httpu_get(const HttpuContext* ctx) {
goto Done;
}

const NetResult getResult = net_http_get_sync(client, ctx->uri, &buffer);
const NetResult getResult = net_http_get_sync(client, ctx->uri, &ctx->auth, &buffer);
if (getResult != NetResult_Success) {
res = 1;
goto Done;
Expand All @@ -137,7 +138,9 @@ static i32 httpu_get(const HttpuContext* ctx) {
return res;
}

static CliId g_optHost, g_optUri, g_optOutput, g_optProtocol, g_optMethod, g_optHelp;
static CliId g_optHost, g_optUri, g_optOutput, g_optProtocol, g_optMethod;
static CliId g_optUser, g_optPassword;
static CliId g_optHelp;

void app_cli_configure(CliApp* app) {
cli_app_register_desc(app, string_lit("Http Utility."));
Expand All @@ -159,13 +162,21 @@ void app_cli_configure(CliApp* app) {
cli_register_desc_choice_array(app, g_optMethod, string_empty, g_methodStrs, 1 /* get */);
cli_register_validator(app, g_optMethod, httpu_validate_method);

g_optUser = cli_register_flag(app, 'U', string_lit("user"), CliOptionFlags_Value);
cli_register_desc(app, g_optUser, string_lit("Http basic auth user."));

g_optPassword = cli_register_flag(app, 'P', string_lit("password"), CliOptionFlags_Value);
cli_register_desc(app, g_optPassword, string_lit("Http basic auth password."));

g_optHelp = cli_register_flag(app, 'h', string_lit("help"), CliOptionFlags_None);
cli_register_desc(app, g_optHelp, string_lit("Display this help page."));
cli_register_exclusions(app, g_optHelp, g_optHost);
cli_register_exclusions(app, g_optHelp, g_optUri);
cli_register_exclusions(app, g_optHelp, g_optOutput);
cli_register_exclusions(app, g_optHelp, g_optProtocol);
cli_register_exclusions(app, g_optHelp, g_optMethod);
cli_register_exclusions(app, g_optHelp, g_optUser);
cli_register_exclusions(app, g_optHelp, g_optPassword);
}

i32 app_cli_run(const CliApp* app, const CliInvocation* invoc) {
Expand All @@ -187,6 +198,12 @@ i32 app_cli_run(const CliApp* app, const CliInvocation* invoc) {
.outputPath = cli_read_string(invoc, g_optOutput, string_empty),
};

if (cli_parse_provided(invoc, g_optUser)) {
ctx.auth.type = NetHttpAuthType_Basic;
ctx.auth.user = cli_read_string(invoc, g_optUser, string_empty);
ctx.auth.pw = cli_read_string(invoc, g_optPassword, string_empty);
}

i32 retCode = 1;

net_init();
Expand Down
15 changes: 13 additions & 2 deletions libs/net/include/net_http.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include "core_string.h"
#include "net.h"

typedef enum {
Expand All @@ -7,6 +8,16 @@ typedef enum {
NetHttpFlags_TlsNoVerify = NetHttpFlags_Tls | 1 << 1, // Https without Tls cert verification.
} NetHttpFlags;

typedef enum {
NetHttpAuthType_None,
NetHttpAuthType_Basic,
} NetHttpAuthType;

typedef struct {
NetHttpAuthType type;
String user, pw;
} NetHttpAuth;

/**
* Http (Hypertext Transfer Protocol) connection.
*/
Expand Down Expand Up @@ -39,13 +50,13 @@ String net_http_remote_name(const NetHttp*);
/**
* Synchonously perform a 'HEAD' request for the given resource.
*/
NetResult net_http_head_sync(NetHttp*, String uri);
NetResult net_http_head_sync(NetHttp*, String uri, const NetHttpAuth*);

/**
* Synchonously perform a 'GET' request for the given resource.
* NOTE: Response body is written to the output DynString.
*/
NetResult net_http_get_sync(NetHttp*, String uri, DynString* out);
NetResult net_http_get_sync(NetHttp*, String uri, const NetHttpAuth*, DynString* out);

/**
* Synchonously shutdown the Http connection.
Expand Down
37 changes: 31 additions & 6 deletions libs/net/src/http.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "core_alloc.h"
#include "core_ascii.h"
#include "core_base64.h"
#include "core_deflate.h"
#include "core_diag.h"
#include "core_dynstring.h"
Expand Down Expand Up @@ -113,10 +114,33 @@ static void http_set_err(NetHttp* http, const NetResult err) {
}
}

static void
http_request_header(const NetHttp* http, const String method, const String uri, DynString* out) {
static void http_auth_write(const NetHttpAuth* auth, DynString* out) {
switch (auth->type) {
case NetHttpAuthType_None:
break;
case NetHttpAuthType_Basic: {
const String creds = fmt_write_scratch("{}:{}", fmt_text(auth->user), fmt_text(auth->pw));
fmt_write(out, "Basic {}", fmt_text(base64_encode_scratch(creds)));
return;
}
}
diag_crash_msg("Unsupported auth type");
}

static void http_request_header(
const NetHttp* http,
const String method,
const String uri,
const NetHttpAuth* auth,
DynString* out) {

fmt_write(out, "{} {} HTTP/1.1\r\n", fmt_text(method), fmt_text(uri));
fmt_write(out, "Host: {}\r\n", fmt_text(http->host));
if (auth && auth->type != NetHttpAuthType_None) {
fmt_write(out, "Authorization: ");
http_auth_write(auth, out);
fmt_write(out, "\r\n");
}
fmt_write(out, "Connection: keep-alive\r\n");
fmt_write(out, "Accept: */*\r\n");
fmt_write(out, "Accept-Encoding: gzip, deflate\r\n");
Expand Down Expand Up @@ -439,15 +463,15 @@ NetResult net_http_status(const NetHttp* http) { return http->status; }
const NetAddr* net_http_remote(const NetHttp* http) { return &http->hostAddr; }
String net_http_remote_name(const NetHttp* http) { return http->host; }

NetResult net_http_head_sync(NetHttp* http, const String uri) {
NetResult net_http_head_sync(NetHttp* http, const String uri, const NetHttpAuth* auth) {
if (http->status != NetResult_Success) {
return http->status;
}
const TimeSteady startTime = time_steady_clock();
const String uriOrRoot = string_is_empty(uri) ? string_lit("/") : uri;

DynString headerBuffer = dynstring_create(g_allocScratch, 4 * usize_kibibyte);
http_request_header(http, string_lit("HEAD"), uriOrRoot, &headerBuffer);
http_request_header(http, string_lit("HEAD"), uriOrRoot, auth, &headerBuffer);

log_d(
"Http: Sending HEAD",
Expand Down Expand Up @@ -489,15 +513,16 @@ NetResult net_http_head_sync(NetHttp* http, const String uri) {
return http->status ? http->status : http_status_result(resp.status);
}

NetResult net_http_get_sync(NetHttp* http, const String uri, DynString* out) {
NetResult
net_http_get_sync(NetHttp* http, const String uri, const NetHttpAuth* auth, DynString* out) {
if (http->status != NetResult_Success) {
return http->status;
}
const TimeSteady startTime = time_steady_clock();
const String uriOrRoot = string_is_empty(uri) ? string_lit("/") : uri;

DynString headerBuffer = dynstring_create(g_allocScratch, 4 * usize_kibibyte);
http_request_header(http, string_lit("GET"), uriOrRoot, &headerBuffer);
http_request_header(http, string_lit("GET"), uriOrRoot, auth, &headerBuffer);

log_d(
"Http: Sending GET",
Expand Down
24 changes: 23 additions & 1 deletion libs/net/test/test_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,34 @@ spec(http) {
check_eq_int(net_http_status(http), NetResult_Success);

DynString data = dynstring_create(g_allocHeap, usize_kibibyte);
check_eq_int(net_http_get_sync(http, uri, &data), NetResult_Success);
check_eq_int(net_http_get_sync(http, uri, null /* auth */, &data), NetResult_Success);
check_eq_string(dynstring_view(&data), string_lit("Hello World!\n"));
dynstring_destroy(&data);

check_eq_int(net_http_shutdown_sync(http), NetResult_Success);

net_http_destroy(http);
}

skip_it("can get an authorized resource") {
const String host = string_lit("bastian.tech");
const String uri = string_lit("/test-auth/hello-world-secure.txt");
const NetHttpAuth auth = {
.type = NetHttpAuthType_Basic,
.user = string_lit("test"),
.pw = string_lit("helloworld"),
};

NetHttp* http = net_http_connect_sync(g_allocHeap, host, NetHttpFlags_TlsNoVerify);
check_eq_int(net_http_status(http), NetResult_Success);

DynString data = dynstring_create(g_allocHeap, usize_kibibyte);
check_eq_int(net_http_get_sync(http, uri, &auth, &data), NetResult_Success);
check_eq_string(dynstring_view(&data), string_lit("Secret Hello World!\n"));
dynstring_destroy(&data);

check_eq_int(net_http_shutdown_sync(http), NetResult_Success);

net_http_destroy(http);
}
}