Skip to content

Commit

Permalink
net: Http basic auth (#1253)
Browse files Browse the repository at this point in the history
* net: Support http basic auth

* net: Add None authenticate type

* httpu: Support auth
  • Loading branch information
BastianBlokland authored Jan 30, 2025
1 parent 6944330 commit 7b67339
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 12 deletions.
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);
}
}

0 comments on commit 7b67339

Please sign in to comment.