Merged in feature/s3integration (pull request #47)

Set Up S3 integration

* cfginterfaceandfirsts3funcs

* addlocalstack

* generallypassesfullsuite

* addedmultipleattemptedpings

* cleanup

* stabiliseplusskip
This commit is contained in:
Michael McGuinness
2025-02-05 12:52:41 +00:00
parent 9254b91d45
commit 92334ad1dd
838 changed files with 139249 additions and 6671 deletions
+5 -13
View File
@@ -11,21 +11,13 @@ func (oscState oscStringState) Handle(b byte) (s state, e error) {
return nextState, err
}
switch {
case isOscStringTerminator(b):
// There are several control characters and sequences which can
// terminate an OSC string. Most of them are handled by the baseState
// handler. The ANSI_BEL character is a special case which behaves as a
// terminator only for an OSC string.
if b == ANSI_BEL {
return oscState.parser.ground, nil
}
return oscState, nil
}
// See below for OSC string terminators for linux
// http://man7.org/linux/man-pages/man4/console_codes.4.html
func isOscStringTerminator(b byte) bool {
if b == ANSI_BEL || b == 0x5C {
return true
}
return false
}
-5
View File
@@ -1,5 +0,0 @@
*.sublime-*
.DS_Store
*.swp
*.swo
tags
-12
View File
@@ -1,12 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- "1.10.x"
- "1.11.x"
- tip
-12
View File
@@ -1,12 +0,0 @@
Copyright (c) 2012, Martin Angers
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-188
View File
@@ -1,188 +0,0 @@
# Purell
Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know...
Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc].
[![build status](https://travis-ci.org/PuerkitoBio/purell.svg?branch=master)](http://travis-ci.org/PuerkitoBio/purell)
## Install
`go get github.com/PuerkitoBio/purell`
## Changelog
* **v1.1.1** : Fix failing test due to Go1.12 changes (thanks to @ianlancetaylor).
* **2016-11-14 (v1.1.0)** : IDN: Conform to RFC 5895: Fold character width (thanks to @beeker1121).
* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich).
* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]).
* **v0.2.0** : Add benchmarks, Attempt IDN support.
* **v0.1.0** : Initial release.
## Examples
From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."):
```go
package purell
import (
"fmt"
"net/url"
)
func ExampleNormalizeURLString() {
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
panic(err)
} else {
fmt.Print(normalized)
}
// Output: http://somewebsite.com:80/Amazing%3F/url/
}
func ExampleMustNormalizeURLString() {
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
FlagsUnsafeGreedy)
fmt.Print(normalized)
// Output: http://somewebsite.com/Amazing%FA/url
}
func ExampleNormalizeURL() {
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
panic(err)
} else {
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
fmt.Print(normalized)
}
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
}
```
## API
As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags:
```go
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
```
For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set.
The [full godoc reference is available on gopkgdoc][godoc].
Some things to note:
* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it.
* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*):
- %24 -> $
- %26 -> &
- %2B-%3B -> +,-./0123456789:;
- %3D -> =
- %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ
- %5F -> _
- %61-%7A -> abcdefghijklmnopqrstuvwxyz
- %7E -> ~
* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization).
* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell.
* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object.
### Safe vs Usually Safe vs Unsafe
Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between.
Consider the following URL:
`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
Normalizing with the `FlagsSafe` gives:
`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
With the `FlagsUsuallySafeGreedy`:
`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid`
And with `FlagsUnsafeGreedy`:
`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3`
## TODOs
* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`.
## Thanks / Contributions
@rogpeppe
@jehiah
@opennota
@pchristopher1275
@zenovich
@beeker1121
## License
The [BSD 3-Clause license][bsd].
[bsd]: http://opensource.org/licenses/BSD-3-Clause
[wiki]: http://en.wikipedia.org/wiki/URL_normalization
[rfc]: http://tools.ietf.org/html/rfc3986#section-6
[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell
[pr5]: https://github.com/PuerkitoBio/purell/pull/5
[iss7]: https://github.com/PuerkitoBio/purell/issues/7
-379
View File
@@ -1,379 +0,0 @@
/*
Package purell offers URL normalization as described on the wikipedia page:
http://en.wikipedia.org/wiki/URL_normalization
*/
package purell
import (
"bytes"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"github.com/PuerkitoBio/urlesc"
"golang.org/x/net/idna"
"golang.org/x/text/unicode/norm"
"golang.org/x/text/width"
)
// A set of normalization flags determines how a URL will
// be normalized.
type NormalizationFlags uint
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
const (
defaultHttpPort = ":80"
defaultHttpsPort = ":443"
)
// Regular expressions used by the normalizations
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
var rxEmptyPort = regexp.MustCompile(`:+$`)
// Map of flags to implementation function.
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
// Since maps have undefined traversing order, make a slice of ordered keys
var flagsOrder = []NormalizationFlags{
FlagLowercaseScheme,
FlagLowercaseHost,
FlagRemoveDefaultPort,
FlagRemoveDirectoryIndex,
FlagRemoveDotSegments,
FlagRemoveFragment,
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
FlagRemoveDuplicateSlashes,
FlagRemoveWWW,
FlagAddWWW,
FlagSortQuery,
FlagDecodeDWORDHost,
FlagDecodeOctalHost,
FlagDecodeHexHost,
FlagRemoveUnnecessaryHostDots,
FlagRemoveEmptyPortSeparator,
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
FlagAddTrailingSlash,
}
// ... and then the map, where order is unimportant
var flags = map[NormalizationFlags]func(*url.URL){
FlagLowercaseScheme: lowercaseScheme,
FlagLowercaseHost: lowercaseHost,
FlagRemoveDefaultPort: removeDefaultPort,
FlagRemoveDirectoryIndex: removeDirectoryIndex,
FlagRemoveDotSegments: removeDotSegments,
FlagRemoveFragment: removeFragment,
FlagForceHTTP: forceHTTP,
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
FlagRemoveWWW: removeWWW,
FlagAddWWW: addWWW,
FlagSortQuery: sortQuery,
FlagDecodeDWORDHost: decodeDWORDHost,
FlagDecodeOctalHost: decodeOctalHost,
FlagDecodeHexHost: decodeHexHost,
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
FlagRemoveTrailingSlash: removeTrailingSlash,
FlagAddTrailingSlash: addTrailingSlash,
}
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
// It takes an URL string as input, as well as the normalization flags.
func MustNormalizeURLString(u string, f NormalizationFlags) string {
result, e := NormalizeURLString(u, f)
if e != nil {
panic(e)
}
return result
}
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
// It takes an URL string as input, as well as the normalization flags.
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
parsed, err := url.Parse(u)
if err != nil {
return "", err
}
if f&FlagLowercaseHost == FlagLowercaseHost {
parsed.Host = strings.ToLower(parsed.Host)
}
// The idna package doesn't fully conform to RFC 5895
// (https://tools.ietf.org/html/rfc5895), so we do it here.
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
// TODO: Remove when (if?) idna package conforms to RFC 5895.
parsed.Host = width.Fold.String(parsed.Host)
parsed.Host = norm.NFC.String(parsed.Host)
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
return "", err
}
return NormalizeURL(parsed, f), nil
}
// NormalizeURL returns the normalized string.
// It takes a parsed URL object as input, as well as the normalization flags.
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
for _, k := range flagsOrder {
if f&k == k {
flags[k](u)
}
}
return urlesc.Escape(u)
}
func lowercaseScheme(u *url.URL) {
if len(u.Scheme) > 0 {
u.Scheme = strings.ToLower(u.Scheme)
}
}
func lowercaseHost(u *url.URL) {
if len(u.Host) > 0 {
u.Host = strings.ToLower(u.Host)
}
}
func removeDefaultPort(u *url.URL) {
if len(u.Host) > 0 {
scheme := strings.ToLower(u.Scheme)
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
return ""
}
return val
})
}
}
func removeTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if strings.HasSuffix(u.Path, "/") {
u.Path = u.Path[:l-1]
}
} else if l = len(u.Host); l > 0 {
if strings.HasSuffix(u.Host, "/") {
u.Host = u.Host[:l-1]
}
}
}
func addTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
} else if l = len(u.Host); l > 0 {
if !strings.HasSuffix(u.Host, "/") {
u.Host += "/"
}
}
}
func removeDotSegments(u *url.URL) {
if len(u.Path) > 0 {
var dotFree []string
var lastIsDot bool
sections := strings.Split(u.Path, "/")
for _, s := range sections {
if s == ".." {
if len(dotFree) > 0 {
dotFree = dotFree[:len(dotFree)-1]
}
} else if s != "." {
dotFree = append(dotFree, s)
}
lastIsDot = (s == "." || s == "..")
}
// Special case if host does not end with / and new path does not begin with /
u.Path = strings.Join(dotFree, "/")
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
u.Path = "/" + u.Path
}
// Special case if the last segment was a dot, make sure the path ends with a slash
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
}
}
func removeDirectoryIndex(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
}
}
func removeFragment(u *url.URL) {
u.Fragment = ""
}
func forceHTTP(u *url.URL) {
if strings.ToLower(u.Scheme) == "https" {
u.Scheme = "http"
}
}
func removeDuplicateSlashes(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
}
}
func removeWWW(u *url.URL) {
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = u.Host[4:]
}
}
func addWWW(u *url.URL) {
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = "www." + u.Host
}
}
func sortQuery(u *url.URL) {
q := u.Query()
if len(q) > 0 {
arKeys := make([]string, len(q))
i := 0
for k := range q {
arKeys[i] = k
i++
}
sort.Strings(arKeys)
buf := new(bytes.Buffer)
for _, k := range arKeys {
sort.Strings(q[k])
for _, v := range q[k] {
if buf.Len() > 0 {
buf.WriteRune('&')
}
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
}
}
// Rebuild the raw query string
u.RawQuery = buf.String()
}
}
func decodeDWORDHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
var parts [4]int64
dword, _ := strconv.ParseInt(matches[1], 10, 0)
for i, shift := range []uint{24, 16, 8, 0} {
parts[i] = dword >> shift & 0xFF
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
}
}
}
func decodeOctalHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
var parts [4]int64
for i := 1; i <= 4; i++ {
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
}
}
}
func decodeHexHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
// Conversion is safe because of regex validation
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
// Set host as DWORD (base 10) encoded host
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
// The rest is the same as decoding a DWORD host
decodeDWORDHost(u)
}
}
}
func removeUnncessaryHostDots(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
// Trim the leading and trailing dots
u.Host = strings.Trim(matches[1], ".")
if len(matches) > 2 {
u.Host += matches[2]
}
}
}
}
func removeEmptyPortSeparator(u *url.URL) {
if len(u.Host) > 0 {
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
}
}
-15
View File
@@ -1,15 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- tip
install:
- go build .
script:
- go test -v
-27
View File
@@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-16
View File
@@ -1,16 +0,0 @@
urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.svg?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc)
======
Package urlesc implements query escaping as per RFC 3986.
It contains some parts of the net/url package, modified so as to allow
some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)).
## Install
go get github.com/PuerkitoBio/urlesc
## License
Go license (BSD-3-Clause)
-180
View File
@@ -1,180 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package urlesc implements query escaping as per RFC 3986.
// It contains some parts of the net/url package, modified so as to allow
// some reserved characters incorrectly escaped by net/url.
// See https://github.com/golang/go/issues/5684
package urlesc
import (
"bytes"
"net/url"
"strings"
)
type encoding int
const (
encodePath encoding = 1 + iota
encodeUserPassword
encodeQueryComponent
encodeFragment
)
// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
func shouldEscape(c byte, mode encoding) bool {
// §2.3 Unreserved characters (alphanum)
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
return false
// §2.2 Reserved characters (reserved)
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch mode {
case encodePath: // §3.3
// The RFC allows sub-delims and : @.
// '/', '[' and ']' can be used to assign meaning to individual path
// segments. This package only manipulates the path as a whole,
// so we allow those as well. That leaves only ? and # to escape.
return c == '?' || c == '#'
case encodeUserPassword: // §3.2.1
// The RFC allows : and sub-delims in
// userinfo. The parsing of userinfo treats ':' as special so we must escape
// all the gen-delims.
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
case encodeQueryComponent: // §3.4
// The RFC allows / and ?.
return c != '/' && c != '?'
case encodeFragment: // §4.1
// The RFC text is silent but the grammar allows
// everything, so escape nothing but #
return c == '#'
}
}
// Everything else must be escaped.
return true
}
// QueryEscape escapes the string so it can be safely placed
// inside a URL query.
func QueryEscape(s string) string {
return escape(s, encodeQueryComponent)
}
func escape(s string, mode encoding) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c, mode) {
if c == ' ' && mode == encodeQueryComponent {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
t := make([]byte, len(s)+2*hexCount)
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && mode == encodeQueryComponent:
t[j] = '+'
j++
case shouldEscape(c, mode):
t[j] = '%'
t[j+1] = "0123456789ABCDEF"[c>>4]
t[j+2] = "0123456789ABCDEF"[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}
var uiReplacer = strings.NewReplacer(
"%21", "!",
"%27", "'",
"%28", "(",
"%29", ")",
"%2A", "*",
)
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
func unescapeUserinfo(s string) string {
return uiReplacer.Replace(s)
}
// Escape reassembles the URL into a valid URL string.
// The general form of the result is one of:
//
// scheme:opaque
// scheme://userinfo@host/path?query#fragment
//
// If u.Opaque is non-empty, String uses the first form;
// otherwise it uses the second form.
//
// In the second form, the following rules apply:
// - if u.Scheme is empty, scheme: is omitted.
// - if u.User is nil, userinfo@ is omitted.
// - if u.Host is empty, host/ is omitted.
// - if u.Scheme and u.Host are empty and u.User is nil,
// the entire scheme://userinfo@host/ is omitted.
// - if u.Host is non-empty and u.Path begins with a /,
// the form host/path does not add its own /.
// - if u.RawQuery is empty, ?query is omitted.
// - if u.Fragment is empty, #fragment is omitted.
func Escape(u *url.URL) string {
var buf bytes.Buffer
if u.Scheme != "" {
buf.WriteString(u.Scheme)
buf.WriteByte(':')
}
if u.Opaque != "" {
buf.WriteString(u.Opaque)
} else {
if u.Scheme != "" || u.Host != "" || u.User != nil {
buf.WriteString("//")
if ui := u.User; ui != nil {
buf.WriteString(unescapeUserinfo(ui.String()))
buf.WriteByte('@')
}
if h := u.Host; h != "" {
buf.WriteString(h)
}
}
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
buf.WriteString(escape(u.Path, encodePath))
}
if u.RawQuery != "" {
buf.WriteByte('?')
buf.WriteString(u.RawQuery)
}
if u.Fragment != "" {
buf.WriteByte('#')
buf.WriteString(escape(u.Fragment, encodeFragment))
}
return buf.String()
}
+92
View File
@@ -0,0 +1,92 @@
// Package arn provides a parser for interacting with Amazon Resource Names.
package arn
import (
"errors"
"strings"
)
const (
arnDelimiter = ":"
arnSections = 6
arnPrefix = "arn:"
// zero-indexed
sectionPartition = 1
sectionService = 2
sectionRegion = 3
sectionAccountID = 4
sectionResource = 5
// errors
invalidPrefix = "arn: invalid prefix"
invalidSections = "arn: not enough sections"
)
// ARN captures the individual fields of an Amazon Resource Name.
// See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html for more information.
type ARN struct {
// The partition that the resource is in. For standard AWS regions, the partition is "aws". If you have resources in
// other partitions, the partition is "aws-partitionname". For example, the partition for resources in the China
// (Beijing) region is "aws-cn".
Partition string
// The service namespace that identifies the AWS product (for example, Amazon S3, IAM, or Amazon RDS). For a list of
// namespaces, see
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces.
Service string
// The region the resource resides in. Note that the ARNs for some resources do not require a region, so this
// component might be omitted.
Region string
// The ID of the AWS account that owns the resource, without the hyphens. For example, 123456789012. Note that the
// ARNs for some resources don't require an account number, so this component might be omitted.
AccountID string
// The content of this part of the ARN varies by service. It often includes an indicator of the type of resource —
// for example, an IAM user or Amazon RDS database - followed by a slash (/) or a colon (:), followed by the
// resource name itself. Some services allows paths for resource names, as described in
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths.
Resource string
}
// Parse parses an ARN into its constituent parts.
//
// Some example ARNs:
// arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment
// arn:aws:iam::123456789012:user/David
// arn:aws:rds:eu-west-1:123456789012:db:mysql-db
// arn:aws:s3:::my_corporate_bucket/exampleobject.png
func Parse(arn string) (ARN, error) {
if !strings.HasPrefix(arn, arnPrefix) {
return ARN{}, errors.New(invalidPrefix)
}
sections := strings.SplitN(arn, arnDelimiter, arnSections)
if len(sections) != arnSections {
return ARN{}, errors.New(invalidSections)
}
return ARN{
Partition: sections[sectionPartition],
Service: sections[sectionService],
Region: sections[sectionRegion],
AccountID: sections[sectionAccountID],
Resource: sections[sectionResource],
}, nil
}
// IsARN returns whether the given string is an arn
// by looking for whether the string starts with arn:
func IsARN(arn string) bool {
return strings.HasPrefix(arn, arnPrefix) && strings.Count(arn, ":") >= arnSections-1
}
// String returns the canonical representation of the ARN
func (arn ARN) String() string {
return arnPrefix +
arn.Partition + arnDelimiter +
arn.Service + arnDelimiter +
arn.Region + arnDelimiter +
arn.AccountID + arnDelimiter +
arn.Resource
}
+33
View File
@@ -0,0 +1,33 @@
package aws
// RequestChecksumCalculation controls request checksum calculation workflow
type RequestChecksumCalculation int
const (
// RequestChecksumCalculationUnset is the unset value for RequestChecksumCalculation
RequestChecksumCalculationUnset RequestChecksumCalculation = iota
// RequestChecksumCalculationWhenSupported indicates request checksum will be calculated
// if the operation supports input checksums
RequestChecksumCalculationWhenSupported
// RequestChecksumCalculationWhenRequired indicates request checksum will be calculated
// if required by the operation or if user elects to set a checksum algorithm in request
RequestChecksumCalculationWhenRequired
)
// ResponseChecksumValidation controls response checksum validation workflow
type ResponseChecksumValidation int
const (
// ResponseChecksumValidationUnset is the unset value for ResponseChecksumValidation
ResponseChecksumValidationUnset ResponseChecksumValidation = iota
// ResponseChecksumValidationWhenSupported indicates response checksum will be validated
// if the operation supports output checksums
ResponseChecksumValidationWhenSupported
// ResponseChecksumValidationWhenRequired indicates response checksum will only
// be validated if the operation requires output checksum validation
ResponseChecksumValidationWhenRequired
)
+27
View File
@@ -165,6 +165,33 @@ type Config struct {
// Controls how a resolved AWS account ID is handled for endpoint routing.
AccountIDEndpointMode AccountIDEndpointMode
// RequestChecksumCalculation determines when request checksum calculation is performed.
//
// There are two possible values for this setting:
//
// 1. RequestChecksumCalculationWhenSupported (default): The checksum is always calculated
// if the operation supports it, regardless of whether the user sets an algorithm in the request.
//
// 2. RequestChecksumCalculationWhenRequired: The checksum is only calculated if the user
// explicitly sets a checksum algorithm in the request.
//
// This setting is sourced from the environment variable AWS_REQUEST_CHECKSUM_CALCULATION
// or the shared config profile attribute "request_checksum_calculation".
RequestChecksumCalculation RequestChecksumCalculation
// ResponseChecksumValidation determines when response checksum validation is performed
//
// There are two possible values for this setting:
//
// 1. ResponseChecksumValidationWhenSupported (default): The checksum is always validated
// if the operation supports it, regardless of whether the user sets the validation mode to ENABLED in request.
//
// 2. ResponseChecksumValidationWhenRequired: The checksum is only validated if the user
// explicitly sets the validation mode to ENABLED in the request
// This variable is sourced from environment variable AWS_RESPONSE_CHECKSUM_VALIDATION or
// the shared config profile attribute "response_checksum_validation".
ResponseChecksumValidation ResponseChecksumValidation
}
// NewConfig returns a new Config pointer that can be chained with builder
+1 -1
View File
@@ -3,4 +3,4 @@
package aws
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.32.5"
const goModuleVersion = "1.36.0"
+31 -14
View File
@@ -34,6 +34,9 @@ const (
FeatureMetadata2
)
// Hardcoded value to specify which version of the user agent we're using
const uaMetadata = "ua/2.1"
func (k SDKAgentKeyType) string() string {
switch k {
case APIMetadata:
@@ -73,19 +76,28 @@ type UserAgentFeature string
// Enumerates UserAgentFeature.
const (
UserAgentFeatureResourceModel UserAgentFeature = "A" // n/a (we don't generate separate resource types)
UserAgentFeatureWaiter = "B"
UserAgentFeaturePaginator = "C"
UserAgentFeatureRetryModeLegacy = "D" // n/a (equivalent to standard)
UserAgentFeatureRetryModeStandard = "E"
UserAgentFeatureRetryModeAdaptive = "F"
UserAgentFeatureS3Transfer = "G"
UserAgentFeatureS3CryptoV1N = "H" // n/a (crypto client is external)
UserAgentFeatureS3CryptoV2 = "I" // n/a
UserAgentFeatureS3ExpressBucket = "J"
UserAgentFeatureS3AccessGrants = "K" // not yet implemented
UserAgentFeatureGZIPRequestCompression = "L"
UserAgentFeatureProtocolRPCV2CBOR = "M"
UserAgentFeatureResourceModel UserAgentFeature = "A" // n/a (we don't generate separate resource types)
UserAgentFeatureWaiter = "B"
UserAgentFeaturePaginator = "C"
UserAgentFeatureRetryModeLegacy = "D" // n/a (equivalent to standard)
UserAgentFeatureRetryModeStandard = "E"
UserAgentFeatureRetryModeAdaptive = "F"
UserAgentFeatureS3Transfer = "G"
UserAgentFeatureS3CryptoV1N = "H" // n/a (crypto client is external)
UserAgentFeatureS3CryptoV2 = "I" // n/a
UserAgentFeatureS3ExpressBucket = "J"
UserAgentFeatureS3AccessGrants = "K" // not yet implemented
UserAgentFeatureGZIPRequestCompression = "L"
UserAgentFeatureProtocolRPCV2CBOR = "M"
UserAgentFeatureRequestChecksumCRC32 = "U"
UserAgentFeatureRequestChecksumCRC32C = "V"
UserAgentFeatureRequestChecksumCRC64 = "W"
UserAgentFeatureRequestChecksumSHA1 = "X"
UserAgentFeatureRequestChecksumSHA256 = "Y"
UserAgentFeatureRequestChecksumWhenSupported = "Z"
UserAgentFeatureRequestChecksumWhenRequired = "a"
UserAgentFeatureResponseChecksumWhenSupported = "b"
UserAgentFeatureResponseChecksumWhenRequired = "c"
)
// RequestUserAgent is a build middleware that set the User-Agent for the request.
@@ -107,6 +119,7 @@ type RequestUserAgent struct {
func NewRequestUserAgent() *RequestUserAgent {
userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder()
addProductName(userAgent)
addUserAgentMetadata(userAgent)
addProductName(sdkAgent)
r := &RequestUserAgent{
@@ -134,6 +147,10 @@ func addProductName(builder *smithyhttp.UserAgentBuilder) {
builder.AddKeyValue(aws.SDKName, aws.SDKVersion)
}
func addUserAgentMetadata(builder *smithyhttp.UserAgentBuilder) {
builder.AddKey(uaMetadata)
}
// AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
func AddUserAgentKey(key string) func(*middleware.Stack) error {
return func(stack *middleware.Stack) error {
@@ -258,10 +275,10 @@ func (u *RequestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildI
func (u *RequestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) {
const userAgent = "User-Agent"
updateHTTPHeader(request, userAgent, u.userAgent.Build())
if len(u.features) > 0 {
updateHTTPHeader(request, userAgent, buildFeatureMetrics(u.features))
}
updateHTTPHeader(request, userAgent, u.userAgent.Build())
}
func (u *RequestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) {
@@ -0,0 +1,134 @@
# v1.6.8 (2025-01-24)
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.6.7 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
# v1.6.6 (2024-10-04)
* No change notes available for this release.
# v1.6.5 (2024-09-20)
* No change notes available for this release.
# v1.6.4 (2024-08-15)
* **Dependency Update**: Bump minimum Go version to 1.21.
# v1.6.3 (2024-06-28)
* No change notes available for this release.
# v1.6.2 (2024-03-29)
* No change notes available for this release.
# v1.6.1 (2024-02-21)
* No change notes available for this release.
# v1.6.0 (2024-02-13)
* **Feature**: Bump minimum Go version to 1.20 per our language support policy.
# v1.5.4 (2023-12-07)
* No change notes available for this release.
# v1.5.3 (2023-11-30)
* No change notes available for this release.
# v1.5.2 (2023-11-29)
* No change notes available for this release.
# v1.5.1 (2023-11-15)
* No change notes available for this release.
# v1.5.0 (2023-10-31)
* **Feature**: **BREAKING CHANGE**: Bump minimum go version to 1.19 per the revised [go version support policy](https://aws.amazon.com/blogs/developer/aws-sdk-for-go-aligns-with-go-release-policy-on-supported-runtimes/).
# v1.4.14 (2023-10-06)
* No change notes available for this release.
# v1.4.13 (2023-08-18)
* No change notes available for this release.
# v1.4.12 (2023-08-07)
* No change notes available for this release.
# v1.4.11 (2023-07-31)
* No change notes available for this release.
# v1.4.10 (2022-12-02)
* No change notes available for this release.
# v1.4.9 (2022-10-24)
* No change notes available for this release.
# v1.4.8 (2022-09-14)
* No change notes available for this release.
# v1.4.7 (2022-09-02)
* No change notes available for this release.
# v1.4.6 (2022-08-31)
* No change notes available for this release.
# v1.4.5 (2022-08-29)
* No change notes available for this release.
# v1.4.4 (2022-08-09)
* No change notes available for this release.
# v1.4.3 (2022-06-29)
* No change notes available for this release.
# v1.4.2 (2022-06-07)
* No change notes available for this release.
# v1.4.1 (2022-03-24)
* No change notes available for this release.
# v1.4.0 (2022-03-08)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
# v1.3.0 (2022-02-24)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
# v1.2.0 (2022-01-14)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
# v1.1.0 (2022-01-07)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
# v1.0.0 (2021-11-06)
* **Announcement**: Support has been added for AWS EventStream APIs for Kinesis, S3, and Transcribe Streaming. Support for the Lex Runtime V2 EventStream API will be added in a future release.
* **Release**: Protocol support has been added for AWS event stream.
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+144
View File
@@ -0,0 +1,144 @@
package eventstream
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
)
type decodedMessage struct {
rawMessage
Headers decodedHeaders `json:"headers"`
}
type jsonMessage struct {
Length json.Number `json:"total_length"`
HeadersLen json.Number `json:"headers_length"`
PreludeCRC json.Number `json:"prelude_crc"`
Headers decodedHeaders `json:"headers"`
Payload []byte `json:"payload"`
CRC json.Number `json:"message_crc"`
}
func (d *decodedMessage) UnmarshalJSON(b []byte) (err error) {
var jsonMsg jsonMessage
if err = json.Unmarshal(b, &jsonMsg); err != nil {
return err
}
d.Length, err = numAsUint32(jsonMsg.Length)
if err != nil {
return err
}
d.HeadersLen, err = numAsUint32(jsonMsg.HeadersLen)
if err != nil {
return err
}
d.PreludeCRC, err = numAsUint32(jsonMsg.PreludeCRC)
if err != nil {
return err
}
d.Headers = jsonMsg.Headers
d.Payload = jsonMsg.Payload
d.CRC, err = numAsUint32(jsonMsg.CRC)
if err != nil {
return err
}
return nil
}
func (d *decodedMessage) MarshalJSON() ([]byte, error) {
jsonMsg := jsonMessage{
Length: json.Number(strconv.Itoa(int(d.Length))),
HeadersLen: json.Number(strconv.Itoa(int(d.HeadersLen))),
PreludeCRC: json.Number(strconv.Itoa(int(d.PreludeCRC))),
Headers: d.Headers,
Payload: d.Payload,
CRC: json.Number(strconv.Itoa(int(d.CRC))),
}
return json.Marshal(jsonMsg)
}
func numAsUint32(n json.Number) (uint32, error) {
v, err := n.Int64()
if err != nil {
return 0, fmt.Errorf("failed to get int64 json number, %v", err)
}
return uint32(v), nil
}
func (d decodedMessage) Message() Message {
return Message{
Headers: Headers(d.Headers),
Payload: d.Payload,
}
}
type decodedHeaders Headers
func (hs *decodedHeaders) UnmarshalJSON(b []byte) error {
var jsonHeaders []struct {
Name string `json:"name"`
Type valueType `json:"type"`
Value interface{} `json:"value"`
}
decoder := json.NewDecoder(bytes.NewReader(b))
decoder.UseNumber()
if err := decoder.Decode(&jsonHeaders); err != nil {
return err
}
var headers Headers
for _, h := range jsonHeaders {
value, err := valueFromType(h.Type, h.Value)
if err != nil {
return err
}
headers.Set(h.Name, value)
}
*hs = decodedHeaders(headers)
return nil
}
func valueFromType(typ valueType, val interface{}) (Value, error) {
switch typ {
case trueValueType:
return BoolValue(true), nil
case falseValueType:
return BoolValue(false), nil
case int8ValueType:
v, err := val.(json.Number).Int64()
return Int8Value(int8(v)), err
case int16ValueType:
v, err := val.(json.Number).Int64()
return Int16Value(int16(v)), err
case int32ValueType:
v, err := val.(json.Number).Int64()
return Int32Value(int32(v)), err
case int64ValueType:
v, err := val.(json.Number).Int64()
return Int64Value(v), err
case bytesValueType:
v, err := base64.StdEncoding.DecodeString(val.(string))
return BytesValue(v), err
case stringValueType:
v, err := base64.StdEncoding.DecodeString(val.(string))
return StringValue(string(v)), err
case timestampValueType:
v, err := val.(json.Number).Int64()
return TimestampValue(timeFromEpochMilli(v)), err
case uuidValueType:
v, err := base64.StdEncoding.DecodeString(val.(string))
var tv UUIDValue
copy(tv[:], v)
return tv, err
default:
panic(fmt.Sprintf("unknown type, %s, %T", typ.String(), val))
}
}
+218
View File
@@ -0,0 +1,218 @@
package eventstream
import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/aws/smithy-go/logging"
"hash"
"hash/crc32"
"io"
)
// DecoderOptions is the Decoder configuration options.
type DecoderOptions struct {
Logger logging.Logger
LogMessages bool
}
// Decoder provides decoding of an Event Stream messages.
type Decoder struct {
options DecoderOptions
}
// NewDecoder initializes and returns a Decoder for decoding event
// stream messages from the reader provided.
func NewDecoder(optFns ...func(*DecoderOptions)) *Decoder {
options := DecoderOptions{}
for _, fn := range optFns {
fn(&options)
}
return &Decoder{
options: options,
}
}
// Decode attempts to decode a single message from the event stream reader.
// Will return the event stream message, or error if decodeMessage fails to read
// the message from the stream.
//
// payloadBuf is a byte slice that will be used in the returned Message.Payload. Callers
// must ensure that the Message.Payload from a previous decode has been consumed before passing in the same underlying
// payloadBuf byte slice.
func (d *Decoder) Decode(reader io.Reader, payloadBuf []byte) (m Message, err error) {
if d.options.Logger != nil && d.options.LogMessages {
debugMsgBuf := bytes.NewBuffer(nil)
reader = io.TeeReader(reader, debugMsgBuf)
defer func() {
logMessageDecode(d.options.Logger, debugMsgBuf, m, err)
}()
}
m, err = decodeMessage(reader, payloadBuf)
return m, err
}
// decodeMessage attempts to decode a single message from the event stream reader.
// Will return the event stream message, or error if decodeMessage fails to read
// the message from the reader.
func decodeMessage(reader io.Reader, payloadBuf []byte) (m Message, err error) {
crc := crc32.New(crc32IEEETable)
hashReader := io.TeeReader(reader, crc)
prelude, err := decodePrelude(hashReader, crc)
if err != nil {
return Message{}, err
}
if prelude.HeadersLen > 0 {
lr := io.LimitReader(hashReader, int64(prelude.HeadersLen))
m.Headers, err = decodeHeaders(lr)
if err != nil {
return Message{}, err
}
}
if payloadLen := prelude.PayloadLen(); payloadLen > 0 {
buf, err := decodePayload(payloadBuf, io.LimitReader(hashReader, int64(payloadLen)))
if err != nil {
return Message{}, err
}
m.Payload = buf
}
msgCRC := crc.Sum32()
if err := validateCRC(reader, msgCRC); err != nil {
return Message{}, err
}
return m, nil
}
func logMessageDecode(logger logging.Logger, msgBuf *bytes.Buffer, msg Message, decodeErr error) {
w := bytes.NewBuffer(nil)
defer func() { logger.Logf(logging.Debug, w.String()) }()
fmt.Fprintf(w, "Raw message:\n%s\n",
hex.Dump(msgBuf.Bytes()))
if decodeErr != nil {
fmt.Fprintf(w, "decodeMessage error: %v\n", decodeErr)
return
}
rawMsg, err := msg.rawMessage()
if err != nil {
fmt.Fprintf(w, "failed to create raw message, %v\n", err)
return
}
decodedMsg := decodedMessage{
rawMessage: rawMsg,
Headers: decodedHeaders(msg.Headers),
}
fmt.Fprintf(w, "Decoded message:\n")
encoder := json.NewEncoder(w)
if err := encoder.Encode(decodedMsg); err != nil {
fmt.Fprintf(w, "failed to generate decoded message, %v\n", err)
}
}
func decodePrelude(r io.Reader, crc hash.Hash32) (messagePrelude, error) {
var p messagePrelude
var err error
p.Length, err = decodeUint32(r)
if err != nil {
return messagePrelude{}, err
}
p.HeadersLen, err = decodeUint32(r)
if err != nil {
return messagePrelude{}, err
}
if err := p.ValidateLens(); err != nil {
return messagePrelude{}, err
}
preludeCRC := crc.Sum32()
if err := validateCRC(r, preludeCRC); err != nil {
return messagePrelude{}, err
}
p.PreludeCRC = preludeCRC
return p, nil
}
func decodePayload(buf []byte, r io.Reader) ([]byte, error) {
w := bytes.NewBuffer(buf[0:0])
_, err := io.Copy(w, r)
return w.Bytes(), err
}
func decodeUint8(r io.Reader) (uint8, error) {
type byteReader interface {
ReadByte() (byte, error)
}
if br, ok := r.(byteReader); ok {
v, err := br.ReadByte()
return v, err
}
var b [1]byte
_, err := io.ReadFull(r, b[:])
return b[0], err
}
func decodeUint16(r io.Reader) (uint16, error) {
var b [2]byte
bs := b[:]
_, err := io.ReadFull(r, bs)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint16(bs), nil
}
func decodeUint32(r io.Reader) (uint32, error) {
var b [4]byte
bs := b[:]
_, err := io.ReadFull(r, bs)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint32(bs), nil
}
func decodeUint64(r io.Reader) (uint64, error) {
var b [8]byte
bs := b[:]
_, err := io.ReadFull(r, bs)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint64(bs), nil
}
func validateCRC(r io.Reader, expect uint32) error {
msgCRC, err := decodeUint32(r)
if err != nil {
return err
}
if msgCRC != expect {
return ChecksumError{}
}
return nil
}
+167
View File
@@ -0,0 +1,167 @@
package eventstream
import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/aws/smithy-go/logging"
"hash"
"hash/crc32"
"io"
)
// EncoderOptions is the configuration options for Encoder.
type EncoderOptions struct {
Logger logging.Logger
LogMessages bool
}
// Encoder provides EventStream message encoding.
type Encoder struct {
options EncoderOptions
headersBuf *bytes.Buffer
messageBuf *bytes.Buffer
}
// NewEncoder initializes and returns an Encoder to encode Event Stream
// messages.
func NewEncoder(optFns ...func(*EncoderOptions)) *Encoder {
o := EncoderOptions{}
for _, fn := range optFns {
fn(&o)
}
return &Encoder{
options: o,
headersBuf: bytes.NewBuffer(nil),
messageBuf: bytes.NewBuffer(nil),
}
}
// Encode encodes a single EventStream message to the io.Writer the Encoder
// was created with. An error is returned if writing the message fails.
func (e *Encoder) Encode(w io.Writer, msg Message) (err error) {
e.headersBuf.Reset()
e.messageBuf.Reset()
var writer io.Writer = e.messageBuf
if e.options.Logger != nil && e.options.LogMessages {
encodeMsgBuf := bytes.NewBuffer(nil)
writer = io.MultiWriter(writer, encodeMsgBuf)
defer func() {
logMessageEncode(e.options.Logger, encodeMsgBuf, msg, err)
}()
}
if err = EncodeHeaders(e.headersBuf, msg.Headers); err != nil {
return err
}
crc := crc32.New(crc32IEEETable)
hashWriter := io.MultiWriter(writer, crc)
headersLen := uint32(e.headersBuf.Len())
payloadLen := uint32(len(msg.Payload))
if err = encodePrelude(hashWriter, crc, headersLen, payloadLen); err != nil {
return err
}
if headersLen > 0 {
if _, err = io.Copy(hashWriter, e.headersBuf); err != nil {
return err
}
}
if payloadLen > 0 {
if _, err = hashWriter.Write(msg.Payload); err != nil {
return err
}
}
msgCRC := crc.Sum32()
if err := binary.Write(writer, binary.BigEndian, msgCRC); err != nil {
return err
}
_, err = io.Copy(w, e.messageBuf)
return err
}
func logMessageEncode(logger logging.Logger, msgBuf *bytes.Buffer, msg Message, encodeErr error) {
w := bytes.NewBuffer(nil)
defer func() { logger.Logf(logging.Debug, w.String()) }()
fmt.Fprintf(w, "Message to encode:\n")
encoder := json.NewEncoder(w)
if err := encoder.Encode(msg); err != nil {
fmt.Fprintf(w, "Failed to get encoded message, %v\n", err)
}
if encodeErr != nil {
fmt.Fprintf(w, "Encode error: %v\n", encodeErr)
return
}
fmt.Fprintf(w, "Raw message:\n%s\n", hex.Dump(msgBuf.Bytes()))
}
func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32) error {
p := messagePrelude{
Length: minMsgLen + headersLen + payloadLen,
HeadersLen: headersLen,
}
if err := p.ValidateLens(); err != nil {
return err
}
err := binaryWriteFields(w, binary.BigEndian,
p.Length,
p.HeadersLen,
)
if err != nil {
return err
}
p.PreludeCRC = crc.Sum32()
err = binary.Write(w, binary.BigEndian, p.PreludeCRC)
if err != nil {
return err
}
return nil
}
// EncodeHeaders writes the header values to the writer encoded in the event
// stream format. Returns an error if a header fails to encode.
func EncodeHeaders(w io.Writer, headers Headers) error {
for _, h := range headers {
hn := headerName{
Len: uint8(len(h.Name)),
}
copy(hn.Name[:hn.Len], h.Name)
if err := hn.encode(w); err != nil {
return err
}
if err := h.Value.encode(w); err != nil {
return err
}
}
return nil
}
func binaryWriteFields(w io.Writer, order binary.ByteOrder, vs ...interface{}) error {
for _, v := range vs {
if err := binary.Write(w, order, v); err != nil {
return err
}
}
return nil
}
+23
View File
@@ -0,0 +1,23 @@
package eventstream
import "fmt"
// LengthError provides the error for items being larger than a maximum length.
type LengthError struct {
Part string
Want int
Have int
Value interface{}
}
func (e LengthError) Error() string {
return fmt.Sprintf("%s length invalid, %d/%d, %v",
e.Part, e.Want, e.Have, e.Value)
}
// ChecksumError provides the error for message checksum invalidation errors.
type ChecksumError struct{}
func (e ChecksumError) Error() string {
return "message checksum mismatch"
}
@@ -0,0 +1,24 @@
package eventstreamapi
// EventStream headers with specific meaning to async API functionality.
const (
ChunkSignatureHeader = `:chunk-signature` // chunk signature for message
DateHeader = `:date` // Date header for signature
ContentTypeHeader = ":content-type" // message payload content-type
// Message header and values
MessageTypeHeader = `:message-type` // Identifies type of message.
EventMessageType = `event`
ErrorMessageType = `error`
ExceptionMessageType = `exception`
// Message Events
EventTypeHeader = `:event-type` // Identifies message event type e.g. "Stats".
// Message Error
ErrorCodeHeader = `:error-code`
ErrorMessageHeader = `:error-message`
// Message Exception
ExceptionTypeHeader = `:exception-type`
)
@@ -0,0 +1,71 @@
package eventstreamapi
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
"io"
)
type eventStreamWriterKey struct{}
// GetInputStreamWriter returns EventTypeHeader io.PipeWriter used for the operation's input event stream.
func GetInputStreamWriter(ctx context.Context) io.WriteCloser {
writeCloser, _ := middleware.GetStackValue(ctx, eventStreamWriterKey{}).(io.WriteCloser)
return writeCloser
}
func setInputStreamWriter(ctx context.Context, writeCloser io.WriteCloser) context.Context {
return middleware.WithStackValue(ctx, eventStreamWriterKey{}, writeCloser)
}
// InitializeStreamWriter is a Finalize middleware initializes an in-memory pipe for sending event stream messages
// via the HTTP request body.
type InitializeStreamWriter struct{}
// AddInitializeStreamWriter adds the InitializeStreamWriter middleware to the provided stack.
func AddInitializeStreamWriter(stack *middleware.Stack) error {
return stack.Finalize.Add(&InitializeStreamWriter{}, middleware.After)
}
// ID returns the identifier for the middleware.
func (i *InitializeStreamWriter) ID() string {
return "InitializeStreamWriter"
}
// HandleFinalize is the middleware implementation.
func (i *InitializeStreamWriter) HandleFinalize(
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
request, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type: %T", in.Request)
}
inputReader, inputWriter := io.Pipe()
defer func() {
if err == nil {
return
}
_ = inputReader.Close()
_ = inputWriter.Close()
}()
request, err = request.SetStream(inputReader)
if err != nil {
return out, metadata, err
}
in.Request = request
ctx = setInputStreamWriter(ctx, inputWriter)
out, metadata, err = next.HandleFinalize(ctx, in)
if err != nil {
return out, metadata, err
}
return out, metadata, err
}
@@ -0,0 +1,13 @@
//go:build go1.18
// +build go1.18
package eventstreamapi
import smithyhttp "github.com/aws/smithy-go/transport/http"
// ApplyHTTPTransportFixes applies fixes to the HTTP request for proper event stream functionality.
//
// This operation is a no-op for Go 1.18 and above.
func ApplyHTTPTransportFixes(r *smithyhttp.Request) error {
return nil
}
@@ -0,0 +1,12 @@
//go:build !go1.18
// +build !go1.18
package eventstreamapi
import smithyhttp "github.com/aws/smithy-go/transport/http"
// ApplyHTTPTransportFixes applies fixes to the HTTP request for proper event stream functionality.
func ApplyHTTPTransportFixes(r *smithyhttp.Request) error {
r.Header.Set("Expect", "100-continue")
return nil
}
@@ -0,0 +1,6 @@
// Code generated by internal/repotools/cmd/updatemodulemeta DO NOT EDIT.
package eventstream
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.6.8"
+175
View File
@@ -0,0 +1,175 @@
package eventstream
import (
"encoding/binary"
"fmt"
"io"
)
// Headers are a collection of EventStream header values.
type Headers []Header
// Header is a single EventStream Key Value header pair.
type Header struct {
Name string
Value Value
}
// Set associates the name with a value. If the header name already exists in
// the Headers the value will be replaced with the new one.
func (hs *Headers) Set(name string, value Value) {
var i int
for ; i < len(*hs); i++ {
if (*hs)[i].Name == name {
(*hs)[i].Value = value
return
}
}
*hs = append(*hs, Header{
Name: name, Value: value,
})
}
// Get returns the Value associated with the header. Nil is returned if the
// value does not exist.
func (hs Headers) Get(name string) Value {
for i := 0; i < len(hs); i++ {
if h := hs[i]; h.Name == name {
return h.Value
}
}
return nil
}
// Del deletes the value in the Headers if it exists.
func (hs *Headers) Del(name string) {
for i := 0; i < len(*hs); i++ {
if (*hs)[i].Name == name {
copy((*hs)[i:], (*hs)[i+1:])
(*hs) = (*hs)[:len(*hs)-1]
}
}
}
// Clone returns a deep copy of the headers
func (hs Headers) Clone() Headers {
o := make(Headers, 0, len(hs))
for _, h := range hs {
o.Set(h.Name, h.Value)
}
return o
}
func decodeHeaders(r io.Reader) (Headers, error) {
hs := Headers{}
for {
name, err := decodeHeaderName(r)
if err != nil {
if err == io.EOF {
// EOF while getting header name means no more headers
break
}
return nil, err
}
value, err := decodeHeaderValue(r)
if err != nil {
return nil, err
}
hs.Set(name, value)
}
return hs, nil
}
func decodeHeaderName(r io.Reader) (string, error) {
var n headerName
var err error
n.Len, err = decodeUint8(r)
if err != nil {
return "", err
}
name := n.Name[:n.Len]
if _, err := io.ReadFull(r, name); err != nil {
return "", err
}
return string(name), nil
}
func decodeHeaderValue(r io.Reader) (Value, error) {
var raw rawValue
typ, err := decodeUint8(r)
if err != nil {
return nil, err
}
raw.Type = valueType(typ)
var v Value
switch raw.Type {
case trueValueType:
v = BoolValue(true)
case falseValueType:
v = BoolValue(false)
case int8ValueType:
var tv Int8Value
err = tv.decode(r)
v = tv
case int16ValueType:
var tv Int16Value
err = tv.decode(r)
v = tv
case int32ValueType:
var tv Int32Value
err = tv.decode(r)
v = tv
case int64ValueType:
var tv Int64Value
err = tv.decode(r)
v = tv
case bytesValueType:
var tv BytesValue
err = tv.decode(r)
v = tv
case stringValueType:
var tv StringValue
err = tv.decode(r)
v = tv
case timestampValueType:
var tv TimestampValue
err = tv.decode(r)
v = tv
case uuidValueType:
var tv UUIDValue
err = tv.decode(r)
v = tv
default:
panic(fmt.Sprintf("unknown value type %d", raw.Type))
}
// Error could be EOF, let caller deal with it
return v, err
}
const maxHeaderNameLen = 255
type headerName struct {
Len uint8
Name [maxHeaderNameLen]byte
}
func (v headerName) encode(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, v.Len); err != nil {
return err
}
_, err := w.Write(v.Name[:v.Len])
return err
}
@@ -0,0 +1,521 @@
package eventstream
import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"strconv"
"time"
)
const maxHeaderValueLen = 1<<15 - 1 // 2^15-1 or 32KB - 1
// valueType is the EventStream header value type.
type valueType uint8
// Header value types
const (
trueValueType valueType = iota
falseValueType
int8ValueType // Byte
int16ValueType // Short
int32ValueType // Integer
int64ValueType // Long
bytesValueType
stringValueType
timestampValueType
uuidValueType
)
func (t valueType) String() string {
switch t {
case trueValueType:
return "bool"
case falseValueType:
return "bool"
case int8ValueType:
return "int8"
case int16ValueType:
return "int16"
case int32ValueType:
return "int32"
case int64ValueType:
return "int64"
case bytesValueType:
return "byte_array"
case stringValueType:
return "string"
case timestampValueType:
return "timestamp"
case uuidValueType:
return "uuid"
default:
return fmt.Sprintf("unknown value type %d", uint8(t))
}
}
type rawValue struct {
Type valueType
Len uint16 // Only set for variable length slices
Value []byte // byte representation of value, BigEndian encoding.
}
func (r rawValue) encodeScalar(w io.Writer, v interface{}) error {
return binaryWriteFields(w, binary.BigEndian,
r.Type,
v,
)
}
func (r rawValue) encodeFixedSlice(w io.Writer, v []byte) error {
binary.Write(w, binary.BigEndian, r.Type)
_, err := w.Write(v)
return err
}
func (r rawValue) encodeBytes(w io.Writer, v []byte) error {
if len(v) > maxHeaderValueLen {
return LengthError{
Part: "header value",
Want: maxHeaderValueLen, Have: len(v),
Value: v,
}
}
r.Len = uint16(len(v))
err := binaryWriteFields(w, binary.BigEndian,
r.Type,
r.Len,
)
if err != nil {
return err
}
_, err = w.Write(v)
return err
}
func (r rawValue) encodeString(w io.Writer, v string) error {
if len(v) > maxHeaderValueLen {
return LengthError{
Part: "header value",
Want: maxHeaderValueLen, Have: len(v),
Value: v,
}
}
r.Len = uint16(len(v))
type stringWriter interface {
WriteString(string) (int, error)
}
err := binaryWriteFields(w, binary.BigEndian,
r.Type,
r.Len,
)
if err != nil {
return err
}
if sw, ok := w.(stringWriter); ok {
_, err = sw.WriteString(v)
} else {
_, err = w.Write([]byte(v))
}
return err
}
func decodeFixedBytesValue(r io.Reader, buf []byte) error {
_, err := io.ReadFull(r, buf)
return err
}
func decodeBytesValue(r io.Reader) ([]byte, error) {
var raw rawValue
var err error
raw.Len, err = decodeUint16(r)
if err != nil {
return nil, err
}
buf := make([]byte, raw.Len)
_, err = io.ReadFull(r, buf)
if err != nil {
return nil, err
}
return buf, nil
}
func decodeStringValue(r io.Reader) (string, error) {
v, err := decodeBytesValue(r)
return string(v), err
}
// Value represents the abstract header value.
type Value interface {
Get() interface{}
String() string
valueType() valueType
encode(io.Writer) error
}
// An BoolValue provides eventstream encoding, and representation
// of a Go bool value.
type BoolValue bool
// Get returns the underlying type
func (v BoolValue) Get() interface{} {
return bool(v)
}
// valueType returns the EventStream header value type value.
func (v BoolValue) valueType() valueType {
if v {
return trueValueType
}
return falseValueType
}
func (v BoolValue) String() string {
return strconv.FormatBool(bool(v))
}
// encode encodes the BoolValue into an eventstream binary value
// representation.
func (v BoolValue) encode(w io.Writer) error {
return binary.Write(w, binary.BigEndian, v.valueType())
}
// An Int8Value provides eventstream encoding, and representation of a Go
// int8 value.
type Int8Value int8
// Get returns the underlying value.
func (v Int8Value) Get() interface{} {
return int8(v)
}
// valueType returns the EventStream header value type value.
func (Int8Value) valueType() valueType {
return int8ValueType
}
func (v Int8Value) String() string {
return fmt.Sprintf("0x%02x", int8(v))
}
// encode encodes the Int8Value into an eventstream binary value
// representation.
func (v Int8Value) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeScalar(w, v)
}
func (v *Int8Value) decode(r io.Reader) error {
n, err := decodeUint8(r)
if err != nil {
return err
}
*v = Int8Value(n)
return nil
}
// An Int16Value provides eventstream encoding, and representation of a Go
// int16 value.
type Int16Value int16
// Get returns the underlying value.
func (v Int16Value) Get() interface{} {
return int16(v)
}
// valueType returns the EventStream header value type value.
func (Int16Value) valueType() valueType {
return int16ValueType
}
func (v Int16Value) String() string {
return fmt.Sprintf("0x%04x", int16(v))
}
// encode encodes the Int16Value into an eventstream binary value
// representation.
func (v Int16Value) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeScalar(w, v)
}
func (v *Int16Value) decode(r io.Reader) error {
n, err := decodeUint16(r)
if err != nil {
return err
}
*v = Int16Value(n)
return nil
}
// An Int32Value provides eventstream encoding, and representation of a Go
// int32 value.
type Int32Value int32
// Get returns the underlying value.
func (v Int32Value) Get() interface{} {
return int32(v)
}
// valueType returns the EventStream header value type value.
func (Int32Value) valueType() valueType {
return int32ValueType
}
func (v Int32Value) String() string {
return fmt.Sprintf("0x%08x", int32(v))
}
// encode encodes the Int32Value into an eventstream binary value
// representation.
func (v Int32Value) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeScalar(w, v)
}
func (v *Int32Value) decode(r io.Reader) error {
n, err := decodeUint32(r)
if err != nil {
return err
}
*v = Int32Value(n)
return nil
}
// An Int64Value provides eventstream encoding, and representation of a Go
// int64 value.
type Int64Value int64
// Get returns the underlying value.
func (v Int64Value) Get() interface{} {
return int64(v)
}
// valueType returns the EventStream header value type value.
func (Int64Value) valueType() valueType {
return int64ValueType
}
func (v Int64Value) String() string {
return fmt.Sprintf("0x%016x", int64(v))
}
// encode encodes the Int64Value into an eventstream binary value
// representation.
func (v Int64Value) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeScalar(w, v)
}
func (v *Int64Value) decode(r io.Reader) error {
n, err := decodeUint64(r)
if err != nil {
return err
}
*v = Int64Value(n)
return nil
}
// An BytesValue provides eventstream encoding, and representation of a Go
// byte slice.
type BytesValue []byte
// Get returns the underlying value.
func (v BytesValue) Get() interface{} {
return []byte(v)
}
// valueType returns the EventStream header value type value.
func (BytesValue) valueType() valueType {
return bytesValueType
}
func (v BytesValue) String() string {
return base64.StdEncoding.EncodeToString([]byte(v))
}
// encode encodes the BytesValue into an eventstream binary value
// representation.
func (v BytesValue) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeBytes(w, []byte(v))
}
func (v *BytesValue) decode(r io.Reader) error {
buf, err := decodeBytesValue(r)
if err != nil {
return err
}
*v = BytesValue(buf)
return nil
}
// An StringValue provides eventstream encoding, and representation of a Go
// string.
type StringValue string
// Get returns the underlying value.
func (v StringValue) Get() interface{} {
return string(v)
}
// valueType returns the EventStream header value type value.
func (StringValue) valueType() valueType {
return stringValueType
}
func (v StringValue) String() string {
return string(v)
}
// encode encodes the StringValue into an eventstream binary value
// representation.
func (v StringValue) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeString(w, string(v))
}
func (v *StringValue) decode(r io.Reader) error {
s, err := decodeStringValue(r)
if err != nil {
return err
}
*v = StringValue(s)
return nil
}
// An TimestampValue provides eventstream encoding, and representation of a Go
// timestamp.
type TimestampValue time.Time
// Get returns the underlying value.
func (v TimestampValue) Get() interface{} {
return time.Time(v)
}
// valueType returns the EventStream header value type value.
func (TimestampValue) valueType() valueType {
return timestampValueType
}
func (v TimestampValue) epochMilli() int64 {
nano := time.Time(v).UnixNano()
msec := nano / int64(time.Millisecond)
return msec
}
func (v TimestampValue) String() string {
msec := v.epochMilli()
return strconv.FormatInt(msec, 10)
}
// encode encodes the TimestampValue into an eventstream binary value
// representation.
func (v TimestampValue) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
msec := v.epochMilli()
return raw.encodeScalar(w, msec)
}
func (v *TimestampValue) decode(r io.Reader) error {
n, err := decodeUint64(r)
if err != nil {
return err
}
*v = TimestampValue(timeFromEpochMilli(int64(n)))
return nil
}
// MarshalJSON implements the json.Marshaler interface
func (v TimestampValue) MarshalJSON() ([]byte, error) {
return []byte(v.String()), nil
}
func timeFromEpochMilli(t int64) time.Time {
secs := t / 1e3
msec := t % 1e3
return time.Unix(secs, msec*int64(time.Millisecond)).UTC()
}
// An UUIDValue provides eventstream encoding, and representation of a UUID
// value.
type UUIDValue [16]byte
// Get returns the underlying value.
func (v UUIDValue) Get() interface{} {
return v[:]
}
// valueType returns the EventStream header value type value.
func (UUIDValue) valueType() valueType {
return uuidValueType
}
func (v UUIDValue) String() string {
var scratch [36]byte
const dash = '-'
hex.Encode(scratch[:8], v[0:4])
scratch[8] = dash
hex.Encode(scratch[9:13], v[4:6])
scratch[13] = dash
hex.Encode(scratch[14:18], v[6:8])
scratch[18] = dash
hex.Encode(scratch[19:23], v[8:10])
scratch[23] = dash
hex.Encode(scratch[24:], v[10:])
return string(scratch[:])
}
// encode encodes the UUIDValue into an eventstream binary value
// representation.
func (v UUIDValue) encode(w io.Writer) error {
raw := rawValue{
Type: v.valueType(),
}
return raw.encodeFixedSlice(w, v[:])
}
func (v *UUIDValue) decode(r io.Reader) error {
tv := (*v)[:]
return decodeFixedBytesValue(r, tv)
}
+117
View File
@@ -0,0 +1,117 @@
package eventstream
import (
"bytes"
"encoding/binary"
"hash/crc32"
)
const preludeLen = 8
const preludeCRCLen = 4
const msgCRCLen = 4
const minMsgLen = preludeLen + preludeCRCLen + msgCRCLen
const maxPayloadLen = 1024 * 1024 * 16 // 16MB
const maxHeadersLen = 1024 * 128 // 128KB
const maxMsgLen = minMsgLen + maxHeadersLen + maxPayloadLen
var crc32IEEETable = crc32.MakeTable(crc32.IEEE)
// A Message provides the eventstream message representation.
type Message struct {
Headers Headers
Payload []byte
}
func (m *Message) rawMessage() (rawMessage, error) {
var raw rawMessage
if len(m.Headers) > 0 {
var headers bytes.Buffer
if err := EncodeHeaders(&headers, m.Headers); err != nil {
return rawMessage{}, err
}
raw.Headers = headers.Bytes()
raw.HeadersLen = uint32(len(raw.Headers))
}
raw.Length = raw.HeadersLen + uint32(len(m.Payload)) + minMsgLen
hash := crc32.New(crc32IEEETable)
binaryWriteFields(hash, binary.BigEndian, raw.Length, raw.HeadersLen)
raw.PreludeCRC = hash.Sum32()
binaryWriteFields(hash, binary.BigEndian, raw.PreludeCRC)
if raw.HeadersLen > 0 {
hash.Write(raw.Headers)
}
// Read payload bytes and update hash for it as well.
if len(m.Payload) > 0 {
raw.Payload = m.Payload
hash.Write(raw.Payload)
}
raw.CRC = hash.Sum32()
return raw, nil
}
// Clone returns a deep copy of the message.
func (m Message) Clone() Message {
var payload []byte
if m.Payload != nil {
payload = make([]byte, len(m.Payload))
copy(payload, m.Payload)
}
return Message{
Headers: m.Headers.Clone(),
Payload: payload,
}
}
type messagePrelude struct {
Length uint32
HeadersLen uint32
PreludeCRC uint32
}
func (p messagePrelude) PayloadLen() uint32 {
return p.Length - p.HeadersLen - minMsgLen
}
func (p messagePrelude) ValidateLens() error {
if p.Length == 0 || p.Length > maxMsgLen {
return LengthError{
Part: "message prelude",
Want: maxMsgLen,
Have: int(p.Length),
}
}
if p.HeadersLen > maxHeadersLen {
return LengthError{
Part: "message headers",
Want: maxHeadersLen,
Have: int(p.HeadersLen),
}
}
if payloadLen := p.PayloadLen(); payloadLen > maxPayloadLen {
return LengthError{
Part: "message payload",
Want: maxPayloadLen,
Have: int(payloadLen),
}
}
return nil
}
type rawMessage struct {
messagePrelude
Headers []byte
Payload []byte
CRC uint32
}
+9 -20
View File
@@ -1,8 +1,8 @@
package query
import (
"fmt"
"net/url"
"strconv"
)
// Array represents the encoding of Query lists and sets. A Query array is a
@@ -21,19 +21,8 @@ type Array struct {
// keys for each element in the list. For example, an entry might have the
// key "ParentStructure.ListName.member.MemberName.1".
//
// While this is currently represented as a string that gets added to, it
// could also be represented as a stack that only gets condensed into a
// string when a finalized key is created. This could potentially reduce
// allocations.
// When the array is not flat the prefix will contain the memberName otherwise the memberName is ignored
prefix string
// Whether the list is flat or not. A list that is not flat will produce the
// following entry to the url.Values for a given entry:
// ListName.MemberName.1=value
// A list that is flat will produce the following:
// ListName.1=value
flat bool
// The location name of the member. In most cases this should be "member".
memberName string
// Elements are stored in values, so we keep track of the list size here.
size int32
// Empty lists are encoded as "<prefix>=", if we add a value later we will
@@ -45,11 +34,14 @@ func newArray(values url.Values, prefix string, flat bool, memberName string) *A
emptyValue := newValue(values, prefix, flat)
emptyValue.String("")
if !flat {
// This uses string concatenation in place of fmt.Sprintf as fmt.Sprintf has a much higher resource overhead
prefix = prefix + keySeparator + memberName
}
return &Array{
values: values,
prefix: prefix,
flat: flat,
memberName: memberName,
emptyValue: emptyValue,
}
}
@@ -63,10 +55,7 @@ func (a *Array) Value() Value {
// Query lists start a 1, so adjust the size first
a.size++
prefix := a.prefix
if !a.flat {
prefix = fmt.Sprintf("%s.%s", prefix, a.memberName)
}
// Lists can't have flat members
return newValue(a.values, fmt.Sprintf("%s.%d", prefix, a.size), false)
// This uses string concatenation in place of fmt.Sprintf as fmt.Sprintf has a much higher resource overhead
return newValue(a.values, a.prefix+keySeparator+strconv.FormatInt(int64(a.size), 10), false)
}
+5 -6
View File
@@ -1,9 +1,6 @@
package query
import (
"fmt"
"net/url"
)
import "net/url"
// Object represents the encoding of Query structures and unions. A Query
// object is a representation of a mapping of string keys to arbitrary
@@ -56,14 +53,16 @@ func (o *Object) FlatKey(name string) Value {
func (o *Object) key(name string, flatValue bool) Value {
if o.prefix != "" {
return newValue(o.values, fmt.Sprintf("%s.%s", o.prefix, name), flatValue)
// This uses string concatenation in place of fmt.Sprintf as fmt.Sprintf has a much higher resource overhead
return newValue(o.values, o.prefix+keySeparator+name, flatValue)
}
return newValue(o.values, name, flatValue)
}
func (o *Object) keyWithValues(name string, flatValue bool) Value {
if o.prefix != "" {
return newAppendValue(o.values, fmt.Sprintf("%s.%s", o.prefix, name), flatValue)
// This uses string concatenation in place of fmt.Sprintf as fmt.Sprintf has a much higher resource overhead
return newAppendValue(o.values, o.prefix+keySeparator+name, flatValue)
}
return newAppendValue(o.values, name, flatValue)
}
+2
View File
@@ -7,6 +7,8 @@ import (
"github.com/aws/smithy-go/encoding/httpbinding"
)
const keySeparator = "."
// Value represents a Query Value type.
type Value struct {
// The query values to add the value to.
+6
View File
@@ -116,7 +116,13 @@ func (r RetryableConnectionError) IsErrorRetryable(err error) aws.Ternary {
case errors.As(err, &conErr) && conErr.ConnectionError():
retryable = true
case strings.Contains(err.Error(), "use of closed network connection"):
fallthrough
case strings.Contains(err.Error(), "connection reset"):
// The errors "connection reset" and "use of closed network connection"
// are effectively the same. It appears to be the difference between
// sync and async read of TCP RST in the stdlib's net.Conn read loop.
// see #2737
retryable = true
case errors.As(err, &urlErr):
+5 -4
View File
@@ -4,10 +4,11 @@ package v4
var IgnoredHeaders = Rules{
ExcludeList{
MapRule{
"Authorization": struct{}{},
"User-Agent": struct{}{},
"X-Amzn-Trace-Id": struct{}{},
"Expect": struct{}{},
"Authorization": struct{}{},
"User-Agent": struct{}{},
"X-Amzn-Trace-Id": struct{}{},
"Expect": struct{}{},
"Transfer-Encoding": struct{}{},
},
},
}
+48
View File
@@ -1,3 +1,51 @@
# v1.29.4 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.29.3 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.29.2 (2025-01-24)
* **Bug Fix**: Fix env config naming and usage of deprecated ioutil
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.29.1 (2025-01-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.29.0 (2025-01-15)
* **Feature**: S3 client behavior is updated to always calculate a checksum by default for operations that support it (such as PutObject or UploadPart), or require it (such as DeleteObjects). The checksum algorithm used by default now becomes CRC32. Checksum behavior can be configured using `when_supported` and `when_required` options - in code using RequestChecksumCalculation, in shared config using request_checksum_calculation, or as env variable using AWS_REQUEST_CHECKSUM_CALCULATION. The S3 client attempts to validate response checksums for all S3 API operations that support checksums. However, if the SDK has not implemented the specified checksum algorithm then this validation is skipped. Checksum validation behavior can be configured using `when_supported` and `when_required` options - in code using ResponseChecksumValidation, in shared config using response_checksum_validation, or as env variable using AWS_RESPONSE_CHECKSUM_VALIDATION.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.11 (2025-01-14)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.10 (2025-01-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.9 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.8 (2025-01-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.7 (2024-12-19)
* **Bug Fix**: Fix improper use of printf-style functions.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.6 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.28.5 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
+7 -1
View File
@@ -83,6 +83,12 @@ var defaultAWSConfigResolvers = []awsConfigResolver{
// Sets the AccountIDEndpointMode if present in env var or shared config profile
resolveAccountIDEndpointMode,
// Sets the RequestChecksumCalculation if present in env var or shared config profile
resolveRequestChecksumCalculation,
// Sets the ResponseChecksumValidation if present in env var or shared config profile
resolveResponseChecksumValidation,
}
// A Config represents a generic configuration value or set of values. This type
@@ -212,7 +218,7 @@ func resolveConfigLoaders(options *LoadOptions) []loader {
loaders[0] = loadEnvConfig
// specification of a profile should cause a load failure if it doesn't exist
if os.Getenv(awsProfileEnvVar) != "" || options.SharedConfigProfile != "" {
if os.Getenv(awsProfileEnv) != "" || options.SharedConfigProfile != "" {
loaders[1] = loadSharedConfig
} else {
loaders[1] = loadSharedConfigIgnoreNotExist
+135 -73
View File
@@ -5,7 +5,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
@@ -21,86 +20,89 @@ const CredentialsSourceName = "EnvConfigCredentials"
// Environment variables that will be read for configuration values.
const (
awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
awsAccessKeyEnvVar = "AWS_ACCESS_KEY"
awsAccessKeyIDEnv = "AWS_ACCESS_KEY_ID"
awsAccessKeyEnv = "AWS_ACCESS_KEY"
awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
awsSecretKeyEnvVar = "AWS_SECRET_KEY"
awsSecretAccessKeyEnv = "AWS_SECRET_ACCESS_KEY"
awsSecretKeyEnv = "AWS_SECRET_KEY"
awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
awsSessionTokenEnv = "AWS_SESSION_TOKEN"
awsContainerCredentialsEndpointEnvVar = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
awsContainerCredentialsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
awsContainerPProviderAuthorizationEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
awsContainerCredentialsFullURIEnv = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
awsContainerCredentialsRelativeURIEnv = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
awsContainerAuthorizationTokenEnv = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
awsRegionEnvVar = "AWS_REGION"
awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
awsRegionEnv = "AWS_REGION"
awsDefaultRegionEnv = "AWS_DEFAULT_REGION"
awsProfileEnvVar = "AWS_PROFILE"
awsDefaultProfileEnvVar = "AWS_DEFAULT_PROFILE"
awsProfileEnv = "AWS_PROFILE"
awsDefaultProfileEnv = "AWS_DEFAULT_PROFILE"
awsSharedCredentialsFileEnvVar = "AWS_SHARED_CREDENTIALS_FILE"
awsSharedCredentialsFileEnv = "AWS_SHARED_CREDENTIALS_FILE"
awsConfigFileEnvVar = "AWS_CONFIG_FILE"
awsConfigFileEnv = "AWS_CONFIG_FILE"
awsCustomCABundleEnvVar = "AWS_CA_BUNDLE"
awsCABundleEnv = "AWS_CA_BUNDLE"
awsWebIdentityTokenFilePathEnvVar = "AWS_WEB_IDENTITY_TOKEN_FILE"
awsWebIdentityTokenFileEnv = "AWS_WEB_IDENTITY_TOKEN_FILE"
awsRoleARNEnvVar = "AWS_ROLE_ARN"
awsRoleSessionNameEnvVar = "AWS_ROLE_SESSION_NAME"
awsRoleARNEnv = "AWS_ROLE_ARN"
awsRoleSessionNameEnv = "AWS_ROLE_SESSION_NAME"
awsEnableEndpointDiscoveryEnvVar = "AWS_ENABLE_ENDPOINT_DISCOVERY"
awsEnableEndpointDiscoveryEnv = "AWS_ENABLE_ENDPOINT_DISCOVERY"
awsS3UseARNRegionEnvVar = "AWS_S3_USE_ARN_REGION"
awsS3UseARNRegionEnv = "AWS_S3_USE_ARN_REGION"
awsEc2MetadataServiceEndpointModeEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"
awsEc2MetadataServiceEndpointModeEnv = "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"
awsEc2MetadataServiceEndpointEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
awsEc2MetadataServiceEndpointEnv = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
awsEc2MetadataDisabled = "AWS_EC2_METADATA_DISABLED"
awsEc2MetadataV1DisabledEnvVar = "AWS_EC2_METADATA_V1_DISABLED"
awsEc2MetadataDisabledEnv = "AWS_EC2_METADATA_DISABLED"
awsEc2MetadataV1DisabledEnv = "AWS_EC2_METADATA_V1_DISABLED"
awsS3DisableMultiRegionAccessPointEnvVar = "AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS"
awsS3DisableMultiRegionAccessPointsEnv = "AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS"
awsUseDualStackEndpoint = "AWS_USE_DUALSTACK_ENDPOINT"
awsUseDualStackEndpointEnv = "AWS_USE_DUALSTACK_ENDPOINT"
awsUseFIPSEndpoint = "AWS_USE_FIPS_ENDPOINT"
awsUseFIPSEndpointEnv = "AWS_USE_FIPS_ENDPOINT"
awsDefaultMode = "AWS_DEFAULTS_MODE"
awsDefaultsModeEnv = "AWS_DEFAULTS_MODE"
awsRetryMaxAttempts = "AWS_MAX_ATTEMPTS"
awsRetryMode = "AWS_RETRY_MODE"
awsSdkAppID = "AWS_SDK_UA_APP_ID"
awsMaxAttemptsEnv = "AWS_MAX_ATTEMPTS"
awsRetryModeEnv = "AWS_RETRY_MODE"
awsSdkUaAppIDEnv = "AWS_SDK_UA_APP_ID"
awsIgnoreConfiguredEndpoints = "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS"
awsEndpointURL = "AWS_ENDPOINT_URL"
awsIgnoreConfiguredEndpointURLEnv = "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS"
awsEndpointURLEnv = "AWS_ENDPOINT_URL"
awsDisableRequestCompression = "AWS_DISABLE_REQUEST_COMPRESSION"
awsRequestMinCompressionSizeBytes = "AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES"
awsDisableRequestCompressionEnv = "AWS_DISABLE_REQUEST_COMPRESSION"
awsRequestMinCompressionSizeBytesEnv = "AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES"
awsS3DisableExpressSessionAuthEnv = "AWS_S3_DISABLE_EXPRESS_SESSION_AUTH"
awsAccountIDEnv = "AWS_ACCOUNT_ID"
awsAccountIDEndpointModeEnv = "AWS_ACCOUNT_ID_ENDPOINT_MODE"
awsRequestChecksumCalculation = "AWS_REQUEST_CHECKSUM_CALCULATION"
awsResponseChecksumValidation = "AWS_RESPONSE_CHECKSUM_VALIDATION"
)
var (
credAccessEnvKeys = []string{
awsAccessKeyIDEnvVar,
awsAccessKeyEnvVar,
awsAccessKeyIDEnv,
awsAccessKeyEnv,
}
credSecretEnvKeys = []string{
awsSecretAccessKeyEnvVar,
awsSecretKeyEnvVar,
awsSecretAccessKeyEnv,
awsSecretKeyEnv,
}
regionEnvKeys = []string{
awsRegionEnvVar,
awsDefaultRegionEnvVar,
awsRegionEnv,
awsDefaultRegionEnv,
}
profileEnvKeys = []string{
awsProfileEnvVar,
awsDefaultProfileEnvVar,
awsProfileEnv,
awsDefaultProfileEnv,
}
)
@@ -296,6 +298,12 @@ type EnvConfig struct {
// Indicates whether account ID will be required/ignored in endpoint2.0 routing
AccountIDEndpointMode aws.AccountIDEndpointMode
// Indicates whether request checksum should be calculated
RequestChecksumCalculation aws.RequestChecksumCalculation
// Indicates whether response checksum should be validated
ResponseChecksumValidation aws.ResponseChecksumValidation
}
// loadEnvConfig reads configuration values from the OS's environment variables.
@@ -316,79 +324,79 @@ func NewEnvConfig() (EnvConfig, error) {
setStringFromEnvVal(&creds.SecretAccessKey, credSecretEnvKeys)
if creds.HasKeys() {
creds.AccountID = os.Getenv(awsAccountIDEnv)
creds.SessionToken = os.Getenv(awsSessionTokenEnvVar)
creds.SessionToken = os.Getenv(awsSessionTokenEnv)
cfg.Credentials = creds
}
cfg.ContainerCredentialsEndpoint = os.Getenv(awsContainerCredentialsEndpointEnvVar)
cfg.ContainerCredentialsRelativePath = os.Getenv(awsContainerCredentialsRelativePathEnvVar)
cfg.ContainerAuthorizationToken = os.Getenv(awsContainerPProviderAuthorizationEnvVar)
cfg.ContainerCredentialsEndpoint = os.Getenv(awsContainerCredentialsFullURIEnv)
cfg.ContainerCredentialsRelativePath = os.Getenv(awsContainerCredentialsRelativeURIEnv)
cfg.ContainerAuthorizationToken = os.Getenv(awsContainerAuthorizationTokenEnv)
setStringFromEnvVal(&cfg.Region, regionEnvKeys)
setStringFromEnvVal(&cfg.SharedConfigProfile, profileEnvKeys)
cfg.SharedCredentialsFile = os.Getenv(awsSharedCredentialsFileEnvVar)
cfg.SharedConfigFile = os.Getenv(awsConfigFileEnvVar)
cfg.SharedCredentialsFile = os.Getenv(awsSharedCredentialsFileEnv)
cfg.SharedConfigFile = os.Getenv(awsConfigFileEnv)
cfg.CustomCABundle = os.Getenv(awsCustomCABundleEnvVar)
cfg.CustomCABundle = os.Getenv(awsCABundleEnv)
cfg.WebIdentityTokenFilePath = os.Getenv(awsWebIdentityTokenFilePathEnvVar)
cfg.WebIdentityTokenFilePath = os.Getenv(awsWebIdentityTokenFileEnv)
cfg.RoleARN = os.Getenv(awsRoleARNEnvVar)
cfg.RoleSessionName = os.Getenv(awsRoleSessionNameEnvVar)
cfg.RoleARN = os.Getenv(awsRoleARNEnv)
cfg.RoleSessionName = os.Getenv(awsRoleSessionNameEnv)
cfg.AppID = os.Getenv(awsSdkAppID)
cfg.AppID = os.Getenv(awsSdkUaAppIDEnv)
if err := setBoolPtrFromEnvVal(&cfg.DisableRequestCompression, []string{awsDisableRequestCompression}); err != nil {
if err := setBoolPtrFromEnvVal(&cfg.DisableRequestCompression, []string{awsDisableRequestCompressionEnv}); err != nil {
return cfg, err
}
if err := setInt64PtrFromEnvVal(&cfg.RequestMinCompressSizeBytes, []string{awsRequestMinCompressionSizeBytes}, smithyrequestcompression.MaxRequestMinCompressSizeBytes); err != nil {
if err := setInt64PtrFromEnvVal(&cfg.RequestMinCompressSizeBytes, []string{awsRequestMinCompressionSizeBytesEnv}, smithyrequestcompression.MaxRequestMinCompressSizeBytes); err != nil {
return cfg, err
}
if err := setEndpointDiscoveryTypeFromEnvVal(&cfg.EnableEndpointDiscovery, []string{awsEnableEndpointDiscoveryEnvVar}); err != nil {
if err := setEndpointDiscoveryTypeFromEnvVal(&cfg.EnableEndpointDiscovery, []string{awsEnableEndpointDiscoveryEnv}); err != nil {
return cfg, err
}
if err := setBoolPtrFromEnvVal(&cfg.S3UseARNRegion, []string{awsS3UseARNRegionEnvVar}); err != nil {
if err := setBoolPtrFromEnvVal(&cfg.S3UseARNRegion, []string{awsS3UseARNRegionEnv}); err != nil {
return cfg, err
}
setEC2IMDSClientEnableState(&cfg.EC2IMDSClientEnableState, []string{awsEc2MetadataDisabled})
if err := setEC2IMDSEndpointMode(&cfg.EC2IMDSEndpointMode, []string{awsEc2MetadataServiceEndpointModeEnvVar}); err != nil {
setEC2IMDSClientEnableState(&cfg.EC2IMDSClientEnableState, []string{awsEc2MetadataDisabledEnv})
if err := setEC2IMDSEndpointMode(&cfg.EC2IMDSEndpointMode, []string{awsEc2MetadataServiceEndpointModeEnv}); err != nil {
return cfg, err
}
cfg.EC2IMDSEndpoint = os.Getenv(awsEc2MetadataServiceEndpointEnvVar)
if err := setBoolPtrFromEnvVal(&cfg.EC2IMDSv1Disabled, []string{awsEc2MetadataV1DisabledEnvVar}); err != nil {
cfg.EC2IMDSEndpoint = os.Getenv(awsEc2MetadataServiceEndpointEnv)
if err := setBoolPtrFromEnvVal(&cfg.EC2IMDSv1Disabled, []string{awsEc2MetadataV1DisabledEnv}); err != nil {
return cfg, err
}
if err := setBoolPtrFromEnvVal(&cfg.S3DisableMultiRegionAccessPoints, []string{awsS3DisableMultiRegionAccessPointEnvVar}); err != nil {
if err := setBoolPtrFromEnvVal(&cfg.S3DisableMultiRegionAccessPoints, []string{awsS3DisableMultiRegionAccessPointsEnv}); err != nil {
return cfg, err
}
if err := setUseDualStackEndpointFromEnvVal(&cfg.UseDualStackEndpoint, []string{awsUseDualStackEndpoint}); err != nil {
if err := setUseDualStackEndpointFromEnvVal(&cfg.UseDualStackEndpoint, []string{awsUseDualStackEndpointEnv}); err != nil {
return cfg, err
}
if err := setUseFIPSEndpointFromEnvVal(&cfg.UseFIPSEndpoint, []string{awsUseFIPSEndpoint}); err != nil {
if err := setUseFIPSEndpointFromEnvVal(&cfg.UseFIPSEndpoint, []string{awsUseFIPSEndpointEnv}); err != nil {
return cfg, err
}
if err := setDefaultsModeFromEnvVal(&cfg.DefaultsMode, []string{awsDefaultMode}); err != nil {
if err := setDefaultsModeFromEnvVal(&cfg.DefaultsMode, []string{awsDefaultsModeEnv}); err != nil {
return cfg, err
}
if err := setIntFromEnvVal(&cfg.RetryMaxAttempts, []string{awsRetryMaxAttempts}); err != nil {
if err := setIntFromEnvVal(&cfg.RetryMaxAttempts, []string{awsMaxAttemptsEnv}); err != nil {
return cfg, err
}
if err := setRetryModeFromEnvVal(&cfg.RetryMode, []string{awsRetryMode}); err != nil {
if err := setRetryModeFromEnvVal(&cfg.RetryMode, []string{awsRetryModeEnv}); err != nil {
return cfg, err
}
setStringFromEnvVal(&cfg.BaseEndpoint, []string{awsEndpointURL})
setStringFromEnvVal(&cfg.BaseEndpoint, []string{awsEndpointURLEnv})
if err := setBoolPtrFromEnvVal(&cfg.IgnoreConfiguredEndpoints, []string{awsIgnoreConfiguredEndpoints}); err != nil {
if err := setBoolPtrFromEnvVal(&cfg.IgnoreConfiguredEndpoints, []string{awsIgnoreConfiguredEndpointURLEnv}); err != nil {
return cfg, err
}
@@ -400,6 +408,13 @@ func NewEnvConfig() (EnvConfig, error) {
return cfg, err
}
if err := setRequestChecksumCalculationFromEnvVal(&cfg.RequestChecksumCalculation, []string{awsRequestChecksumCalculation}); err != nil {
return cfg, err
}
if err := setResponseChecksumValidationFromEnvVal(&cfg.ResponseChecksumValidation, []string{awsResponseChecksumValidation}); err != nil {
return cfg, err
}
return cfg, nil
}
@@ -432,6 +447,14 @@ func (c EnvConfig) getAccountIDEndpointMode(context.Context) (aws.AccountIDEndpo
return c.AccountIDEndpointMode, len(c.AccountIDEndpointMode) > 0, nil
}
func (c EnvConfig) getRequestChecksumCalculation(context.Context) (aws.RequestChecksumCalculation, bool, error) {
return c.RequestChecksumCalculation, c.RequestChecksumCalculation > 0, nil
}
func (c EnvConfig) getResponseChecksumValidation(context.Context) (aws.ResponseChecksumValidation, bool, error) {
return c.ResponseChecksumValidation, c.ResponseChecksumValidation > 0, nil
}
// GetRetryMaxAttempts returns the value of AWS_MAX_ATTEMPTS if was specified,
// and not 0.
func (c EnvConfig) GetRetryMaxAttempts(ctx context.Context) (int, bool, error) {
@@ -528,6 +551,45 @@ func setAIDEndPointModeFromEnvVal(m *aws.AccountIDEndpointMode, keys []string) e
return nil
}
func setRequestChecksumCalculationFromEnvVal(m *aws.RequestChecksumCalculation, keys []string) error {
for _, k := range keys {
value := os.Getenv(k)
if len(value) == 0 {
continue
}
switch strings.ToLower(value) {
case checksumWhenSupported:
*m = aws.RequestChecksumCalculationWhenSupported
case checksumWhenRequired:
*m = aws.RequestChecksumCalculationWhenRequired
default:
return fmt.Errorf("invalid value for environment variable, %s=%s, must be when_supported/when_required", k, value)
}
}
return nil
}
func setResponseChecksumValidationFromEnvVal(m *aws.ResponseChecksumValidation, keys []string) error {
for _, k := range keys {
value := os.Getenv(k)
if len(value) == 0 {
continue
}
switch strings.ToLower(value) {
case checksumWhenSupported:
*m = aws.ResponseChecksumValidationWhenSupported
case checksumWhenRequired:
*m = aws.ResponseChecksumValidationWhenRequired
default:
return fmt.Errorf("invalid value for environment variable, %s=%s, must be when_supported/when_required", k, value)
}
}
return nil
}
// GetRegion returns the AWS Region if set in the environment. Returns an empty
// string if not set.
func (c EnvConfig) getRegion(ctx context.Context) (string, bool, error) {
@@ -584,7 +646,7 @@ func (c EnvConfig) getCustomCABundle(context.Context) (io.Reader, bool, error) {
return nil, false, nil
}
b, err := ioutil.ReadFile(c.CustomCABundle)
b, err := os.ReadFile(c.CustomCABundle)
if err != nil {
return nil, false, err
}
@@ -608,7 +670,7 @@ func (c EnvConfig) getBaseEndpoint(context.Context) (string, bool, error) {
// GetServiceBaseEndpoint is used to retrieve a normalized SDK ID for use
// with configured endpoints.
func (c EnvConfig) GetServiceBaseEndpoint(ctx context.Context, sdkID string) (string, bool, error) {
if endpt := os.Getenv(fmt.Sprintf("%s_%s", awsEndpointURL, normalizeEnv(sdkID))); endpt != "" {
if endpt := os.Getenv(fmt.Sprintf("%s_%s", awsEndpointURLEnv, normalizeEnv(sdkID))); endpt != "" {
return endpt, true, nil
}
return "", false, nil
+1 -1
View File
@@ -3,4 +3,4 @@
package config
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.28.5"
const goModuleVersion = "1.29.4"
+35
View File
@@ -216,8 +216,15 @@ type LoadOptions struct {
// Whether S3 Express auth is disabled.
S3DisableExpressAuth *bool
// Whether account id should be built into endpoint resolution
AccountIDEndpointMode aws.AccountIDEndpointMode
// Specify if request checksum should be calculated
RequestChecksumCalculation aws.RequestChecksumCalculation
// Specifies if response checksum should be validated
ResponseChecksumValidation aws.ResponseChecksumValidation
// Service endpoint override. This value is not necessarily final and is
// passed to the service's EndpointResolverV2 for further delegation.
BaseEndpoint string
@@ -288,6 +295,14 @@ func (o LoadOptions) getAccountIDEndpointMode(ctx context.Context) (aws.AccountI
return o.AccountIDEndpointMode, len(o.AccountIDEndpointMode) > 0, nil
}
func (o LoadOptions) getRequestChecksumCalculation(ctx context.Context) (aws.RequestChecksumCalculation, bool, error) {
return o.RequestChecksumCalculation, o.RequestChecksumCalculation > 0, nil
}
func (o LoadOptions) getResponseChecksumValidation(ctx context.Context) (aws.ResponseChecksumValidation, bool, error) {
return o.ResponseChecksumValidation, o.ResponseChecksumValidation > 0, nil
}
func (o LoadOptions) getBaseEndpoint(context.Context) (string, bool, error) {
return o.BaseEndpoint, o.BaseEndpoint != "", nil
}
@@ -357,6 +372,26 @@ func WithAccountIDEndpointMode(m aws.AccountIDEndpointMode) LoadOptionsFunc {
}
}
// WithRequestChecksumCalculation is a helper function to construct functional options
// that sets RequestChecksumCalculation on config's LoadOptions
func WithRequestChecksumCalculation(c aws.RequestChecksumCalculation) LoadOptionsFunc {
return func(o *LoadOptions) error {
if c > 0 {
o.RequestChecksumCalculation = c
}
return nil
}
}
// WithResponseChecksumValidation is a helper function to construct functional options
// that sets ResponseChecksumValidation on config's LoadOptions
func WithResponseChecksumValidation(v aws.ResponseChecksumValidation) LoadOptionsFunc {
return func(o *LoadOptions) error {
o.ResponseChecksumValidation = v
return nil
}
}
// getDefaultRegion returns DefaultRegion from config's LoadOptions
func (o LoadOptions) getDefaultRegion(ctx context.Context) (string, bool, error) {
if len(o.DefaultRegion) == 0 {
+34
View File
@@ -242,6 +242,40 @@ func getAccountIDEndpointMode(ctx context.Context, configs configs) (value aws.A
return
}
// requestChecksumCalculationProvider provides access to the RequestChecksumCalculation
type requestChecksumCalculationProvider interface {
getRequestChecksumCalculation(context.Context) (aws.RequestChecksumCalculation, bool, error)
}
func getRequestChecksumCalculation(ctx context.Context, configs configs) (value aws.RequestChecksumCalculation, found bool, err error) {
for _, cfg := range configs {
if p, ok := cfg.(requestChecksumCalculationProvider); ok {
value, found, err = p.getRequestChecksumCalculation(ctx)
if err != nil || found {
break
}
}
}
return
}
// responseChecksumValidationProvider provides access to the ResponseChecksumValidation
type responseChecksumValidationProvider interface {
getResponseChecksumValidation(context.Context) (aws.ResponseChecksumValidation, bool, error)
}
func getResponseChecksumValidation(ctx context.Context, configs configs) (value aws.ResponseChecksumValidation, found bool, err error) {
for _, cfg := range configs {
if p, ok := cfg.(responseChecksumValidationProvider); ok {
value, found, err = p.getResponseChecksumValidation(ctx)
if err != nil || found {
break
}
}
}
return
}
// ec2IMDSRegionProvider provides access to the ec2 imds region
// configuration value
type ec2IMDSRegionProvider interface {
+30
View File
@@ -182,6 +182,36 @@ func resolveAccountIDEndpointMode(ctx context.Context, cfg *aws.Config, configs
return nil
}
// resolveRequestChecksumCalculation extracts the RequestChecksumCalculation from the configs slice's
// SharedConfig or EnvConfig
func resolveRequestChecksumCalculation(ctx context.Context, cfg *aws.Config, configs configs) error {
c, found, err := getRequestChecksumCalculation(ctx, configs)
if err != nil {
return err
}
if !found {
c = aws.RequestChecksumCalculationWhenSupported
}
cfg.RequestChecksumCalculation = c
return nil
}
// resolveResponseValidation extracts the ResponseChecksumValidation from the configs slice's
// SharedConfig or EnvConfig
func resolveResponseChecksumValidation(ctx context.Context, cfg *aws.Config, configs configs) error {
c, found, err := getResponseChecksumValidation(ctx, configs)
if err != nil {
return err
}
if !found {
c = aws.ResponseChecksumValidationWhenSupported
}
cfg.ResponseChecksumValidation = c
return nil
}
// resolveDefaultRegion extracts the first instance of a default region and sets `aws.Config.Region` to the default
// region if region had not been resolved from other sources.
func resolveDefaultRegion(ctx context.Context, cfg *aws.Config, configs configs) error {
+62
View File
@@ -118,6 +118,11 @@ const (
accountIDKey = "aws_account_id"
accountIDEndpointMode = "account_id_endpoint_mode"
requestChecksumCalculationKey = "request_checksum_calculation"
responseChecksumValidationKey = "response_checksum_validation"
checksumWhenSupported = "when_supported"
checksumWhenRequired = "when_required"
)
// defaultSharedConfigProfile allows for swapping the default profile for testing
@@ -346,6 +351,12 @@ type SharedConfig struct {
S3DisableExpressAuth *bool
AccountIDEndpointMode aws.AccountIDEndpointMode
// RequestChecksumCalculation indicates if the request checksum should be calculated
RequestChecksumCalculation aws.RequestChecksumCalculation
// ResponseChecksumValidation indicates if the response checksum should be validated
ResponseChecksumValidation aws.ResponseChecksumValidation
}
func (c SharedConfig) getDefaultsMode(ctx context.Context) (value aws.DefaultsMode, ok bool, err error) {
@@ -1133,6 +1144,13 @@ func (c *SharedConfig) setFromIniSection(profile string, section ini.Section) er
return fmt.Errorf("failed to load %s from shared config, %w", accountIDEndpointMode, err)
}
if err := updateRequestChecksumCalculation(&c.RequestChecksumCalculation, section, requestChecksumCalculationKey); err != nil {
return fmt.Errorf("failed to load %s from shared config, %w", requestChecksumCalculationKey, err)
}
if err := updateResponseChecksumValidation(&c.ResponseChecksumValidation, section, responseChecksumValidationKey); err != nil {
return fmt.Errorf("failed to load %s from shared config, %w", responseChecksumValidationKey, err)
}
// Shared Credentials
creds := aws.Credentials{
AccessKeyID: section.String(accessKeyIDKey),
@@ -1207,6 +1225,42 @@ func updateAIDEndpointMode(m *aws.AccountIDEndpointMode, sec ini.Section, key st
return nil
}
func updateRequestChecksumCalculation(m *aws.RequestChecksumCalculation, sec ini.Section, key string) error {
if !sec.Has(key) {
return nil
}
v := sec.String(key)
switch strings.ToLower(v) {
case checksumWhenSupported:
*m = aws.RequestChecksumCalculationWhenSupported
case checksumWhenRequired:
*m = aws.RequestChecksumCalculationWhenRequired
default:
return fmt.Errorf("invalid value for shared config profile field, %s=%s, must be when_supported/when_required", key, v)
}
return nil
}
func updateResponseChecksumValidation(m *aws.ResponseChecksumValidation, sec ini.Section, key string) error {
if !sec.Has(key) {
return nil
}
v := sec.String(key)
switch strings.ToLower(v) {
case checksumWhenSupported:
*m = aws.ResponseChecksumValidationWhenSupported
case checksumWhenRequired:
*m = aws.ResponseChecksumValidationWhenRequired
default:
return fmt.Errorf("invalid value for shared config profile field, %s=%s, must be when_supported/when_required", key, v)
}
return nil
}
func (c SharedConfig) getRequestMinCompressSizeBytes(ctx context.Context) (int64, bool, error) {
if c.RequestMinCompressSizeBytes == nil {
return 0, false, nil
@@ -1225,6 +1279,14 @@ func (c SharedConfig) getAccountIDEndpointMode(ctx context.Context) (aws.Account
return c.AccountIDEndpointMode, len(c.AccountIDEndpointMode) > 0, nil
}
func (c SharedConfig) getRequestChecksumCalculation(ctx context.Context) (aws.RequestChecksumCalculation, bool, error) {
return c.RequestChecksumCalculation, c.RequestChecksumCalculation > 0, nil
}
func (c SharedConfig) getResponseChecksumValidation(ctx context.Context) (aws.ResponseChecksumValidation, bool, error) {
return c.ResponseChecksumValidation, c.ResponseChecksumValidation > 0, nil
}
func updateDefaultsMode(mode *aws.DefaultsMode, section ini.Section, key string) error {
if !section.Has(key) {
return nil
+46
View File
@@ -1,3 +1,49 @@
# v1.17.57 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.56 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.55 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.17.54 (2025-01-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.53 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.52 (2025-01-14)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.51 (2025-01-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.50 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.49 (2025-01-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.48 (2024-12-19)
* **Bug Fix**: Fix improper use of printf-style functions.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.47 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.46 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
+1 -1
View File
@@ -3,4 +3,4 @@
package credentials
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.17.46"
const goModuleVersion = "1.17.57"
+29
View File
@@ -1,3 +1,32 @@
# v1.16.27 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.26 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.25 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.16.24 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.23 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.22 (2024-12-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.21 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.20 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
@@ -3,4 +3,4 @@
package imds
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.16.20"
const goModuleVersion = "1.16.27"
@@ -1,3 +1,32 @@
# v1.3.31 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.30 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.29 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.3.28 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.27 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.26 (2024-12-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.25 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.24 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
@@ -3,4 +3,4 @@
package configsources
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.3.24"
const goModuleVersion = "1.3.31"
@@ -47,6 +47,9 @@
"ap-southeast-5" : {
"description" : "Asia Pacific (Malaysia)"
},
"ap-southeast-7" : {
"description" : "Asia Pacific (Thailand)"
},
"aws-global" : {
"description" : "AWS Standard global region"
},
@@ -89,6 +92,9 @@
"me-south-1" : {
"description" : "Middle East (Bahrain)"
},
"mx-central-1" : {
"description" : "Mexico (Central)"
},
"sa-east-1" : {
"description" : "South America (Sao Paulo)"
},
+30
View File
@@ -1,3 +1,33 @@
# v2.6.31 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.6.30 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.6.29 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v2.6.28 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.6.27 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.6.26 (2024-12-19)
* **Bug Fix**: Fix improper use of printf-style functions.
* **Dependency Update**: Updated to the latest SDK module versions
# v2.6.25 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.6.24 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
@@ -3,4 +3,4 @@
package endpoints
// goModuleVersion is the tagged release for this module
const goModuleVersion = "2.6.24"
const goModuleVersion = "2.6.31"
+4
View File
@@ -1,3 +1,7 @@
# v1.8.2 (2025-01-24)
* **Bug Fix**: Refactor filepath.Walk to filepath.WalkDir
# v1.8.1 (2024-08-15)
* **Dependency Update**: Bump minimum Go version to 1.21.
+1 -1
View File
@@ -3,4 +3,4 @@
package ini
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.8.1"
const goModuleVersion = "1.8.2"
+325
View File
@@ -0,0 +1,325 @@
# v1.3.31 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.30 (2025-01-30)
* **Bug Fix**: Do not sign Transfer-Encoding header in Sigv4[a]. Fixes a signer mismatch issue with S3 Accelerate.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.29 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.3.28 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.27 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.26 (2024-12-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.25 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.24 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.23 (2024-11-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.22 (2024-10-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.21 (2024-10-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.20 (2024-10-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.19 (2024-10-04)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.18 (2024-09-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.17 (2024-09-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.16 (2024-08-15)
* **Dependency Update**: Bump minimum Go version to 1.21.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.15 (2024-07-10.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.14 (2024-07-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.13 (2024-06-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.12 (2024-06-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.11 (2024-06-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.10 (2024-06-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.9 (2024-06-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.8 (2024-06-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.7 (2024-05-16)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.6 (2024-05-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.5 (2024-03-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.4 (2024-03-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.3 (2024-03-07)
* **Bug Fix**: Remove dependency on go-cmp.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.2 (2024-02-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.1 (2024-02-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.0 (2024-02-13)
* **Feature**: Bump minimum Go version to 1.20 per our language support policy.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.10 (2024-01-04)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.9 (2023-12-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.8 (2023-12-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.7 (2023-11-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.6 (2023-11-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.5 (2023-11-28.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.4 (2023-11-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.3 (2023-11-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.2 (2023-11-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.1 (2023-11-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.0 (2023-10-31)
* **Feature**: **BREAKING CHANGE**: Bump minimum go version to 1.19 per the revised [go version support policy](https://aws.amazon.com/blogs/developer/aws-sdk-for-go-aligns-with-go-release-policy-on-supported-runtimes/).
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.6 (2023-10-12)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.5 (2023-10-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.4 (2023-08-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.3 (2023-08-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.2 (2023-08-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.1 (2023-08-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.0 (2023-07-31)
* **Feature**: Adds support for smithy-modeled endpoint resolution. A new rules-based endpoint resolution will be added to the SDK which will supercede and deprecate existing endpoint resolution. Specifically, EndpointResolver will be deprecated while BaseEndpoint and EndpointResolverV2 will take its place. For more information, please see the Endpoints section in our Developer Guide.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.28 (2023-07-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.27 (2023-07-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.26 (2023-06-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.25 (2023-04-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.24 (2023-04-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.23 (2023-03-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.22 (2023-03-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.21 (2023-02-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.20 (2023-02-14)
* No change notes available for this release.
# v1.0.19 (2023-02-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.18 (2022-12-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.17 (2022-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.16 (2022-10-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.15 (2022-10-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.14 (2022-09-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.13 (2022-09-14)
* **Bug Fix**: Fixes an issues where an error from an underlying SigV4 credential provider would not be surfaced from the SigV4a credential provider. Contribution by [sakthipriyan-aqfer](https://github.com/sakthipriyan-aqfer).
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.12 (2022-09-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.11 (2022-08-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.10 (2022-08-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.9 (2022-08-11)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.8 (2022-08-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.7 (2022-08-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.6 (2022-08-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.5 (2022-07-05)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.4 (2022-06-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.3 (2022-06-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.2 (2022-05-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.1 (2022-04-25)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.0 (2022-04-07)
* **Release**: New internal v4a signing module location.
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+141
View File
@@ -0,0 +1,141 @@
package v4a
import (
"context"
"crypto/ecdsa"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/internal/sdk"
)
// Credentials is Context, ECDSA, and Optional Session Token that can be used
// to sign requests using SigV4a
type Credentials struct {
Context string
PrivateKey *ecdsa.PrivateKey
SessionToken string
// Time the credentials will expire.
CanExpire bool
Expires time.Time
}
// Expired returns if the credentials have expired.
func (v Credentials) Expired() bool {
if v.CanExpire {
return !v.Expires.After(sdk.NowTime())
}
return false
}
// HasKeys returns if the credentials keys are set.
func (v Credentials) HasKeys() bool {
return len(v.Context) > 0 && v.PrivateKey != nil
}
// SymmetricCredentialAdaptor wraps a SigV4 AccessKey/SecretKey provider and adapts the credentials
// to a ECDSA PrivateKey for signing with SiV4a
type SymmetricCredentialAdaptor struct {
SymmetricProvider aws.CredentialsProvider
asymmetric atomic.Value
m sync.Mutex
}
// Retrieve retrieves symmetric credentials from the underlying provider.
func (s *SymmetricCredentialAdaptor) Retrieve(ctx context.Context) (aws.Credentials, error) {
symCreds, err := s.retrieveFromSymmetricProvider(ctx)
if err != nil {
return aws.Credentials{}, err
}
if asymCreds := s.getCreds(); asymCreds == nil {
return symCreds, nil
}
s.m.Lock()
defer s.m.Unlock()
asymCreds := s.getCreds()
if asymCreds == nil {
return symCreds, nil
}
// if the context does not match the access key id clear it
if asymCreds.Context != symCreds.AccessKeyID {
s.asymmetric.Store((*Credentials)(nil))
}
return symCreds, nil
}
// RetrievePrivateKey returns credentials suitable for SigV4a signing
func (s *SymmetricCredentialAdaptor) RetrievePrivateKey(ctx context.Context) (Credentials, error) {
if asymCreds := s.getCreds(); asymCreds != nil {
return *asymCreds, nil
}
s.m.Lock()
defer s.m.Unlock()
if asymCreds := s.getCreds(); asymCreds != nil {
return *asymCreds, nil
}
symmetricCreds, err := s.retrieveFromSymmetricProvider(ctx)
if err != nil {
return Credentials{}, fmt.Errorf("failed to retrieve symmetric credentials: %v", err)
}
privateKey, err := deriveKeyFromAccessKeyPair(symmetricCreds.AccessKeyID, symmetricCreds.SecretAccessKey)
if err != nil {
return Credentials{}, fmt.Errorf("failed to derive assymetric key from credentials")
}
creds := Credentials{
Context: symmetricCreds.AccessKeyID,
PrivateKey: privateKey,
SessionToken: symmetricCreds.SessionToken,
CanExpire: symmetricCreds.CanExpire,
Expires: symmetricCreds.Expires,
}
s.asymmetric.Store(&creds)
return creds, nil
}
func (s *SymmetricCredentialAdaptor) getCreds() *Credentials {
v := s.asymmetric.Load()
if v == nil {
return nil
}
c := v.(*Credentials)
if c != nil && c.HasKeys() && !c.Expired() {
return c
}
return nil
}
func (s *SymmetricCredentialAdaptor) retrieveFromSymmetricProvider(ctx context.Context) (aws.Credentials, error) {
credentials, err := s.SymmetricProvider.Retrieve(ctx)
if err != nil {
return aws.Credentials{}, err
}
return credentials, nil
}
// CredentialsProvider is the interface for a provider to retrieve credentials
// to sign requests with.
type CredentialsProvider interface {
RetrievePrivateKey(context.Context) (Credentials, error)
}
+17
View File
@@ -0,0 +1,17 @@
package v4a
import "fmt"
// SigningError indicates an error condition occurred while performing SigV4a signing
type SigningError struct {
Err error
}
func (e *SigningError) Error() string {
return fmt.Sprintf("failed to sign request: %v", e.Err)
}
// Unwrap returns the underlying error cause
func (e *SigningError) Unwrap() error {
return e.Err
}
@@ -0,0 +1,6 @@
// Code generated by internal/repotools/cmd/updatemodulemeta DO NOT EDIT.
package v4a
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.3.31"
@@ -0,0 +1,30 @@
package crypto
import "fmt"
// ConstantTimeByteCompare is a constant-time byte comparison of x and y. This function performs an absolute comparison
// if the two byte slices assuming they represent a big-endian number.
//
// error if len(x) != len(y)
// -1 if x < y
// 0 if x == y
// +1 if x > y
func ConstantTimeByteCompare(x, y []byte) (int, error) {
if len(x) != len(y) {
return 0, fmt.Errorf("slice lengths do not match")
}
xLarger, yLarger := 0, 0
for i := 0; i < len(x); i++ {
xByte, yByte := int(x[i]), int(y[i])
x := ((yByte - xByte) >> 8) & 1
y := ((xByte - yByte) >> 8) & 1
xLarger |= x &^ yLarger
yLarger |= y &^ xLarger
}
return xLarger - yLarger, nil
}
+113
View File
@@ -0,0 +1,113 @@
package crypto
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
"encoding/asn1"
"encoding/binary"
"fmt"
"hash"
"math"
"math/big"
)
type ecdsaSignature struct {
R, S *big.Int
}
// ECDSAKey takes the given elliptic curve, and private key (d) byte slice
// and returns the private ECDSA key.
func ECDSAKey(curve elliptic.Curve, d []byte) *ecdsa.PrivateKey {
return ECDSAKeyFromPoint(curve, (&big.Int{}).SetBytes(d))
}
// ECDSAKeyFromPoint takes the given elliptic curve and point and returns the
// private and public keypair
func ECDSAKeyFromPoint(curve elliptic.Curve, d *big.Int) *ecdsa.PrivateKey {
pX, pY := curve.ScalarBaseMult(d.Bytes())
privKey := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: curve,
X: pX,
Y: pY,
},
D: d,
}
return privKey
}
// ECDSAPublicKey takes the provide curve and (x, y) coordinates and returns
// *ecdsa.PublicKey. Returns an error if the given points are not on the curve.
func ECDSAPublicKey(curve elliptic.Curve, x, y []byte) (*ecdsa.PublicKey, error) {
xPoint := (&big.Int{}).SetBytes(x)
yPoint := (&big.Int{}).SetBytes(y)
if !curve.IsOnCurve(xPoint, yPoint) {
return nil, fmt.Errorf("point(%v, %v) is not on the given curve", xPoint.String(), yPoint.String())
}
return &ecdsa.PublicKey{
Curve: curve,
X: xPoint,
Y: yPoint,
}, nil
}
// VerifySignature takes the provided public key, hash, and asn1 encoded signature and returns
// whether the given signature is valid.
func VerifySignature(key *ecdsa.PublicKey, hash []byte, signature []byte) (bool, error) {
var ecdsaSignature ecdsaSignature
_, err := asn1.Unmarshal(signature, &ecdsaSignature)
if err != nil {
return false, err
}
return ecdsa.Verify(key, hash, ecdsaSignature.R, ecdsaSignature.S), nil
}
// HMACKeyDerivation provides an implementation of a NIST-800-108 of a KDF (Key Derivation Function) in Counter Mode.
// For the purposes of this implantation HMAC is used as the PRF (Pseudorandom function), where the value of
// `r` is defined as a 4 byte counter.
func HMACKeyDerivation(hash func() hash.Hash, bitLen int, key []byte, label, context []byte) ([]byte, error) {
// verify that we won't overflow the counter
n := int64(math.Ceil((float64(bitLen) / 8) / float64(hash().Size())))
if n > 0x7FFFFFFF {
return nil, fmt.Errorf("unable to derive key of size %d using 32-bit counter", bitLen)
}
// verify the requested bit length is not larger then the length encoding size
if int64(bitLen) > 0x7FFFFFFF {
return nil, fmt.Errorf("bitLen is greater than 32-bits")
}
fixedInput := bytes.NewBuffer(nil)
fixedInput.Write(label)
fixedInput.WriteByte(0x00)
fixedInput.Write(context)
if err := binary.Write(fixedInput, binary.BigEndian, int32(bitLen)); err != nil {
return nil, fmt.Errorf("failed to write bit length to fixed input string: %v", err)
}
var output []byte
h := hmac.New(hash, key)
for i := int64(1); i <= n; i++ {
h.Reset()
if err := binary.Write(h, binary.BigEndian, int32(i)); err != nil {
return nil, err
}
_, err := h.Write(fixedInput.Bytes())
if err != nil {
return nil, err
}
output = append(output, h.Sum(nil)...)
}
return output[:bitLen/8], nil
}
+36
View File
@@ -0,0 +1,36 @@
package v4
const (
// EmptyStringSHA256 is the hex encoded sha256 value of an empty string
EmptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
// UnsignedPayload indicates that the request payload body is unsigned
UnsignedPayload = "UNSIGNED-PAYLOAD"
// AmzAlgorithmKey indicates the signing algorithm
AmzAlgorithmKey = "X-Amz-Algorithm"
// AmzSecurityTokenKey indicates the security token to be used with temporary credentials
AmzSecurityTokenKey = "X-Amz-Security-Token"
// AmzDateKey is the UTC timestamp for the request in the format YYYYMMDD'T'HHMMSS'Z'
AmzDateKey = "X-Amz-Date"
// AmzCredentialKey is the access key ID and credential scope
AmzCredentialKey = "X-Amz-Credential"
// AmzSignedHeadersKey is the set of headers signed for the request
AmzSignedHeadersKey = "X-Amz-SignedHeaders"
// AmzSignatureKey is the query parameter to store the SigV4 signature
AmzSignatureKey = "X-Amz-Signature"
// TimeFormat is the time format to be used in the X-Amz-Date header or query parameter
TimeFormat = "20060102T150405Z"
// ShortTimeFormat is the shorten time format used in the credential scope
ShortTimeFormat = "20060102"
// ContentSHAKey is the SHA256 of request body
ContentSHAKey = "X-Amz-Content-Sha256"
)
@@ -0,0 +1,82 @@
package v4
import (
sdkstrings "github.com/aws/aws-sdk-go-v2/internal/strings"
)
// Rules houses a set of Rule needed for validation of a
// string value
type Rules []Rule
// Rule interface allows for more flexible rules and just simply
// checks whether or not a value adheres to that Rule
type Rule interface {
IsValid(value string) bool
}
// IsValid will iterate through all rules and see if any rules
// apply to the value and supports nested rules
func (r Rules) IsValid(value string) bool {
for _, rule := range r {
if rule.IsValid(value) {
return true
}
}
return false
}
// MapRule generic Rule for maps
type MapRule map[string]struct{}
// IsValid for the map Rule satisfies whether it exists in the map
func (m MapRule) IsValid(value string) bool {
_, ok := m[value]
return ok
}
// AllowList is a generic Rule for whitelisting
type AllowList struct {
Rule
}
// IsValid for AllowList checks if the value is within the AllowList
func (w AllowList) IsValid(value string) bool {
return w.Rule.IsValid(value)
}
// DenyList is a generic Rule for blacklisting
type DenyList struct {
Rule
}
// IsValid for AllowList checks if the value is within the AllowList
func (b DenyList) IsValid(value string) bool {
return !b.Rule.IsValid(value)
}
// Patterns is a list of strings to match against
type Patterns []string
// IsValid for Patterns checks each pattern and returns if a match has
// been found
func (p Patterns) IsValid(value string) bool {
for _, pattern := range p {
if sdkstrings.HasPrefixFold(value, pattern) {
return true
}
}
return false
}
// InclusiveRules rules allow for rules to depend on one another
type InclusiveRules []Rule
// IsValid will return true if all rules are true
func (r InclusiveRules) IsValid(value string) bool {
for _, rule := range r {
if !rule.IsValid(value) {
return false
}
}
return true
}
@@ -0,0 +1,68 @@
package v4
// IgnoredHeaders is a list of headers that are ignored during signing
var IgnoredHeaders = Rules{
DenyList{
MapRule{
"Authorization": struct{}{},
"User-Agent": struct{}{},
"X-Amzn-Trace-Id": struct{}{},
"Transfer-Encoding": struct{}{},
},
},
}
// RequiredSignedHeaders is a whitelist for Build canonical headers.
var RequiredSignedHeaders = Rules{
AllowList{
MapRule{
"Cache-Control": struct{}{},
"Content-Disposition": struct{}{},
"Content-Encoding": struct{}{},
"Content-Language": struct{}{},
"Content-Md5": struct{}{},
"Content-Type": struct{}{},
"Expires": struct{}{},
"If-Match": struct{}{},
"If-Modified-Since": struct{}{},
"If-None-Match": struct{}{},
"If-Unmodified-Since": struct{}{},
"Range": struct{}{},
"X-Amz-Acl": struct{}{},
"X-Amz-Copy-Source": struct{}{},
"X-Amz-Copy-Source-If-Match": struct{}{},
"X-Amz-Copy-Source-If-Modified-Since": struct{}{},
"X-Amz-Copy-Source-If-None-Match": struct{}{},
"X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
"X-Amz-Copy-Source-Range": struct{}{},
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{},
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
"X-Amz-Grant-Full-control": struct{}{},
"X-Amz-Grant-Read": struct{}{},
"X-Amz-Grant-Read-Acp": struct{}{},
"X-Amz-Grant-Write": struct{}{},
"X-Amz-Grant-Write-Acp": struct{}{},
"X-Amz-Metadata-Directive": struct{}{},
"X-Amz-Mfa": struct{}{},
"X-Amz-Request-Payer": struct{}{},
"X-Amz-Server-Side-Encryption": struct{}{},
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{},
"X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{},
"X-Amz-Server-Side-Encryption-Customer-Key": struct{}{},
"X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
"X-Amz-Storage-Class": struct{}{},
"X-Amz-Website-Redirect-Location": struct{}{},
"X-Amz-Content-Sha256": struct{}{},
"X-Amz-Tagging": struct{}{},
},
},
Patterns{"X-Amz-Meta-"},
}
// AllowedQueryHoisting is a whitelist for Build query headers. The boolean value
// represents whether or not it is a pattern.
var AllowedQueryHoisting = InclusiveRules{
DenyList{RequiredSignedHeaders},
Patterns{"X-Amz-"},
}
+13
View File
@@ -0,0 +1,13 @@
package v4
import (
"crypto/hmac"
"crypto/sha256"
)
// HMACSHA256 computes a HMAC-SHA256 of data given the provided key.
func HMACSHA256(key []byte, data []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(nil)
}
+75
View File
@@ -0,0 +1,75 @@
package v4
import (
"net/http"
"strings"
)
// SanitizeHostForHeader removes default port from host and updates request.Host
func SanitizeHostForHeader(r *http.Request) {
host := getHost(r)
port := portOnly(host)
if port != "" && isDefaultPort(r.URL.Scheme, port) {
r.Host = stripPort(host)
}
}
// Returns host from request
func getHost(r *http.Request) string {
if r.Host != "" {
return r.Host
}
return r.URL.Host
}
// Hostname returns u.Host, without any port number.
//
// If Host is an IPv6 literal with a port number, Hostname returns the
// IPv6 literal without the square brackets. IPv6 literals may include
// a zone identifier.
//
// Copied from the Go 1.8 standard library (net/url)
func stripPort(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return hostport
}
if i := strings.IndexByte(hostport, ']'); i != -1 {
return strings.TrimPrefix(hostport[:i], "[")
}
return hostport[:colon]
}
// Port returns the port part of u.Host, without the leading colon.
// If u.Host doesn't contain a port, Port returns an empty string.
//
// Copied from the Go 1.8 standard library (net/url)
func portOnly(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return ""
}
if i := strings.Index(hostport, "]:"); i != -1 {
return hostport[i+len("]:"):]
}
if strings.Contains(hostport, "]") {
return ""
}
return hostport[colon+len(":"):]
}
// Returns true if the specified URI is using the standard port
// (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs)
func isDefaultPort(scheme, port string) bool {
if port == "" {
return true
}
lowerCaseScheme := strings.ToLower(scheme)
if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") {
return true
}
return false
}
+36
View File
@@ -0,0 +1,36 @@
package v4
import "time"
// SigningTime provides a wrapper around a time.Time which provides cached values for SigV4 signing.
type SigningTime struct {
time.Time
timeFormat string
shortTimeFormat string
}
// NewSigningTime creates a new SigningTime given a time.Time
func NewSigningTime(t time.Time) SigningTime {
return SigningTime{
Time: t,
}
}
// TimeFormat provides a time formatted in the X-Amz-Date format.
func (m *SigningTime) TimeFormat() string {
return m.format(&m.timeFormat, TimeFormat)
}
// ShortTimeFormat provides a time formatted of 20060102.
func (m *SigningTime) ShortTimeFormat() string {
return m.format(&m.shortTimeFormat, ShortTimeFormat)
}
func (m *SigningTime) format(target *string, format string) string {
if len(*target) > 0 {
return *target
}
v := m.Time.Format(format)
*target = v
return v
}
+64
View File
@@ -0,0 +1,64 @@
package v4
import (
"net/url"
"strings"
)
const doubleSpace = " "
// StripExcessSpaces will rewrite the passed in slice's string values to not
// contain muliple side-by-side spaces.
func StripExcessSpaces(str string) string {
var j, k, l, m, spaces int
// Trim trailing spaces
for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
}
// Trim leading spaces
for k = 0; k < j && str[k] == ' '; k++ {
}
str = str[k : j+1]
// Strip multiple spaces.
j = strings.Index(str, doubleSpace)
if j < 0 {
return str
}
buf := []byte(str)
for k, m, l = j, j, len(buf); k < l; k++ {
if buf[k] == ' ' {
if spaces == 0 {
// First space.
buf[m] = buf[k]
m++
}
spaces++
} else {
// End of multiple spaces.
spaces = 0
buf[m] = buf[k]
m++
}
}
return string(buf[:m])
}
// GetURIPath returns the escaped URI component from the provided URL
func GetURIPath(u *url.URL) string {
var uri string
if len(u.Opaque) > 0 {
uri = "/" + strings.Join(strings.Split(u.Opaque, "/")[3:], "/")
} else {
uri = u.EscapedPath()
}
if len(uri) == 0 {
uri = "/"
}
return uri
}
+118
View File
@@ -0,0 +1,118 @@
package v4a
import (
"context"
"fmt"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
internalauth "github.com/aws/aws-sdk-go-v2/internal/auth"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
"net/http"
"time"
)
// HTTPSigner is SigV4a HTTP signer implementation
type HTTPSigner interface {
SignHTTP(ctx context.Context, credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, optfns ...func(*SignerOptions)) error
}
// SignHTTPRequestMiddlewareOptions is the middleware options for constructing a SignHTTPRequestMiddleware.
type SignHTTPRequestMiddlewareOptions struct {
Credentials CredentialsProvider
Signer HTTPSigner
LogSigning bool
}
// SignHTTPRequestMiddleware is a middleware for signing an HTTP request using SigV4a.
type SignHTTPRequestMiddleware struct {
credentials CredentialsProvider
signer HTTPSigner
logSigning bool
}
// NewSignHTTPRequestMiddleware constructs a SignHTTPRequestMiddleware using the given SignHTTPRequestMiddlewareOptions.
func NewSignHTTPRequestMiddleware(options SignHTTPRequestMiddlewareOptions) *SignHTTPRequestMiddleware {
return &SignHTTPRequestMiddleware{
credentials: options.Credentials,
signer: options.Signer,
logSigning: options.LogSigning,
}
}
// ID the middleware identifier.
func (s *SignHTTPRequestMiddleware) ID() string {
return "Signing"
}
// HandleFinalize signs an HTTP request using SigV4a.
func (s *SignHTTPRequestMiddleware) HandleFinalize(
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
if !hasCredentialProvider(s.credentials) {
return next.HandleFinalize(ctx, in)
}
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unexpected request middleware type %T", in.Request)
}
signingName, signingRegion := awsmiddleware.GetSigningName(ctx), awsmiddleware.GetSigningRegion(ctx)
payloadHash := v4.GetPayloadHash(ctx)
if len(payloadHash) == 0 {
return out, metadata, &SigningError{Err: fmt.Errorf("computed payload hash missing from context")}
}
credentials, err := s.credentials.RetrievePrivateKey(ctx)
if err != nil {
return out, metadata, &SigningError{Err: fmt.Errorf("failed to retrieve credentials: %w", err)}
}
signerOptions := []func(o *SignerOptions){
func(o *SignerOptions) {
o.Logger = middleware.GetLogger(ctx)
o.LogSigning = s.logSigning
},
}
// existing DisableURIPathEscaping is equivalent in purpose
// to authentication scheme property DisableDoubleEncoding
disableDoubleEncoding, overridden := internalauth.GetDisableDoubleEncoding(ctx)
if overridden {
signerOptions = append(signerOptions, func(o *SignerOptions) {
o.DisableURIPathEscaping = disableDoubleEncoding
})
}
err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, []string{signingRegion}, time.Now().UTC(), signerOptions...)
if err != nil {
return out, metadata, &SigningError{Err: fmt.Errorf("failed to sign http request, %w", err)}
}
return next.HandleFinalize(ctx, in)
}
func hasCredentialProvider(p CredentialsProvider) bool {
if p == nil {
return false
}
return true
}
// RegisterSigningMiddleware registers the SigV4a signing middleware to the stack. If a signing middleware is already
// present, this provided middleware will be swapped. Otherwise the middleware will be added at the tail of the
// finalize step.
func RegisterSigningMiddleware(stack *middleware.Stack, signingMiddleware *SignHTTPRequestMiddleware) (err error) {
const signedID = "Signing"
_, present := stack.Finalize.Get(signedID)
if present {
_, err = stack.Finalize.Swap(signedID, signingMiddleware)
} else {
err = stack.Finalize.Add(signingMiddleware, middleware.After)
}
return err
}
+117
View File
@@ -0,0 +1,117 @@
package v4a
import (
"context"
"fmt"
"net/http"
"time"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/internal/sdk"
"github.com/aws/smithy-go/middleware"
smithyHTTP "github.com/aws/smithy-go/transport/http"
)
// HTTPPresigner is an interface to a SigV4a signer that can sign create a
// presigned URL for a HTTP requests.
type HTTPPresigner interface {
PresignHTTP(
ctx context.Context, credentials Credentials, r *http.Request,
payloadHash string, service string, regionSet []string, signingTime time.Time,
optFns ...func(*SignerOptions),
) (url string, signedHeader http.Header, err error)
}
// PresignHTTPRequestMiddlewareOptions is the options for the PresignHTTPRequestMiddleware middleware.
type PresignHTTPRequestMiddlewareOptions struct {
CredentialsProvider CredentialsProvider
Presigner HTTPPresigner
LogSigning bool
}
// PresignHTTPRequestMiddleware provides the Finalize middleware for creating a
// presigned URL for an HTTP request.
//
// Will short circuit the middleware stack and not forward onto the next
// Finalize handler.
type PresignHTTPRequestMiddleware struct {
credentialsProvider CredentialsProvider
presigner HTTPPresigner
logSigning bool
}
// NewPresignHTTPRequestMiddleware returns a new PresignHTTPRequestMiddleware
// initialized with the presigner.
func NewPresignHTTPRequestMiddleware(options PresignHTTPRequestMiddlewareOptions) *PresignHTTPRequestMiddleware {
return &PresignHTTPRequestMiddleware{
credentialsProvider: options.CredentialsProvider,
presigner: options.Presigner,
logSigning: options.LogSigning,
}
}
// ID provides the middleware ID.
func (*PresignHTTPRequestMiddleware) ID() string { return "PresignHTTPRequest" }
// HandleFinalize will take the provided input and create a presigned url for
// the http request using the SigV4 presign authentication scheme.
func (s *PresignHTTPRequestMiddleware) HandleFinalize(
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*smithyHTTP.Request)
if !ok {
return out, metadata, &SigningError{
Err: fmt.Errorf("unexpected request middleware type %T", in.Request),
}
}
httpReq := req.Build(ctx)
if !hasCredentialProvider(s.credentialsProvider) {
out.Result = &v4.PresignedHTTPRequest{
URL: httpReq.URL.String(),
Method: httpReq.Method,
SignedHeader: http.Header{},
}
return out, metadata, nil
}
signingName := awsmiddleware.GetSigningName(ctx)
signingRegion := awsmiddleware.GetSigningRegion(ctx)
payloadHash := v4.GetPayloadHash(ctx)
if len(payloadHash) == 0 {
return out, metadata, &SigningError{
Err: fmt.Errorf("computed payload hash missing from context"),
}
}
credentials, err := s.credentialsProvider.RetrievePrivateKey(ctx)
if err != nil {
return out, metadata, &SigningError{
Err: fmt.Errorf("failed to retrieve credentials: %w", err),
}
}
u, h, err := s.presigner.PresignHTTP(ctx, credentials,
httpReq, payloadHash, signingName, []string{signingRegion}, sdk.NowTime(),
func(o *SignerOptions) {
o.Logger = middleware.GetLogger(ctx)
o.LogSigning = s.logSigning
})
if err != nil {
return out, metadata, &SigningError{
Err: fmt.Errorf("failed to sign http request, %w", err),
}
}
out.Result = &v4.PresignedHTTPRequest{
URL: u,
Method: httpReq.Method,
SignedHeader: h,
}
return out, metadata, nil
}
+92
View File
@@ -0,0 +1,92 @@
package v4a
import (
"context"
"fmt"
"time"
internalcontext "github.com/aws/aws-sdk-go-v2/internal/context"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/internal/sdk"
"github.com/aws/smithy-go"
"github.com/aws/smithy-go/auth"
"github.com/aws/smithy-go/logging"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// CredentialsAdapter adapts v4a.Credentials to smithy auth.Identity.
type CredentialsAdapter struct {
Credentials Credentials
}
var _ auth.Identity = (*CredentialsAdapter)(nil)
// Expiration returns the time of expiration for the credentials.
func (v *CredentialsAdapter) Expiration() time.Time {
return v.Credentials.Expires
}
// CredentialsProviderAdapter adapts v4a.CredentialsProvider to
// auth.IdentityResolver.
type CredentialsProviderAdapter struct {
Provider CredentialsProvider
}
var _ (auth.IdentityResolver) = (*CredentialsProviderAdapter)(nil)
// GetIdentity retrieves v4a credentials using the underlying provider.
func (v *CredentialsProviderAdapter) GetIdentity(ctx context.Context, _ smithy.Properties) (
auth.Identity, error,
) {
creds, err := v.Provider.RetrievePrivateKey(ctx)
if err != nil {
return nil, fmt.Errorf("get credentials: %w", err)
}
return &CredentialsAdapter{Credentials: creds}, nil
}
// SignerAdapter adapts v4a.HTTPSigner to smithy http.Signer.
type SignerAdapter struct {
Signer HTTPSigner
Logger logging.Logger
LogSigning bool
}
var _ (smithyhttp.Signer) = (*SignerAdapter)(nil)
// SignRequest signs the request with the provided identity.
func (v *SignerAdapter) SignRequest(ctx context.Context, r *smithyhttp.Request, identity auth.Identity, props smithy.Properties) error {
ca, ok := identity.(*CredentialsAdapter)
if !ok {
return fmt.Errorf("unexpected identity type: %T", identity)
}
name, ok := smithyhttp.GetSigV4SigningName(&props)
if !ok {
return fmt.Errorf("sigv4a signing name is required")
}
regions, ok := smithyhttp.GetSigV4ASigningRegions(&props)
if !ok {
return fmt.Errorf("sigv4a signing region is required")
}
hash := v4.GetPayloadHash(ctx)
signingTime := sdk.NowTime()
if skew := internalcontext.GetAttemptSkewContext(ctx); skew != 0 {
signingTime.Add(skew)
}
err := v.Signer.SignHTTP(ctx, ca.Credentials, r.Request, hash, name, regions, signingTime, func(o *SignerOptions) {
o.DisableURIPathEscaping, _ = smithyhttp.GetDisableDoubleEncoding(&props)
o.Logger = v.Logger
o.LogSigning = v.LogSigning
})
if err != nil {
return fmt.Errorf("sign http: %w", err)
}
return nil
}
+520
View File
@@ -0,0 +1,520 @@
package v4a
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"math/big"
"net/http"
"net/textproto"
"net/url"
"sort"
"strconv"
"strings"
"time"
signerCrypto "github.com/aws/aws-sdk-go-v2/internal/v4a/internal/crypto"
v4Internal "github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4"
"github.com/aws/smithy-go/encoding/httpbinding"
"github.com/aws/smithy-go/logging"
)
const (
// AmzRegionSetKey represents the region set header used for sigv4a
AmzRegionSetKey = "X-Amz-Region-Set"
amzAlgorithmKey = v4Internal.AmzAlgorithmKey
amzSecurityTokenKey = v4Internal.AmzSecurityTokenKey
amzDateKey = v4Internal.AmzDateKey
amzCredentialKey = v4Internal.AmzCredentialKey
amzSignedHeadersKey = v4Internal.AmzSignedHeadersKey
authorizationHeader = "Authorization"
signingAlgorithm = "AWS4-ECDSA-P256-SHA256"
timeFormat = "20060102T150405Z"
shortTimeFormat = "20060102"
// EmptyStringSHA256 is a hex encoded SHA-256 hash of an empty string
EmptyStringSHA256 = v4Internal.EmptyStringSHA256
// Version of signing v4a
Version = "SigV4A"
)
var (
p256 elliptic.Curve
nMinusTwoP256 *big.Int
one = new(big.Int).SetInt64(1)
)
func init() {
// Ensure the elliptic curve parameters are initialized on package import rather then on first usage
p256 = elliptic.P256()
nMinusTwoP256 = new(big.Int).SetBytes(p256.Params().N.Bytes())
nMinusTwoP256 = nMinusTwoP256.Sub(nMinusTwoP256, new(big.Int).SetInt64(2))
}
// SignerOptions is the SigV4a signing options for constructing a Signer.
type SignerOptions struct {
Logger logging.Logger
LogSigning bool
// Disables the Signer's moving HTTP header key/value pairs from the HTTP
// request header to the request's query string. This is most commonly used
// with pre-signed requests preventing headers from being added to the
// request's query string.
DisableHeaderHoisting bool
// Disables the automatic escaping of the URI path of the request for the
// siganture's canonical string's path. For services that do not need additional
// escaping then use this to disable the signer escaping the path.
//
// S3 is an example of a service that does not need additional escaping.
//
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
DisableURIPathEscaping bool
}
// Signer is a SigV4a HTTP signing implementation
type Signer struct {
options SignerOptions
}
// NewSigner constructs a SigV4a Signer.
func NewSigner(optFns ...func(*SignerOptions)) *Signer {
options := SignerOptions{}
for _, fn := range optFns {
fn(&options)
}
return &Signer{options: options}
}
// deriveKeyFromAccessKeyPair derives a NIST P-256 PrivateKey from the given
// IAM AccessKey and SecretKey pair.
//
// Based on FIPS.186-4 Appendix B.4.2
func deriveKeyFromAccessKeyPair(accessKey, secretKey string) (*ecdsa.PrivateKey, error) {
params := p256.Params()
bitLen := params.BitSize // Testing random candidates does not require an additional 64 bits
counter := 0x01
buffer := make([]byte, 1+len(accessKey)) // 1 byte counter + len(accessKey)
kdfContext := bytes.NewBuffer(buffer)
inputKey := append([]byte("AWS4A"), []byte(secretKey)...)
d := new(big.Int)
for {
kdfContext.Reset()
kdfContext.WriteString(accessKey)
kdfContext.WriteByte(byte(counter))
key, err := signerCrypto.HMACKeyDerivation(sha256.New, bitLen, inputKey, []byte(signingAlgorithm), kdfContext.Bytes())
if err != nil {
return nil, err
}
// Check key first before calling SetBytes if key key is in fact a valid candidate.
// This ensures the byte slice is the correct length (32-bytes) to compare in constant-time
cmp, err := signerCrypto.ConstantTimeByteCompare(key, nMinusTwoP256.Bytes())
if err != nil {
return nil, err
}
if cmp == -1 {
d.SetBytes(key)
break
}
counter++
if counter > 0xFF {
return nil, fmt.Errorf("exhausted single byte external counter")
}
}
d = d.Add(d, one)
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = p256
priv.D = d
priv.PublicKey.X, priv.PublicKey.Y = p256.ScalarBaseMult(d.Bytes())
return priv, nil
}
type httpSigner struct {
Request *http.Request
ServiceName string
RegionSet []string
Time time.Time
Credentials Credentials
IsPreSign bool
Logger logging.Logger
Debug bool
// PayloadHash is the hex encoded SHA-256 hash of the request payload
// If len(PayloadHash) == 0 the signer will attempt to send the request
// as an unsigned payload. Note: Unsigned payloads only work for a subset of services.
PayloadHash string
DisableHeaderHoisting bool
DisableURIPathEscaping bool
}
// SignHTTP takes the provided http.Request, payload hash, service, regionSet, and time and signs using SigV4a.
// The passed in request will be modified in place.
func (s *Signer) SignHTTP(ctx context.Context, credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, optFns ...func(*SignerOptions)) error {
options := s.options
for _, fn := range optFns {
fn(&options)
}
signer := &httpSigner{
Request: r,
PayloadHash: payloadHash,
ServiceName: service,
RegionSet: regionSet,
Credentials: credentials,
Time: signingTime.UTC(),
DisableHeaderHoisting: options.DisableHeaderHoisting,
DisableURIPathEscaping: options.DisableURIPathEscaping,
}
signedRequest, err := signer.Build()
if err != nil {
return err
}
logHTTPSigningInfo(ctx, options, signedRequest)
return nil
}
// PresignHTTP takes the provided http.Request, payload hash, service, regionSet, and time and presigns using SigV4a
// Returns the presigned URL along with the headers that were signed with the request.
//
// PresignHTTP will not set the expires time of the presigned request
// automatically. To specify the expire duration for a request add the
// "X-Amz-Expires" query parameter on the request with the value as the
// duration in seconds the presigned URL should be considered valid for. This
// parameter is not used by all AWS services, and is most notable used by
// Amazon S3 APIs.
func (s *Signer) PresignHTTP(ctx context.Context, credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, optFns ...func(*SignerOptions)) (signedURI string, signedHeaders http.Header, err error) {
options := s.options
for _, fn := range optFns {
fn(&options)
}
signer := &httpSigner{
Request: r,
PayloadHash: payloadHash,
ServiceName: service,
RegionSet: regionSet,
Credentials: credentials,
Time: signingTime.UTC(),
IsPreSign: true,
DisableHeaderHoisting: options.DisableHeaderHoisting,
DisableURIPathEscaping: options.DisableURIPathEscaping,
}
signedRequest, err := signer.Build()
if err != nil {
return "", nil, err
}
logHTTPSigningInfo(ctx, options, signedRequest)
signedHeaders = make(http.Header)
// For the signed headers we canonicalize the header keys in the returned map.
// This avoids situations where can standard library double headers like host header. For example the standard
// library will set the Host header, even if it is present in lower-case form.
for k, v := range signedRequest.SignedHeaders {
key := textproto.CanonicalMIMEHeaderKey(k)
signedHeaders[key] = append(signedHeaders[key], v...)
}
return signedRequest.Request.URL.String(), signedHeaders, nil
}
func (s *httpSigner) setRequiredSigningFields(headers http.Header, query url.Values) {
amzDate := s.Time.Format(timeFormat)
if s.IsPreSign {
query.Set(AmzRegionSetKey, strings.Join(s.RegionSet, ","))
query.Set(amzDateKey, amzDate)
query.Set(amzAlgorithmKey, signingAlgorithm)
if len(s.Credentials.SessionToken) > 0 {
query.Set(amzSecurityTokenKey, s.Credentials.SessionToken)
}
return
}
headers.Set(AmzRegionSetKey, strings.Join(s.RegionSet, ","))
headers.Set(amzDateKey, amzDate)
if len(s.Credentials.SessionToken) > 0 {
headers.Set(amzSecurityTokenKey, s.Credentials.SessionToken)
}
}
func (s *httpSigner) Build() (signedRequest, error) {
req := s.Request
query := req.URL.Query()
headers := req.Header
s.setRequiredSigningFields(headers, query)
// Sort Each Query Key's Values
for key := range query {
sort.Strings(query[key])
}
v4Internal.SanitizeHostForHeader(req)
credentialScope := s.buildCredentialScope()
credentialStr := s.Credentials.Context + "/" + credentialScope
if s.IsPreSign {
query.Set(amzCredentialKey, credentialStr)
}
unsignedHeaders := headers
if s.IsPreSign && !s.DisableHeaderHoisting {
urlValues := url.Values{}
urlValues, unsignedHeaders = buildQuery(v4Internal.AllowedQueryHoisting, unsignedHeaders)
for k := range urlValues {
query[k] = urlValues[k]
}
}
host := req.URL.Host
if len(req.Host) > 0 {
host = req.Host
}
signedHeaders, signedHeadersStr, canonicalHeaderStr := s.buildCanonicalHeaders(host, v4Internal.IgnoredHeaders, unsignedHeaders, s.Request.ContentLength)
if s.IsPreSign {
query.Set(amzSignedHeadersKey, signedHeadersStr)
}
rawQuery := strings.Replace(query.Encode(), "+", "%20", -1)
canonicalURI := v4Internal.GetURIPath(req.URL)
if !s.DisableURIPathEscaping {
canonicalURI = httpbinding.EscapePath(canonicalURI, false)
}
canonicalString := s.buildCanonicalString(
req.Method,
canonicalURI,
rawQuery,
signedHeadersStr,
canonicalHeaderStr,
)
strToSign := s.buildStringToSign(credentialScope, canonicalString)
signingSignature, err := s.buildSignature(strToSign)
if err != nil {
return signedRequest{}, err
}
if s.IsPreSign {
rawQuery += "&X-Amz-Signature=" + signingSignature
} else {
headers[authorizationHeader] = append(headers[authorizationHeader][:0], buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature))
}
req.URL.RawQuery = rawQuery
return signedRequest{
Request: req,
SignedHeaders: signedHeaders,
CanonicalString: canonicalString,
StringToSign: strToSign,
PreSigned: s.IsPreSign,
}, nil
}
func buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature string) string {
const credential = "Credential="
const signedHeaders = "SignedHeaders="
const signature = "Signature="
const commaSpace = ", "
var parts strings.Builder
parts.Grow(len(signingAlgorithm) + 1 +
len(credential) + len(credentialStr) + len(commaSpace) +
len(signedHeaders) + len(signedHeadersStr) + len(commaSpace) +
len(signature) + len(signingSignature),
)
parts.WriteString(signingAlgorithm)
parts.WriteRune(' ')
parts.WriteString(credential)
parts.WriteString(credentialStr)
parts.WriteString(commaSpace)
parts.WriteString(signedHeaders)
parts.WriteString(signedHeadersStr)
parts.WriteString(commaSpace)
parts.WriteString(signature)
parts.WriteString(signingSignature)
return parts.String()
}
func (s *httpSigner) buildCredentialScope() string {
return strings.Join([]string{
s.Time.Format(shortTimeFormat),
s.ServiceName,
"aws4_request",
}, "/")
}
func buildQuery(r v4Internal.Rule, header http.Header) (url.Values, http.Header) {
query := url.Values{}
unsignedHeaders := http.Header{}
for k, h := range header {
if r.IsValid(k) {
query[k] = h
} else {
unsignedHeaders[k] = h
}
}
return query, unsignedHeaders
}
func (s *httpSigner) buildCanonicalHeaders(host string, rule v4Internal.Rule, header http.Header, length int64) (signed http.Header, signedHeaders, canonicalHeadersStr string) {
signed = make(http.Header)
var headers []string
const hostHeader = "host"
headers = append(headers, hostHeader)
signed[hostHeader] = append(signed[hostHeader], host)
if length > 0 {
const contentLengthHeader = "content-length"
headers = append(headers, contentLengthHeader)
signed[contentLengthHeader] = append(signed[contentLengthHeader], strconv.FormatInt(length, 10))
}
for k, v := range header {
if !rule.IsValid(k) {
continue // ignored header
}
lowerCaseKey := strings.ToLower(k)
if _, ok := signed[lowerCaseKey]; ok {
// include additional values
signed[lowerCaseKey] = append(signed[lowerCaseKey], v...)
continue
}
headers = append(headers, lowerCaseKey)
signed[lowerCaseKey] = v
}
sort.Strings(headers)
signedHeaders = strings.Join(headers, ";")
var canonicalHeaders strings.Builder
n := len(headers)
const colon = ':'
for i := 0; i < n; i++ {
if headers[i] == hostHeader {
canonicalHeaders.WriteString(hostHeader)
canonicalHeaders.WriteRune(colon)
canonicalHeaders.WriteString(v4Internal.StripExcessSpaces(host))
} else {
canonicalHeaders.WriteString(headers[i])
canonicalHeaders.WriteRune(colon)
// Trim out leading, trailing, and dedup inner spaces from signed header values.
values := signed[headers[i]]
for j, v := range values {
cleanedValue := strings.TrimSpace(v4Internal.StripExcessSpaces(v))
canonicalHeaders.WriteString(cleanedValue)
if j < len(values)-1 {
canonicalHeaders.WriteRune(',')
}
}
}
canonicalHeaders.WriteRune('\n')
}
canonicalHeadersStr = canonicalHeaders.String()
return signed, signedHeaders, canonicalHeadersStr
}
func (s *httpSigner) buildCanonicalString(method, uri, query, signedHeaders, canonicalHeaders string) string {
return strings.Join([]string{
method,
uri,
query,
canonicalHeaders,
signedHeaders,
s.PayloadHash,
}, "\n")
}
func (s *httpSigner) buildStringToSign(credentialScope, canonicalRequestString string) string {
return strings.Join([]string{
signingAlgorithm,
s.Time.Format(timeFormat),
credentialScope,
hex.EncodeToString(makeHash(sha256.New(), []byte(canonicalRequestString))),
}, "\n")
}
func makeHash(hash hash.Hash, b []byte) []byte {
hash.Reset()
hash.Write(b)
return hash.Sum(nil)
}
func (s *httpSigner) buildSignature(strToSign string) (string, error) {
sig, err := s.Credentials.PrivateKey.Sign(rand.Reader, makeHash(sha256.New(), []byte(strToSign)), crypto.SHA256)
if err != nil {
return "", err
}
return hex.EncodeToString(sig), nil
}
const logSignInfoMsg = `Request Signature:
---[ CANONICAL STRING ]-----------------------------
%s
---[ STRING TO SIGN ]--------------------------------
%s%s
-----------------------------------------------------`
const logSignedURLMsg = `
---[ SIGNED URL ]------------------------------------
%s`
func logHTTPSigningInfo(ctx context.Context, options SignerOptions, r signedRequest) {
if !options.LogSigning {
return
}
signedURLMsg := ""
if r.PreSigned {
signedURLMsg = fmt.Sprintf(logSignedURLMsg, r.Request.URL.String())
}
logger := logging.WithContext(ctx, options.Logger)
logger.Logf(logging.Debug, logSignInfoMsg, r.CanonicalString, r.StringToSign, signedURLMsg)
}
type signedRequest struct {
Request *http.Request
SignedHeaders http.Header
CanonicalString string
StringToSign string
PreSigned bool
}
@@ -1,3 +1,7 @@
# v1.12.2 (2025-01-24)
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.12.1 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
@@ -3,4 +3,4 @@
package acceptencoding
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.12.1"
const goModuleVersion = "1.12.2"
@@ -0,0 +1,361 @@
# v1.5.5 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.4 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.3 (2025-01-24)
* **Bug Fix**: Enable request checksum validation mode by default
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.5.2 (2025-01-17)
* **Bug Fix**: Fix bug where credentials weren't refreshed during retry loop.
# v1.5.1 (2025-01-16)
* **Bug Fix**: Fix nil dereference panic for operations that require checksums, but do not have an input setting for which algorithm to use.
# v1.5.0 (2025-01-15)
* **Feature**: S3 client behavior is updated to always calculate a checksum by default for operations that support it (such as PutObject or UploadPart), or require it (such as DeleteObjects). The checksum algorithm used by default now becomes CRC32. Checksum behavior can be configured using `when_supported` and `when_required` options - in code using RequestChecksumCalculation, in shared config using request_checksum_calculation, or as env variable using AWS_REQUEST_CHECKSUM_CALCULATION. The S3 client attempts to validate response checksums for all S3 API operations that support checksums. However, if the SDK has not implemented the specified checksum algorithm then this validation is skipped. Checksum validation behavior can be configured using `when_supported` and `when_required` options - in code using ResponseChecksumValidation, in shared config using response_checksum_validation, or as env variable using AWS_RESPONSE_CHECKSUM_VALIDATION.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.8 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.7 (2024-12-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.6 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.5 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.4 (2024-11-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.3 (2024-10-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.2 (2024-10-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.1 (2024-10-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.0 (2024-10-04)
* **Feature**: Add support for HTTP client metrics.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.20 (2024-09-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.19 (2024-09-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.18 (2024-08-15)
* **Dependency Update**: Bump minimum Go version to 1.21.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.17 (2024-07-10.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.16 (2024-07-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.15 (2024-06-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.14 (2024-06-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.13 (2024-06-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.12 (2024-06-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.11 (2024-06-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.10 (2024-06-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.9 (2024-05-16)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.8 (2024-05-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.7 (2024-03-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.6 (2024-03-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.5 (2024-03-07)
* **Bug Fix**: Remove dependency on go-cmp.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.4 (2024-03-05)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.3 (2024-03-04)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.2 (2024-02-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.1 (2024-02-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.0 (2024-02-13)
* **Feature**: Bump minimum Go version to 1.20 per our language support policy.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.10 (2024-01-04)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.9 (2023-12-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.8 (2023-12-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.7 (2023-11-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.6 (2023-11-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.5 (2023-11-28.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.4 (2023-11-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.3 (2023-11-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.2 (2023-11-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.1 (2023-11-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.0 (2023-10-31)
* **Feature**: **BREAKING CHANGE**: Bump minimum go version to 1.19 per the revised [go version support policy](https://aws.amazon.com/blogs/developer/aws-sdk-for-go-aligns-with-go-release-policy-on-supported-runtimes/).
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.38 (2023-10-12)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.37 (2023-10-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.36 (2023-08-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.35 (2023-08-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.34 (2023-08-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.33 (2023-08-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.32 (2023-07-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.31 (2023-07-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.30 (2023-07-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.29 (2023-06-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.28 (2023-04-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.27 (2023-04-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.26 (2023-03-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.25 (2023-03-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.24 (2023-02-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.23 (2023-02-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.22 (2022-12-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.21 (2022-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.20 (2022-10-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.19 (2022-10-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.18 (2022-09-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.17 (2022-09-14)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.16 (2022-09-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.15 (2022-08-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.14 (2022-08-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.13 (2022-08-11)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.12 (2022-08-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.11 (2022-08-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.10 (2022-08-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.9 (2022-07-05)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.8 (2022-06-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.7 (2022-06-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.6 (2022-05-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.5 (2022-04-27)
* **Bug Fix**: Fixes a bug that could cause the SigV4 payload hash to be incorrectly encoded, leading to signing errors.
# v1.1.4 (2022-04-25)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.3 (2022-03-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.2 (2022-03-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.1 (2022-03-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.0 (2022-03-08)
* **Feature**: Updates the SDK's checksum validation logic to require opt-in to output response payload validation. The SDK was always preforming output response payload checksum validation, not respecting the output validation model option. Fixes [#1606](https://github.com/aws/aws-sdk-go-v2/issues/1606)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.0.0 (2022-02-24)
* **Release**: New module for computing checksums
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,326 @@
package checksum
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"hash"
"hash/crc32"
"io"
"strings"
"sync"
)
// Algorithm represents the checksum algorithms supported
type Algorithm string
// Enumeration values for supported checksum Algorithms.
const (
// AlgorithmCRC32C represents CRC32C hash algorithm
AlgorithmCRC32C Algorithm = "CRC32C"
// AlgorithmCRC32 represents CRC32 hash algorithm
AlgorithmCRC32 Algorithm = "CRC32"
// AlgorithmSHA1 represents SHA1 hash algorithm
AlgorithmSHA1 Algorithm = "SHA1"
// AlgorithmSHA256 represents SHA256 hash algorithm
AlgorithmSHA256 Algorithm = "SHA256"
// AlgorithmCRC64NVME represents CRC64NVME hash algorithm
AlgorithmCRC64NVME Algorithm = "CRC64NVME"
)
var supportedAlgorithms = []Algorithm{
AlgorithmCRC32C,
AlgorithmCRC32,
AlgorithmSHA1,
AlgorithmSHA256,
}
func (a Algorithm) String() string { return string(a) }
// ParseAlgorithm attempts to parse the provided value into a checksum
// algorithm, matching without case. Returns the algorithm matched, or an error
// if the algorithm wasn't matched.
func ParseAlgorithm(v string) (Algorithm, error) {
for _, a := range supportedAlgorithms {
if strings.EqualFold(string(a), v) {
return a, nil
}
}
return "", fmt.Errorf("unknown checksum algorithm, %v", v)
}
// FilterSupportedAlgorithms filters the set of algorithms, returning a slice
// of algorithms that are supported.
func FilterSupportedAlgorithms(vs []string) []Algorithm {
found := map[Algorithm]struct{}{}
supported := make([]Algorithm, 0, len(supportedAlgorithms))
for _, v := range vs {
for _, a := range supportedAlgorithms {
// Only consider algorithms that are supported
if !strings.EqualFold(v, string(a)) {
continue
}
// Ignore duplicate algorithms in list.
if _, ok := found[a]; ok {
continue
}
supported = append(supported, a)
found[a] = struct{}{}
}
}
return supported
}
// NewAlgorithmHash returns a hash.Hash for the checksum algorithm. Error is
// returned if the algorithm is unknown.
func NewAlgorithmHash(v Algorithm) (hash.Hash, error) {
switch v {
case AlgorithmSHA1:
return sha1.New(), nil
case AlgorithmSHA256:
return sha256.New(), nil
case AlgorithmCRC32:
return crc32.NewIEEE(), nil
case AlgorithmCRC32C:
return crc32.New(crc32.MakeTable(crc32.Castagnoli)), nil
default:
return nil, fmt.Errorf("unknown checksum algorithm, %v", v)
}
}
// AlgorithmChecksumLength returns the length of the algorithm's checksum in
// bytes. If the algorithm is not known, an error is returned.
func AlgorithmChecksumLength(v Algorithm) (int, error) {
switch v {
case AlgorithmSHA1:
return sha1.Size, nil
case AlgorithmSHA256:
return sha256.Size, nil
case AlgorithmCRC32:
return crc32.Size, nil
case AlgorithmCRC32C:
return crc32.Size, nil
default:
return 0, fmt.Errorf("unknown checksum algorithm, %v", v)
}
}
const awsChecksumHeaderPrefix = "x-amz-checksum-"
// AlgorithmHTTPHeader returns the HTTP header for the algorithm's hash.
func AlgorithmHTTPHeader(v Algorithm) string {
return awsChecksumHeaderPrefix + strings.ToLower(string(v))
}
// base64EncodeHashSum computes base64 encoded checksum of a given running
// hash. The running hash must already have content written to it. Returns the
// byte slice of checksum and an error
func base64EncodeHashSum(h hash.Hash) []byte {
sum := h.Sum(nil)
sum64 := make([]byte, base64.StdEncoding.EncodedLen(len(sum)))
base64.StdEncoding.Encode(sum64, sum)
return sum64
}
// hexEncodeHashSum computes hex encoded checksum of a given running hash. The
// running hash must already have content written to it. Returns the byte slice
// of checksum and an error
func hexEncodeHashSum(h hash.Hash) []byte {
sum := h.Sum(nil)
sumHex := make([]byte, hex.EncodedLen(len(sum)))
hex.Encode(sumHex, sum)
return sumHex
}
// computeMD5Checksum computes base64 MD5 checksum of an io.Reader's contents.
// Returns the byte slice of MD5 checksum and an error.
func computeMD5Checksum(r io.Reader) ([]byte, error) {
h := md5.New()
// Copy errors may be assumed to be from the body.
if _, err := io.Copy(h, r); err != nil {
return nil, fmt.Errorf("failed compute MD5 hash of reader, %w", err)
}
// Encode the MD5 checksum in base64.
return base64EncodeHashSum(h), nil
}
// computeChecksumReader provides a reader wrapping an underlying io.Reader to
// compute the checksum of the stream's bytes.
type computeChecksumReader struct {
stream io.Reader
algorithm Algorithm
hasher hash.Hash
base64ChecksumLen int
mux sync.RWMutex
lockedChecksum string
lockedErr error
}
// newComputeChecksumReader returns a computeChecksumReader for the stream and
// algorithm specified. Returns error if unable to create the reader, or
// algorithm is unknown.
func newComputeChecksumReader(stream io.Reader, algorithm Algorithm) (*computeChecksumReader, error) {
hasher, err := NewAlgorithmHash(algorithm)
if err != nil {
return nil, err
}
checksumLength, err := AlgorithmChecksumLength(algorithm)
if err != nil {
return nil, err
}
return &computeChecksumReader{
stream: io.TeeReader(stream, hasher),
algorithm: algorithm,
hasher: hasher,
base64ChecksumLen: base64.StdEncoding.EncodedLen(checksumLength),
}, nil
}
// Read wraps the underlying reader. When the underlying reader returns EOF,
// the checksum of the reader will be computed, and can be retrieved with
// ChecksumBase64String.
func (r *computeChecksumReader) Read(p []byte) (int, error) {
n, err := r.stream.Read(p)
if err == nil {
return n, nil
} else if err != io.EOF {
r.mux.Lock()
defer r.mux.Unlock()
r.lockedErr = err
return n, err
}
b := base64EncodeHashSum(r.hasher)
r.mux.Lock()
defer r.mux.Unlock()
r.lockedChecksum = string(b)
return n, err
}
func (r *computeChecksumReader) Algorithm() Algorithm {
return r.algorithm
}
// Base64ChecksumLength returns the base64 encoded length of the checksum for
// algorithm.
func (r *computeChecksumReader) Base64ChecksumLength() int {
return r.base64ChecksumLen
}
// Base64Checksum returns the base64 checksum for the algorithm, or error if
// the underlying reader returned a non-EOF error.
//
// Safe to be called concurrently, but will return an error until after the
// underlying reader is returns EOF.
func (r *computeChecksumReader) Base64Checksum() (string, error) {
r.mux.RLock()
defer r.mux.RUnlock()
if r.lockedErr != nil {
return "", r.lockedErr
}
if r.lockedChecksum == "" {
return "", fmt.Errorf(
"checksum not available yet, called before reader returns EOF",
)
}
return r.lockedChecksum, nil
}
// validateChecksumReader implements io.ReadCloser interface. The wrapper
// performs checksum validation when the underlying reader has been fully read.
type validateChecksumReader struct {
originalBody io.ReadCloser
body io.Reader
hasher hash.Hash
algorithm Algorithm
expectChecksum string
}
// newValidateChecksumReader returns a configured io.ReadCloser that performs
// checksum validation when the underlying reader has been fully read.
func newValidateChecksumReader(
body io.ReadCloser,
algorithm Algorithm,
expectChecksum string,
) (*validateChecksumReader, error) {
hasher, err := NewAlgorithmHash(algorithm)
if err != nil {
return nil, err
}
return &validateChecksumReader{
originalBody: body,
body: io.TeeReader(body, hasher),
hasher: hasher,
algorithm: algorithm,
expectChecksum: expectChecksum,
}, nil
}
// Read attempts to read from the underlying stream while also updating the
// running hash. If the underlying stream returns with an EOF error, the
// checksum of the stream will be collected, and compared against the expected
// checksum. If the checksums do not match, an error will be returned.
//
// If a non-EOF error occurs when reading the underlying stream, that error
// will be returned and the checksum for the stream will be discarded.
func (c *validateChecksumReader) Read(p []byte) (n int, err error) {
n, err = c.body.Read(p)
if err == io.EOF {
if checksumErr := c.validateChecksum(); checksumErr != nil {
return n, checksumErr
}
}
return n, err
}
// Close closes the underlying reader, returning any error that occurred in the
// underlying reader.
func (c *validateChecksumReader) Close() (err error) {
return c.originalBody.Close()
}
func (c *validateChecksumReader) validateChecksum() error {
// Compute base64 encoded checksum hash of the payload's read bytes.
v := base64EncodeHashSum(c.hasher)
if e, a := c.expectChecksum, string(v); !strings.EqualFold(e, a) {
return validationError{
Algorithm: c.algorithm, Expect: e, Actual: a,
}
}
return nil
}
type validationError struct {
Algorithm Algorithm
Expect string
Actual string
}
func (v validationError) Error() string {
return fmt.Sprintf("checksum did not match: algorithm %v, expect %v, actual %v",
v.Algorithm, v.Expect, v.Actual)
}
@@ -0,0 +1,389 @@
package checksum
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
const (
crlf = "\r\n"
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
defaultChunkLength = 1024 * 64
awsTrailerHeaderName = "x-amz-trailer"
decodedContentLengthHeaderName = "x-amz-decoded-content-length"
contentEncodingHeaderName = "content-encoding"
awsChunkedContentEncodingHeaderValue = "aws-chunked"
trailerKeyValueSeparator = ":"
)
var (
crlfBytes = []byte(crlf)
finalChunkBytes = []byte("0" + crlf)
)
type awsChunkedEncodingOptions struct {
// The total size of the stream. For unsigned encoding this implies that
// there will only be a single chunk containing the underlying payload,
// unless ChunkLength is also specified.
StreamLength int64
// Set of trailer key:value pairs that will be appended to the end of the
// payload after the end chunk has been written.
Trailers map[string]awsChunkedTrailerValue
// The maximum size of each chunk to be sent. Default value of -1, signals
// that optimal chunk length will be used automatically. ChunkSize must be
// at least 8KB.
//
// If ChunkLength and StreamLength are both specified, the stream will be
// broken up into ChunkLength chunks. The encoded length of the aws-chunked
// encoding can still be determined as long as all trailers, if any, have a
// fixed length.
ChunkLength int
}
type awsChunkedTrailerValue struct {
// Function to retrieve the value of the trailer. Will only be called after
// the underlying stream returns EOF error.
Get func() (string, error)
// If the length of the value can be pre-determined, and is constant
// specify the length. A value of -1 means the length is unknown, or
// cannot be pre-determined.
Length int
}
// awsChunkedEncoding provides a reader that wraps the payload such that
// payload is read as a single aws-chunk payload. This reader can only be used
// if the content length of payload is known. Content-Length is used as size of
// the single payload chunk. The final chunk and trailing checksum is appended
// at the end.
//
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#sigv4-chunked-body-definition
//
// Here is the aws-chunked payload stream as read from the awsChunkedEncoding
// if original request stream is "Hello world", and checksum hash used is SHA256
//
// <b>\r\n
// Hello world\r\n
// 0\r\n
// x-amz-checksum-sha256:ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=\r\n
// \r\n
type awsChunkedEncoding struct {
options awsChunkedEncodingOptions
encodedStream io.Reader
trailerEncodedLength int
}
// newUnsignedAWSChunkedEncoding returns a new awsChunkedEncoding configured
// for unsigned aws-chunked content encoding. Any additional trailers that need
// to be appended after the end chunk must be included as via Trailer
// callbacks.
func newUnsignedAWSChunkedEncoding(
stream io.Reader,
optFns ...func(*awsChunkedEncodingOptions),
) *awsChunkedEncoding {
options := awsChunkedEncodingOptions{
Trailers: map[string]awsChunkedTrailerValue{},
StreamLength: -1,
ChunkLength: -1,
}
for _, fn := range optFns {
fn(&options)
}
var chunkReader io.Reader
if options.ChunkLength != -1 || options.StreamLength == -1 {
if options.ChunkLength == -1 {
options.ChunkLength = defaultChunkLength
}
chunkReader = newBufferedAWSChunkReader(stream, options.ChunkLength)
} else {
chunkReader = newUnsignedChunkReader(stream, options.StreamLength)
}
trailerReader := newAWSChunkedTrailerReader(options.Trailers)
return &awsChunkedEncoding{
options: options,
encodedStream: io.MultiReader(chunkReader,
trailerReader,
bytes.NewBuffer(crlfBytes),
),
trailerEncodedLength: trailerReader.EncodedLength(),
}
}
// EncodedLength returns the final length of the aws-chunked content encoded
// stream if it can be determined without reading the underlying stream or lazy
// header values, otherwise -1 is returned.
func (e *awsChunkedEncoding) EncodedLength() int64 {
var length int64
if e.options.StreamLength == -1 || e.trailerEncodedLength == -1 {
return -1
}
if e.options.StreamLength != 0 {
// If the stream length is known, and there is no chunk length specified,
// only a single chunk will be used. Otherwise the stream length needs to
// include the multiple chunk padding content.
if e.options.ChunkLength == -1 {
length += getUnsignedChunkBytesLength(e.options.StreamLength)
} else {
// Compute chunk header and payload length
numChunks := e.options.StreamLength / int64(e.options.ChunkLength)
length += numChunks * getUnsignedChunkBytesLength(int64(e.options.ChunkLength))
if remainder := e.options.StreamLength % int64(e.options.ChunkLength); remainder != 0 {
length += getUnsignedChunkBytesLength(remainder)
}
}
}
// End chunk
length += int64(len(finalChunkBytes))
// Trailers
length += int64(e.trailerEncodedLength)
// Encoding terminator
length += int64(len(crlf))
return length
}
func getUnsignedChunkBytesLength(payloadLength int64) int64 {
payloadLengthStr := strconv.FormatInt(payloadLength, 16)
return int64(len(payloadLengthStr)) + int64(len(crlf)) + payloadLength + int64(len(crlf))
}
// HTTPHeaders returns the set of headers that must be included the request for
// aws-chunked to work. This includes the content-encoding: aws-chunked header.
//
// If there are multiple layered content encoding, the aws-chunked encoding
// must be appended to the previous layers the stream's encoding. The best way
// to do this is to append all header values returned to the HTTP request's set
// of headers.
func (e *awsChunkedEncoding) HTTPHeaders() map[string][]string {
headers := map[string][]string{
contentEncodingHeaderName: {
awsChunkedContentEncodingHeaderValue,
},
}
if len(e.options.Trailers) != 0 {
trailers := make([]string, 0, len(e.options.Trailers))
for name := range e.options.Trailers {
trailers = append(trailers, strings.ToLower(name))
}
headers[awsTrailerHeaderName] = trailers
}
return headers
}
func (e *awsChunkedEncoding) Read(b []byte) (n int, err error) {
return e.encodedStream.Read(b)
}
// awsChunkedTrailerReader provides a lazy reader for reading of aws-chunked
// content encoded trailers. The trailer values will not be retrieved until the
// reader is read from.
type awsChunkedTrailerReader struct {
reader *bytes.Buffer
trailers map[string]awsChunkedTrailerValue
trailerEncodedLength int
}
// newAWSChunkedTrailerReader returns an initialized awsChunkedTrailerReader to
// lazy reading aws-chunk content encoded trailers.
func newAWSChunkedTrailerReader(trailers map[string]awsChunkedTrailerValue) *awsChunkedTrailerReader {
return &awsChunkedTrailerReader{
trailers: trailers,
trailerEncodedLength: trailerEncodedLength(trailers),
}
}
func trailerEncodedLength(trailers map[string]awsChunkedTrailerValue) (length int) {
for name, trailer := range trailers {
length += len(name) + len(trailerKeyValueSeparator)
l := trailer.Length
if l == -1 {
return -1
}
length += l + len(crlf)
}
return length
}
// EncodedLength returns the length of the encoded trailers if the length could
// be determined without retrieving the header values. Returns -1 if length is
// unknown.
func (r *awsChunkedTrailerReader) EncodedLength() (length int) {
return r.trailerEncodedLength
}
// Read populates the passed in byte slice with bytes from the encoded
// trailers. Will lazy read header values first time Read is called.
func (r *awsChunkedTrailerReader) Read(p []byte) (int, error) {
if r.trailerEncodedLength == 0 {
return 0, io.EOF
}
if r.reader == nil {
trailerLen := r.trailerEncodedLength
if r.trailerEncodedLength == -1 {
trailerLen = 0
}
r.reader = bytes.NewBuffer(make([]byte, 0, trailerLen))
for name, trailer := range r.trailers {
r.reader.WriteString(name)
r.reader.WriteString(trailerKeyValueSeparator)
v, err := trailer.Get()
if err != nil {
return 0, fmt.Errorf("failed to get trailer value, %w", err)
}
r.reader.WriteString(v)
r.reader.WriteString(crlf)
}
}
return r.reader.Read(p)
}
// newUnsignedChunkReader returns an io.Reader encoding the underlying reader
// as unsigned aws-chunked chunks. The returned reader will also include the
// end chunk, but not the aws-chunked final `crlf` segment so trailers can be
// added.
//
// If the payload size is -1 for unknown length the content will be buffered in
// defaultChunkLength chunks before wrapped in aws-chunked chunk encoding.
func newUnsignedChunkReader(reader io.Reader, payloadSize int64) io.Reader {
if payloadSize == -1 {
return newBufferedAWSChunkReader(reader, defaultChunkLength)
}
var endChunk bytes.Buffer
if payloadSize == 0 {
endChunk.Write(finalChunkBytes)
return &endChunk
}
endChunk.WriteString(crlf)
endChunk.Write(finalChunkBytes)
var header bytes.Buffer
header.WriteString(strconv.FormatInt(payloadSize, 16))
header.WriteString(crlf)
return io.MultiReader(
&header,
reader,
&endChunk,
)
}
// Provides a buffered aws-chunked chunk encoder of an underlying io.Reader.
// Will include end chunk, but not the aws-chunked final `crlf` segment so
// trailers can be added.
//
// Note does not implement support for chunk extensions, e.g. chunk signing.
type bufferedAWSChunkReader struct {
reader io.Reader
chunkSize int
chunkSizeStr string
headerBuffer *bytes.Buffer
chunkBuffer *bytes.Buffer
multiReader io.Reader
multiReaderLen int
endChunkDone bool
}
// newBufferedAWSChunkReader returns an bufferedAWSChunkReader for reading
// aws-chunked encoded chunks.
func newBufferedAWSChunkReader(reader io.Reader, chunkSize int) *bufferedAWSChunkReader {
return &bufferedAWSChunkReader{
reader: reader,
chunkSize: chunkSize,
chunkSizeStr: strconv.FormatInt(int64(chunkSize), 16),
headerBuffer: bytes.NewBuffer(make([]byte, 0, 64)),
chunkBuffer: bytes.NewBuffer(make([]byte, 0, chunkSize+len(crlf))),
}
}
// Read attempts to read from the underlying io.Reader writing aws-chunked
// chunk encoded bytes to p. When the underlying io.Reader has been completed
// read the end chunk will be available. Once the end chunk is read, the reader
// will return EOF.
func (r *bufferedAWSChunkReader) Read(p []byte) (n int, err error) {
if r.multiReaderLen == 0 && r.endChunkDone {
return 0, io.EOF
}
if r.multiReader == nil || r.multiReaderLen == 0 {
r.multiReader, r.multiReaderLen, err = r.newMultiReader()
if err != nil {
return 0, err
}
}
n, err = r.multiReader.Read(p)
r.multiReaderLen -= n
if err == io.EOF && !r.endChunkDone {
// Edge case handling when the multi-reader has been completely read,
// and returned an EOF, make sure that EOF only gets returned if the
// end chunk was included in the multi-reader. Otherwise, the next call
// to read will initialize the next chunk's multi-reader.
err = nil
}
return n, err
}
// newMultiReader returns a new io.Reader for wrapping the next chunk. Will
// return an error if the underlying reader can not be read from. Will never
// return io.EOF.
func (r *bufferedAWSChunkReader) newMultiReader() (io.Reader, int, error) {
// io.Copy eats the io.EOF returned by io.LimitReader. Any error that
// occurs here is due to an actual read error.
n, err := io.Copy(r.chunkBuffer, io.LimitReader(r.reader, int64(r.chunkSize)))
if err != nil {
return nil, 0, err
}
if n == 0 {
// Early exit writing out only the end chunk. This does not include
// aws-chunk's final `crlf` so that trailers can still be added by
// upstream reader.
r.headerBuffer.Reset()
r.headerBuffer.WriteString("0")
r.headerBuffer.WriteString(crlf)
r.endChunkDone = true
return r.headerBuffer, r.headerBuffer.Len(), nil
}
r.chunkBuffer.WriteString(crlf)
chunkSizeStr := r.chunkSizeStr
if int(n) != r.chunkSize {
chunkSizeStr = strconv.FormatInt(n, 16)
}
r.headerBuffer.Reset()
r.headerBuffer.WriteString(chunkSizeStr)
r.headerBuffer.WriteString(crlf)
return io.MultiReader(
r.headerBuffer,
r.chunkBuffer,
), r.headerBuffer.Len() + r.chunkBuffer.Len(), nil
}
@@ -0,0 +1,6 @@
// Code generated by internal/repotools/cmd/updatemodulemeta DO NOT EDIT.
package checksum
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.5.5"
@@ -0,0 +1,175 @@
package checksum
import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/smithy-go/middleware"
)
// InputMiddlewareOptions provides the options for the request
// checksum middleware setup.
type InputMiddlewareOptions struct {
// GetAlgorithm is a function to get the checksum algorithm of the
// input payload from the input parameters.
//
// Given the input parameter value, the function must return the algorithm
// and true, or false if no algorithm is specified.
GetAlgorithm func(interface{}) (string, bool)
// RequireChecksum indicates whether operation model forces middleware to compute the input payload's checksum.
// If RequireChecksum is set to true, checksum will be calculated and RequestChecksumCalculation will be ignored,
// otherwise RequestChecksumCalculation will be used to indicate if checksum will be calculated
RequireChecksum bool
// RequestChecksumCalculation is the user config to opt-in/out request checksum calculation. If RequireChecksum is
// set to true, checksum will be calculated and this field will be ignored, otherwise
// RequestChecksumCalculation will be used to indicate if checksum will be calculated
RequestChecksumCalculation aws.RequestChecksumCalculation
// Enables support for wrapping the serialized input payload with a
// content-encoding: aws-check wrapper, and including a trailer for the
// algorithm's checksum value.
//
// The checksum will not be computed, nor added as trailing checksum, if
// the Algorithm's header is already set on the request.
EnableTrailingChecksum bool
// Enables support for computing the SHA256 checksum of input payloads
// along with the algorithm specified checksum. Prevents downstream
// middleware handlers (computePayloadSHA256) re-reading the payload.
//
// The SHA256 payload checksum will only be used for computed for requests
// that are not TLS, or do not enable trailing checksums.
//
// The SHA256 payload hash will not be computed, if the Algorithm's header
// is already set on the request.
EnableComputeSHA256PayloadHash bool
// Enables support for setting the aws-chunked decoded content length
// header for the decoded length of the underlying stream. Will only be set
// when used with trailing checksums, and aws-chunked content-encoding.
EnableDecodedContentLengthHeader bool
}
// AddInputMiddleware adds the middleware for performing checksum computing
// of request payloads, and checksum validation of response payloads.
//
// Deprecated: This internal-only runtime API is frozen. Do not call or modify
// it in new code. Checksum-enabled service operations now generate this
// middleware setup code inline per #2507.
func AddInputMiddleware(stack *middleware.Stack, options InputMiddlewareOptions) (err error) {
// Initial checksum configuration look up middleware
err = stack.Initialize.Add(&SetupInputContext{
GetAlgorithm: options.GetAlgorithm,
RequireChecksum: options.RequireChecksum,
RequestChecksumCalculation: options.RequestChecksumCalculation,
}, middleware.Before)
if err != nil {
return err
}
stack.Build.Remove("ContentChecksum")
inputChecksum := &ComputeInputPayloadChecksum{
EnableTrailingChecksum: options.EnableTrailingChecksum,
EnableComputePayloadHash: options.EnableComputeSHA256PayloadHash,
EnableDecodedContentLengthHeader: options.EnableDecodedContentLengthHeader,
}
if err := stack.Finalize.Insert(inputChecksum, "ResolveEndpointV2", middleware.After); err != nil {
return err
}
// If trailing checksum is not supported no need for finalize handler to be added.
if options.EnableTrailingChecksum {
trailerMiddleware := &AddInputChecksumTrailer{
EnableTrailingChecksum: inputChecksum.EnableTrailingChecksum,
EnableComputePayloadHash: inputChecksum.EnableComputePayloadHash,
EnableDecodedContentLengthHeader: inputChecksum.EnableDecodedContentLengthHeader,
}
if err := stack.Finalize.Insert(trailerMiddleware, "Retry", middleware.After); err != nil {
return err
}
}
return nil
}
// RemoveInputMiddleware Removes the compute input payload checksum middleware
// handlers from the stack.
func RemoveInputMiddleware(stack *middleware.Stack) {
id := (*SetupInputContext)(nil).ID()
stack.Initialize.Remove(id)
id = (*ComputeInputPayloadChecksum)(nil).ID()
stack.Finalize.Remove(id)
}
// OutputMiddlewareOptions provides options for configuring output checksum
// validation middleware.
type OutputMiddlewareOptions struct {
// GetValidationMode is a function to get the checksum validation
// mode of the output payload from the input parameters.
//
// Given the input parameter value, the function must return the validation
// mode and true, or false if no mode is specified.
GetValidationMode func(interface{}) (string, bool)
// SetValidationMode is a function to set the checksum validation mode of input parameters
SetValidationMode func(interface{}, string)
// ResponseChecksumValidation is the user config to opt-in/out response checksum validation
ResponseChecksumValidation aws.ResponseChecksumValidation
// The set of checksum algorithms that should be used for response payload
// checksum validation. The algorithm(s) used will be a union of the
// output's returned algorithms and this set.
//
// Only the first algorithm in the union is currently used.
ValidationAlgorithms []string
// If set the middleware will ignore output multipart checksums. Otherwise
// a checksum format error will be returned by the middleware.
IgnoreMultipartValidation bool
// When set the middleware will log when output does not have checksum or
// algorithm to validate.
LogValidationSkipped bool
// When set the middleware will log when the output contains a multipart
// checksum that was, skipped and not validated.
LogMultipartValidationSkipped bool
}
// AddOutputMiddleware adds the middleware for validating response payload's
// checksum.
func AddOutputMiddleware(stack *middleware.Stack, options OutputMiddlewareOptions) error {
err := stack.Initialize.Add(&setupOutputContext{
GetValidationMode: options.GetValidationMode,
SetValidationMode: options.SetValidationMode,
ResponseChecksumValidation: options.ResponseChecksumValidation,
}, middleware.Before)
if err != nil {
return err
}
// Resolve a supported priority order list of algorithms to validate.
algorithms := FilterSupportedAlgorithms(options.ValidationAlgorithms)
m := &validateOutputPayloadChecksum{
Algorithms: algorithms,
IgnoreMultipartValidation: options.IgnoreMultipartValidation,
LogMultipartValidationSkipped: options.LogMultipartValidationSkipped,
LogValidationSkipped: options.LogValidationSkipped,
}
return stack.Deserialize.Add(m, middleware.After)
}
// RemoveOutputMiddleware Removes the compute input payload checksum middleware
// handlers from the stack.
func RemoveOutputMiddleware(stack *middleware.Stack) {
id := (*setupOutputContext)(nil).ID()
stack.Initialize.Remove(id)
id = (*validateOutputPayloadChecksum)(nil).ID()
stack.Deserialize.Remove(id)
}
@@ -0,0 +1,90 @@
package checksum
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
var supportedChecksumFeatures = map[Algorithm]awsmiddleware.UserAgentFeature{
AlgorithmCRC32: awsmiddleware.UserAgentFeatureRequestChecksumCRC32,
AlgorithmCRC32C: awsmiddleware.UserAgentFeatureRequestChecksumCRC32C,
AlgorithmSHA1: awsmiddleware.UserAgentFeatureRequestChecksumSHA1,
AlgorithmSHA256: awsmiddleware.UserAgentFeatureRequestChecksumSHA256,
AlgorithmCRC64NVME: awsmiddleware.UserAgentFeatureRequestChecksumCRC64,
}
// RequestChecksumMetricsTracking is the middleware to track operation request's checksum usage
type RequestChecksumMetricsTracking struct {
RequestChecksumCalculation aws.RequestChecksumCalculation
UserAgent *awsmiddleware.RequestUserAgent
}
// ID provides the middleware identifier
func (m *RequestChecksumMetricsTracking) ID() string {
return "AWSChecksum:RequestMetricsTracking"
}
// HandleBuild checks request checksum config and checksum value sent
// and sends corresponding feature id to user agent
func (m *RequestChecksumMetricsTracking) HandleBuild(
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown request type %T", req)
}
switch m.RequestChecksumCalculation {
case aws.RequestChecksumCalculationWhenSupported:
m.UserAgent.AddUserAgentFeature(awsmiddleware.UserAgentFeatureRequestChecksumWhenSupported)
case aws.RequestChecksumCalculationWhenRequired:
m.UserAgent.AddUserAgentFeature(awsmiddleware.UserAgentFeatureRequestChecksumWhenRequired)
}
for algo, feat := range supportedChecksumFeatures {
checksumHeader := AlgorithmHTTPHeader(algo)
if checksum := req.Header.Get(checksumHeader); checksum != "" {
m.UserAgent.AddUserAgentFeature(feat)
}
}
return next.HandleBuild(ctx, in)
}
// ResponseChecksumMetricsTracking is the middleware to track operation response's checksum usage
type ResponseChecksumMetricsTracking struct {
ResponseChecksumValidation aws.ResponseChecksumValidation
UserAgent *awsmiddleware.RequestUserAgent
}
// ID provides the middleware identifier
func (m *ResponseChecksumMetricsTracking) ID() string {
return "AWSChecksum:ResponseMetricsTracking"
}
// HandleBuild checks the response checksum config and sends corresponding feature id to user agent
func (m *ResponseChecksumMetricsTracking) HandleBuild(
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown request type %T", req)
}
switch m.ResponseChecksumValidation {
case aws.ResponseChecksumValidationWhenSupported:
m.UserAgent.AddUserAgentFeature(awsmiddleware.UserAgentFeatureResponseChecksumWhenSupported)
case aws.ResponseChecksumValidationWhenRequired:
m.UserAgent.AddUserAgentFeature(awsmiddleware.UserAgentFeatureResponseChecksumWhenRequired)
}
return next.HandleBuild(ctx, in)
}
@@ -0,0 +1,428 @@
package checksum
import (
"context"
"crypto/sha256"
"fmt"
"hash"
"io"
"strconv"
"strings"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
internalcontext "github.com/aws/aws-sdk-go-v2/internal/context"
presignedurlcust "github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const (
streamingUnsignedPayloadTrailerPayloadHash = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"
)
// computedInputChecksumsKey is the metadata key for recording the algorithm the
// checksum was computed for and the checksum value.
type computedInputChecksumsKey struct{}
// GetComputedInputChecksums returns the map of checksum algorithm to their
// computed value stored in the middleware Metadata. Returns false if no values
// were stored in the Metadata.
func GetComputedInputChecksums(m middleware.Metadata) (map[string]string, bool) {
vs, ok := m.Get(computedInputChecksumsKey{}).(map[string]string)
return vs, ok
}
// SetComputedInputChecksums stores the map of checksum algorithm to their
// computed value in the middleware Metadata. Overwrites any values that
// currently exist in the metadata.
func SetComputedInputChecksums(m *middleware.Metadata, vs map[string]string) {
m.Set(computedInputChecksumsKey{}, vs)
}
// ComputeInputPayloadChecksum middleware computes payload checksum
type ComputeInputPayloadChecksum struct {
// Enables support for wrapping the serialized input payload with a
// content-encoding: aws-check wrapper, and including a trailer for the
// algorithm's checksum value.
//
// The checksum will not be computed, nor added as trailing checksum, if
// the Algorithm's header is already set on the request.
EnableTrailingChecksum bool
// Enables support for computing the SHA256 checksum of input payloads
// along with the algorithm specified checksum. Prevents downstream
// middleware handlers (computePayloadSHA256) re-reading the payload.
//
// The SHA256 payload hash will only be used for computed for requests
// that are not TLS, or do not enable trailing checksums.
//
// The SHA256 payload hash will not be computed, if the Algorithm's header
// is already set on the request.
EnableComputePayloadHash bool
// Enables support for setting the aws-chunked decoded content length
// header for the decoded length of the underlying stream. Will only be set
// when used with trailing checksums, and aws-chunked content-encoding.
EnableDecodedContentLengthHeader bool
useTrailer bool
}
type useTrailer struct{}
// ID provides the middleware's identifier.
func (m *ComputeInputPayloadChecksum) ID() string {
return "AWSChecksum:ComputeInputPayloadChecksum"
}
type computeInputHeaderChecksumError struct {
Msg string
Err error
}
func (e computeInputHeaderChecksumError) Error() string {
const intro = "compute input header checksum failed"
if e.Err != nil {
return fmt.Sprintf("%s, %s, %v", intro, e.Msg, e.Err)
}
return fmt.Sprintf("%s, %s", intro, e.Msg)
}
func (e computeInputHeaderChecksumError) Unwrap() error { return e.Err }
// HandleFinalize handles computing the payload's checksum, in the following cases:
// - Is HTTP, not HTTPS
// - RequireChecksum is true, and no checksums were specified via the Input
// - Trailing checksums are not supported
//
// The build handler must be inserted in the stack before ContentPayloadHash
// and after ComputeContentLength.
func (m *ComputeInputPayloadChecksum) HandleFinalize(
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
var checksum string
algorithm, ok, err := getInputAlgorithm(ctx)
if err != nil {
return out, metadata, err
}
if !ok {
return next.HandleFinalize(ctx, in)
}
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, computeInputHeaderChecksumError{
Msg: fmt.Sprintf("unknown request type %T", req),
}
}
defer func() {
if algorithm == "" || checksum == "" || err != nil {
return
}
// Record the checksum and algorithm that was computed
SetComputedInputChecksums(&metadata, map[string]string{
string(algorithm): checksum,
})
}()
// If any checksum header is already set nothing to do.
for header := range req.Header {
h := strings.ToUpper(header)
if strings.HasPrefix(h, "X-AMZ-CHECKSUM-") {
algorithm = Algorithm(strings.TrimPrefix(h, "X-AMZ-CHECKSUM-"))
checksum = req.Header.Get(header)
return next.HandleFinalize(ctx, in)
}
}
computePayloadHash := m.EnableComputePayloadHash
if v := v4.GetPayloadHash(ctx); v != "" {
computePayloadHash = false
}
stream := req.GetStream()
streamLength, err := getRequestStreamLength(req)
if err != nil {
return out, metadata, computeInputHeaderChecksumError{
Msg: "failed to determine stream length",
Err: err,
}
}
// If trailing checksums are supported, the request is HTTPS, and the
// stream is not nil or empty, instead switch to a trailing checksum.
//
// Nil and empty streams will always be handled as a request header,
// regardless if the operation supports trailing checksums or not.
if req.IsHTTPS() && !presignedurlcust.GetIsPresigning(ctx) {
if stream != nil && streamLength != 0 && m.EnableTrailingChecksum {
if m.EnableComputePayloadHash {
// ContentSHA256Header middleware handles the header
ctx = v4.SetPayloadHash(ctx, streamingUnsignedPayloadTrailerPayloadHash)
}
m.useTrailer = true
ctx = middleware.WithStackValue(ctx, useTrailer{}, true)
return next.HandleFinalize(ctx, in)
}
// If trailing checksums are not enabled but protocol is still HTTPS
// disabling computing the payload hash. Downstream middleware handler
// (ComputetPayloadHash) will set the payload hash to unsigned payload,
// if signing was used.
computePayloadHash = false
}
// Only seekable streams are supported for non-trailing checksums, because
// the stream needs to be rewound before the handler can continue.
if stream != nil && !req.IsStreamSeekable() {
return out, metadata, computeInputHeaderChecksumError{
Msg: "unseekable stream is not supported without TLS and trailing checksum",
}
}
var sha256Checksum string
checksum, sha256Checksum, err = computeStreamChecksum(
algorithm, stream, computePayloadHash)
if err != nil {
return out, metadata, computeInputHeaderChecksumError{
Msg: "failed to compute stream checksum",
Err: err,
}
}
if err := req.RewindStream(); err != nil {
return out, metadata, computeInputHeaderChecksumError{
Msg: "failed to rewind stream",
Err: err,
}
}
checksumHeader := AlgorithmHTTPHeader(algorithm)
req.Header.Set(checksumHeader, checksum)
if computePayloadHash {
ctx = v4.SetPayloadHash(ctx, sha256Checksum)
}
return next.HandleFinalize(ctx, in)
}
type computeInputTrailingChecksumError struct {
Msg string
Err error
}
func (e computeInputTrailingChecksumError) Error() string {
const intro = "compute input trailing checksum failed"
if e.Err != nil {
return fmt.Sprintf("%s, %s, %v", intro, e.Msg, e.Err)
}
return fmt.Sprintf("%s, %s", intro, e.Msg)
}
func (e computeInputTrailingChecksumError) Unwrap() error { return e.Err }
// AddInputChecksumTrailer adds HTTP checksum when
// - Is HTTPS, not HTTP
// - A checksum was specified via the Input
// - Trailing checksums are supported.
type AddInputChecksumTrailer struct {
EnableTrailingChecksum bool
EnableComputePayloadHash bool
EnableDecodedContentLengthHeader bool
}
// ID identifies this middleware.
func (*AddInputChecksumTrailer) ID() string {
return "addInputChecksumTrailer"
}
// HandleFinalize wraps the request body to write the trailing checksum.
func (m *AddInputChecksumTrailer) HandleFinalize(
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
algorithm, ok, err := getInputAlgorithm(ctx)
if err != nil {
return out, metadata, computeInputTrailingChecksumError{
Msg: "failed to get algorithm",
Err: err,
}
} else if !ok {
return next.HandleFinalize(ctx, in)
}
if enabled, _ := middleware.GetStackValue(ctx, useTrailer{}).(bool); !enabled {
return next.HandleFinalize(ctx, in)
}
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, computeInputTrailingChecksumError{
Msg: fmt.Sprintf("unknown request type %T", req),
}
}
// Trailing checksums are only supported when TLS is enabled.
if !req.IsHTTPS() {
return out, metadata, computeInputTrailingChecksumError{
Msg: "HTTPS required",
}
}
// If any checksum header is already set nothing to do.
for header := range req.Header {
if strings.HasPrefix(strings.ToLower(header), "x-amz-checksum-") {
return next.HandleFinalize(ctx, in)
}
}
stream := req.GetStream()
streamLength, err := getRequestStreamLength(req)
if err != nil {
return out, metadata, computeInputTrailingChecksumError{
Msg: "failed to determine stream length",
Err: err,
}
}
if stream == nil || streamLength == 0 {
// Nil and empty streams are handled by the Build handler. They are not
// supported by the trailing checksums finalize handler. There is no
// benefit to sending them as trailers compared to headers.
return out, metadata, computeInputTrailingChecksumError{
Msg: "nil or empty streams are not supported",
}
}
checksumReader, err := newComputeChecksumReader(stream, algorithm)
if err != nil {
return out, metadata, computeInputTrailingChecksumError{
Msg: "failed to created checksum reader",
Err: err,
}
}
awsChunkedReader := newUnsignedAWSChunkedEncoding(checksumReader,
func(o *awsChunkedEncodingOptions) {
o.Trailers[AlgorithmHTTPHeader(checksumReader.Algorithm())] = awsChunkedTrailerValue{
Get: checksumReader.Base64Checksum,
Length: checksumReader.Base64ChecksumLength(),
}
o.StreamLength = streamLength
})
for key, values := range awsChunkedReader.HTTPHeaders() {
for _, value := range values {
req.Header.Add(key, value)
}
}
// Setting the stream on the request will create a copy. The content length
// is not updated until after the request is copied to prevent impacting
// upstream middleware.
req, err = req.SetStream(awsChunkedReader)
if err != nil {
return out, metadata, computeInputTrailingChecksumError{
Msg: "failed updating request to trailing checksum wrapped stream",
Err: err,
}
}
req.ContentLength = awsChunkedReader.EncodedLength()
in.Request = req
// Add decoded content length header if original stream's content length is known.
if streamLength != -1 && m.EnableDecodedContentLengthHeader {
req.Header.Set(decodedContentLengthHeaderName, strconv.FormatInt(streamLength, 10))
}
out, metadata, err = next.HandleFinalize(ctx, in)
if err == nil {
checksum, err := checksumReader.Base64Checksum()
if err != nil {
return out, metadata, fmt.Errorf("failed to get computed checksum, %w", err)
}
// Record the checksum and algorithm that was computed
SetComputedInputChecksums(&metadata, map[string]string{
string(algorithm): checksum,
})
}
return out, metadata, err
}
func getInputAlgorithm(ctx context.Context) (Algorithm, bool, error) {
ctxAlgorithm := internalcontext.GetChecksumInputAlgorithm(ctx)
if ctxAlgorithm == "" {
return "", false, nil
}
algorithm, err := ParseAlgorithm(ctxAlgorithm)
if err != nil {
return "", false, fmt.Errorf(
"failed to parse algorithm, %w", err)
}
return algorithm, true, nil
}
func computeStreamChecksum(algorithm Algorithm, stream io.Reader, computePayloadHash bool) (
checksum string, sha256Checksum string, err error,
) {
hasher, err := NewAlgorithmHash(algorithm)
if err != nil {
return "", "", fmt.Errorf(
"failed to get hasher for checksum algorithm, %w", err)
}
var sha256Hasher hash.Hash
var batchHasher io.Writer = hasher
// Compute payload hash for the protocol. To prevent another handler
// (computePayloadSHA256) re-reading body also compute the SHA256 for
// request signing. If configured checksum algorithm is SHA256, don't
// double wrap stream with another SHA256 hasher.
if computePayloadHash && algorithm != AlgorithmSHA256 {
sha256Hasher = sha256.New()
batchHasher = io.MultiWriter(hasher, sha256Hasher)
}
if stream != nil {
if _, err = io.Copy(batchHasher, stream); err != nil {
return "", "", fmt.Errorf(
"failed to read stream to compute hash, %w", err)
}
}
checksum = string(base64EncodeHashSum(hasher))
if computePayloadHash {
if algorithm != AlgorithmSHA256 {
sha256Checksum = string(hexEncodeHashSum(sha256Hasher))
} else {
sha256Checksum = string(hexEncodeHashSum(hasher))
}
}
return checksum, sha256Checksum, nil
}
func getRequestStreamLength(req *smithyhttp.Request) (int64, error) {
if v := req.ContentLength; v > 0 {
return v, nil
}
if length, ok, err := req.StreamLength(); err != nil {
return 0, fmt.Errorf("failed getting request stream's length, %w", err)
} else if ok {
return length, nil
}
return -1, nil
}
@@ -0,0 +1,122 @@
package checksum
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
internalcontext "github.com/aws/aws-sdk-go-v2/internal/context"
"github.com/aws/smithy-go/middleware"
)
const (
checksumValidationModeEnabled = "ENABLED"
)
// SetupInputContext is the initial middleware that looks up the input
// used to configure checksum behavior. This middleware must be executed before
// input validation step or any other checksum middleware.
type SetupInputContext struct {
// GetAlgorithm is a function to get the checksum algorithm of the
// input payload from the input parameters.
//
// Given the input parameter value, the function must return the algorithm
// and true, or false if no algorithm is specified.
GetAlgorithm func(interface{}) (string, bool)
// RequireChecksum indicates whether operation model forces middleware to compute the input payload's checksum.
// If RequireChecksum is set to true, checksum will be calculated and RequestChecksumCalculation will be ignored,
// otherwise RequestChecksumCalculation will be used to indicate if checksum will be calculated
RequireChecksum bool
// RequestChecksumCalculation is the user config to opt-in/out request checksum calculation. If RequireChecksum is
// set to true, checksum will be calculated and this field will be ignored, otherwise
// RequestChecksumCalculation will be used to indicate if checksum will be calculated
RequestChecksumCalculation aws.RequestChecksumCalculation
}
// ID for the middleware
func (m *SetupInputContext) ID() string {
return "AWSChecksum:SetupInputContext"
}
// HandleInitialize initialization middleware that setups up the checksum
// context based on the input parameters provided in the stack.
func (m *SetupInputContext) HandleInitialize(
ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (
out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
// nil check here is for operations that require checksum but do not have input algorithm setting
if m.GetAlgorithm != nil {
if algorithm, ok := m.GetAlgorithm(in.Parameters); ok {
ctx = internalcontext.SetChecksumInputAlgorithm(ctx, algorithm)
return next.HandleInitialize(ctx, in)
}
}
if m.RequireChecksum || m.RequestChecksumCalculation == aws.RequestChecksumCalculationWhenSupported {
ctx = internalcontext.SetChecksumInputAlgorithm(ctx, string(AlgorithmCRC32))
}
return next.HandleInitialize(ctx, in)
}
type setupOutputContext struct {
// GetValidationMode is a function to get the checksum validation
// mode of the output payload from the input parameters.
//
// Given the input parameter value, the function must return the validation
// mode and true, or false if no mode is specified.
GetValidationMode func(interface{}) (string, bool)
// SetValidationMode is a function to set the checksum validation mode of input parameters
SetValidationMode func(interface{}, string)
// ResponseChecksumValidation states user config to opt-in/out checksum validation
ResponseChecksumValidation aws.ResponseChecksumValidation
}
// ID for the middleware
func (m *setupOutputContext) ID() string {
return "AWSChecksum:SetupOutputContext"
}
// HandleInitialize initialization middleware that setups up the checksum
// context based on the input parameters provided in the stack.
func (m *setupOutputContext) HandleInitialize(
ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (
out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
mode, _ := m.GetValidationMode(in.Parameters)
if m.ResponseChecksumValidation == aws.ResponseChecksumValidationWhenSupported || mode == checksumValidationModeEnabled {
m.SetValidationMode(in.Parameters, checksumValidationModeEnabled)
ctx = setContextOutputValidationMode(ctx, checksumValidationModeEnabled)
}
return next.HandleInitialize(ctx, in)
}
// outputValidationModeKey is the key set on context used to identify if
// output checksum validation is enabled.
type outputValidationModeKey struct{}
// setContextOutputValidationMode sets the request checksum
// algorithm on the context.
//
// Scoped to stack values.
func setContextOutputValidationMode(ctx context.Context, value string) context.Context {
return middleware.WithStackValue(ctx, outputValidationModeKey{}, value)
}
// getContextOutputValidationMode returns response checksum validation state,
// if one was specified. Empty string is returned if one is not specified.
//
// Scoped to stack values.
func getContextOutputValidationMode(ctx context.Context) (v string) {
v, _ = middleware.GetStackValue(ctx, outputValidationModeKey{}).(string)
return v
}
@@ -0,0 +1,128 @@
package checksum
import (
"context"
"fmt"
"strings"
"github.com/aws/smithy-go"
"github.com/aws/smithy-go/logging"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// outputValidationAlgorithmsUsedKey is the metadata key for indexing the algorithms
// that were used, by the middleware's validation.
type outputValidationAlgorithmsUsedKey struct{}
// GetOutputValidationAlgorithmsUsed returns the checksum algorithms used
// stored in the middleware Metadata. Returns false if no algorithms were
// stored in the Metadata.
func GetOutputValidationAlgorithmsUsed(m middleware.Metadata) ([]string, bool) {
vs, ok := m.Get(outputValidationAlgorithmsUsedKey{}).([]string)
return vs, ok
}
// SetOutputValidationAlgorithmsUsed stores the checksum algorithms used in the
// middleware Metadata.
func SetOutputValidationAlgorithmsUsed(m *middleware.Metadata, vs []string) {
m.Set(outputValidationAlgorithmsUsedKey{}, vs)
}
// validateOutputPayloadChecksum middleware computes payload checksum of the
// received response and validates with checksum returned by the service.
type validateOutputPayloadChecksum struct {
// Algorithms represents a priority-ordered list of valid checksum
// algorithm that should be validated when present in HTTP response
// headers.
Algorithms []Algorithm
// IgnoreMultipartValidation indicates multipart checksums ending with "-#"
// will be ignored.
IgnoreMultipartValidation bool
// When set the middleware will log when output does not have checksum or
// algorithm to validate.
LogValidationSkipped bool
// When set the middleware will log when the output contains a multipart
// checksum that was, skipped and not validated.
LogMultipartValidationSkipped bool
}
func (m *validateOutputPayloadChecksum) ID() string {
return "AWSChecksum:ValidateOutputPayloadChecksum"
}
// HandleDeserialize is a Deserialize middleware that wraps the HTTP response
// body with an io.ReadCloser that will validate its checksum.
func (m *validateOutputPayloadChecksum) HandleDeserialize(
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err = next.HandleDeserialize(ctx, in)
if err != nil {
return out, metadata, err
}
if mode := getContextOutputValidationMode(ctx); mode != checksumValidationModeEnabled {
return out, metadata, err
}
response, ok := out.RawResponse.(*smithyhttp.Response)
if !ok {
return out, metadata, &smithy.DeserializationError{
Err: fmt.Errorf("unknown transport type %T", out.RawResponse),
}
}
var expectedChecksum string
var algorithmToUse Algorithm
for _, algorithm := range m.Algorithms {
value := response.Header.Get(AlgorithmHTTPHeader(algorithm))
if len(value) == 0 {
continue
}
expectedChecksum = value
algorithmToUse = algorithm
}
logger := middleware.GetLogger(ctx)
// Skip validation if no checksum algorithm or checksum is available.
if len(expectedChecksum) == 0 || len(algorithmToUse) == 0 {
if m.LogValidationSkipped {
// TODO this probably should have more information about the
// operation output that won't be validated.
logger.Logf(logging.Warn,
"Response has no supported checksum. Not validating response payload.")
}
return out, metadata, nil
}
// Ignore multipart validation
if m.IgnoreMultipartValidation && strings.Contains(expectedChecksum, "-") {
if m.LogMultipartValidationSkipped {
// TODO this probably should have more information about the
// operation output that won't be validated.
logger.Logf(logging.Warn, "Skipped validation of multipart checksum.")
}
return out, metadata, nil
}
body, err := newValidateChecksumReader(response.Body, algorithmToUse, expectedChecksum)
if err != nil {
return out, metadata, fmt.Errorf("failed to create checksum validation reader, %w", err)
}
response.Body = body
// Update the metadata to include the set of the checksum algorithms that
// will be validated.
SetOutputValidationAlgorithmsUsed(&metadata, []string{
string(algorithmToUse),
})
return out, metadata, nil
}
@@ -1,3 +1,32 @@
# v1.12.12 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.11 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.10 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.12.9 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.8 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.7 (2024-12-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.6 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.5 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
@@ -3,4 +3,4 @@
package presignedurl
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.12.5"
const goModuleVersion = "1.12.12"
@@ -0,0 +1,416 @@
# v1.18.12 (2025-01-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.11 (2025-01-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.10 (2025-01-24)
* **Dependency Update**: Updated to the latest SDK module versions
* **Dependency Update**: Upgrade to smithy-go v1.22.2.
# v1.18.9 (2025-01-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.8 (2025-01-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.7 (2024-12-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.6 (2024-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.5 (2024-11-18)
* **Dependency Update**: Update to smithy-go v1.22.1.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.4 (2024-11-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.3 (2024-10-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.2 (2024-10-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.1 (2024-10-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.0 (2024-10-04)
* **Feature**: Add support for HTTP client metrics.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.18 (2024-09-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.17 (2024-09-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.16 (2024-08-15)
* **Dependency Update**: Bump minimum Go version to 1.21.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.15 (2024-07-10.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.14 (2024-07-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.13 (2024-06-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.12 (2024-06-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.11 (2024-06-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.10 (2024-06-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.9 (2024-06-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.8 (2024-06-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.7 (2024-05-16)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.6 (2024-05-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.5 (2024-03-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.4 (2024-03-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.3 (2024-03-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.2 (2024-02-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.1 (2024-02-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.17.0 (2024-02-13)
* **Feature**: Bump minimum Go version to 1.20 per our language support policy.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.10 (2024-01-04)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.9 (2023-12-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.8 (2023-12-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.7 (2023-11-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.6 (2023-11-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.5 (2023-11-28.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.4 (2023-11-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.3 (2023-11-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.2 (2023-11-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.1 (2023-11-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.0 (2023-10-31)
* **Feature**: **BREAKING CHANGE**: Bump minimum go version to 1.19 per the revised [go version support policy](https://aws.amazon.com/blogs/developer/aws-sdk-for-go-aligns-with-go-release-policy-on-supported-runtimes/).
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.6 (2023-10-12)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.5 (2023-10-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.4 (2023-08-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.3 (2023-08-18)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.2 (2023-08-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.1 (2023-08-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.15.0 (2023-07-31)
* **Feature**: Adds support for smithy-modeled endpoint resolution. A new rules-based endpoint resolution will be added to the SDK which will supercede and deprecate existing endpoint resolution. Specifically, EndpointResolver will be deprecated while BaseEndpoint and EndpointResolverV2 will take its place. For more information, please see the Endpoints section in our Developer Guide.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.14.5 (2023-07-28)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.14.4 (2023-07-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.14.3 (2023-06-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.14.2 (2023-04-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.14.1 (2023-04-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.14.0 (2023-03-21)
* **Feature**: port v1 sdk 100-continue http header customization for s3 PutObject/UploadPart request and enable user config
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.24 (2023-03-10)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.23 (2023-02-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.22 (2023-02-03)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.21 (2022-12-15)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.20 (2022-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.19 (2022-10-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.18 (2022-10-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.17 (2022-09-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.16 (2022-09-14)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.15 (2022-09-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.14 (2022-08-31)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.13 (2022-08-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.12 (2022-08-11)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.11 (2022-08-09)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.10 (2022-08-08)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.9 (2022-08-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.8 (2022-07-05)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.7 (2022-06-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.6 (2022-06-07)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.5 (2022-05-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.4 (2022-04-25)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.3 (2022-03-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.2 (2022-03-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.1 (2022-03-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.13.0 (2022-03-08)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.12.0 (2022-02-24)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.11.0 (2022-01-14)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.10.0 (2022-01-07)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.9.2 (2021-12-02)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.9.1 (2021-11-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.9.0 (2021-11-06)
* **Feature**: The SDK now supports configuration of FIPS and DualStack endpoints using environment variables, shared configuration, or programmatically.
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.8.0 (2021-10-21)
* **Feature**: Updated to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.7.2 (2021-10-11)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.7.1 (2021-09-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.7.0 (2021-09-02)
* **Feature**: Add support for S3 Multi-Region Access Point ARNs.
# v1.6.0 (2021-08-27)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.3 (2021-08-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.2 (2021-08-04)
* **Dependency Update**: Updated `github.com/aws/smithy-go` to latest version.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.1 (2021-07-15)
* **Dependency Update**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.0 (2021-06-25)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.0 (2021-06-04)
* **Feature**: The handling of AccessPoint and Outpost ARNs have been updated.
# v1.3.1 (2021-05-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.0 (2021-05-14)
* **Feature**: Constant has been added to modules to enable runtime version inspection for reporting.
* **Dependency Update**: Updated to the latest SDK module versions
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,53 @@
package arn
import (
"strings"
"github.com/aws/aws-sdk-go-v2/aws/arn"
)
// AccessPointARN provides representation
type AccessPointARN struct {
arn.ARN
AccessPointName string
}
// GetARN returns the base ARN for the Access Point resource
func (a AccessPointARN) GetARN() arn.ARN {
return a.ARN
}
// ParseAccessPointResource attempts to parse the ARN's resource as an
// AccessPoint resource.
//
// Supported Access point resource format:
// - Access point format: arn:{partition}:s3:{region}:{accountId}:accesspoint/{accesspointName}
// - example: arn:aws:s3:us-west-2:012345678901:accesspoint/myaccesspoint
func ParseAccessPointResource(a arn.ARN, resParts []string) (AccessPointARN, error) {
if isFIPS(a.Region) {
return AccessPointARN{}, InvalidARNError{ARN: a, Reason: "FIPS region not allowed in ARN"}
}
if len(a.AccountID) == 0 {
return AccessPointARN{}, InvalidARNError{ARN: a, Reason: "account-id not set"}
}
if len(resParts) == 0 {
return AccessPointARN{}, InvalidARNError{ARN: a, Reason: "resource-id not set"}
}
if len(resParts) > 1 {
return AccessPointARN{}, InvalidARNError{ARN: a, Reason: "sub resource not supported"}
}
resID := resParts[0]
if len(strings.TrimSpace(resID)) == 0 {
return AccessPointARN{}, InvalidARNError{ARN: a, Reason: "resource-id not set"}
}
return AccessPointARN{
ARN: a,
AccessPointName: resID,
}, nil
}
func isFIPS(region string) bool {
return strings.HasPrefix(region, "fips-") || strings.HasSuffix(region, "-fips")
}
@@ -0,0 +1,85 @@
package arn
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go-v2/aws/arn"
)
var supportedServiceARN = []string{
"s3",
"s3-outposts",
"s3-object-lambda",
}
func isSupportedServiceARN(service string) bool {
for _, name := range supportedServiceARN {
if name == service {
return true
}
}
return false
}
// Resource provides the interfaces abstracting ARNs of specific resource
// types.
type Resource interface {
GetARN() arn.ARN
String() string
}
// ResourceParser provides the function for parsing an ARN's resource
// component into a typed resource.
type ResourceParser func(arn.ARN) (Resource, error)
// ParseResource parses an AWS ARN into a typed resource for the S3 API.
func ParseResource(a arn.ARN, resParser ResourceParser) (resARN Resource, err error) {
if len(a.Partition) == 0 {
return nil, InvalidARNError{ARN: a, Reason: "partition not set"}
}
if !isSupportedServiceARN(a.Service) {
return nil, InvalidARNError{ARN: a, Reason: "service is not supported"}
}
if len(a.Resource) == 0 {
return nil, InvalidARNError{ARN: a, Reason: "resource not set"}
}
return resParser(a)
}
// SplitResource splits the resource components by the ARN resource delimiters.
func SplitResource(v string) []string {
var parts []string
var offset int
for offset <= len(v) {
idx := strings.IndexAny(v[offset:], "/:")
if idx < 0 {
parts = append(parts, v[offset:])
break
}
parts = append(parts, v[offset:idx+offset])
offset += idx + 1
}
return parts
}
// IsARN returns whether the given string is an ARN
func IsARN(s string) bool {
return arn.IsARN(s)
}
// InvalidARNError provides the error for an invalid ARN error.
type InvalidARNError struct {
ARN arn.ARN
Reason string
}
// Error returns a string denoting the occurred InvalidARNError
func (e InvalidARNError) Error() string {
return fmt.Sprintf("invalid Amazon %s ARN, %s, %s", e.ARN.Service, e.Reason, e.ARN.String())
}
@@ -0,0 +1,32 @@
package arn
import "fmt"
// arnable is implemented by the relevant S3/S3Control
// operations which have members that may need ARN
// processing.
type arnable interface {
SetARNMember(string) error
GetARNMember() (*string, bool)
}
// GetARNField would be called during middleware execution
// to retrieve a member value that is an ARN in need of
// processing.
func GetARNField(input interface{}) (*string, bool) {
v, ok := input.(arnable)
if !ok {
return nil, false
}
return v.GetARNMember()
}
// SetARNField would called during middleware exeuction
// to set a member value that required ARN processing.
func SetARNField(input interface{}, v string) error {
params, ok := input.(arnable)
if !ok {
return fmt.Errorf("Params does not contain arn field member")
}
return params.SetARNMember(v)
}
@@ -0,0 +1,128 @@
package arn
import (
"strings"
"github.com/aws/aws-sdk-go-v2/aws/arn"
)
// OutpostARN interface that should be satisfied by outpost ARNs
type OutpostARN interface {
Resource
GetOutpostID() string
}
// ParseOutpostARNResource will parse a provided ARNs resource using the appropriate ARN format
// and return a specific OutpostARN type
//
// Currently supported outpost ARN formats:
// * Outpost AccessPoint ARN format:
// - ARN format: arn:{partition}:s3-outposts:{region}:{accountId}:outpost/{outpostId}/accesspoint/{accesspointName}
// - example: arn:aws:s3-outposts:us-west-2:012345678901:outpost/op-1234567890123456/accesspoint/myaccesspoint
//
// * Outpost Bucket ARN format:
// - ARN format: arn:{partition}:s3-outposts:{region}:{accountId}:outpost/{outpostId}/bucket/{bucketName}
// - example: arn:aws:s3-outposts:us-west-2:012345678901:outpost/op-1234567890123456/bucket/mybucket
//
// Other outpost ARN formats may be supported and added in the future.
func ParseOutpostARNResource(a arn.ARN, resParts []string) (OutpostARN, error) {
if len(a.Region) == 0 {
return nil, InvalidARNError{ARN: a, Reason: "region not set"}
}
if isFIPS(a.Region) {
return nil, InvalidARNError{ARN: a, Reason: "FIPS region not allowed in ARN"}
}
if len(a.AccountID) == 0 {
return nil, InvalidARNError{ARN: a, Reason: "account-id not set"}
}
// verify if outpost id is present and valid
if len(resParts) == 0 || len(strings.TrimSpace(resParts[0])) == 0 {
return nil, InvalidARNError{ARN: a, Reason: "outpost resource-id not set"}
}
// verify possible resource type exists
if len(resParts) < 3 {
return nil, InvalidARNError{
ARN: a, Reason: "incomplete outpost resource type. Expected bucket or access-point resource to be present",
}
}
// Since we know this is a OutpostARN fetch outpostID
outpostID := strings.TrimSpace(resParts[0])
switch resParts[1] {
case "accesspoint":
accesspointARN, err := ParseAccessPointResource(a, resParts[2:])
if err != nil {
return OutpostAccessPointARN{}, err
}
return OutpostAccessPointARN{
AccessPointARN: accesspointARN,
OutpostID: outpostID,
}, nil
case "bucket":
bucketName, err := parseBucketResource(a, resParts[2:])
if err != nil {
return nil, err
}
return OutpostBucketARN{
ARN: a,
BucketName: bucketName,
OutpostID: outpostID,
}, nil
default:
return nil, InvalidARNError{ARN: a, Reason: "unknown resource set for outpost ARN"}
}
}
// OutpostAccessPointARN represents outpost access point ARN.
type OutpostAccessPointARN struct {
AccessPointARN
OutpostID string
}
// GetOutpostID returns the outpost id of outpost access point arn
func (o OutpostAccessPointARN) GetOutpostID() string {
return o.OutpostID
}
// OutpostBucketARN represents the outpost bucket ARN.
type OutpostBucketARN struct {
arn.ARN
BucketName string
OutpostID string
}
// GetOutpostID returns the outpost id of outpost bucket arn
func (o OutpostBucketARN) GetOutpostID() string {
return o.OutpostID
}
// GetARN retrives the base ARN from outpost bucket ARN resource
func (o OutpostBucketARN) GetARN() arn.ARN {
return o.ARN
}
// parseBucketResource attempts to parse the ARN's bucket resource and retrieve the
// bucket resource id.
//
// parseBucketResource only parses the bucket resource id.
func parseBucketResource(a arn.ARN, resParts []string) (bucketName string, err error) {
if len(resParts) == 0 {
return bucketName, InvalidARNError{ARN: a, Reason: "bucket resource-id not set"}
}
if len(resParts) > 1 {
return bucketName, InvalidARNError{ARN: a, Reason: "sub resource not supported"}
}
bucketName = strings.TrimSpace(resParts[0])
if len(bucketName) == 0 {
return bucketName, InvalidARNError{ARN: a, Reason: "bucket resource-id not set"}
}
return bucketName, err
}
@@ -0,0 +1,15 @@
package arn
// S3ObjectLambdaARN represents an ARN for the s3-object-lambda service
type S3ObjectLambdaARN interface {
Resource
isS3ObjectLambdasARN()
}
// S3ObjectLambdaAccessPointARN is an S3ObjectLambdaARN for the Access Point resource type
type S3ObjectLambdaAccessPointARN struct {
AccessPointARN
}
func (s S3ObjectLambdaAccessPointARN) isS3ObjectLambdasARN() {}
@@ -0,0 +1,73 @@
package s3shared
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
"github.com/aws/aws-sdk-go-v2/aws/arn"
)
// ARNLookup is the initial middleware that looks up if an arn is provided.
// This middleware is responsible for fetching ARN from a arnable field, and registering the ARN on
// middleware context. This middleware must be executed before input validation step or any other
// arn processing middleware.
type ARNLookup struct {
// GetARNValue takes in a input interface and returns a ptr to string and a bool
GetARNValue func(interface{}) (*string, bool)
}
// ID for the middleware
func (m *ARNLookup) ID() string {
return "S3Shared:ARNLookup"
}
// HandleInitialize handles the behavior of this initialize step
func (m *ARNLookup) HandleInitialize(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (
out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
// check if GetARNValue is supported
if m.GetARNValue == nil {
return next.HandleInitialize(ctx, in)
}
// check is input resource is an ARN; if not go to next
v, ok := m.GetARNValue(in.Parameters)
if !ok || v == nil || !arn.IsARN(*v) {
return next.HandleInitialize(ctx, in)
}
// if ARN process ResourceRequest and put it on ctx
av, err := arn.Parse(*v)
if err != nil {
return out, metadata, fmt.Errorf("error parsing arn: %w", err)
}
// set parsed arn on context
ctx = setARNResourceOnContext(ctx, av)
return next.HandleInitialize(ctx, in)
}
// arnResourceKey is the key set on context used to identify, retrive an ARN resource
// if present on the context.
type arnResourceKey struct{}
// SetARNResourceOnContext sets the S3 ARN on the context.
//
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
// to clear all stack values.
func setARNResourceOnContext(ctx context.Context, value arn.ARN) context.Context {
return middleware.WithStackValue(ctx, arnResourceKey{}, value)
}
// GetARNResourceFromContext returns an ARN from context and a bool indicating
// presence of ARN on ctx.
//
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
// to clear all stack values.
func GetARNResourceFromContext(ctx context.Context) (arn.ARN, bool) {
v, ok := middleware.GetStackValue(ctx, arnResourceKey{}).(arn.ARN)
return v, ok
}
@@ -0,0 +1,41 @@
package config
import "context"
// UseARNRegionProvider is an interface for retrieving external configuration value for UseARNRegion
type UseARNRegionProvider interface {
GetS3UseARNRegion(ctx context.Context) (value bool, found bool, err error)
}
// DisableMultiRegionAccessPointsProvider is an interface for retrieving external configuration value for DisableMultiRegionAccessPoints
type DisableMultiRegionAccessPointsProvider interface {
GetS3DisableMultiRegionAccessPoints(ctx context.Context) (value bool, found bool, err error)
}
// ResolveUseARNRegion extracts the first instance of a UseARNRegion from the config slice.
// Additionally returns a boolean to indicate if the value was found in provided configs, and error if one is encountered.
func ResolveUseARNRegion(ctx context.Context, configs []interface{}) (value bool, found bool, err error) {
for _, cfg := range configs {
if p, ok := cfg.(UseARNRegionProvider); ok {
value, found, err = p.GetS3UseARNRegion(ctx)
if err != nil || found {
break
}
}
}
return
}
// ResolveDisableMultiRegionAccessPoints extracts the first instance of a DisableMultiRegionAccessPoints from the config slice.
// Additionally returns a boolean to indicate if the value was found in provided configs, and error if one is encountered.
func ResolveDisableMultiRegionAccessPoints(ctx context.Context, configs []interface{}) (value bool, found bool, err error) {
for _, cfg := range configs {
if p, ok := cfg.(DisableMultiRegionAccessPointsProvider); ok {
value, found, err = p.GetS3DisableMultiRegionAccessPoints(ctx)
if err != nil || found {
break
}
}
}
return
}
@@ -0,0 +1,183 @@
package s3shared
import (
"fmt"
"github.com/aws/aws-sdk-go-v2/service/internal/s3shared/arn"
)
// TODO: fix these error statements to be relevant to v2 sdk
const (
invalidARNErrorErrCode = "InvalidARNError"
configurationErrorErrCode = "ConfigurationError"
)
// InvalidARNError denotes the error for Invalid ARN
type InvalidARNError struct {
message string
resource arn.Resource
origErr error
}
// Error returns the InvalidARN error string
func (e InvalidARNError) Error() string {
var extra string
if e.resource != nil {
extra = "ARN: " + e.resource.String()
}
msg := invalidARNErrorErrCode + " : " + e.message
if extra != "" {
msg = msg + "\n\t" + extra
}
return msg
}
// OrigErr is the original error wrapped by Invalid ARN Error
func (e InvalidARNError) Unwrap() error {
return e.origErr
}
// NewInvalidARNError denotes invalid arn error
func NewInvalidARNError(resource arn.Resource, err error) InvalidARNError {
return InvalidARNError{
message: "invalid ARN",
origErr: err,
resource: resource,
}
}
// NewInvalidARNWithUnsupportedPartitionError ARN not supported for the target partition
func NewInvalidARNWithUnsupportedPartitionError(resource arn.Resource, err error) InvalidARNError {
return InvalidARNError{
message: "resource ARN not supported for the target ARN partition",
origErr: err,
resource: resource,
}
}
// NewInvalidARNWithFIPSError ARN not supported for FIPS region
//
// Deprecated: FIPS will not appear in the ARN region component.
func NewInvalidARNWithFIPSError(resource arn.Resource, err error) InvalidARNError {
return InvalidARNError{
message: "resource ARN not supported for FIPS region",
resource: resource,
origErr: err,
}
}
// ConfigurationError is used to denote a client configuration error
type ConfigurationError struct {
message string
resource arn.Resource
clientPartitionID string
clientRegion string
origErr error
}
// Error returns the Configuration error string
func (e ConfigurationError) Error() string {
extra := fmt.Sprintf("ARN: %s, client partition: %s, client region: %s",
e.resource, e.clientPartitionID, e.clientRegion)
msg := configurationErrorErrCode + " : " + e.message
if extra != "" {
msg = msg + "\n\t" + extra
}
return msg
}
// OrigErr is the original error wrapped by Configuration Error
func (e ConfigurationError) Unwrap() error {
return e.origErr
}
// NewClientPartitionMismatchError stub
func NewClientPartitionMismatchError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "client partition does not match provided ARN partition",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewClientRegionMismatchError denotes cross region access error
func NewClientRegionMismatchError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "client region does not match provided ARN region",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewFailedToResolveEndpointError denotes endpoint resolving error
func NewFailedToResolveEndpointError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "endpoint resolver failed to find an endpoint for the provided ARN region",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewClientConfiguredForFIPSError denotes client config error for unsupported cross region FIPS access
func NewClientConfiguredForFIPSError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "client configured for fips but cross-region resource ARN provided",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewFIPSConfigurationError denotes a configuration error when a client or request is configured for FIPS
func NewFIPSConfigurationError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "use of ARN is not supported when client or request is configured for FIPS",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewClientConfiguredForAccelerateError denotes client config error for unsupported S3 accelerate
func NewClientConfiguredForAccelerateError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "client configured for S3 Accelerate but is not supported with resource ARN",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewClientConfiguredForCrossRegionFIPSError denotes client config error for unsupported cross region FIPS request
func NewClientConfiguredForCrossRegionFIPSError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "client configured for FIPS with cross-region enabled but is supported with cross-region resource ARN",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
// NewClientConfiguredForDualStackError denotes client config error for unsupported S3 Dual-stack
func NewClientConfiguredForDualStackError(resource arn.Resource, clientPartitionID, clientRegion string, err error) ConfigurationError {
return ConfigurationError{
message: "client configured for S3 Dual-stack but is not supported with resource ARN",
origErr: err,
resource: resource,
clientPartitionID: clientPartitionID,
clientRegion: clientRegion,
}
}
@@ -0,0 +1,6 @@
// Code generated by internal/repotools/cmd/updatemodulemeta DO NOT EDIT.
package s3shared
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.18.12"
@@ -0,0 +1,29 @@
package s3shared
import (
"github.com/aws/smithy-go/middleware"
)
// hostID is used to retrieve host id from response metadata
type hostID struct {
}
// SetHostIDMetadata sets the provided host id over middleware metadata
func SetHostIDMetadata(metadata *middleware.Metadata, id string) {
metadata.Set(hostID{}, id)
}
// GetHostIDMetadata retrieves the host id from middleware metadata
// returns host id as string along with a boolean indicating presence of
// hostId on middleware metadata.
func GetHostIDMetadata(metadata middleware.Metadata) (string, bool) {
if !metadata.Has(hostID{}) {
return "", false
}
v, ok := metadata.Get(hostID{}).(string)
if !ok {
return "", true
}
return v, true
}
@@ -0,0 +1,28 @@
package s3shared
import (
"context"
"github.com/aws/smithy-go/middleware"
)
// clonedInputKey used to denote if request input was cloned.
type clonedInputKey struct{}
// SetClonedInputKey sets a key on context to denote input was cloned previously.
//
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
// to clear all stack values.
func SetClonedInputKey(ctx context.Context, value bool) context.Context {
return middleware.WithStackValue(ctx, clonedInputKey{}, value)
}
// IsClonedInput retrieves if context key for cloned input was set.
// If set, we can infer that the reuqest input was cloned previously.
//
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
// to clear all stack values.
func IsClonedInput(ctx context.Context) bool {
v, _ := middleware.GetStackValue(ctx, clonedInputKey{}).(bool)
return v
}
@@ -0,0 +1,57 @@
package s3shared
import (
"context"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/smithy-go/middleware"
"github.com/aws/smithy-go/tracing"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const metadataRetrieverID = "S3MetadataRetriever"
// AddMetadataRetrieverMiddleware adds request id, host id retriever middleware
func AddMetadataRetrieverMiddleware(stack *middleware.Stack) error {
// add metadata retriever middleware before operation deserializers so that it can retrieve metadata such as
// host id, request id from response header returned by operation deserializers
return stack.Deserialize.Insert(&metadataRetriever{}, "OperationDeserializer", middleware.Before)
}
type metadataRetriever struct {
}
// ID returns the middleware identifier
func (m *metadataRetriever) ID() string {
return metadataRetrieverID
}
func (m *metadataRetriever) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err = next.HandleDeserialize(ctx, in)
span, _ := tracing.GetSpan(ctx)
resp, ok := out.RawResponse.(*smithyhttp.Response)
if !ok {
// No raw response to wrap with.
return out, metadata, err
}
// check for header for Request id
if v := resp.Header.Get("X-Amz-Request-Id"); len(v) != 0 {
// set reqID on metadata for successful responses.
awsmiddleware.SetRequestIDMetadata(&metadata, v)
span.SetProperty("aws.request_id", v)
}
// look up host-id
if v := resp.Header.Get("X-Amz-Id-2"); len(v) != 0 {
// set reqID on metadata for successful responses.
SetHostIDMetadata(&metadata, v)
span.SetProperty("aws.extended_request_id", v)
}
return out, metadata, err
}
@@ -0,0 +1,77 @@
package s3shared
import (
"fmt"
"strings"
awsarn "github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/internal/s3shared/arn"
)
// ResourceRequest represents an ARN resource and api request metadata
type ResourceRequest struct {
Resource arn.Resource
// RequestRegion is the region configured on the request config
RequestRegion string
// SigningRegion is the signing region resolved for the request
SigningRegion string
// PartitionID is the resolved partition id for the provided request region
PartitionID string
// UseARNRegion indicates if client should use the region provided in an ARN resource
UseARNRegion bool
// UseFIPS indicates if te client is configured for FIPS
UseFIPS bool
}
// ARN returns the resource ARN
func (r ResourceRequest) ARN() awsarn.ARN {
return r.Resource.GetARN()
}
// ResourceConfiguredForFIPS returns true if resource ARNs region is FIPS
//
// Deprecated: FIPS will not be present in the ARN region
func (r ResourceRequest) ResourceConfiguredForFIPS() bool {
return IsFIPS(r.ARN().Region)
}
// AllowCrossRegion returns a bool value to denote if S3UseARNRegion flag is set
func (r ResourceRequest) AllowCrossRegion() bool {
return r.UseARNRegion
}
// IsCrossPartition returns true if request is configured for region of another partition, than
// the partition that resource ARN region resolves to. IsCrossPartition will not return an error,
// if request is not configured with a specific partition id. This might happen if customer provides
// custom endpoint url, but does not associate a partition id with it.
func (r ResourceRequest) IsCrossPartition() (bool, error) {
rv := r.PartitionID
if len(rv) == 0 {
return false, nil
}
av := r.Resource.GetARN().Partition
if len(av) == 0 {
return false, fmt.Errorf("no partition id for provided ARN")
}
return !strings.EqualFold(rv, av), nil
}
// IsCrossRegion returns true if request signing region is not same as arn region
func (r ResourceRequest) IsCrossRegion() bool {
v := r.SigningRegion
return !strings.EqualFold(v, r.Resource.GetARN().Region)
}
// IsFIPS returns true if region is a fips pseudo-region
//
// Deprecated: FIPS should be specified via EndpointOptions.
func IsFIPS(region string) bool {
return strings.HasPrefix(region, "fips-") ||
strings.HasSuffix(region, "-fips")
}

Some files were not shown because too many files have changed in this diff Show More