Skip to content

s3

stable

Utility functions for building AWS S3 request components: endpoints, signed URLs, presigned URLs, XML parsing, and payload hashing.

use plugin s3::{build_endpoint, build_object_url, content_type, …}
10 functions Database
/ filter jk navigate Esc clear
Functions (10)
  1. build_endpoint Build an S3 bucket endpoint URL
  2. build_object_url Build a full URL for an S3 object
  3. content_type Map a file extension to a MIME type
  4. sign_v4_header Compute an AWS Signature V4 Authorization header
  5. parse_object_key Split an S3 key into directory, name, and extension
  6. build_presigned_url Generate a presigned GET URL with expiry
  7. build_copy_source Build the x-amz-copy-source header value
  8. parse_list_xml Parse a ListBucketResult XML response
  9. build_delete_xml Build XML body for multi-object delete
  10. sha256 Compute the SHA-256 hex digest of a string

Overview

s3 is a dependency-free toolkit of pure helper functions for assembling the pieces of an AWS S3 (or S3-compatible) HTTP request by hand — endpoint and object URLs, AWS Signature Version 4 Authorization headers, presigned GET URLs, request/response XML bodies, MIME types, and SHA-256 payload hashes. It is stateless and holds no client or connection: every function takes plain strings, numbers, and tables and returns a plain string or table, so you stay in full control of which HTTP library actually sends the request. Reach for it when you need to talk to S3 from a thin runtime, sign requests yourself, or generate shareable links without pulling in a heavyweight SDK.

The mental model is to compose: build a URL or canonical request from the bucket, key, and region, hash the payload with sha256, feed everything into sign_v4_header to authorize it, then parse the XML that S3 sends back with parse_list_xml.

Common patterns

Sign a PUT upload: hash the payload, then build the Authorization header.

use plugin s3::{build_object_url, content_type, sha256, sign_v4_header}

let bucket = "my-bucket"
let region = "us-east-1"
let key = "uploads/report.pdf"
let body = "...file bytes..."

let url = build_object_url(bucket, key, region)
let ct = content_type("pdf")
let payload_hash = sha256(body)

let auth = sign_v4_header(
    "PUT",
    "/{key}",
    "",
    #{
        "host": "{bucket}.s3.{region}.amazonaws.com",
        "x-amz-content-sha256": payload_hash,
        "x-amz-date": "20240101T120000Z",
        "content-type": ct
    },
    payload_hash,
    "AKIAIOSFODNN7EXAMPLE",
    "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    region,
    "s3",
    "20240101T120000Z"
)
print("PUT {url}")
print("Authorization: {auth}")

Hand out a temporary download link without exposing credentials.

use plugin s3::{build_presigned_url}

let link = build_presigned_url(
    "my-bucket",
    "private/invoice.pdf",
    "us-east-1",
    "AKIAIOSFODNN7EXAMPLE",
    "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "20240101T120000Z",
    3600
)
print("share this for one hour: {link}")

List a bucket, then group the returned objects by file type.

use plugin s3::{parse_list_xml, parse_object_key}

let xml = "<ListBucketResult><Contents><Key>img/logo.png</Key><Size>2048</Size><LastModified>2024-01-01</LastModified></Contents></ListBucketResult>"
let objects = parse_list_xml(xml)

for obj in objects {
    let parts = parse_object_key(obj["key"])
    print("{parts["name"]} ({parts["ext"]}) — {obj["size"]} bytes")
}

Build an S3 bucket endpoint URL

Returns the standard AWS S3 endpoint URL for a bucket in the given region.

use plugin s3::{build_endpoint}

let url = build_endpoint("my-bucket", "us-east-1")
print(url)  // https://my-bucket.s3.us-east-1.amazonaws.com

Build a full URL for an S3 object

Returns the full HTTPS URL for a specific object in an S3 bucket. Leading slashes in the key are stripped automatically.

use plugin s3::{build_object_url}

let url = build_object_url("my-bucket", "images/photo.jpg", "eu-west-1")
print(url)  // https://my-bucket.s3.eu-west-1.amazonaws.com/images/photo.jpg

Map a file extension to a MIME type

Returns the MIME content type for a given file extension. Supports common web, image, audio, video, font, and archive types. Falls back to application/octet-stream for unknown extensions.

use plugin s3::{content_type}

print(content_type("png"))   // image/png
print(content_type(".json")) // application/json
print(content_type("wasm"))  // application/wasm
print(content_type("xyz"))   // application/octet-stream

