~/bend-docscommunity

sigv4.bend relies on unsafe/foreign

raw source on the hub · import bend-kit-sigv4@0.3.0.0/sigv4.bend as Sigv4

AWS Signature V4 signing and S3 object storage over Hairpin. Source: https://github.com/paymog/bend-kit/tree/main/sigv4

9 imports
import Base
import bend-kit-crypto@0.1.1.0/crypto.bend as Crypto
import bend-kit-hairpin@0.3.0.0/hairpin.bend as Hairpin
import bend-kit-hairpin@0.3.0.0/retry.bend as Retry
import bend-kit-http@0.32.0.0/http.bend as Http
import bend-kit-time@0.1.2.1/time.bend as Time
import 0x1f2d80f53f971b16c6de6a65cb1918ae/url.bend as Url
import 0xcfc8be7b076f41f95c8e118383892d55/encoding.bend as Enc
import 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytes

Types

type Credentials source · line 17 · raw

Data

Text (path, query, header values) is a String of code points; non-ASCII is sent as UTF-8. S3 decodes and re-encodes wire escapes once; other services encode an escaped wire path twice. SigV4 encoding keeps only A-Z a-z 0-9 - _ . ~ and uses uppercase %XX. date is the x-amz-date form, YYYYMMDDTHHMMSSZ. token is "" for long-term keys.

type Style source · line 419 · raw

Data

Path: {endpoint}/{bucket}/{key}. Virtual: {bucket}.{endpoint host}/{key}.

type Config source · line 424 · raw

Data

endpoint: scheme://host[:port], as https://s3.us-east-1.amazonaws.com or http://127.0.0.1:9000. Its path is ignored.

type Client source · line 427 · raw

Type

type Err source · line 433 · raw

Data

ErrNet: no response. ErrSign: the signer failed. ErrConfig: a bad endpoint or a missing setting. ErrS3: any status outside 2xx, redirects included, with S3's error Code and Message ("" when there is no error body, as for HEAD). ErrXml: a 2xx body that is not the XML asked for, with its text.

type Object source · line 441 · raw

Data

etag keeps S3's quotes. modified is S3's ISO 8601 text.

type Page source · line 445 · raw

Data

next: the continuation token when S3 cut the listing off.

type Meta source · line 449 · raw

Data

From a HEAD: Content-Length, ETag, Content-Type, and Last-Modified.

type Leaf source · line 519 · raw

Data

One closed element: its parent's name, its name, and its own text, entities decoded. Names drop any namespace prefix.

type Lx source · line 525 · raw

Data

