Files
bun.sh/src/s3/credentials.zig
Jarred Sumner fe8f8242fd Make BoundedArray more compact, shrink Data in sql from 32 bytes to 24 bytes (#22210)
### What does this PR do?

- Instead of storing `len` in `BoundedArray` as a `usize`, store it as
either a `u8` or ` u16` depending on the `buffer_capacity`
- Copy-paste `BoundedArray` from the standard library into Bun's
codebase as it was removed in
https://github.com/ziglang/zig/pull/24699/files#diff-cbd8cbbc17583cb9ea5cc0f711ce0ad447b446e62ea5ddbe29274696dce89e4f
and we will probably continue using it

### How did you verify your code works?

Ran `bun run zig:check`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: taylor.fish <contact@taylor.fish>
2025-08-28 17:34:35 -07:00

1141 lines
66 KiB
Zig

pub const S3Credentials = struct {
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
ref_count: RefCount,
accessKeyId: []const u8,
secretAccessKey: []const u8,
region: []const u8,
endpoint: []const u8,
bucket: []const u8,
sessionToken: []const u8,
storage_class: ?StorageClass = null,
/// Important for MinIO support.
insecure_http: bool = false,
/// indicates if the endpoint is a virtual hosted style bucket
virtual_hosted_style: bool = false,
pub fn estimatedSize(this: *const @This()) usize {
return @sizeOf(S3Credentials) + this.accessKeyId.len + this.region.len + this.secretAccessKey.len + this.endpoint.len + this.bucket.len;
}
fn hashConst(acl: []const u8) u64 {
var hasher = std.hash.Wyhash.init(0);
var remain = acl;
var buf: [@sizeOf(@TypeOf(hasher.buf))]u8 = undefined;
while (remain.len > 0) {
const end = @min(hasher.buf.len, remain.len);
hasher.update(strings.copyLowercaseIfNeeded(remain[0..end], &buf));
remain = remain[end..];
}
return hasher.final();
}
pub fn getCredentialsWithOptions(this: S3Credentials, default_options: MultiPartUploadOptions, options: ?jsc.JSValue, default_acl: ?ACL, default_storage_class: ?StorageClass, globalObject: *jsc.JSGlobalObject) bun.JSError!S3CredentialsWithOptions {
bun.analytics.Features.s3 += 1;
// get ENV config
var new_credentials = S3CredentialsWithOptions{
.credentials = this,
.options = default_options,
.acl = default_acl,
.storage_class = default_storage_class,
};
errdefer {
new_credentials.deinit();
}
if (options) |opts| {
if (opts.isObject()) {
if (try opts.getTruthyComptime(globalObject, "accessKeyId")) |js_value| {
if (!js_value.isEmptyOrUndefinedOrNull()) {
if (js_value.isString()) {
const str = try bun.String.fromJS(js_value, globalObject);
defer str.deref();
if (str.tag != .Empty and str.tag != .Dead) {
new_credentials._accessKeyIdSlice = str.toUTF8(bun.default_allocator);
new_credentials.credentials.accessKeyId = new_credentials._accessKeyIdSlice.?.slice();
new_credentials.changed_credentials = true;
}
} else {
return globalObject.throwInvalidArgumentTypeValue("accessKeyId", "string", js_value);
}
}
}
if (try opts.getTruthyComptime(globalObject, "secretAccessKey")) |js_value| {
if (!js_value.isEmptyOrUndefinedOrNull()) {
if (js_value.isString()) {
const str = try bun.String.fromJS(js_value, globalObject);
defer str.deref();
if (str.tag != .Empty and str.tag != .Dead) {
new_credentials._secretAccessKeySlice = str.toUTF8(bun.default_allocator);
new_credentials.credentials.secretAccessKey = new_credentials._secretAccessKeySlice.?.slice();
new_credentials.changed_credentials = true;
}
} else {
return globalObject.throwInvalidArgumentTypeValue("secretAccessKey", "string", js_value);
}
}
}
if (try opts.getTruthyComptime(globalObject, "region")) |js_value| {
if (!js_value.isEmptyOrUndefinedOrNull()) {
if (js_value.isString()) {
const str = try bun.String.fromJS(js_value, globalObject);
defer str.deref();
if (str.tag != .Empty and str.tag != .Dead) {
new_credentials._regionSlice = str.toUTF8(bun.default_allocator);
new_credentials.credentials.region = new_credentials._regionSlice.?.slice();
new_credentials.changed_credentials = true;
}
} else {
return globalObject.throwInvalidArgumentTypeValue("region", "string", js_value);
}
}
}
if (try opts.getTruthyComptime(globalObject, "endpoint")) |js_value| {
if (!js_value.isEmptyOrUndefinedOrNull()) {
if (js_value.isString()) {
const str = try bun.String.fromJS(js_value, globalObject);
defer str.deref();
if (str.tag != .Empty and str.tag != .Dead) {
new_credentials._endpointSlice = str.toUTF8(bun.default_allocator);
const endpoint = new_credentials._endpointSlice.?.slice();
const url = bun.URL.parse(endpoint);
const normalized_endpoint = url.hostWithPath();
if (normalized_endpoint.len > 0) {
new_credentials.credentials.endpoint = normalized_endpoint;
// Default to https://
// Only use http:// if the endpoint specifically starts with 'http://'
new_credentials.credentials.insecure_http = url.isHTTP();
new_credentials.changed_credentials = true;
} else if (endpoint.len > 0) {
// endpoint is not a valid URL
return globalObject.throwInvalidArgumentTypeValue("endpoint", "string", js_value);
}
}
} else {
return globalObject.throwInvalidArgumentTypeValue("endpoint", "string", js_value);
}
}
}
if (try opts.getTruthyComptime(globalObject, "bucket")) |js_value| {
if (!js_value.isEmptyOrUndefinedOrNull()) {
if (js_value.isString()) {
const str = try bun.String.fromJS(js_value, globalObject);
defer str.deref();
if (str.tag != .Empty and str.tag != .Dead) {
new_credentials._bucketSlice = str.toUTF8(bun.default_allocator);
new_credentials.credentials.bucket = new_credentials._bucketSlice.?.slice();
new_credentials.changed_credentials = true;
}
} else {
return globalObject.throwInvalidArgumentTypeValue("bucket", "string", js_value);
}
}
}
if (try opts.getBooleanStrict(globalObject, "virtualHostedStyle")) |virtual_hosted_style| {
new_credentials.credentials.virtual_hosted_style = virtual_hosted_style;
new_credentials.changed_credentials = true;
}
if (try opts.getTruthyComptime(globalObject, "sessionToken")) |js_value| {
if (!js_value.isEmptyOrUndefinedOrNull()) {
if (js_value.isString()) {
const str = try bun.String.fromJS(js_value, globalObject);
defer str.deref();
if (str.tag != .Empty and str.tag != .Dead) {
new_credentials._sessionTokenSlice = str.toUTF8(bun.default_allocator);
new_credentials.credentials.sessionToken = new_credentials._sessionTokenSlice.?.slice();
new_credentials.changed_credentials = true;
}
} else {
return globalObject.throwInvalidArgumentTypeValue("bucket", "string", js_value);
}
}
}
if (try opts.getOptional(globalObject, "pageSize", i64)) |pageSize| {
if (pageSize < MultiPartUploadOptions.MIN_SINGLE_UPLOAD_SIZE and pageSize > MultiPartUploadOptions.MAX_SINGLE_UPLOAD_SIZE) {
return globalObject.throwRangeError(pageSize, .{
.min = @intCast(MultiPartUploadOptions.MIN_SINGLE_UPLOAD_SIZE),
.max = @intCast(MultiPartUploadOptions.MAX_SINGLE_UPLOAD_SIZE),
.field_name = "pageSize",
});
} else {
new_credentials.options.partSize = @intCast(pageSize);
}
}
if (try opts.getOptional(globalObject, "partSize", i64)) |partSize| {
if (partSize < MultiPartUploadOptions.MIN_SINGLE_UPLOAD_SIZE and partSize > MultiPartUploadOptions.MAX_SINGLE_UPLOAD_SIZE) {
return globalObject.throwRangeError(partSize, .{
.min = @intCast(MultiPartUploadOptions.MIN_SINGLE_UPLOAD_SIZE),
.max = @intCast(MultiPartUploadOptions.MAX_SINGLE_UPLOAD_SIZE),
.field_name = "partSize",
});
} else {
new_credentials.options.partSize = @intCast(partSize);
}
}
if (try opts.getOptional(globalObject, "queueSize", i32)) |queueSize| {
if (queueSize < 1) {
return globalObject.throwRangeError(queueSize, .{
.min = 1,
.field_name = "queueSize",
});
} else {
new_credentials.options.queueSize = @intCast(@max(queueSize, std.math.maxInt(u8)));
}
}
if (try opts.getOptional(globalObject, "retry", i32)) |retry| {
if (retry < 0 and retry > 255) {
return globalObject.throwRangeError(retry, .{
.min = 0,
.max = 255,
.field_name = "retry",
});
} else {
new_credentials.options.retry = @intCast(retry);
}
}
if (try opts.getOptionalEnum(globalObject, "acl", ACL)) |acl| {
new_credentials.acl = acl;
}
if (try opts.getOptionalEnum(globalObject, "storageClass", StorageClass)) |storage_class| {
new_credentials.storage_class = storage_class;
}
}
}
return new_credentials;
}
pub fn dupe(this: *const @This()) *S3Credentials {
return bun.new(S3Credentials, .{
.ref_count = .init(),
.accessKeyId = if (this.accessKeyId.len > 0)
bun.handleOom(bun.default_allocator.dupe(u8, this.accessKeyId))
else
"",
.secretAccessKey = if (this.secretAccessKey.len > 0)
bun.handleOom(bun.default_allocator.dupe(u8, this.secretAccessKey))
else
"",
.region = if (this.region.len > 0)
bun.handleOom(bun.default_allocator.dupe(u8, this.region))
else
"",
.endpoint = if (this.endpoint.len > 0)
bun.handleOom(bun.default_allocator.dupe(u8, this.endpoint))
else
"",
.bucket = if (this.bucket.len > 0)
bun.handleOom(bun.default_allocator.dupe(u8, this.bucket))
else
"",
.sessionToken = if (this.sessionToken.len > 0)
bun.handleOom(bun.default_allocator.dupe(u8, this.sessionToken))
else
"",
.insecure_http = this.insecure_http,
.virtual_hosted_style = this.virtual_hosted_style,
});
}
fn deinit(this: *@This()) void {
if (this.accessKeyId.len > 0) {
bun.default_allocator.free(this.accessKeyId);
}
if (this.secretAccessKey.len > 0) {
bun.default_allocator.free(this.secretAccessKey);
}
if (this.region.len > 0) {
bun.default_allocator.free(this.region);
}
if (this.endpoint.len > 0) {
bun.default_allocator.free(this.endpoint);
}
if (this.bucket.len > 0) {
bun.default_allocator.free(this.bucket);
}
if (this.sessionToken.len > 0) {
bun.default_allocator.free(this.sessionToken);
}
bun.destroy(this);
}
const log = bun.Output.scoped(.AWS, .visible);
const DateResult = struct {
// numeric representation of year, month and day (excluding time components)
numeric_day: u64,
date: []const u8,
};
fn getAMZDate(allocator: std.mem.Allocator) DateResult {
// We can also use Date.now() but would be slower and would add jsc dependency
// var buffer: [28]u8 = undefined;
// the code bellow is the same as new Date(Date.now()).toISOString()
// jsc.JSValue.getDateNowISOString(globalObject, &buffer);
// Create UTC timestamp
const secs: u64 = @intCast(@divFloor(std.time.milliTimestamp(), 1000));
const utc_seconds = std.time.epoch.EpochSeconds{ .secs = secs };
const utc_day = utc_seconds.getEpochDay();
const year_and_day = utc_day.calculateYearDay();
const month_and_day = year_and_day.calculateMonthDay();
// Get UTC date components
const year = year_and_day.year;
const day = @as(u32, month_and_day.day_index) + 1; // this starts in 0
const month = month_and_day.month.numeric(); // starts in 1
// Get UTC time components
const time = utc_seconds.getDaySeconds();
const hours = time.getHoursIntoDay();
const minutes = time.getMinutesIntoHour();
const seconds = time.getSecondsIntoMinute();
// Format the date
return .{
.numeric_day = secs - time.secs,
.date = std.fmt.allocPrint(allocator, "{d:0>4}{d:0>2}{d:0>2}T{d:0>2}{d:0>2}{d:0>2}Z", .{
year,
month,
day,
hours,
minutes,
seconds,
}) catch |err| bun.handleOom(err),
};
}
const DIGESTED_HMAC_256_LEN = 32;
pub const SignResult = struct {
amz_date: []const u8,
host: []const u8,
authorization: []const u8,
url: []const u8,
content_disposition: []const u8 = "",
content_md5: []const u8 = "",
session_token: []const u8 = "",
acl: ?ACL = null,
storage_class: ?StorageClass = null,
_headers: [8]picohttp.Header = .{
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
},
_headers_len: u8 = 0,
pub fn headers(this: *const @This()) []const picohttp.Header {
return this._headers[0..this._headers_len];
}
pub fn mixWithHeader(this: *const @This(), headers_buffer: []picohttp.Header, header: picohttp.Header) []const picohttp.Header {
// copy the headers to buffer
const len = this._headers_len;
for (this._headers[0..len], 0..len) |existing_header, i| {
headers_buffer[i] = existing_header;
}
headers_buffer[len] = header;
return headers_buffer[0 .. len + 1];
}
pub fn deinit(this: *const @This()) void {
if (this.amz_date.len > 0) {
bun.freeSensitive(bun.default_allocator, this.amz_date);
}
if (this.session_token.len > 0) {
bun.freeSensitive(bun.default_allocator, this.session_token);
}
if (this.content_disposition.len > 0) {
bun.freeSensitive(bun.default_allocator, this.content_disposition);
}
if (this.host.len > 0) {
bun.freeSensitive(bun.default_allocator, this.host);
}
if (this.authorization.len > 0) {
bun.freeSensitive(bun.default_allocator, this.authorization);
}
if (this.url.len > 0) {
bun.freeSensitive(bun.default_allocator, this.url);
}
if (this.content_md5.len > 0) {
bun.default_allocator.free(this.content_md5);
}
}
};
pub const SignQueryOptions = struct {
expires: usize = 86400,
};
pub const SignOptions = struct {
path: []const u8,
method: bun.http.Method,
content_hash: ?[]const u8 = null,
content_md5: ?[]const u8 = null,
search_params: ?[]const u8 = null,
content_disposition: ?[]const u8 = null,
acl: ?ACL = null,
storage_class: ?StorageClass = null,
};
/// This is not used for signing but for console.log output, is just nice to have
pub fn guessBucket(endpoint: []const u8) ?[]const u8 {
// check if is amazonaws.com
if (strings.indexOf(endpoint, ".amazonaws.com")) |_| {
// check if is .s3. virtual host style
if (strings.indexOf(endpoint, ".s3.")) |end| {
// its https://bucket-name.s3.region-code.amazonaws.com/key-name
const start = strings.indexOf(endpoint, "/") orelse {
return endpoint[0..end];
};
return endpoint[start + 1 .. end];
}
} else if (strings.indexOf(endpoint, ".r2.cloudflarestorage.com")) |r2_start| {
// check if is <BUCKET>.<ACCOUNT_ID>.r2.cloudflarestorage.com
const end = strings.indexOf(endpoint, ".") orelse return null; // actually unreachable
if (end > 0 and r2_start == end) {
// its https://<ACCOUNT_ID>.r2.cloudflarestorage.com
return null;
}
// ok its virtual host style
const start = strings.indexOf(endpoint, "/") orelse {
return endpoint[0..end];
};
return endpoint[start + 1 .. end];
}
return null;
}
pub fn guessRegion(endpoint: []const u8) []const u8 {
if (endpoint.len > 0) {
if (strings.endsWith(endpoint, ".r2.cloudflarestorage.com")) return "auto";
if (strings.indexOf(endpoint, ".amazonaws.com")) |end| {
if (strings.indexOf(endpoint, "s3.")) |start| {
return endpoint[start + 3 .. end];
}
}
// endpoint is informed but is not s3 so auto detect
return "auto";
}
// no endpoint so we default to us-east-1 because s3.us-east-1.amazonaws.com is the default endpoint
return "us-east-1";
}
fn toHexChar(value: u8) !u8 {
return switch (value) {
0...9 => value + '0',
10...15 => (value - 10) + 'A',
else => error.InvalidHexChar,
};
}
pub fn encodeURIComponent(input: []const u8, buffer: []u8, comptime encode_slash: bool) ![]const u8 {
var written: usize = 0;
for (input) |c| {
switch (c) {
// RFC 3986 Unreserved Characters (do not encode)
'A'...'Z', 'a'...'z', '0'...'9', '-', '_', '.', '~' => {
if (written >= buffer.len) return error.BufferTooSmall;
buffer[written] = c;
written += 1;
},
// All other characters need to be percent-encoded
else => {
if (!encode_slash and (c == '/' or c == '\\')) {
if (written >= buffer.len) return error.BufferTooSmall;
buffer[written] = if (c == '\\') '/' else c;
written += 1;
continue;
}
if (written + 3 > buffer.len) return error.BufferTooSmall;
buffer[written] = '%';
// Convert byte to hex
const high_nibble: u8 = (c >> 4) & 0xF;
const low_nibble: u8 = c & 0xF;
buffer[written + 1] = try toHexChar(high_nibble);
buffer[written + 2] = try toHexChar(low_nibble);
written += 3;
},
}
}
return buffer[0..written];
}
fn normalizeName(name: []const u8) []const u8 {
if (name.len == 0) return name;
return std.mem.trim(u8, name, "/\\");
}
pub fn signRequest(this: *const @This(), signOptions: SignOptions, comptime allow_empty_path: bool, signQueryOption: ?SignQueryOptions) !SignResult {
const method = signOptions.method;
const request_path = signOptions.path;
const content_hash = signOptions.content_hash;
var content_md5 = signOptions.content_md5;
if (content_md5) |content_md5_val| {
const len = bun.base64.encodeLen(content_md5_val);
const content_md5_as_base64 = bun.handleOom(bun.default_allocator.alloc(u8, len));
content_md5 = content_md5_as_base64[0..bun.base64.encode(content_md5_as_base64, content_md5_val)];
}
const search_params = signOptions.search_params;
var content_disposition = signOptions.content_disposition;
if (content_disposition != null and content_disposition.?.len == 0) {
content_disposition = null;
}
const session_token: ?[]const u8 = if (this.sessionToken.len == 0) null else this.sessionToken;
const acl: ?[]const u8 = if (signOptions.acl) |acl_value| acl_value.toString() else null;
const storage_class: ?[]const u8 = if (signOptions.storage_class) |storage_class| storage_class.toString() else null;
if (this.accessKeyId.len == 0 or this.secretAccessKey.len == 0) return error.MissingCredentials;
const signQuery = signQueryOption != null;
const expires = if (signQueryOption) |options| options.expires else 0;
const method_name = switch (method) {
.GET => "GET",
.POST => "POST",
.PUT => "PUT",
.DELETE => "DELETE",
.HEAD => "HEAD",
else => return error.InvalidMethod,
};
const region = if (this.region.len > 0) this.region else guessRegion(this.endpoint);
var full_path = request_path;
// handle \\ on bucket name
if (strings.startsWith(full_path, "/")) {
full_path = full_path[1..];
} else if (strings.startsWith(full_path, "\\")) {
full_path = full_path[1..];
}
var path: []const u8 = full_path;
var bucket: []const u8 = this.bucket;
if (!this.virtual_hosted_style) {
if (bucket.len == 0) {
// guess bucket using path
if (strings.indexOf(full_path, "/")) |end| {
if (strings.indexOf(full_path, "\\")) |backslash_index| {
if (backslash_index < end) {
bucket = full_path[0..backslash_index];
path = full_path[backslash_index + 1 ..];
}
}
bucket = full_path[0..end];
path = full_path[end + 1 ..];
} else if (strings.indexOf(full_path, "\\")) |backslash_index| {
bucket = full_path[0..backslash_index];
path = full_path[backslash_index + 1 ..];
} else {
return error.InvalidPath;
}
}
}
path = normalizeName(path);
bucket = normalizeName(bucket);
// if we allow path.len == 0 it will list the bucket for now we disallow
if (!allow_empty_path and path.len == 0) return error.InvalidPath;
var normalized_path_buffer: [1024 + 63 + 2]u8 = undefined; // 1024 max key size and 63 max bucket name
var path_buffer: [1024]u8 = undefined;
var bucket_buffer: [63]u8 = undefined;
bucket = encodeURIComponent(bucket, &bucket_buffer, false) catch return error.InvalidPath;
path = encodeURIComponent(path, &path_buffer, false) catch return error.InvalidPath;
// Default to https. Only use http if they explicit pass "http://" as the endpoint.
const protocol = if (this.insecure_http) "http" else "https";
// detect service name and host from region or endpoint
var endpoint = this.endpoint;
var extra_path: []const u8 = "";
const host = brk_host: {
if (this.endpoint.len > 0) {
if (this.endpoint.len >= 2048) return error.InvalidEndpoint;
var host = this.endpoint;
if (bun.strings.indexOf(this.endpoint, "/")) |index| {
host = this.endpoint[0..index];
extra_path = this.endpoint[index..];
}
// only the host part is needed here
break :brk_host try bun.default_allocator.dupe(u8, host);
} else {
if (this.virtual_hosted_style) {
// virtual hosted style requires a bucket name if an endpoint is not provided
if (bucket.len == 0) {
return error.InvalidEndpoint;
}
// default to https://<BUCKET_NAME>.s3.<REGION>.amazonaws.com/
endpoint = try std.fmt.allocPrint(bun.default_allocator, "{s}.s3.{s}.amazonaws.com", .{ bucket, region });
break :brk_host endpoint;
}
endpoint = try std.fmt.allocPrint(bun.default_allocator, "s3.{s}.amazonaws.com", .{region});
break :brk_host endpoint;
}
};
errdefer bun.default_allocator.free(host);
const normalizedPath = brk: {
if (this.virtual_hosted_style) {
break :brk std.fmt.bufPrint(&normalized_path_buffer, "{s}/{s}", .{ extra_path, path }) catch return error.InvalidPath;
} else {
break :brk std.fmt.bufPrint(&normalized_path_buffer, "{s}/{s}/{s}", .{ extra_path, bucket, path }) catch return error.InvalidPath;
}
};
const date_result = getAMZDate(bun.default_allocator);
const amz_date = date_result.date;
errdefer bun.default_allocator.free(amz_date);
const amz_day = amz_date[0..8];
const signed_headers = if (signQuery) "host" else brk: {
if (content_md5 != null) {
if (storage_class != null) {
if (acl != null) {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "content-disposition;content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
} else {
if (session_token != null) {
break :brk "content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
}
} else {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "content-disposition;content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
} else {
if (session_token != null) {
break :brk "content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
}
}
} else {
if (acl != null) {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "content-disposition;content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date";
}
} else {
if (session_token != null) {
break :brk "content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "content-md5;host;x-amz-acl;x-amz-content-sha256;x-amz-date";
}
}
} else {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "content-disposition;content-md5;host;x-amz-content-sha256;x-amz-date";
}
} else {
if (session_token != null) {
break :brk "content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "content-md5;host;x-amz-content-sha256;x-amz-date";
}
}
}
}
} else {
if (storage_class != null) {
if (acl != null) {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "content-disposition;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
} else {
if (session_token != null) {
break :brk "host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
}
} else {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "content-disposition;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
} else {
if (session_token != null) {
break :brk "host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-storage-class";
} else {
break :brk "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
}
}
}
} else {
if (acl != null) {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "content-disposition;host;x-amz-acl;x-amz-content-sha256;x-amz-date";
}
} else {
if (session_token != null) {
break :brk "host;x-amz-acl;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "host;x-amz-acl;x-amz-content-sha256;x-amz-date";
}
}
} else {
if (content_disposition != null) {
if (session_token != null) {
break :brk "content-disposition;host;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "content-disposition;host;x-amz-content-sha256;x-amz-date";
}
} else {
if (session_token != null) {
break :brk "host;x-amz-content-sha256;x-amz-date;x-amz-security-token";
} else {
break :brk "host;x-amz-content-sha256;x-amz-date";
}
}
}
}
}
};
const service_name = "s3";
const aws_content_hash = if (content_hash) |hash| hash else ("UNSIGNED-PAYLOAD");
var tmp_buffer: [4096]u8 = undefined;
const authorization = brk: {
// we hash the hash so we need 2 buffers
var hmac_sig_service: [bun.BoringSSL.c.EVP_MAX_MD_SIZE]u8 = undefined;
var hmac_sig_service2: [bun.BoringSSL.c.EVP_MAX_MD_SIZE]u8 = undefined;
const sigDateRegionServiceReq = brk_sign: {
const key = try std.fmt.bufPrint(&tmp_buffer, "{s}{s}{s}", .{ region, service_name, this.secretAccessKey });
var cache = (jsc.VirtualMachine.getMainThreadVM() orelse jsc.VirtualMachine.get()).rareData().awsCache();
if (cache.get(date_result.numeric_day, key)) |cached| {
break :brk_sign cached;
}
// not cached yet lets generate a new one
const sigDate = bun.hmac.generate(try std.fmt.bufPrint(&tmp_buffer, "AWS4{s}", .{this.secretAccessKey}), amz_day, .sha256, &hmac_sig_service) orelse return error.FailedToGenerateSignature;
const sigDateRegion = bun.hmac.generate(sigDate, region, .sha256, &hmac_sig_service2) orelse return error.FailedToGenerateSignature;
const sigDateRegionService = bun.hmac.generate(sigDateRegion, service_name, .sha256, &hmac_sig_service) orelse return error.FailedToGenerateSignature;
const result = bun.hmac.generate(sigDateRegionService, "aws4_request", .sha256, &hmac_sig_service2) orelse return error.FailedToGenerateSignature;
cache.set(date_result.numeric_day, key, hmac_sig_service2[0..DIGESTED_HMAC_256_LEN].*);
break :brk_sign result;
};
if (signQuery) {
var token_encoded_buffer: [2048]u8 = undefined; // token is normaly like 600-700 but can be up to 2k
var encoded_session_token: ?[]const u8 = null;
if (session_token) |token| {
encoded_session_token = encodeURIComponent(token, &token_encoded_buffer, true) catch return error.InvalidSessionToken;
}
var content_md5_encoded_buffer: [128]u8 = undefined; // MD5 as base64 (which is required for AWS SigV4) is always 44, when encoded its always 46 (44 + ==)
var encoded_content_md5: ?[]const u8 = null;
if (content_md5) |content_md5_value| {
encoded_content_md5 = encodeURIComponent(content_md5_value, &content_md5_encoded_buffer, true) catch return error.FailedToGenerateSignature;
}
// Build query parameters in alphabetical order for AWS Signature V4 canonical request
const canonical = brk_canonical: {
var stack_fallback = std.heap.stackFallback(512, bun.default_allocator);
const allocator = stack_fallback.get();
var query_parts: bun.BoundedArray([]const u8, 10) = .{};
// Add parameters in alphabetical order: Content-MD5, X-Amz-Acl, X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-Security-Token, X-Amz-SignedHeaders, x-amz-storage-class
if (encoded_content_md5) |encoded_content_md5_value| {
try query_parts.append(try std.fmt.allocPrint(allocator, "Content-MD5={s}", .{encoded_content_md5_value}));
}
if (acl) |acl_value| {
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-Acl={s}", .{acl_value}));
}
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-Algorithm=AWS4-HMAC-SHA256", .{}));
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-Credential={s}%2F{s}%2F{s}%2F{s}%2Faws4_request", .{ this.accessKeyId, amz_day, region, service_name }));
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-Date={s}", .{amz_date}));
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-Expires={}", .{expires}));
if (encoded_session_token) |token| {
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-Security-Token={s}", .{token}));
}
try query_parts.append(try std.fmt.allocPrint(allocator, "X-Amz-SignedHeaders=host", .{}));
if (storage_class) |storage_class_value| {
try query_parts.append(try std.fmt.allocPrint(allocator, "x-amz-storage-class={s}", .{storage_class_value}));
}
// Join query parameters with &
var query_string = std.ArrayList(u8).init(allocator);
defer query_string.deinit();
for (query_parts.slice(), 0..) |part, i| {
if (i > 0) try query_string.append('&');
try query_string.appendSlice(part);
allocator.free(part);
}
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\n\nhost\n{s}", .{ method_name, normalizedPath, query_string.items, host, aws_content_hash });
};
var sha_digest = std.mem.zeroes(bun.sha.SHA256.Digest);
bun.sha.SHA256.hash(canonical, &sha_digest, jsc.VirtualMachine.get().rareData().boringEngine());
const signValue = try std.fmt.bufPrint(&tmp_buffer, "AWS4-HMAC-SHA256\n{s}\n{s}/{s}/{s}/aws4_request\n{s}", .{ amz_date, amz_day, region, service_name, std.fmt.bytesToHex(sha_digest[0..bun.sha.SHA256.digest], .lower) });
const signature = bun.hmac.generate(sigDateRegionServiceReq, signValue, .sha256, &hmac_sig_service) orelse return error.FailedToGenerateSignature;
// Build final URL with query parameters in alphabetical order to match canonical request
var url_stack_fallback = std.heap.stackFallback(512, bun.default_allocator);
const url_allocator = url_stack_fallback.get();
var url_query_parts: bun.BoundedArray([]const u8, 10) = .{};
// Add parameters in alphabetical order: Content-MD5, X-Amz-Acl, X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-Security-Token, X-Amz-SignedHeaders, x-amz-storage-class, X-Amz-Signature
if (encoded_content_md5) |encoded_content_md5_value| {
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "Content-MD5={s}", .{encoded_content_md5_value}));
}
if (acl) |acl_value| {
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Acl={s}", .{acl_value}));
}
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Algorithm=AWS4-HMAC-SHA256", .{}));
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Credential={s}%2F{s}%2F{s}%2F{s}%2Faws4_request", .{ this.accessKeyId, amz_day, region, service_name }));
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Date={s}", .{amz_date}));
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Expires={}", .{expires}));
if (encoded_session_token) |token| {
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Security-Token={s}", .{token}));
}
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-Signature={s}", .{std.fmt.bytesToHex(signature[0..DIGESTED_HMAC_256_LEN], .lower)}));
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "X-Amz-SignedHeaders=host", .{}));
if (storage_class) |storage_class_value| {
try url_query_parts.append(try std.fmt.allocPrint(url_allocator, "x-amz-storage-class={s}", .{storage_class_value}));
}
// Join URL query parameters with &
var url_query_string = std.ArrayList(u8).init(url_allocator);
defer url_query_string.deinit();
for (url_query_parts.slice(), 0..) |part, i| {
if (i > 0) try url_query_string.append('&');
try url_query_string.appendSlice(part);
url_allocator.free(part);
}
break :brk try std.fmt.allocPrint(bun.default_allocator, "{s}://{s}{s}?{s}", .{ protocol, host, normalizedPath, url_query_string.items });
} else {
var encoded_content_disposition_buffer: [255]u8 = undefined;
const encoded_content_disposition: []const u8 = if (content_disposition) |cd| encodeURIComponent(cd, &encoded_content_disposition_buffer, true) catch return error.ContentTypeIsTooLong else "";
const canonical = brk_canonical: {
if (content_md5) |content_md5_value| {
if (storage_class) |storage_class_value| {
if (acl) |acl_value| {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, acl_value, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, acl_value, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, acl_value, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, acl_value, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
}
} else {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
}
}
} else {
if (acl) |acl_value| {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, acl_value, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, acl_value, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, acl_value, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, acl_value, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
}
} else {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, content_md5_value, host, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", content_md5_value, host, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-md5:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{
method_name,
normalizedPath,
if (search_params) |p| p[1..] else "",
content_md5_value,
host,
aws_content_hash,
amz_date,
signed_headers,
aws_content_hash,
});
}
}
}
}
} else {
if (storage_class) |storage_class_value| {
if (acl) |acl_value| {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, acl_value, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, acl_value, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, acl_value, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, acl_value, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
}
} else {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, aws_content_hash, amz_date, token, storage_class_value, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-storage-class:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, aws_content_hash, amz_date, storage_class_value, signed_headers, aws_content_hash });
}
}
}
} else {
if (acl) |acl_value| {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, acl_value, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, acl_value, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, acl_value, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-acl:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, acl_value, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
}
} else {
if (content_disposition != null) {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\ncontent-disposition:{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", encoded_content_disposition, host, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
} else {
if (session_token) |token| {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\nx-amz-security-token:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, aws_content_hash, amz_date, token, signed_headers, aws_content_hash });
} else {
break :brk_canonical try std.fmt.bufPrint(&tmp_buffer, "{s}\n{s}\n{s}\nhost:{s}\nx-amz-content-sha256:{s}\nx-amz-date:{s}\n\n{s}\n{s}", .{ method_name, normalizedPath, if (search_params) |p| p[1..] else "", host, aws_content_hash, amz_date, signed_headers, aws_content_hash });
}
}
}
}
}
};
var sha_digest = std.mem.zeroes(bun.sha.SHA256.Digest);
bun.sha.SHA256.hash(canonical, &sha_digest, jsc.VirtualMachine.get().rareData().boringEngine());
const signValue = try std.fmt.bufPrint(&tmp_buffer, "AWS4-HMAC-SHA256\n{s}\n{s}/{s}/{s}/aws4_request\n{s}", .{ amz_date, amz_day, region, service_name, std.fmt.bytesToHex(sha_digest[0..bun.sha.SHA256.digest], .lower) });
const signature = bun.hmac.generate(sigDateRegionServiceReq, signValue, .sha256, &hmac_sig_service) orelse return error.FailedToGenerateSignature;
break :brk try std.fmt.allocPrint(
bun.default_allocator,
"AWS4-HMAC-SHA256 Credential={s}/{s}/{s}/{s}/aws4_request, SignedHeaders={s}, Signature={s}",
.{ this.accessKeyId, amz_day, region, service_name, signed_headers, std.fmt.bytesToHex(signature[0..DIGESTED_HMAC_256_LEN], .lower) },
);
}
};
errdefer bun.default_allocator.free(authorization);
if (signQuery) {
defer bun.default_allocator.free(host);
defer bun.default_allocator.free(amz_date);
return SignResult{
.amz_date = "",
.host = "",
.authorization = "",
.acl = signOptions.acl,
.url = authorization,
.storage_class = signOptions.storage_class,
};
}
var result = SignResult{
.amz_date = amz_date,
.host = host,
.authorization = authorization,
.acl = signOptions.acl,
.storage_class = signOptions.storage_class,
.url = try std.fmt.allocPrint(bun.default_allocator, "{s}://{s}{s}{s}", .{ protocol, host, normalizedPath, if (search_params) |s| s else "" }),
._headers = [_]picohttp.Header{
.{ .name = "x-amz-content-sha256", .value = aws_content_hash },
.{ .name = "x-amz-date", .value = amz_date },
.{ .name = "Host", .value = host },
.{ .name = "Authorization", .value = authorization[0..] },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
.{ .name = "", .value = "" },
},
._headers_len = 4,
};
if (acl) |acl_value| {
result._headers[result._headers_len] = .{ .name = "x-amz-acl", .value = acl_value };
result._headers_len += 1;
}
if (session_token) |token| {
const session_token_value = bun.handleOom(bun.default_allocator.dupe(u8, token));
result.session_token = session_token_value;
result._headers[result._headers_len] = .{ .name = "x-amz-security-token", .value = session_token_value };
result._headers_len += 1;
}
if (content_disposition) |cd| {
const content_disposition_value = bun.handleOom(bun.default_allocator.dupe(u8, cd));
result.content_disposition = content_disposition_value;
result._headers[result._headers_len] = .{ .name = "Content-Disposition", .value = content_disposition_value };
result._headers_len += 1;
}
if (storage_class) |storage_class_value| {
result._headers[result._headers_len] = .{ .name = "x-amz-storage-class", .value = storage_class_value };
result._headers_len += 1;
}
if (content_md5) |c_md5| {
const content_md5_value = bun.handleOom(bun.default_allocator.dupe(u8, c_md5));
result.content_md5 = content_md5_value;
result._headers[result._headers_len] = .{ .name = "content-md5", .value = content_md5_value };
result._headers_len += 1;
}
return result;
}
};
pub const S3CredentialsWithOptions = struct {
credentials: S3Credentials,
options: MultiPartUploadOptions = .{},
acl: ?ACL = null,
storage_class: ?StorageClass = null,
/// indicates if the credentials have changed
changed_credentials: bool = false,
/// indicates if the virtual hosted style is used
virtual_hosted_style: bool = false,
_accessKeyIdSlice: ?jsc.ZigString.Slice = null,
_secretAccessKeySlice: ?jsc.ZigString.Slice = null,
_regionSlice: ?jsc.ZigString.Slice = null,
_endpointSlice: ?jsc.ZigString.Slice = null,
_bucketSlice: ?jsc.ZigString.Slice = null,
_sessionTokenSlice: ?jsc.ZigString.Slice = null,
pub fn deinit(this: *@This()) void {
if (this._accessKeyIdSlice) |slice| slice.deinit();
if (this._secretAccessKeySlice) |slice| slice.deinit();
if (this._regionSlice) |slice| slice.deinit();
if (this._endpointSlice) |slice| slice.deinit();
if (this._bucketSlice) |slice| slice.deinit();
if (this._sessionTokenSlice) |slice| slice.deinit();
}
};
const std = @import("std");
const ACL = @import("./acl.zig").ACL;
const MultiPartUploadOptions = @import("./multipart_options.zig").MultiPartUploadOptions;
const StorageClass = @import("./storage_class.zig").StorageClass;
const bun = @import("bun");
const jsc = bun.jsc;
const picohttp = bun.picohttp;
const strings = bun.strings;