Derive the type from a key by combining it with parse_object_key, so an upload sets the right Content-Type header automatically:

use plugin s3::{parse_object_key, content_type}

let parts = parse_object_key("assets/styles/main.css")
print(content_type(parts["ext"]))  // text/css

Compute an AWS Signature V4 Authorization header

Computes a complete AWS Signature Version 4 Authorization header value. The datetime_str must be in ISO 8601 basic format (e.g. "20240101T120000Z"). The headers table must contain lowercase header names.

use plugin s3::{sign_v4_header, sha256}

let payload_hash = sha256("")
let auth = sign_v4_header(
    "PUT",
    "/my-bucket/file.txt",
    "",
    #{"host": "my-bucket.s3.us-east-1.amazonaws.com", "x-amz-date": "20240101T120000Z"},
    payload_hash,
    "AKIAIOSFODNN7EXAMPLE",
    "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "us-east-1",
    "s3",
    "20240101T120000Z"
)
print(auth)

Split an S3 key into directory, name, and extension

Splits an S3 object key into its dir, name, and ext parts. Useful for routing objects by type or grouping by prefix.

use plugin s3::{parse_object_key}

let parts = parse_object_key("uploads/2024/photo.jpg")
print(parts["dir"])   // uploads/2024/
print(parts["name"])  // photo.jpg
print(parts["ext"])   // jpg

Keys with no directory or no extension yield empty strings for those fields:

use plugin s3::{parse_object_key}

let parts = parse_object_key("README")
print("dir=[{parts["dir"]}] name=[{parts["name"]}] ext=[{parts["ext"]}]")
// dir=[] name=[README] ext=[]

Generate a presigned GET URL with expiry

Generates a presigned GET URL that grants temporary access to a private S3 object without requiring AWS credentials on the client. The URL expires after expires_seconds seconds.

use plugin s3::{build_presigned_url}

let url = build_presigned_url(
    "my-bucket",
    "reports/q4.pdf",
    "us-east-1",
    "AKIAIOSFODNN7EXAMPLE",
    "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "20240101T120000Z",
    3600
)
print(url)

Build the x-amz-copy-source header value

Returns the value for the x-amz-copy-source header used in S3 CopyObject requests.

use plugin s3::{build_copy_source}

let source = build_copy_source("source-bucket", "original/file.txt")
print(source)  // /source-bucket/original/file.txt

Pair it with build_object_url to describe a full copy operation:

use plugin s3::{build_copy_source, build_object_url}

let source = build_copy_source("backups", "db/2024.sql")
let dest = build_object_url("archive", "db/2024.sql", "us-east-1")
print("copy {source} -> {dest}")

Parse a ListBucketResult XML response

Parses an S3 ListBucketResult XML response and returns a table of entries. Each entry has key, size, and last_modified fields.

use plugin s3::{parse_list_xml}

let xml = "<ListBucketResult><Contents><Key>img.png</Key><Size>1024</Size><LastModified>2024-01-01</LastModified></Contents></ListBucketResult>"
let objects = parse_list_xml(xml)
print(objects[1]["key"])   // img.png
print(objects[1]["size"])  // 1024

The result is a 1-indexed table you can iterate to total up the bucket size:

use plugin s3::{parse_list_xml}

let xml = "<ListBucketResult><Contents><Key>a.txt</Key><Size>10</Size><LastModified>2024-01-01</LastModified></Contents><Contents><Key>b.txt</Key><Size>20</Size><LastModified>2024-01-02</LastModified></Contents></ListBucketResult>"
let objects = parse_list_xml(xml)
for obj in objects {
    print("{obj["key"]} last modified {obj["last_modified"]}")
}

Build XML body for multi-object delete

Builds the XML request body for an S3 multi-object delete operation. The keys argument is a table of string keys. Special XML characters in keys are escaped automatically.

use plugin s3::{build_delete_xml}

let xml = build_delete_xml(["old/file1.txt", "old/file2.txt"])
print(xml)
// <?xml ...><Delete><Object><Key>old/file1.txt</Key></Object>...</Delete>

Compute the SHA-256 hex digest of a string

Returns the lowercase hex-encoded SHA-256 digest of the given string. Use this to compute the x-amz-content-sha256 payload hash required for V4 signing.

use plugin s3::{sha256}

let hash = sha256("hello world")
print(hash)  // b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576b9c5cdf9c37c5e67 (truncated)

let empty_hash = sha256("")
print(empty_hash)  // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
enespt-br