forked from Azure/azure-storage-file-go
/
parsing_urls.go
159 lines (142 loc) · 5.32 KB
/
parsing_urls.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
package azfile
import (
"net"
"net/url"
"strings"
)
const (
shareSnapshot = "sharesnapshot"
)
// A FileURLParts object represents the components that make up an Azure Storage Share/Directory/File URL. You parse an
// existing URL into its parts by calling NewFileURLParts(). You construct a URL from parts by calling URL().
// NOTE: Changing any SAS-related field requires computing a new SAS signature.
type FileURLParts struct {
Scheme string // Ex: "https://"
Host string // Ex: "account.share.core.windows.net", "10.132.141.33", "10.132.141.33:80"
ShareName string // Share name, Ex: "myshare"
DirectoryOrFilePath string // Path of directory or file, Ex: "mydirectory/myfile"
ShareSnapshot string // IsZero is true if not a snapshot
SAS SASQueryParameters
UnparsedParams string
IPEndpointStyleInfo IPEndpointStyleInfo // Useful Parts for IP endpoint style URL.
}
// IPEndpointStyleInfo is used for IP endpoint style URL.
// It's commonly used when working with Azure storage emulator or testing environments.
// Ex: "https://10.132.141.33/accountname/sharename"
type IPEndpointStyleInfo struct {
AccountName string // "" if not using IP endpoint style
}
// isIPEndpointStyle checkes if URL's host is IP, in this case the storage account endpoint will be composed as:
// http(s)://IP(:port)/storageaccount/share(||container||etc)/...
// As url's Host property, host could be both host or host:port
func isIPEndpointStyle(host string) bool {
if host == "" {
return false
}
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
}
// For IPv6, there could be case where SplitHostPort fails for cannot finding port.
// In this case, eliminate the '[' and ']' in the URL.
// For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732
if host[0] == '[' && host[len(host)-1] == ']' {
host = host[1 : len(host)-1]
}
return net.ParseIP(host) != nil
}
// NewFileURLParts parses a URL initializing FileURLParts' fields including any SAS-related & sharesnapshot query parameters. Any other
// query parameters remain in the UnparsedParams field. This method overwrites all fields in the FileURLParts object.
func NewFileURLParts(u url.URL) FileURLParts {
up := FileURLParts{
Scheme: u.Scheme,
Host: u.Host,
IPEndpointStyleInfo: IPEndpointStyleInfo{},
}
if u.Path != "" {
path := u.Path
if path[0] == '/' {
path = path[1:]
}
if isIPEndpointStyle(up.Host) {
if accountEndIndex := strings.Index(path, "/"); accountEndIndex == -1 { // Slash not found; path has account name & no share, path of directory or file
up.IPEndpointStyleInfo.AccountName = path
} else {
up.IPEndpointStyleInfo.AccountName = path[:accountEndIndex] // The account name is the part between the slashes
path = path[accountEndIndex+1:]
// Find the next slash (if it exists)
if shareEndIndex := strings.Index(path, "/"); shareEndIndex == -1 { // Slash not found; path has share name & no path of directory or file
up.ShareName = path
} else { // Slash found; path has share name & path of directory or file
up.ShareName = path[:shareEndIndex]
up.DirectoryOrFilePath = path[shareEndIndex+1:]
}
}
} else {
// Find the next slash (if it exists)
if shareEndIndex := strings.Index(path, "/"); shareEndIndex == -1 { // Slash not found; path has share name & no path of directory or file
up.ShareName = path
} else { // Slash found; path has share name & path of directory or file
up.ShareName = path[:shareEndIndex]
up.DirectoryOrFilePath = path[shareEndIndex+1:]
}
}
}
// Convert the query parameters to a case-sensitive map & trim whitespace
paramsMap := u.Query()
if snapshotStr, ok := caseInsensitiveValues(paramsMap).Get(shareSnapshot); ok {
up.ShareSnapshot = snapshotStr[0]
// If we recognized the query parameter, remove it from the map
delete(paramsMap, shareSnapshot)
}
up.SAS = newSASQueryParameters(paramsMap, true)
up.UnparsedParams = paramsMap.Encode()
return up
}
type caseInsensitiveValues url.Values // map[string][]string
func (values caseInsensitiveValues) Get(key string) ([]string, bool) {
key = strings.ToLower(key)
for k, v := range values {
if strings.ToLower(k) == key {
return v, true
}
}
return []string{}, false
}
// URL returns a URL object whose fields are initialized from the FileURLParts fields. The URL's RawQuery
// field contains the SAS, snapshot, and unparsed query parameters.
func (up FileURLParts) URL() url.URL {
path := ""
// Concatenate account name for IP endpoint style URL
if isIPEndpointStyle(up.Host) && up.IPEndpointStyleInfo.AccountName != "" {
path += "/" + up.IPEndpointStyleInfo.AccountName
}
// Concatenate share & path of directory or file (if they exist)
if up.ShareName != "" {
path += "/" + up.ShareName
if up.DirectoryOrFilePath != "" {
path += "/" + up.DirectoryOrFilePath
}
}
rawQuery := up.UnparsedParams
// Concatenate share snapshot query parameter (if it exists)
if up.ShareSnapshot != "" {
if len(rawQuery) > 0 {
rawQuery += "&"
}
rawQuery += shareSnapshot + "=" + up.ShareSnapshot
}
sas := up.SAS.Encode()
if sas != "" {
if len(rawQuery) > 0 {
rawQuery += "&"
}
rawQuery += sas
}
u := url.URL{
Scheme: up.Scheme,
Host: up.Host,
Path: path,
RawQuery: rawQuery,
}
return u
}