forked from rclone/gofakes3
-
Notifications
You must be signed in to change notification settings - Fork 1
/
prefix.go
170 lines (146 loc) · 4.08 KB
/
prefix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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}
}