-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
158 lines (143 loc) · 4 KB
/
index.ts
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
import range from 'rrjx'
/**
good name to test
' +-:*@==;!@#$%^&*() 今晩は ee5fc9fe4cd692e33716 (1) {}%`'"|><~[]&<> .png.
*/
/**
aws states that some characters should be avoided. But in fact, they still admit them.
this flag should be disabled when we want a one-to-one relationship between the input key and encodedKey such as in the image handler service.
*/
let disallowAvoidedCharacters = false
const s3AvoidCharSet = new Set([
'{',
...[...range(128, 255, 1, true)].map(i => String.fromCharCode(i)),
'^',
'}',
'%',
'`',
']',
// '\'', safe
'"',
'“',
'‘',
'”',
'’',
'>',
'[',
'~',
'<',
'#',
'|',
])
/**
char not encoded by encodeURI
A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#description
*/
const makeS3Encodings = () => ({
// A-Z a-z 0-9: safe
';': '%3B',
',': '%2C',
// /: safe
'?': '%3F',
':': '%3A',
'@': '%40',
'&': '%26',
'=': '%3D',
'+': '%2B',
$: '%24',
// -: safe
// _: safe
// .: safe
// '!': '%21', safe
...!disallowAvoidedCharacters && {'~': '%7E'}, // avoid
// '*': '%2A', safe
// ': safe
// '(': '%28', safe
// ')': '%29', safe
...!disallowAvoidedCharacters && {'#': '%23'}, // avoid
// https://cryptii.com/pipes/text-octal
/**
requirements:
before encode:
- consecutive spaces are combined to one (optional)
- spaces are converted to +
- avoid chars are converted to +
after encode:
- all AWS-safe characters must not be HEX-encoded. Fortunately, encodeURI satisfies this.
- all AWS-unsafe characters must be HEX-encoded: some are not encoded with encodeURI, and we define them in s3Encodings.
*/
/**
https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
* from aws: safe characters
0-9 a-z A-Z / ! - _ . * ' ( )
from aws: Characters that might require special handling
& $
ASCII character ranges 00–1F hex (0–31 decimal) and 7F (127 decimal)
@ = ; : + (space) , ?
NOTE (by me): S3 converts space to +
Characters to avoid
\ {
Non-printable ASCII characters (128–255 decimal characters)
^ } % ` ]
Quotation marks
> [ ~ < # |
XML related object key constraints
' as '
” as "
& as &
< as <
> as >
\r as or 
\n as or 

*/
})
let s3Encodings = makeS3Encodings()
export const setDisallowAvoidedCharacters = (val: boolean) => {
disallowAvoidedCharacters = val
// reset s3Encodings
s3Encodings = makeS3Encodings()
}
export const sanitizeS3Key = (
key: string,
{
charLimit = 512,
replacementCharacter = '+',
} = {}
) => {
/**
some forbidden patterns
await flipPromise(uploadWithKey('public/*.png'))
await flipPromise(uploadWithKey('../public/ab.png'))
await flipPromise(uploadWithKey('\\../public/ab.png'))
await flipPromise(uploadWithKey('/../public/ab.png'))
somehow, s3 does not allow * in key name, even though * is a valid char
https://stackoverflow.com/a/67037092/1398479
https://stackoverflow.com/questions/63750619/aws-amplify-storage-put-does-not-like-asterisk
*/
// aws allows multiple/single spaces even if they lead the filename
// however, because it confuses our encoder, we convert them to replacementChar beforehand
key = key.replaceAll(' ', replacementCharacter)
// previously, this was a bug
// https://github.com/aws/aws-sdk-js-v3/issues/2596
// .replaceAll('*', replacementChar)
.replace(/^\.\.\//, '+../')
.replace(/^\.\.$/, '+..')
.replace(/^\\/, '+\\')
.replace(/^\//, '+/')
key = disallowAvoidedCharacters
? key
.split('')
.map(c => s3AvoidCharSet.has(c) ? replacementCharacter : c)
.join('')
: key
return key.slice(0, charLimit)
}
// key must be sanitized with sanitizeS3Key before passing to this function
export const encodeS3Key = (key: string) => encodeURI(key)
.split('')
.map(char => s3Encodings[char as keyof typeof s3Encodings] || char)
.join('')
export const decodeS3Key = (encodedKey: string) => decodeURI(Object
.entries(s3Encodings)
.reduce((acc, [c, cc]) => acc.replaceAll(cc, c), encodedKey))