Where the lexer is: text; after '<'; a start tag's name; its attributes, outside or in "..." or '...'; after '/' in a start tag; an end tag's name; <?...?>, after '?'; after '<!'; a comment, after one or two '-'; <![CDATA[ up to its '['; CDATA, after one or two ']'; an entity; <!DOCTYPE ...>.

type St source · line 549 · raw

Data

stack: open names, innermost first. txt and nm: text and the name or entity so far, reversed. out: leaves, last first. ok turns False at a mismatched end tag or an unknown entity.

type Where source · line 868 · raw

Data

origin: scheme://host field. host: the Host field, as Http sends it. path: the wire path.

type Acc source · line 1081 · raw

Data

cur: the <Contents> being read. Leaves come in document order, so a Contents closes after its fields.

Definitions

def utf8.c3 source · line 21 · raw

@three:Bool -> @+c:U32 -> @acc:String -> String

Code points to UTF-8 octets, one Char per octet. acc is reversed.

def utf8.c2 source · line 28 · raw

@two:Bool -> @+c:U32 -> @acc:String -> String

def utf8.c1 source · line 35 · raw

@one:Bool -> @+c:U32 -> @acc:String -> String

def utf8.go source · line 42 · raw

@s:String -> @acc:String -> String

def utf8 source · line 49 · raw

@s:String -> String

def encode source · line 53 · raw

@s:String -> @slash:Bool -> String

SigV4 URI encoding of text: UTF-8, then every octet but the unreserved ones as %XX. slash keeps '/'.

def dec.of source · line 60 · raw

@m:Maybe<&2, String> -> @+raw:String -> String

def norm source · line 68 · raw

@s:String -> String

A wire component, re-encoded. A malformed escape stays literal, so its '%' is encoded.

def norms source · line 72 · raw

@xs:List<&2, String> -> List<&2, String>

def wire.segs source · line 80 · raw

@xs:List<&2, String> -> List<&2, String>

Non-S3 services encode the wire path a second time; '%' becomes '%25'.

def dots.seg source · line 88 · raw

@skip:Bool -> @up:Bool -> @+seg:String -> @st:List<&2, String> -> List<&2, String>

Canonical URI (RFC 3986 §5.2.4 for services other than s3): "" and "." segments drop, ".." pops.

def dots source · line 97 · raw

@xs:List<&2, String> -> @st:List<&2, String> -> List<&2, String>

def uri.slash source · line 104 · raw

@empty:Bool -> @trail:Bool -> @+body:String -> String

def uri.dots source · line 113 · raw

@+path:String -> String

def uri.s3 source · line 119 · raw

@empty:Bool -> @+path:String -> String

S3 signs the path as sent: each segment re-encoded, nothing removed.

def uri.if source · line 126 · raw

@s3:Bool -> @+path:String -> String

def uri source · line 133 · raw

@+service:String -> @+path:String -> String

def pair source · line 137 · raw

@xs:List<&2, String> -> Sigma<&2, &2, String, _ => String>

Canonical query: split on '&', each "k=v" (a bare "k" is "k="), re-encoded, sorted by key then value.

def pairs.if source · line 144 · raw

@empty:Bool -> @+h:String -> @rest:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, Sigma<&2, &2, String, _ => String>>

def pairs source · line 151 · raw

@xs:List<&2, String> -> List<&2, Sigma<&2, &2, String, _ => String>>

def pair.le.of source · line 158 · raw

@c:Cmp -> @av:String -> @bv:String -> Bool

def pair.le source · line 167 · raw

@a:Sigma<&2, &2, String, _ => String> -> @b:Sigma<&2, &2, String, _ => String> -> Bool

def pairs.show source · line 172 · raw

@xs:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, String>

def query source · line 179 · raw

@q:String -> String

def squash.if source · line 184 · raw

@space:Bool -> @sp:Bool -> @+c:U32 -> @rest:String -> String

Canonical headers: lowercase names, trimmed values with space runs squashed, sorted by name, and a repeated name's values joined by ',' in the order given.

def squash source · line 191 · raw

@s:String -> @sp:Bool -> String

def hdrs.low source · line 198 · raw

@xs:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, Sigma<&2, &2, String, _ => String>>

def name.le source · line 205 · raw

@a:Sigma<&2, &2, String, _ => String> -> @b:Sigma<&2, &2, String, _ => String> -> Bool

def grp.if source · line 210 · raw

@same:Bool -> @+n:String -> @+v:String -> @+pn:String -> @+pv:String -> @rest:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, Sigma<&2, &2, String, _ => String>>

def grp.add source · line 217 · raw

@acc:List<&2, Sigma<&2, &2, String, _ => String>> -> @+n:String -> @+v:String -> List<&2, Sigma<&2, &2, String, _ => String>>

def grp source · line 224 · raw

@xs:List<&2, Sigma<&2, &2, String, _ => String>> -> @acc:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, Sigma<&2, &2, String, _ => String>>

def headers source · line 231 · raw

@hs:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, Sigma<&2, &2, String, _ => String>>

def names source · line 234 · raw

@xs:List<&2, Sigma<&2, &2, String, _ => String>> -> List<&2, String>

def lines source · line 241 · raw

@xs:List<&2, Sigma<&2, &2, String, _ => String>> -> String

def signed source · line 249 · raw

@hs:List<&2, Sigma<&2, &2, String, _ => String>> -> String

The SignedHeaders value for headers hs.

def canonical source · line 254 · raw

@+service:String -> @+method:String -> @+path:String -> @+q:String -> @+hs:List<&2, Sigma<&2, &2, String, _ => String>> -> @+phash:String -> String

The canonical request. hs must hold every header to sign, host included. phash is the payload's lowercase hex SHA-256, or "UNSIGNED-PAYLOAD".

def scope source · line 258 · raw

@+date:String -> @+region:String -> @+service:String -> String

def to_sign source · line 262 · raw

@+date:String -> @+scope:String -> @+hash:String -> String

The string to sign, from the hex SHA-256 of the canonical request.

def hex.of source · line 270 · raw

@r:Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)> -> Result<&1, &1, Pair(U32, String), String>

def sha.words source · line 278 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)

