Merge branch 'dev' of http://gitlab.fjmaimaimai.com/mmm-go/openapi into dev
正在显示
8 个修改的文件
包含
607 行增加
和
20 行删除
controllers/v2/base.go
0 → 100644
| @@ -4,8 +4,10 @@ go 1.12 | @@ -4,8 +4,10 @@ go 1.12 | ||
| 4 | 4 | ||
| 5 | require ( | 5 | require ( |
| 6 | github.com/aliyun/alibaba-cloud-sdk-go v1.60.348 | 6 | github.com/aliyun/alibaba-cloud-sdk-go v1.60.348 |
| 7 | + github.com/aliyun/aliyun-sts-go-sdk v0.0.0-20171106034748-98d3903a2309 | ||
| 7 | github.com/astaxie/beego v1.10.0 | 8 | github.com/astaxie/beego v1.10.0 |
| 8 | github.com/klauspost/cpuid v1.2.3 // indirect | 9 | github.com/klauspost/cpuid v1.2.3 // indirect |
| 10 | + github.com/satori/go.uuid v1.2.0 | ||
| 9 | gitlab.fjmaimaimai.com/mmm-go/gocomm v0.0.1 | 11 | gitlab.fjmaimaimai.com/mmm-go/gocomm v0.0.1 |
| 10 | ) | 12 | ) |
| 11 | 13 |
| 1 | package aliyun | 1 | package aliyun |
| 2 | 2 | ||
| 3 | -import ( | ||
| 4 | - "github.com/astaxie/beego" | ||
| 5 | -) | ||
| 6 | - | ||
| 7 | /* | 3 | /* |
| 8 | AK AccessKey 完全访问权限 | 4 | AK AccessKey 完全访问权限 |
| 9 | STS (Security Token Service) 临时访问控制 | 5 | STS (Security Token Service) 临时访问控制 |
| 10 | */ | 6 | */ |
| 11 | - | ||
| 12 | -var ( | ||
| 13 | - Endpoint string = beego.AppConfig.String("end_point") | ||
| 14 | - BucketName string = beego.AppConfig.String("bucket_name") | ||
| 15 | - //BucketNameCdn string = beego.AppConfig.String("bucket_name_cdn") | ||
| 16 | - //EndpointCdn string = beego.AppConfig.String("end_point_cdn") | ||
| 17 | -) | ||
| 18 | - | ||
| 19 | -//type OssSts struct{ | ||
| 20 | -// | ||
| 21 | -//} | ||
| 22 | - | ||
| 23 | -//func NewSTS(){ | ||
| 24 | -// client,_ :=oos.NewClientWithAccessKey(RegionID,AccessKeyID,AccessKeySecret) | ||
| 25 | -// client.DoAction() | ||
| 26 | -//} | 7 | +type ossConfig struct { |
| 8 | + AccessID string | ||
| 9 | + AccessKey string | ||
| 10 | + RoleAcs string | ||
| 11 | + EndPoint string | ||
| 12 | + BuckName string | ||
| 13 | + CallbackUrl string | ||
| 14 | +} |
internal/log/log.go
0 → 100644
| 1 | +package log | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "github.com/astaxie/beego" | ||
| 5 | +) | ||
| 6 | + | ||
| 7 | +func Warn(msg string) { | ||
| 8 | + beego.Warn(msg) | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +func Trace(msg string) { | ||
| 12 | + beego.Trace(msg) | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +func Debug(msg string) { | ||
| 16 | + beego.Debug(msg) | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +func Info(msg string) { | ||
| 20 | + beego.Info(msg) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +func Error(msg string) { | ||
| 24 | + beego.Error(msg) | ||
| 25 | +} |
services/oss/config.go
0 → 100644
| 1 | +package oss | ||
| 2 | + | ||
| 3 | +type OssConfig struct { | ||
| 4 | + accessID string | ||
| 5 | + accessKey string | ||
| 6 | + roleAcs string | ||
| 7 | + endPoint string | ||
| 8 | + bucketName string | ||
| 9 | + callbackUrl string | ||
| 10 | + sessionName string | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +func NewOssConfig() *OssConfig { | ||
| 14 | + return &OssConfig{ | ||
| 15 | + accessID: "", | ||
| 16 | + accessKey: "", | ||
| 17 | + roleAcs: "", | ||
| 18 | + bucketName: "", | ||
| 19 | + callbackUrl: "", | ||
| 20 | + endPoint: "", | ||
| 21 | + sessionName: "", | ||
| 22 | + } | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +func (config *OssConfig) SetBucketName(name string) { | ||
| 26 | + config.bucketName = name | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +func (config *OssConfig) SetCallbackUrl(url string) { | ||
| 30 | + config.callbackUrl = url | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +func (config *OssConfig) SetSessionName(name string) { | ||
| 34 | + config.sessionName = name | ||
| 35 | +} |
services/oss/policy.go
0 → 100644
| 1 | +package oss | ||
| 2 | + | ||
| 3 | +type PolicyToken struct { | ||
| 4 | + AccessKeyId string `json:"accessid"` | ||
| 5 | + Host string `json:"host"` | ||
| 6 | + Expire int64 `json:"expire"` | ||
| 7 | + Signature string `json:"signature"` | ||
| 8 | + Policy string `json:"policy"` | ||
| 9 | + Directory string `json:"dir"` | ||
| 10 | + Callback string `json:"callback"` | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +type CallbackParam struct { | ||
| 14 | + CallbackUrl string `json:"callbackUrl"` | ||
| 15 | + CallbackBody string `json:"callbackBody"` | ||
| 16 | + CallbackBodyType string `json:"callbackBodyType"` | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +// func get_policy_token() string { | ||
| 20 | +// now := time.Now().Unix() | ||
| 21 | +// expire_end := now + expire_time | ||
| 22 | +// var tokenExpire = get_gmt_iso8601(expire_end) | ||
| 23 | + | ||
| 24 | +// //create post policy json | ||
| 25 | +// var config ConfigStruct | ||
| 26 | +// config.Expiration = tokenExpire | ||
| 27 | +// var condition []string | ||
| 28 | +// condition = append(condition, "starts-with") | ||
| 29 | +// condition = append(condition, "$key") | ||
| 30 | +// condition = append(condition, upload_dir) | ||
| 31 | +// config.Conditions = append(config.Conditions, condition) | ||
| 32 | + | ||
| 33 | +// //calucate signature | ||
| 34 | +// result,err:=json.Marshal(config) | ||
| 35 | +// debyte := base64.StdEncoding.EncodeToString(result) | ||
| 36 | +// h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(accessKeySecret)) | ||
| 37 | +// io.WriteString(h, debyte) | ||
| 38 | +// signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) | ||
| 39 | + | ||
| 40 | +// var callbackParam CallbackParam | ||
| 41 | +// callbackParam.CallbackUrl = callbackUrl | ||
| 42 | +// callbackParam.CallbackBody = "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}" | ||
| 43 | +// callbackParam.CallbackBodyType = "application/x-www-form-urlencoded" | ||
| 44 | +// callback_str,err:=json.Marshal(callbackParam) | ||
| 45 | +// if err != nil { | ||
| 46 | +// fmt.Println("callback json err:", err) | ||
| 47 | +// } | ||
| 48 | +// callbackBase64 := base64.StdEncoding.EncodeToString(callback_str) | ||
| 49 | + | ||
| 50 | +// var policyToken PolicyToken | ||
| 51 | +// policyToken.AccessKeyId = accessKeyId | ||
| 52 | +// policyToken.Host = host | ||
| 53 | +// policyToken.Expire = expire_end | ||
| 54 | +// policyToken.Signature = string(signedStr) | ||
| 55 | +// policyToken.Directory = upload_dir | ||
| 56 | +// policyToken.Policy = string(debyte) | ||
| 57 | +// policyToken.Callback = string(callbackBase64) | ||
| 58 | +// response,err:=json.Marshal(policyToken) | ||
| 59 | +// if err != nil { | ||
| 60 | +// fmt.Println("json err:", err) | ||
| 61 | +// } | ||
| 62 | +// return string(response) | ||
| 63 | +// } |
services/oss/sts.go
0 → 100644
| 1 | +package oss | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "github.com/aliyun/aliyun-sts-go-sdk/sts" | ||
| 5 | +) | ||
| 6 | + | ||
| 7 | +type StsCredentials struct { | ||
| 8 | + AccessKeyId string `json:"access_key_id"` | ||
| 9 | + AccessKeySecret string `json:"access_key_secret"` | ||
| 10 | + Expiration int64 `json:"expiration"` | ||
| 11 | + SecurityToken string `json:"security_token"` | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +type AssumedRoleUser struct { | ||
| 15 | + AssumedRoleId string `json:"assumed_role_id"` | ||
| 16 | + Arn string `json:"arn"` | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +type StsData struct { | ||
| 20 | + RequestId string `json:"request_id,omitempty"` | ||
| 21 | + AssumedRoleUser AssumedRoleUser `json:"assumed_role_user,omitempty"` | ||
| 22 | + Credentials StsCredentials `json:"credentials,omitempty"` | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +func GetStsCredentials() (*StsData, error) { | ||
| 26 | + ossconfig := NewOssConfig() | ||
| 27 | + stsClient := sts.NewClient(ossconfig.accessID, ossconfig.accessKey, ossconfig.roleAcs, ossconfig.sessionName) | ||
| 28 | + resp, err := stsClient.AssumeRole(3600) | ||
| 29 | + if err != nil { | ||
| 30 | + return nil, err | ||
| 31 | + } | ||
| 32 | + c := StsCredentials{ | ||
| 33 | + AccessKeyId: resp.Credentials.AccessKeyId, | ||
| 34 | + AccessKeySecret: resp.Credentials.AccessKeySecret, | ||
| 35 | + Expiration: resp.Credentials.Expiration.Unix(), | ||
| 36 | + SecurityToken: resp.Credentials.SecurityToken, | ||
| 37 | + } | ||
| 38 | + ar := AssumedRoleUser{ | ||
| 39 | + AssumedRoleId: resp.AssumedRoleUser.AssumedRoleId, | ||
| 40 | + Arn: resp.AssumedRoleUser.Arn, | ||
| 41 | + } | ||
| 42 | + return &StsData{ | ||
| 43 | + RequestId: resp.RequestId, | ||
| 44 | + Credentials: c, | ||
| 45 | + AssumedRoleUser: ar, | ||
| 46 | + }, nil | ||
| 47 | +} |
services/oss/valid.go
0 → 100644
| 1 | +package oss | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "crypto" | ||
| 5 | + "crypto/md5" | ||
| 6 | + "crypto/rsa" | ||
| 7 | + "crypto/x509" | ||
| 8 | + "encoding/base64" | ||
| 9 | + "encoding/pem" | ||
| 10 | + "errors" | ||
| 11 | + "fmt" | ||
| 12 | + "io/ioutil" | ||
| 13 | + "net/http" | ||
| 14 | + "strconv" | ||
| 15 | + | ||
| 16 | + "github.com/astaxie/beego" | ||
| 17 | +) | ||
| 18 | + | ||
| 19 | +func ValidSignure(r *http.Request, requestBody []byte) bool { | ||
| 20 | + if r.Method != "POST" { | ||
| 21 | + beego.Error("request.Method!=post") | ||
| 22 | + return false | ||
| 23 | + } | ||
| 24 | + // Get PublicKey bytes | ||
| 25 | + bytePublicKey, err := getPublicKey(r) | ||
| 26 | + if err != nil { | ||
| 27 | + beego.Error(err) | ||
| 28 | + return false | ||
| 29 | + } | ||
| 30 | + // Get Authorization bytes : decode from Base64String | ||
| 31 | + byteAuthorization, err := getAuthorization(r) | ||
| 32 | + if err != nil { | ||
| 33 | + beego.Error(err) | ||
| 34 | + return false | ||
| 35 | + } | ||
| 36 | + // Get MD5 bytes from Newly Constructed Authrization String. | ||
| 37 | + byteMD5, err := getMD5FromNewAuthString(r, requestBody) | ||
| 38 | + if err != nil { | ||
| 39 | + beego.Error(err) | ||
| 40 | + return false | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + // VerifySignature and response to client | ||
| 44 | + if verifySignature(bytePublicKey, byteMD5, byteAuthorization) { | ||
| 45 | + return true | ||
| 46 | + } else { | ||
| 47 | + beego.Error("verifySignature return false") | ||
| 48 | + return false | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + return false | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +func handlerRequest(w http.ResponseWriter, r *http.Request) { | ||
| 55 | + if r.Method == "POST" { | ||
| 56 | + fmt.Println("\nHandle Post Request...") | ||
| 57 | + | ||
| 58 | + // Get PublicKey bytes | ||
| 59 | + bytePublicKey, err := getPublicKey(r) | ||
| 60 | + if err != nil { | ||
| 61 | + responseFailed(w) | ||
| 62 | + return | ||
| 63 | + } | ||
| 64 | + beego.Debug("bytePublicKey:", string(bytePublicKey)) | ||
| 65 | + // Get Authorization bytes : decode from Base64String | ||
| 66 | + byteAuthorization, err := getAuthorization(r) | ||
| 67 | + if err != nil { | ||
| 68 | + responseFailed(w) | ||
| 69 | + return | ||
| 70 | + } | ||
| 71 | + beego.Debug("byteAuthorization:", string(byteAuthorization)) | ||
| 72 | + // Get MD5 bytes from Newly Constructed Authrization String. | ||
| 73 | + byteMD5, err := getMD5FromNewAuthString(r, []byte{}) | ||
| 74 | + if err != nil { | ||
| 75 | + responseFailed(w) | ||
| 76 | + return | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + // VerifySignature and response to client | ||
| 80 | + if verifySignature(bytePublicKey, byteMD5, byteAuthorization) { | ||
| 81 | + | ||
| 82 | + // Do something you want accoding to callback_body ... | ||
| 83 | + | ||
| 84 | + // response OK : 200 | ||
| 85 | + responseSuccess(w) | ||
| 86 | + } else { | ||
| 87 | + // response FAILED : 400 | ||
| 88 | + responseFailed(w) | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +// getPublicKey : Get PublicKey bytes from Request.URL | ||
| 94 | +func getPublicKey(r *http.Request) ([]byte, error) { | ||
| 95 | + var bytePublicKey []byte | ||
| 96 | + | ||
| 97 | + // get PublicKey URL | ||
| 98 | + publicKeyURLBase64 := r.Header.Get("x-oss-pub-key-url") | ||
| 99 | + if publicKeyURLBase64 == "" { | ||
| 100 | + fmt.Println("GetPublicKey from Request header failed : No x-oss-pub-key-url field. ") | ||
| 101 | + return bytePublicKey, errors.New("no x-oss-pub-key-url field in Request header ") | ||
| 102 | + } | ||
| 103 | + publicKeyURL, _ := base64.StdEncoding.DecodeString(publicKeyURLBase64) | ||
| 104 | + // get PublicKey Content from URL | ||
| 105 | + responsePublicKeyURL, err := http.Get(string(publicKeyURL)) | ||
| 106 | + if err != nil { | ||
| 107 | + fmt.Printf("Get PublicKey Content from URL failed : %s \n", err.Error()) | ||
| 108 | + return bytePublicKey, err | ||
| 109 | + } | ||
| 110 | + defer responsePublicKeyURL.Body.Close() | ||
| 111 | + bytePublicKey, err = ioutil.ReadAll(responsePublicKeyURL.Body) | ||
| 112 | + if err != nil { | ||
| 113 | + fmt.Printf("Read PublicKey Content from URL failed : %s \n", err.Error()) | ||
| 114 | + return bytePublicKey, err | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + return bytePublicKey, nil | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +// getAuthorization : decode from Base64String | ||
| 121 | +func getAuthorization(r *http.Request) ([]byte, error) { | ||
| 122 | + var byteAuthorization []byte | ||
| 123 | + | ||
| 124 | + strAuthorizationBase64 := r.Header.Get("authorization") | ||
| 125 | + if strAuthorizationBase64 == "" { | ||
| 126 | + fmt.Println("Failed to get authorization field from request header. ") | ||
| 127 | + return byteAuthorization, errors.New("no authorization field in Request header") | ||
| 128 | + } | ||
| 129 | + byteAuthorization, _ = base64.StdEncoding.DecodeString(strAuthorizationBase64) | ||
| 130 | + | ||
| 131 | + return byteAuthorization, nil | ||
| 132 | +} | ||
| 133 | + | ||
| 134 | +// getMD5FromNewAuthString : Get MD5 bytes from Newly Constructed Authrization String. | ||
| 135 | +func getMD5FromNewAuthString(r *http.Request, requestBody []byte) ([]byte, error) { | ||
| 136 | + var byteMD5 []byte | ||
| 137 | + // Construct the New Auth String from URI+Query+Body | ||
| 138 | + _, err := ioutil.ReadAll(r.Body) | ||
| 139 | + r.Body.Close() | ||
| 140 | + if err != nil { | ||
| 141 | + fmt.Printf("Read Request Body failed : %s \n", err.Error()) | ||
| 142 | + return byteMD5, err | ||
| 143 | + } | ||
| 144 | + //strCallbackBody := string(bodyContent) | ||
| 145 | + strCallbackBody := string(requestBody) | ||
| 146 | + strURLPathDecode, errUnescape := unescapePath(r.URL.Path, encodePathSegment) | ||
| 147 | + if errUnescape != nil { | ||
| 148 | + fmt.Printf("url.PathUnescape failed : URL.Path=%s, error=%s \n", r.URL.Path, err.Error()) | ||
| 149 | + return byteMD5, errUnescape | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + // Generate New Auth String prepare for MD5 | ||
| 153 | + strAuth := "" | ||
| 154 | + if r.URL.RawQuery == "" { | ||
| 155 | + strAuth = fmt.Sprintf("%s\n%s", strURLPathDecode, strCallbackBody) | ||
| 156 | + } else { | ||
| 157 | + strAuth = fmt.Sprintf("%s?%s\n%s", strURLPathDecode, r.URL.RawQuery, strCallbackBody) | ||
| 158 | + } | ||
| 159 | + // Generate MD5 from the New Auth String | ||
| 160 | + md5Ctx := md5.New() | ||
| 161 | + md5Ctx.Write([]byte(strAuth)) | ||
| 162 | + byteMD5 = md5Ctx.Sum(nil) | ||
| 163 | + | ||
| 164 | + return byteMD5, nil | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +// verifySignature | ||
| 168 | +func verifySignature(bytePublicKey []byte, byteMd5 []byte, authorization []byte) bool { | ||
| 169 | + pubBlock, _ := pem.Decode(bytePublicKey) | ||
| 170 | + if pubBlock == nil { | ||
| 171 | + beego.Error("Failed to parse PEM block containing the public key") | ||
| 172 | + return false | ||
| 173 | + } | ||
| 174 | + pubInterface, err := x509.ParsePKIXPublicKey(pubBlock.Bytes) | ||
| 175 | + if (pubInterface == nil) || (err != nil) { | ||
| 176 | + beego.Error(fmt.Sprintf("x509.ParsePKIXPublicKey(publicKey) failed : %s \n", err.Error())) | ||
| 177 | + return false | ||
| 178 | + } | ||
| 179 | + pub := pubInterface.(*rsa.PublicKey) | ||
| 180 | + | ||
| 181 | + errorVerifyPKCS1v15 := rsa.VerifyPKCS1v15(pub, crypto.MD5, byteMd5, authorization) | ||
| 182 | + if errorVerifyPKCS1v15 != nil { | ||
| 183 | + beego.Error(fmt.Sprintf("Signature Verification is Failed : %s \n", errorVerifyPKCS1v15.Error())) | ||
| 184 | + return false | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + fmt.Printf("Signature Verification is Successful. \n") | ||
| 188 | + return true | ||
| 189 | +} | ||
| 190 | + | ||
| 191 | +// responseSuccess : Response 200 to client | ||
| 192 | +func responseSuccess(w http.ResponseWriter) { | ||
| 193 | + strResponseBody := "{\"Status\":\"OK\"}" | ||
| 194 | + w.Header().Set("Content-Type", "application/json") | ||
| 195 | + w.Header().Set("Content-Length", strconv.Itoa(len(strResponseBody))) | ||
| 196 | + w.WriteHeader(http.StatusOK) | ||
| 197 | + w.Write([]byte(strResponseBody)) | ||
| 198 | + fmt.Printf("\nPost Response : 200 OK . \n") | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +// responseFailed : Response 400 to client | ||
| 202 | +func responseFailed(w http.ResponseWriter) { | ||
| 203 | + w.WriteHeader(http.StatusBadRequest) | ||
| 204 | + fmt.Printf("\nPost Response : 400 BAD . \n") | ||
| 205 | +} | ||
| 206 | + | ||
| 207 | +func printByteArray(byteArrary []byte, arrName string) { | ||
| 208 | + fmt.Printf("printByteArray : ArrayName=%s, ArrayLength=%d \n", arrName, len(byteArrary)) | ||
| 209 | + for i := 0; i < len(byteArrary); i++ { | ||
| 210 | + fmt.Printf("%02x", byteArrary[i]) | ||
| 211 | + } | ||
| 212 | + fmt.Printf("printByteArray : End . \n") | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +// EscapeError Escape Error | ||
| 216 | +type EscapeError string | ||
| 217 | + | ||
| 218 | +func (e EscapeError) Error() string { | ||
| 219 | + return "invalid URL escape " + strconv.Quote(string(e)) | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +// InvalidHostError Invalid Host Error | ||
| 223 | +type InvalidHostError string | ||
| 224 | + | ||
| 225 | +func (e InvalidHostError) Error() string { | ||
| 226 | + return "invalid character " + strconv.Quote(string(e)) + " in host name" | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +type encoding int | ||
| 230 | + | ||
| 231 | +const ( | ||
| 232 | + encodePath encoding = 1 + iota | ||
| 233 | + encodePathSegment | ||
| 234 | + encodeHost | ||
| 235 | + encodeZone | ||
| 236 | + encodeUserPassword | ||
| 237 | + encodeQueryComponent | ||
| 238 | + encodeFragment | ||
| 239 | +) | ||
| 240 | + | ||
| 241 | +// unescapePath : unescapes a string; the mode specifies, which section of the URL string is being unescaped. | ||
| 242 | +func unescapePath(s string, mode encoding) (string, error) { | ||
| 243 | + | ||
| 244 | + // Count %, check that they're well-formed. | ||
| 245 | + mode = encodePathSegment | ||
| 246 | + n := 0 | ||
| 247 | + hasPlus := false | ||
| 248 | + for i := 0; i < len(s); { | ||
| 249 | + switch s[i] { | ||
| 250 | + case '%': | ||
| 251 | + n++ | ||
| 252 | + if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { | ||
| 253 | + s = s[i:] | ||
| 254 | + if len(s) > 3 { | ||
| 255 | + s = s[:3] | ||
| 256 | + } | ||
| 257 | + return "", EscapeError(s) | ||
| 258 | + } | ||
| 259 | + // Per https://tools.ietf.org/html/rfc3986#page-21 | ||
| 260 | + // in the host component %-encoding can only be used | ||
| 261 | + // for non-ASCII bytes. | ||
| 262 | + // But https://tools.ietf.org/html/rfc6874#section-2 | ||
| 263 | + // introduces %25 being allowed to escape a percent sign | ||
| 264 | + // in IPv6 scoped-address literals. Yay. | ||
| 265 | + if mode == encodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" { | ||
| 266 | + return "", EscapeError(s[i : i+3]) | ||
| 267 | + } | ||
| 268 | + if mode == encodeZone { | ||
| 269 | + // RFC 6874 says basically "anything goes" for zone identifiers | ||
| 270 | + // and that even non-ASCII can be redundantly escaped, | ||
| 271 | + // but it seems prudent to restrict %-escaped bytes here to those | ||
| 272 | + // that are valid host name bytes in their unescaped form. | ||
| 273 | + // That is, you can use escaping in the zone identifier but not | ||
| 274 | + // to introduce bytes you couldn't just write directly. | ||
| 275 | + // But Windows puts spaces here! Yay. | ||
| 276 | + v := unhex(s[i+1])<<4 | unhex(s[i+2]) | ||
| 277 | + if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, encodeHost) { | ||
| 278 | + return "", EscapeError(s[i : i+3]) | ||
| 279 | + } | ||
| 280 | + } | ||
| 281 | + i += 3 | ||
| 282 | + case '+': | ||
| 283 | + hasPlus = mode == encodeQueryComponent | ||
| 284 | + i++ | ||
| 285 | + default: | ||
| 286 | + if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) { | ||
| 287 | + return "", InvalidHostError(s[i : i+1]) | ||
| 288 | + } | ||
| 289 | + i++ | ||
| 290 | + } | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + if n == 0 && !hasPlus { | ||
| 294 | + return s, nil | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + t := make([]byte, len(s)-2*n) | ||
| 298 | + j := 0 | ||
| 299 | + for i := 0; i < len(s); { | ||
| 300 | + switch s[i] { | ||
| 301 | + case '%': | ||
| 302 | + t[j] = unhex(s[i+1])<<4 | unhex(s[i+2]) | ||
| 303 | + j++ | ||
| 304 | + i += 3 | ||
| 305 | + case '+': | ||
| 306 | + if mode == encodeQueryComponent { | ||
| 307 | + t[j] = ' ' | ||
| 308 | + } else { | ||
| 309 | + t[j] = '+' | ||
| 310 | + } | ||
| 311 | + j++ | ||
| 312 | + i++ | ||
| 313 | + default: | ||
| 314 | + t[j] = s[i] | ||
| 315 | + j++ | ||
| 316 | + i++ | ||
| 317 | + } | ||
| 318 | + } | ||
| 319 | + return string(t), nil | ||
| 320 | +} | ||
| 321 | + | ||
| 322 | +// Please be informed that for now shouldEscape does not check all | ||
| 323 | +// reserved characters correctly. See golang.org/issue/5684. | ||
| 324 | +func shouldEscape(c byte, mode encoding) bool { | ||
| 325 | + // §2.3 Unreserved characters (alphanum) | ||
| 326 | + if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { | ||
| 327 | + return false | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + if mode == encodeHost || mode == encodeZone { | ||
| 331 | + // §3.2.2 Host allows | ||
| 332 | + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" | ||
| 333 | + // as part of reg-name. | ||
| 334 | + // We add : because we include :port as part of host. | ||
| 335 | + // We add [ ] because we include [ipv6]:port as part of host. | ||
| 336 | + // We add < > because they're the only characters left that | ||
| 337 | + // we could possibly allow, and Parse will reject them if we | ||
| 338 | + // escape them (because hosts can't use %-encoding for | ||
| 339 | + // ASCII bytes). | ||
| 340 | + switch c { | ||
| 341 | + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"': | ||
| 342 | + return false | ||
| 343 | + } | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + switch c { | ||
| 347 | + case '-', '_', '.', '~': // §2.3 Unreserved characters (mark) | ||
| 348 | + return false | ||
| 349 | + | ||
| 350 | + case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved) | ||
| 351 | + // Different sections of the URL allow a few of | ||
| 352 | + // the reserved characters to appear unescaped. | ||
| 353 | + switch mode { | ||
| 354 | + case encodePath: // §3.3 | ||
| 355 | + // The RFC allows : @ & = + $ but saves / ; , for assigning | ||
| 356 | + // meaning to individual path segments. This package | ||
| 357 | + // only manipulates the path as a whole, so we allow those | ||
| 358 | + // last three as well. That leaves only ? to escape. | ||
| 359 | + return c == '?' | ||
| 360 | + | ||
| 361 | + case encodePathSegment: // §3.3 | ||
| 362 | + // The RFC allows : @ & = + $ but saves / ; , for assigning | ||
| 363 | + // meaning to individual path segments. | ||
| 364 | + return c == '/' || c == ';' || c == ',' || c == '?' | ||
| 365 | + | ||
| 366 | + case encodeUserPassword: // §3.2.1 | ||
| 367 | + // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in | ||
| 368 | + // userinfo, so we must escape only '@', '/', and '?'. | ||
| 369 | + // The parsing of userinfo treats ':' as special so we must escape | ||
| 370 | + // that too. | ||
| 371 | + return c == '@' || c == '/' || c == '?' || c == ':' | ||
| 372 | + | ||
| 373 | + case encodeQueryComponent: // §3.4 | ||
| 374 | + // The RFC reserves (so we must escape) everything. | ||
| 375 | + return true | ||
| 376 | + | ||
| 377 | + case encodeFragment: // §4.1 | ||
| 378 | + // The RFC text is silent but the grammar allows | ||
| 379 | + // everything, so escape nothing. | ||
| 380 | + return false | ||
| 381 | + } | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + // Everything else must be escaped. | ||
| 385 | + return true | ||
| 386 | +} | ||
| 387 | + | ||
| 388 | +func ishex(c byte) bool { | ||
| 389 | + switch { | ||
| 390 | + case '0' <= c && c <= '9': | ||
| 391 | + return true | ||
| 392 | + case 'a' <= c && c <= 'f': | ||
| 393 | + return true | ||
| 394 | + case 'A' <= c && c <= 'F': | ||
| 395 | + return true | ||
| 396 | + } | ||
| 397 | + return false | ||
| 398 | +} | ||
| 399 | + | ||
| 400 | +func unhex(c byte) byte { | ||
| 401 | + switch { | ||
| 402 | + case '0' <= c && c <= '9': | ||
| 403 | + return c - '0' | ||
| 404 | + case 'a' <= c && c <= 'f': | ||
| 405 | + return c - 'a' + 10 | ||
| 406 | + case 'A' <= c && c <= 'F': | ||
| 407 | + return c - 'A' + 10 | ||
| 408 | + } | ||
| 409 | + return 0 | ||
| 410 | +} |
-
请 注册 或 登录 后发表评论