# `s3`

Portable S3-compatible client library layered on top of the normalized `http_client` stack. The current implementation focuses on general-purpose bucket operations and AWS Signature Version 4 request signing.

The library depends on the contributed `xml_parser` parser to decode S3 XML responses and wraps it behind the `s3_xml` adapter so the public API exposes stable S3-specific terms instead of generic XML parse trees.

This library can be used with backend Prolog systems that support unbound integer arithmetic and the `sockets` library: ECLiPSe, SICStus Prolog, SWI-Prolog, Trealla Prolog, and XVM.

## API documentation

Open the ../../apis/library_index.html#s3 link in a web browser.

## Loading

To load the library, load the `loader.lgt` file:

    | ?- logtalk_load(s3(loader)).

## Testing

To test this library, load the `tester.lgt` file:

    | ?- logtalk_load(s3(tester)).

## Current scope

- `list_buckets/2`

- `head_bucket/3`

- `list_objects_v2/4`

- `head_object/4`

- `get_object/5`

- `presigned_get_object/4`

- `put_object/6`

- `presigned_put_object/4`

- `presigned_post_object/4`

- `delete_object/4`

- `copy_object/5`

- explicit session wrapper via `s3_session(_HTTPSocket_)`

- AWS Signature Version 4 request signing for service `s3`

- virtual-hosted and path-style endpoint generation

- direct support for custom S3-compatible endpoints such as MinIO

## Basic usage

For typical file transfers, prefer the file-based arities:

- `get_object/5` downloads an object to a local file path and returns response metadata separately.

- `put_object/6` uploads a local file path and returns the object ETag and response metadata separately.

The `presigned_get_object/4`, `presigned_put_object/4`, and `presigned_post_object/4` predicates generate portable SigV4 query-string URLs for external HTTP clients. They default to `UNSIGNED-PAYLOAD`, accept `expires(Seconds)` with a maximum of `604800` seconds, support signed payloads using `payload_hash_mode(signed)` plus `payload_hash(Hash)`, and sign any extra request headers supplied using `headers/1`.

Result terms for the predicates returning a `Result` argument are:

- `delete_object/4` -> `delete_result(Properties)`

- `copy_object/5` -> `copy_result(ETag, Properties)`

List buckets using the default TLS-capable transport:

    | ?- s3_client::list_buckets(
             Buckets,
             [
                 credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
                 amz_date('20240627T120000Z')
             ]
         ).

Download `report.txt` from a custom S3-compatible endpoint into a local file:

    | ?- s3_client::get_object(
            photos,
            'report.txt',
            'report.txt',
            Properties,
            [
                 endpoint('http://127.0.0.1:9000'),
                 addressing_style(path),
                 credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
                 amz_date('20240627T120000Z')
             ]
         ).

Upload the same local `report.txt` file to the same custom endpoint:

    | ?- s3_client::put_object(
            photos,
            'report.txt',
            'report.txt',
            ETag,
            Properties,
            [
                endpoint('http://127.0.0.1:9000'),
                addressing_style(path),
                credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
                amz_date('20240627T120000Z')
            ]
         ).

Use an explicit session handle to carry defaults:

    | ?- s3_session(http_process_transport)::open(
             Session,
             [
                 region('eu-west-1'),
                 credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
                 amz_date('20240627T120000Z')
             ]
         ),
         s3_session(http_process_transport)::list_buckets(Session, Buckets, []),
         s3_session(http_process_transport)::close(Session).

Generate a presigned download URL for `report.txt`:

    | ?- s3_client::presigned_get_object(
            photos,
            'report.txt',
            URL,
            [
                region('eu-west-1'),
                credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
                amz_date('20240627T120000Z'),
                expires(600)
            ]
         ).

Generate a presigned upload URL for the same `report.txt` object:

    | ?- s3_client::presigned_put_object(
            photos,
            'report.txt',
            URL,
            [
                endpoint('http://127.0.0.1:9000'),
                addressing_style(path),
                credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
                headers([content_type-'text/plain']),
                amz_date('20240627T120000Z'),
                expires(300)
            ]
         ).

Generate a presigned POST URL for the same `report.txt` object:

    | ?- s3_client::presigned_post_object(
            photos,
            'report.txt',
            URL,
            [
                endpoint('http://127.0.0.1:9000'),
                addressing_style(path),
                credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
                amz_date('20240627T120000Z'),
                expires(300)
            ]
         ).

Generate a presigned URL that signs a precomputed SHA-256 payload hash:

    | ?- s3_client::presigned_put_object(
            photos,
            'report.txt',
            URL,
            [
                region('eu-west-1'),
                credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
                amz_date('20240627T120000Z'),
                payload_hash_mode(signed),
                payload_hash('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824')
            ]
         ).

When using signed extra headers such as `content_type-'text/plain'`, the eventual HTTP client must send the same headers when dereferencing the URL.

## Current limitations

- Time signing defaults derive UTC from the local system clock plus the configurable `utc_offset_seconds/1` option. For deterministic use and tests, prefer passing `amz_date/1` or `request_time/1`.

- Multipart uploads are not implemented yet.

- S3 Express / directory bucket APIs are outside the current scope.