Hex SHA-256 of a byte string.

def sha source · line 282 · raw

@+s:String -> IO(Result<&1, &1, Pair(U32, String), String>)

def mac.words source · line 287 · raw

@+kn:U32 -> @kb:Array<U32> -> @d:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)

def words source · line 291 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>

def mac source · line 296 · raw

@k:Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)> -> @+msg:String -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)

HMAC-SHA256 of msg under the key k holds, or k's failure.

def key source · line 304 · raw

@+secret:String -> @+day:String -> @+region:String -> @+service:String -> IO(Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>)

The signing key: HMAC(HMAC(HMAC(HMAC("AWS4" + secret, day), region), service), "aws4_request").

def signature.of source · line 311 · raw

@h:Result<&1, &1, Pair(U32, String), String> -> @+secret:String -> @+date:String -> @+region:String -> @+service:String -> IO(Result<&1, &1, Pair(U32, String), String>)

def signature source · line 322 · raw

@+secret:String -> @+date:String -> @+region:String -> @+service:String -> @+creq:String -> IO(Result<&1, &1, Pair(U32, String), String>)

The hex signature of a canonical request.

def header.of source · line 327 · raw

@r:Result<&1, &1, Pair(U32, String), String> -> @+access:String -> @+scope:String -> @+signed:String -> Result<&1, &1, Pair(U32, String), String>

def sign source · line 337 · raw

@creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+hs:List<&2, Sigma<&2, &2, String, _ => String>> -> @+phash:String -> IO(Result<&1, &1, Pair(U32, String), String>)

The Authorization header over exactly the headers hs: the general form, for any header set. hs must include host and x-amz-date, and x-amz-security-token for temporary credentials; creds.token is not added here (authorize.hash adds it). Send every header in hs.

def token.hdr source · line 343 · raw

@empty:Bool -> @+token:String -> List<&2, Sigma<&2, &2, String, _ => String>>

def authorize.hash source · line 353 · raw

@creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+host:String -> @+phash:String -> IO(Result<&1, &1, Pair(U32, String), String>)

The Authorization header over host;x-amz-content-sha256;x-amz-date, plus x-amz-security-token when creds has a token. Send those headers with these values. phash: the payload's hex SHA-256, or "UNSIGNED-PAYLOAD".

def authorize.pair source · line 357 · raw

@r:Result<&1, &1, Pair(U32, String), String> -> @+phash:String -> Result<&1, &1, Pair(U32, String), Pair(String, String)>

def authorize.of source · line 364 · raw

@h:Result<&1, &1, Pair(U32, String), String> -> @creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+host:String -> IO(Result<&1, &1, Pair(U32, String), Pair(String, String)>)

def authorize source · line 374 · raw

@creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+host:String -> @payload:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Pair(U32, String), Pair(String, String)>)

authorize.hash over the payload's SHA-256. Done{(authorization, x-amz-content-sha256)}.

def token.q source · line 379 · raw

@empty:Bool -> @+token:String -> String

def presign.query source · line 387 · raw

@+access:String -> @+token:String -> @+scope:String -> @+date:String -> @+expires:U32 -> @+q:String -> String

The query a presigned request adds to q, before X-Amz-Signature.

def presign.of source · line 390 · raw

@r:Result<&1, &1, Pair(U32, String), String> -> @+cq:String -> Result<&1, &1, Pair(U32, String), String>

def presign.go source · line 397 · raw

@+creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+host:String -> @+expires:U32 -> IO(Result<&1, &1, Pair(U32, String), String>)

def presign.if source · line 404 · raw

@ok:Bool -> @+creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+host:String -> @+expires:U32 -> IO(Result<&1, &1, Pair(U32, String), String>)

def presign source · line 413 · raw

@+creds:Credentials -> @+region:String -> @+service:String -> @+date:String -> @+method:String -> @+path:String -> @+q:String -> @+host:String -> @+expires:U32 -> IO(Result<&1, &1, Pair(U32, String), String>)

A presigned URL's query, signing host only over UNSIGNED-PAYLOAD, valid for expires seconds, 1 to 604800 (else Fail EINVAL). Request https://{host}{path}?{result} with no auth headers.

def new source · line 455 · raw

