作者 yangfu

Merge branch 'dev' of http://gitlab.fjmaimaimai.com/mmm-go/openapi into dev

  1 +package v2
  2 +
  3 +import (
  4 + "github.com/astaxie/beego"
  5 +)
  6 +
  7 +type BaseController struct {
  8 + beego.Controller
  9 +}
  10 +
  11 +func (base *BaseController) Prepare() {
  12 +
  13 +}
  14 +
  15 +func (base *BaseController) Finish() {
  16 +
  17 +}
@@ -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 +}
  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 +}
  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 +}
  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 +// }
  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 +}
  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 +}