mirror of
https://github.com/alist-org/gofakes3.git
synced 2024-08-14 19:38:30 +08:00
171 lines
4.1 KiB
Go
171 lines
4.1 KiB
Go
package gofakes3
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type Prefix struct {
|
|
HasPrefix bool
|
|
Prefix string
|
|
|
|
HasDelimiter bool
|
|
Delimiter string
|
|
}
|
|
|
|
func prefixFromQuery(query url.Values) Prefix {
|
|
prefix := Prefix{
|
|
Prefix: query.Get("prefix"),
|
|
Delimiter: query.Get("delimiter"),
|
|
}
|
|
_, prefix.HasPrefix = query["prefix"]
|
|
_, prefix.HasDelimiter = query["delimiter"]
|
|
return prefix
|
|
}
|
|
|
|
func NewPrefix(prefix, delim *string) (p Prefix) {
|
|
if prefix != nil {
|
|
p.HasPrefix, p.Prefix = true, *prefix
|
|
}
|
|
if delim != nil {
|
|
p.HasDelimiter, p.Delimiter = true, *delim
|
|
}
|
|
return p
|
|
}
|
|
|
|
func NewFolderPrefix(prefix string) (p Prefix) {
|
|
p.HasPrefix, p.Prefix = true, prefix
|
|
p.HasDelimiter, p.Delimiter = true, "/"
|
|
return p
|
|
}
|
|
|
|
// FilePrefix returns the path portion, then the remaining portion of the
|
|
// Prefix if the Delimiter is "/". If the Delimiter is not set, or not "/",
|
|
// ok will be false.
|
|
//
|
|
// For example:
|
|
//
|
|
// /foo/bar/ : path: /foo/bar remaining: ""
|
|
// /foo/bar/b : path: /foo/bar remaining: "b"
|
|
// /foo/bar : path: /foo remaining: "bar"
|
|
func (p Prefix) FilePrefix() (path, remaining string, ok bool) {
|
|
if !p.HasPrefix || !p.HasDelimiter || p.Delimiter != "/" {
|
|
return "", "", ok
|
|
}
|
|
|
|
idx := strings.LastIndexByte(p.Prefix, '/')
|
|
if idx < 0 {
|
|
return "", p.Prefix, true
|
|
} else {
|
|
return p.Prefix[:idx], p.Prefix[idx+1:], true
|
|
}
|
|
}
|
|
|
|
// PrefixMatch checks whether key starts with prefix. If the prefix does not
|
|
// match, nil is returned.
|
|
//
|
|
// It is a best-effort attempt to implement the prefix/delimiter matching found
|
|
// in S3.
|
|
//
|
|
// To check whether the key belongs in Contents or CommonPrefixes, compare the
|
|
// result to key.
|
|
func (p Prefix) Match(key string, match *PrefixMatch) (ok bool) {
|
|
if !p.HasPrefix && !p.HasDelimiter {
|
|
// If there is no prefix in the search, the match is the prefix:
|
|
if match != nil {
|
|
*match = PrefixMatch{Key: key, MatchedPart: key}
|
|
}
|
|
return true
|
|
}
|
|
|
|
if !p.HasDelimiter {
|
|
// If the request does not contain a delimiter, prefix matching is a
|
|
// simple string prefix:
|
|
if strings.HasPrefix(key, p.Prefix) {
|
|
if match != nil {
|
|
*match = PrefixMatch{Key: key, MatchedPart: p.Prefix}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Delimited + Prefix matches, for example:
|
|
// $ aws s3 ls s3://my-bucket/
|
|
// PRE AWSLogs/
|
|
// $ aws s3 ls s3://my-bucket/AWSLogs
|
|
// PRE AWSLogs/
|
|
// $ aws s3 ls s3://my-bucket/AWSLogs/
|
|
// PRE 260839334643/
|
|
// $ aws s3 ls s3://my-bucket/AWSLogs/2608
|
|
// PRE 260839334643/
|
|
|
|
keyParts := strings.Split(strings.TrimLeft(key, p.Delimiter), p.Delimiter)
|
|
preParts := strings.Split(strings.TrimLeft(p.Prefix, p.Delimiter), p.Delimiter)
|
|
|
|
if len(keyParts) < len(preParts) {
|
|
return false
|
|
}
|
|
|
|
// If the key exactly matches the prefix, but only up to a delimiter,
|
|
// AWS appends the delimiter to the result:
|
|
// $ aws s3 ls s3://my-bucket/AWSLogs
|
|
// PRE AWSLogs/
|
|
appendDelim := len(keyParts) != len(preParts)
|
|
matched := 0
|
|
|
|
last := len(preParts) - 1
|
|
for i := 0; i < len(preParts); i++ {
|
|
if i == last {
|
|
if !strings.HasPrefix(keyParts[i], preParts[i]) {
|
|
return false
|
|
}
|
|
|
|
} else {
|
|
if keyParts[i] != preParts[i] {
|
|
return false
|
|
}
|
|
}
|
|
matched++
|
|
}
|
|
|
|
if matched == 0 {
|
|
return false
|
|
}
|
|
|
|
out := strings.Join(keyParts[:matched], p.Delimiter)
|
|
if appendDelim {
|
|
out += p.Delimiter
|
|
}
|
|
|
|
if match != nil {
|
|
*match = PrefixMatch{Key: key, CommonPrefix: out != key, MatchedPart: out}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p Prefix) String() string {
|
|
if p.HasDelimiter {
|
|
return fmt.Sprintf("prefix:%q, delim:%q", p.Prefix, p.Delimiter)
|
|
} else {
|
|
return fmt.Sprintf("prefix:%q", p.Prefix)
|
|
}
|
|
}
|
|
|
|
type PrefixMatch struct {
|
|
// Input key passed to PrefixMatch.
|
|
Key string
|
|
|
|
// CommonPrefix indicates whether this key should be returned in the bucket
|
|
// contents or the common prefixes part of the "list bucket" response.
|
|
CommonPrefix bool
|
|
|
|
// The longest matched part of the key.
|
|
MatchedPart string
|
|
}
|
|
|
|
func (match *PrefixMatch) AsCommonPrefix() CommonPrefix {
|
|
return CommonPrefix{Prefix: match.MatchedPart}
|
|
}
|