@cfg:Config -> Client

Redirects come back as ErrS3: a signed request must not follow one.

def close source · line 458 · raw

@c:Client -> IO(Unit)

def retry source · line 463 · raw

@c:Client -> @n:U32 -> Client

Up to n more tries after a connect error, a timeout, or 408, 429, 500, 502, 503, or 504. The signature stays valid for 15 minutes.

def timeout source · line 467 · raw

@c:Client -> @ms:U32 -> Client

def aws source · line 472 · raw

@+region:String -> @creds:Credentials -> Config

AWS itself: https://s3.{region}.amazonaws.com, virtual-hosted.

def env source · line 475 · raw

@r:Result<&1, &1, Pair(U32, String), String> -> String

def or source · line 482 · raw

@+a:String -> @b:String -> String

def from_env.cfg source · line 485 · raw

@custom:Bool -> @+region:String -> @endpoint:String -> @creds:Credentials -> Config

def from_env.if source · line 492 · raw

@missing:Bool -> @access:String -> @secret:String -> @token:String -> @region:String -> @+endpoint:String -> Result<&1, &1, Err, Config>

def from_env.of source · line 500 · raw

@+access:String -> @+secret:String -> @token:String -> @region:String -> @endpoint:String -> Result<&1, &1, Err, Config>

def from_env source · line 505 · raw

IO(Result<&1, &1, Err, Config>)

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN; AWS_REGION, else AWS_DEFAULT_REGION, else us-east-1; AWS_ENDPOINT_URL_S3, else AWS_ENDPOINT_URL. A custom endpoint is path-style, as MinIO wants; AWS is virtual-hosted.

def first source · line 552 · raw

@xs:List<&2, String> -> String

def local.go source · line 559 · raw

@s:String -> @acc:String -> String

def local source · line 569 · raw

@s:String -> String

The name after its namespace prefix, as Key for s3:Key.

def st.txt source · line 572 · raw

@st:St -> @c:Char -> St

def st.nm source · line 576 · raw

@st:St -> @c:Char -> St

def st.nm0 source · line 580 · raw

@st:St -> St

def st.open source · line 584 · raw

@st:St -> St

def st.shut source · line 588 · raw

@stack:List<&2, String> -> @txt:String -> @out:List<&2, Leaf> -> @ok:Bool -> @+name:String -> St

def st.close source · line 595 · raw

@st:St -> St

def st.close.top source · line 599 · raw

@st:St -> St

def hex.digit source · line 603 · raw

@+c:U32 -> Maybe<&2, U32>

def hex.step source · line 608 · raw

@m:Maybe<&2, U32> -> @+acc:U32 -> Maybe<&2, U32>

def hex source · line 616 · raw

@s:String -> @m:Maybe<&2, U32> -> Maybe<&2, U32>

ponytail: no overflow check; a reference past U+10FFFF wraps and decodes to junk rather than failing.

def ent.num source · line 627 · raw

@+s:String -> Maybe<&2, U32>

def ent source · line 631 · raw

@+e:String -> Maybe<&2, U32>

The five XML entities and &#NN; / &#xHH;. None for any other.

def st.ent.of source · line 639 · raw

@m:Maybe<&2, U32> -> @st:St -> St

def st.ent source · line 647 · raw

@st:St -> St

def lx.idle source · line 651 · raw

@m:Lx -> Bool

def st.end source · line 658 · raw

@m:Lx -> @st:St -> St

def lex source · line 663 · raw

@s:String -> @m:Lx -> @st:St -> St

One pass over the text. Each step takes one char, so it ends.

def xml.if source · line 788 · raw

@ok:Bool -> @out:List<&2, Leaf> -> Maybe<&2, List<&2, Leaf>>

def closed source · line 795 · raw

@xs:List<&2, String> -> Bool

def xml.fin source · line 802 · raw

@st:St -> Maybe<&2, List<&2, Leaf>>

def xml source · line 808 · raw

@s:String -> Maybe<&2, List<&2, Leaf>>

The closed elements of a document in document order, or None when it is not well formed. ponytail: no DTD internal subsets, and no check of names or attribute syntax; S3 sends neither.

def fault.scan source · line 814 · raw

@ls:List<&2, Leaf> -> @code:String -> @msg:String -> Pair(String, String)

S3's <Error><Code>..</Code><Message>..</Message></Error>.

