1package oss
2
3import (
4	"fmt"
5	"net/http"
6	"strconv"
7	"strings"
8	"time"
9)
10
11type optionType string
12
13const (
14	optionParam optionType = "HTTPParameter" // URL parameter
15	optionHTTP  optionType = "HTTPHeader"    // HTTP header
16	optionArg   optionType = "FuncArgument"  // Function argument
17)
18
19const (
20	deleteObjectsQuiet = "delete-objects-quiet"
21	routineNum         = "x-routine-num"
22	checkpointConfig   = "x-cp-config"
23	initCRC64          = "init-crc64"
24	progressListener   = "x-progress-listener"
25	storageClass       = "storage-class"
26)
27
28type (
29	optionValue struct {
30		Value interface{}
31		Type  optionType
32	}
33
34	// Option HTTP option
35	Option func(map[string]optionValue) error
36)
37
38// ACL is an option to set X-Oss-Acl header
39func ACL(acl ACLType) Option {
40	return setHeader(HTTPHeaderOssACL, string(acl))
41}
42
43// ContentType is an option to set Content-Type header
44func ContentType(value string) Option {
45	return setHeader(HTTPHeaderContentType, value)
46}
47
48// ContentLength is an option to set Content-Length header
49func ContentLength(length int64) Option {
50	return setHeader(HTTPHeaderContentLength, strconv.FormatInt(length, 10))
51}
52
53// CacheControl is an option to set Cache-Control header
54func CacheControl(value string) Option {
55	return setHeader(HTTPHeaderCacheControl, value)
56}
57
58// ContentDisposition is an option to set Content-Disposition header
59func ContentDisposition(value string) Option {
60	return setHeader(HTTPHeaderContentDisposition, value)
61}
62
63// ContentEncoding is an option to set Content-Encoding header
64func ContentEncoding(value string) Option {
65	return setHeader(HTTPHeaderContentEncoding, value)
66}
67
68// ContentLanguage is an option to set Content-Language header
69func ContentLanguage(value string) Option {
70	return setHeader(HTTPHeaderContentLanguage, value)
71}
72
73// ContentMD5 is an option to set Content-MD5 header
74func ContentMD5(value string) Option {
75	return setHeader(HTTPHeaderContentMD5, value)
76}
77
78// Expires is an option to set Expires header
79func Expires(t time.Time) Option {
80	return setHeader(HTTPHeaderExpires, t.Format(http.TimeFormat))
81}
82
83// Meta is an option to set Meta header
84func Meta(key, value string) Option {
85	return setHeader(HTTPHeaderOssMetaPrefix+key, value)
86}
87
88// Range is an option to set Range header, [start, end]
89func Range(start, end int64) Option {
90	return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%d-%d", start, end))
91}
92
93// NormalizedRange is an option to set Range header, such as 1024-2048 or 1024- or -2048
94func NormalizedRange(nr string) Option {
95	return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%s", strings.TrimSpace(nr)))
96}
97
98// AcceptEncoding is an option to set Accept-Encoding header
99func AcceptEncoding(value string) Option {
100	return setHeader(HTTPHeaderAcceptEncoding, value)
101}
102
103// IfModifiedSince is an option to set If-Modified-Since header
104func IfModifiedSince(t time.Time) Option {
105	return setHeader(HTTPHeaderIfModifiedSince, t.Format(http.TimeFormat))
106}
107
108// IfUnmodifiedSince is an option to set If-Unmodified-Since header
109func IfUnmodifiedSince(t time.Time) Option {
110	return setHeader(HTTPHeaderIfUnmodifiedSince, t.Format(http.TimeFormat))
111}
112
113// IfMatch is an option to set If-Match header
114func IfMatch(value string) Option {
115	return setHeader(HTTPHeaderIfMatch, value)
116}
117
118// IfNoneMatch is an option to set IfNoneMatch header
119func IfNoneMatch(value string) Option {
120	return setHeader(HTTPHeaderIfNoneMatch, value)
121}
122
123// CopySource is an option to set X-Oss-Copy-Source header
124func CopySource(sourceBucket, sourceObject string) Option {
125	return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject)
126}
127
128// CopySourceRange is an option to set X-Oss-Copy-Source header
129func CopySourceRange(startPosition, partSize int64) Option {
130	val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" +
131		strconv.FormatInt((startPosition+partSize-1), 10)
132	return setHeader(HTTPHeaderOssCopySourceRange, val)
133}
134
135// CopySourceIfMatch is an option to set X-Oss-Copy-Source-If-Match header
136func CopySourceIfMatch(value string) Option {
137	return setHeader(HTTPHeaderOssCopySourceIfMatch, value)
138}
139
140// CopySourceIfNoneMatch is an option to set X-Oss-Copy-Source-If-None-Match header
141func CopySourceIfNoneMatch(value string) Option {
142	return setHeader(HTTPHeaderOssCopySourceIfNoneMatch, value)
143}
144
145// CopySourceIfModifiedSince is an option to set X-Oss-CopySource-If-Modified-Since header
146func CopySourceIfModifiedSince(t time.Time) Option {
147	return setHeader(HTTPHeaderOssCopySourceIfModifiedSince, t.Format(http.TimeFormat))
148}
149
150// CopySourceIfUnmodifiedSince is an option to set X-Oss-Copy-Source-If-Unmodified-Since header
151func CopySourceIfUnmodifiedSince(t time.Time) Option {
152	return setHeader(HTTPHeaderOssCopySourceIfUnmodifiedSince, t.Format(http.TimeFormat))
153}
154
155// MetadataDirective is an option to set X-Oss-Metadata-Directive header
156func MetadataDirective(directive MetadataDirectiveType) Option {
157	return setHeader(HTTPHeaderOssMetadataDirective, string(directive))
158}
159
160// ServerSideEncryption is an option to set X-Oss-Server-Side-Encryption header
161func ServerSideEncryption(value string) Option {
162	return setHeader(HTTPHeaderOssServerSideEncryption, value)
163}
164
165// ServerSideEncryptionKeyID is an option to set X-Oss-Server-Side-Encryption-Key-Id header
166func ServerSideEncryptionKeyID(value string) Option {
167	return setHeader(HTTPHeaderOssServerSideEncryptionKeyID, value)
168}
169
170// ObjectACL is an option to set X-Oss-Object-Acl header
171func ObjectACL(acl ACLType) Option {
172	return setHeader(HTTPHeaderOssObjectACL, string(acl))
173}
174
175// symlinkTarget is an option to set X-Oss-Symlink-Target
176func symlinkTarget(targetObjectKey string) Option {
177	return setHeader(HTTPHeaderOssSymlinkTarget, targetObjectKey)
178}
179
180// Origin is an option to set Origin header
181func Origin(value string) Option {
182	return setHeader(HTTPHeaderOrigin, value)
183}
184
185// ObjectStorageClass is an option to set the storage class of object
186func ObjectStorageClass(storageClass StorageClassType) Option {
187	return setHeader(HTTPHeaderOssStorageClass, string(storageClass))
188}
189
190// Callback is an option to set callback values
191func Callback(callback string) Option {
192	return setHeader(HTTPHeaderOssCallback, callback)
193}
194
195// CallbackVar is an option to set callback user defined values
196func CallbackVar(callbackVar string) Option {
197	return setHeader(HTTPHeaderOssCallbackVar, callbackVar)
198}
199
200// RequestPayer is an option to set payer who pay for the request
201func RequestPayer(payerType PayerType) Option {
202	return setHeader(HTTPHeaderOSSRequester, string(payerType))
203}
204
205// Delimiter is an option to set delimiler parameter
206func Delimiter(value string) Option {
207	return addParam("delimiter", value)
208}
209
210// Marker is an option to set marker parameter
211func Marker(value string) Option {
212	return addParam("marker", value)
213}
214
215// MaxKeys is an option to set maxkeys parameter
216func MaxKeys(value int) Option {
217	return addParam("max-keys", strconv.Itoa(value))
218}
219
220// Prefix is an option to set prefix parameter
221func Prefix(value string) Option {
222	return addParam("prefix", value)
223}
224
225// EncodingType is an option to set encoding-type parameter
226func EncodingType(value string) Option {
227	return addParam("encoding-type", value)
228}
229
230// MaxUploads is an option to set max-uploads parameter
231func MaxUploads(value int) Option {
232	return addParam("max-uploads", strconv.Itoa(value))
233}
234
235// KeyMarker is an option to set key-marker parameter
236func KeyMarker(value string) Option {
237	return addParam("key-marker", value)
238}
239
240// UploadIDMarker is an option to set upload-id-marker parameter
241func UploadIDMarker(value string) Option {
242	return addParam("upload-id-marker", value)
243}
244
245// MaxParts is an option to set max-parts parameter
246func MaxParts(value int) Option {
247	return addParam("max-parts", strconv.Itoa(value))
248}
249
250// PartNumberMarker is an option to set part-number-marker parameter
251func PartNumberMarker(value int) Option {
252	return addParam("part-number-marker", strconv.Itoa(value))
253}
254
255// DeleteObjectsQuiet false:DeleteObjects in verbose mode; true:DeleteObjects in quite mode. Default is false.
256func DeleteObjectsQuiet(isQuiet bool) Option {
257	return addArg(deleteObjectsQuiet, isQuiet)
258}
259
260// StorageClass bucket storage class
261func StorageClass(value StorageClassType) Option {
262	return addArg(storageClass, value)
263}
264
265// Checkpoint configuration
266type cpConfig struct {
267	IsEnable bool
268	FilePath string
269	DirPath  string
270}
271
272// Checkpoint sets the isEnable flag and checkpoint file path for DownloadFile/UploadFile.
273func Checkpoint(isEnable bool, filePath string) Option {
274	return addArg(checkpointConfig, &cpConfig{IsEnable: isEnable, FilePath: filePath})
275}
276
277// CheckpointDir sets the isEnable flag and checkpoint dir path for DownloadFile/UploadFile.
278func CheckpointDir(isEnable bool, dirPath string) Option {
279	return addArg(checkpointConfig, &cpConfig{IsEnable: isEnable, DirPath: dirPath})
280}
281
282// Routines DownloadFile/UploadFile routine count
283func Routines(n int) Option {
284	return addArg(routineNum, n)
285}
286
287// InitCRC Init AppendObject CRC
288func InitCRC(initCRC uint64) Option {
289	return addArg(initCRC64, initCRC)
290}
291
292// Progress set progress listener
293func Progress(listener ProgressListener) Option {
294	return addArg(progressListener, listener)
295}
296
297// ResponseContentType is an option to set response-content-type param
298func ResponseContentType(value string) Option {
299	return addParam("response-content-type", value)
300}
301
302// ResponseContentLanguage is an option to set response-content-language param
303func ResponseContentLanguage(value string) Option {
304	return addParam("response-content-language", value)
305}
306
307// ResponseExpires is an option to set response-expires param
308func ResponseExpires(value string) Option {
309	return addParam("response-expires", value)
310}
311
312// ResponseCacheControl is an option to set response-cache-control param
313func ResponseCacheControl(value string) Option {
314	return addParam("response-cache-control", value)
315}
316
317// ResponseContentDisposition is an option to set response-content-disposition param
318func ResponseContentDisposition(value string) Option {
319	return addParam("response-content-disposition", value)
320}
321
322// ResponseContentEncoding is an option to set response-content-encoding param
323func ResponseContentEncoding(value string) Option {
324	return addParam("response-content-encoding", value)
325}
326
327// Process is an option to set x-oss-process param
328func Process(value string) Option {
329	return addParam("x-oss-process", value)
330}
331
332func setHeader(key string, value interface{}) Option {
333	return func(params map[string]optionValue) error {
334		if value == nil {
335			return nil
336		}
337		params[key] = optionValue{value, optionHTTP}
338		return nil
339	}
340}
341
342func addParam(key string, value interface{}) Option {
343	return func(params map[string]optionValue) error {
344		if value == nil {
345			return nil
346		}
347		params[key] = optionValue{value, optionParam}
348		return nil
349	}
350}
351
352func addArg(key string, value interface{}) Option {
353	return func(params map[string]optionValue) error {
354		if value == nil {
355			return nil
356		}
357		params[key] = optionValue{value, optionArg}
358		return nil
359	}
360}
361
362func handleOptions(headers map[string]string, options []Option) error {
363	params := map[string]optionValue{}
364	for _, option := range options {
365		if option != nil {
366			if err := option(params); err != nil {
367				return err
368			}
369		}
370	}
371
372	for k, v := range params {
373		if v.Type == optionHTTP {
374			headers[k] = v.Value.(string)
375		}
376	}
377	return nil
378}
379
380func getRawParams(options []Option) (map[string]interface{}, error) {
381	// Option
382	params := map[string]optionValue{}
383	for _, option := range options {
384		if option != nil {
385			if err := option(params); err != nil {
386				return nil, err
387			}
388		}
389	}
390
391	paramsm := map[string]interface{}{}
392	// Serialize
393	for k, v := range params {
394		if v.Type == optionParam {
395			vs := params[k]
396			paramsm[k] = vs.Value.(string)
397		}
398	}
399
400	return paramsm, nil
401}
402
403func findOption(options []Option, param string, defaultVal interface{}) (interface{}, error) {
404	params := map[string]optionValue{}
405	for _, option := range options {
406		if option != nil {
407			if err := option(params); err != nil {
408				return nil, err
409			}
410		}
411	}
412
413	if val, ok := params[param]; ok {
414		return val.Value, nil
415	}
416	return defaultVal, nil
417}
418
419func isOptionSet(options []Option, option string) (bool, interface{}, error) {
420	params := map[string]optionValue{}
421	for _, option := range options {
422		if option != nil {
423			if err := option(params); err != nil {
424				return false, nil, err
425			}
426		}
427	}
428
429	if val, ok := params[option]; ok {
430		return true, val.Value, nil
431	}
432	return false, nil, nil
433}
434