parser

Simple Amazon S3 Server

Author: MoKo [August 19, 2026]
Version:
Tags: S3

Only part of the S3 API is implemented — single-part and multipart object uploads, GET/HEAD/DELETE, and a ListObjectsV2-style bucket listing — enough for the server to work as a backend for a real S3 client such as rclone.

Hooking it up

To hook up the script, point Apache's CGI/mod_parser handler at s3-server.p and rewrite everything that isn't already an existing static file to it:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .* - [L]
RewriteRule .* /s3-server.p [L,QSA]

The first rule serves directly any file that already exists on disk. Everything else — writes, multipart operations, bucket listing, and the redirect trick for GET/HEAD on individual keys — falls through to the script.

S3 clients pass the access key id in the Authorization header, but Apache strips that header from CGI requests by default. For the script to see it as $env:HTTP_AUTHORIZATION, this directive is needed:

CGIPassAuth On

It has to apply specifically in the context of the directory the CGI script actually runs from (usually cgi-bin), not wherever the RewriteRules above live — set it directly in Apache's config for that directory, or in cgi-bin/.htaccess if AllowOverride is enabled there.

Without it, ^auth[] will never see the Credential=... value — any bucket with $.accessKeyId set in $ALLOW will reject requests with 403. This doesn't affect buckets where only the IP is checked.

Access and adding buckets

There's no separate bucket-creation API — the PUT clients normally use to create a bucket simply does nothing here. Which buckets a client is actually allowed to write to is a separate matter, handled by the $ALLOW hash in @auto[], keyed by bucket name:

$ALLOW[
	$.default[
		$.ip[^^127\.0\.0\.1]
		$.accessKeyId[]
	]
]

To add a new bucket, add another entry to $ALLOW with the bucket name as the key:

$ALLOW[
	$.default[
		$.ip[^^127\.0\.0\.1]
		$.accessKeyId[]
	]
	$.my-bucket[
		$.ip[^^10\.0\.0\.]
		$.accessKeyId[some-secret-id]
	]
]

Each entry has two independent, optional checks:

  • $.ip — a regular expression matched against $env:REMOTE_ADDR. Not specified — any address matches.
  • $.accessKeyId — matched against the access key id extracted from the request's Authorization header (the Credential=... part). Not specified — any key matches.

Requests for a bucket not listed in $ALLOW are rejected immediately. Note that this is deliberately not a real AWS SigV4 signature check — there's no verification that the request was actually signed with the corresponding secret key. It's only enough to restrict access to a bucket to a known IP and/or a shared access key id acting as a bearer token; treat $.accessKeyId as a shared secret, not as real request authentication.

Storage layout

  • $PREFIX (/storage) — where finished objects live, one subdirectory per bucket; the object's key maps directly to a path on disk.
  • $MULTIPART (/multipart) — a temporary area for in-progress multipart uploads. Parts are written to bucket/key/uploadId/partNumber and are only assembled into the final object once the client sends CompleteMultipartUpload; an aborted or abandoned upload just leaves temporary files there.
  • The request log can optionally be written to a file.

Supported operations

  • Create bucket — PUT with no key; the bucket's directory appears on disk on the first object write.
  • List objects — GET with no key: ListObjectsV2-style, supports prefix, delimiter, max-keys, and pagination via continuation-token/marker.
  • Upload object — PUT: upload; the response sets ETag from the md5 of the request body.
  • Get object / HEAD — GET / HEAD: served via a CGI Local Redirect, i.e. Apache serves the file directly, not Parser.
  • Delete object — DELETE.
  • Initiate multipart upload — POST without uploadId.
  • Upload part — PUT with partNumber and uploadId.
  • Complete multipart upload — POST with uploadId: parts are assembled in ascending part-number order.
  • Abort multipart upload — DELETE with uploadId: deletes all uploaded parts.
Download:

s3-server.zip (20.08.2026  2.9 KB)
Simple S3-compatible server.