def fault.of source · line 823 · raw

@+status:U32 -> @cm:Pair(String, String) -> Err

def fault source · line 827 · raw

@+status:U32 -> @m:Maybe<&2, List<&2, Leaf>> -> Err

def ok source · line 834 · raw

@+s:U32 -> Bool

def judge.res source · line 837 · raw

@good:Bool -> @+status:U32 -> @h:Map<&2, List<&2, String>> -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>

def judge source · line 844 · raw

@r:Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>

def stamp.of source · line 854 · raw

@m:Maybe<&2, 0xe00bbf72c3ff9dc6d7b077524522b3a8/time.DateTime> -> String

def stamp source · line 862 · raw

IO(String)

Now as YYYYMMDDTHHMMSSZ. "" only outside years 0 to 9999, which S3 then refuses.

def seg source · line 872 · raw

@+key:String -> String

"" for the bucket itself.

def loc.at source · line 875 · raw

@style:Style -> @+bucket:String -> @+key:String -> @a:0x1f2d80f53f971b16c6de6a65cb1918ae/url.Abs -> Where

def loc source · line 886 · raw

@style:Style -> @+bucket:String -> @+key:String -> @m:Maybe<&2, 0x1f2d80f53f971b16c6de6a65cb1918ae/url.Abs> -> Maybe<&2, Where>

def token source · line 893 · raw

@c:Credentials -> String

def with.token source · line 897 · raw

@+token:String -> @+h:Map<&2, List<&2, String>> -> Map<&2, List<&2, String>>

def request.signed source · line 901 · raw

@extra:Map<&2, List<&2, String>> -> @date:String -> @+token:String -> @got:Pair(String, String) -> Map<&2, List<&2, String>>

extra, and the headers the signature covers. Host is Http's own, from the same URL.

def url source · line 905 · raw

@origin:String -> @path:String -> @+query:String -> String

def call.done source · line 908 · raw

@cfg:Config -> @x:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>)

def send.budget source · line 914 · raw

@+method:String -> @url:String -> @headers:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @y:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, 0x5f997a9351e7133e43ba9611e734f8a6/retry.Budget) -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(0x5f997a9351e7133e43ba9611e734f8a6/retry.Budget, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>)))

Hairpin's request with the codings "identity": Http then leaves a stored Content-Encoding (gzip, br, ..) as sent, so GET returns the object's bytes and HEAD's size matches. ponytail: Http still undoes x-gzip, which S3 rarely stores.

def send source · line 919 · raw

@c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @+method:String -> @url:String -> @headers:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

def call.send source · line 926 · raw

@s:Result<&1, &1, Pair(U32, String), Pair(String, String)> -> @cfg:Config -> @http:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @+method:String -> @url:String -> @extra:Map<&2, List<&2, String>> -> @date:String -> @token:String -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

def call.sign source · line 938 · raw

@got:Where -> @+endpoint:String -> @+region:String -> @+style:Style -> @+creds:Credentials -> @http:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @+method:String -> @+query:String -> @extra:Map<&2, List<&2, String>> -> @+date:String -> @b:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> IO(Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

The signer hashes one copy of the body; the other goes on the wire.

def call.at source · line 947 · raw

@w:Maybe<&2, Where> -> @+endpoint:String -> @+region:String -> @+style:Style -> @+creds:Credentials -> @http:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @+method:String -> @+query:String -> @extra:Map<&2, List<&2, String>> -> @+date:String -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

def call.run source · line 958 · raw

@c:Client -> @+method:String -> @+bucket:String -> @+key:String -> @+query:String -> @extra:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

Object calls require a bucket and a key. ListObjectsV2 targets the bucket with list-type=2. The absolute URL bypasses base resolution, so '.' and '..' in object keys survive.

def call.guard source · line 966 · raw

@bad:Bool -> @c:Client -> @+method:String -> @+bucket:String -> @+key:String -> @+query:String -> @extra:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

def call source · line 974 · raw

@c:Client -> @+method:String -> @+bucket:String -> @+key:String -> @+query:String -> @extra:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

def etag.of source · line 981 · raw

@r:Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, String>

def put.done source · line 989 · raw

@x:Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(Client, Result<&1, &1, Err, String>)

def put source · line 994 · raw

@c:Client -> @bucket:String -> @key:String -> @+mime:String -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, String>))

PutObject. mime: Content-Type, "" for none. The ETag comes back.

def xgzip.values source · line 1000 · raw

@xs:List<&2, String> -> Bool

def xgzip.fields source · line 1007 · raw

@xs:List<&2, String> -> Bool

def body.encoding source · line 1014 · raw

@gzip:Bool -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

def body.of source · line 1021 · raw

@r:Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

def get.done source · line 1029 · raw

@x:Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(Client, Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

def get source · line 1035 · raw

@c:Client -> @bucket:String -> @key:String -> IO(Pair(Client, Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>))

GetObject: the object's bytes. A missing key is ErrS3{404, "NoSuchKey", ..}. ponytail: Hairpin decodes x-gzip; reject it rather than return bytes that differ from the stored object.

def nat source · line 1040 · raw

@s:String -> Nat

def meta.of source · line 1043 · raw

@r:Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, Meta>

def head.done source · line 1051 · raw

@x:Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(Client, Result<&1, &1, Err, Meta>)

def unit.of source · line 1061 · raw

@r:Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, Unit>

def delete.done source · line 1068 · raw

@x:Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(Client, Result<&1, &1, Err, Unit>)

def delete source · line 1073 · raw

@c:Client -> @bucket:String -> @key:String -> IO(Pair(Client, Result<&1, &1, Err, Unit>))

DeleteObject. S3 answers 204 for a key that was never there too.

def blank source · line 1084 · raw

Object

def obj.set source · line 1087 · raw

@o:Object -> @+n:String -> @+x:String -> Object

def page.leaf source · line 1092 · raw

@a:Acc -> @+p:String -> @+n:String -> @+x:String -> Acc

def page.scan source · line 1102 · raw

@ls:List<&2, Leaf> -> @a:Acc -> Acc

def page.fin source · line 1109 · raw

@a:Acc -> Page

def page.root.last source · line 1114 · raw

@ls:List<&2, Leaf> -> @+n:String -> String

def page.if source · line 1121 · raw

@good:Bool -> @s:String -> @ls:List<&2, Leaf> -> Result<&1, &1, Err, Page>

def page.doc source · line 1128 · raw

@+s:String -> @m:Maybe<&2, List<&2, Leaf>> -> Result<&1, &1, Err, Page>

def page.of source · line 1135 · raw

@r:Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, Page>

def list.done source · line 1144 · raw

@x:Pair(Client, Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(Client, Result<&1, &1, Err, Page>)

def q.kv source · line 1148 · raw

@k:String -> @+v:String -> String

def q.opt source · line 1151 · raw

@k:String -> @+v:String -> @+rest:List<&2, String> -> List<&2, String>

def q.token source · line 1154 · raw

@t:Maybe<&2, String> -> @rest:List<&2, String> -> List<&2, String>

def q.max source · line 1161 · raw

@+n:U32 -> List<&2, String>

def list.query source · line 1165 · raw

@prefix:String -> @delim:String -> @+max:U32 -> @t:Maybe<&2, String> -> String

In the signer's order: continuation-token, delimiter, list-type, max-keys, prefix.

def list source · line 1169 · raw

@c:Client -> @+bucket:String -> @prefix:String -> @delim:String -> @+max:U32 -> @token:Maybe<&2, String> -> IO(Pair(Client, Result<&1, &1, Err, Page>))

One page of ListObjectsV2. prefix, delim: "" for none. max: keys per page, 0 for S3's 1000. token: a page's next.

def all.merge source · line 1175 · raw

@objs:List<&2, Object> -> @pre:List<&2, String> -> @r:Result<&1, &1, Err, Page> -> Result<&1, &1, Err, Page>

def all.go source · line 1183 · raw

@f:Nat -> @+bucket:String -> @+prefix:String -> @+delim:String -> @+max:U32 -> @objs:List<&2, Object> -> @pre:List<&2, String> -> @x:Pair(Client, Result<&1, &1, Err, Page>) -> IO(Pair(Client, Result<&1, &1, Err, Page>))

f: pages left. When they run out, next holds the token to go on from.

def list.all source · line 1204 · raw

@c:Client -> @+bucket:String -> @+prefix:String -> @+delim:String -> @+max:U32 -> IO(Pair(Client, Result<&1, &1, Err, Page>))

Every page, following NextContinuationToken, up to 100000 pages.