github.com/astaxie/beego@v1.12.3/context/input.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package context 16 17 import ( 18 "bytes" 19 "compress/gzip" 20 "errors" 21 "io" 22 "io/ioutil" 23 "net" 24 "net/http" 25 "net/url" 26 "reflect" 27 "regexp" 28 "strconv" 29 "strings" 30 "sync" 31 32 "github.com/astaxie/beego/session" 33 ) 34 35 // Regexes for checking the accept headers 36 // TODO make sure these are correct 37 var ( 38 acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`) 39 acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`) 40 acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`) 41 acceptsYAMLRegex = regexp.MustCompile(`(application/x-yaml)(?:,|$)`) 42 maxParam = 50 43 ) 44 45 // BeegoInput operates the http request header, data, cookie and body. 46 // it also contains router params and current session. 47 type BeegoInput struct { 48 Context *Context 49 CruSession session.Store 50 pnames []string 51 pvalues []string 52 data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. 53 dataLock sync.RWMutex 54 RequestBody []byte 55 RunMethod string 56 RunController reflect.Type 57 } 58 59 // NewInput return BeegoInput generated by Context. 60 func NewInput() *BeegoInput { 61 return &BeegoInput{ 62 pnames: make([]string, 0, maxParam), 63 pvalues: make([]string, 0, maxParam), 64 data: make(map[interface{}]interface{}), 65 } 66 } 67 68 // Reset init the BeegoInput 69 func (input *BeegoInput) Reset(ctx *Context) { 70 input.Context = ctx 71 input.CruSession = nil 72 input.pnames = input.pnames[:0] 73 input.pvalues = input.pvalues[:0] 74 input.dataLock.Lock() 75 input.data = nil 76 input.dataLock.Unlock() 77 input.RequestBody = []byte{} 78 } 79 80 // Protocol returns request protocol name, such as HTTP/1.1 . 81 func (input *BeegoInput) Protocol() string { 82 return input.Context.Request.Proto 83 } 84 85 // URI returns full request url with query string, fragment. 86 func (input *BeegoInput) URI() string { 87 return input.Context.Request.RequestURI 88 } 89 90 // URL returns request url path (without query string, fragment). 91 func (input *BeegoInput) URL() string { 92 return input.Context.Request.URL.EscapedPath() 93 } 94 95 // Site returns base site url as scheme://domain type. 96 func (input *BeegoInput) Site() string { 97 return input.Scheme() + "://" + input.Domain() 98 } 99 100 // Scheme returns request scheme as "http" or "https". 101 func (input *BeegoInput) Scheme() string { 102 if scheme := input.Header("X-Forwarded-Proto"); scheme != "" { 103 return scheme 104 } 105 if input.Context.Request.URL.Scheme != "" { 106 return input.Context.Request.URL.Scheme 107 } 108 if input.Context.Request.TLS == nil { 109 return "http" 110 } 111 return "https" 112 } 113 114 // Domain returns host name. 115 // Alias of Host method. 116 func (input *BeegoInput) Domain() string { 117 return input.Host() 118 } 119 120 // Host returns host name. 121 // if no host info in request, return localhost. 122 func (input *BeegoInput) Host() string { 123 if input.Context.Request.Host != "" { 124 if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil { 125 return hostPart 126 } 127 return input.Context.Request.Host 128 } 129 return "localhost" 130 } 131 132 // Method returns http request method. 133 func (input *BeegoInput) Method() string { 134 return input.Context.Request.Method 135 } 136 137 // Is returns boolean of this request is on given method, such as Is("POST"). 138 func (input *BeegoInput) Is(method string) bool { 139 return input.Method() == method 140 } 141 142 // IsGet Is this a GET method request? 143 func (input *BeegoInput) IsGet() bool { 144 return input.Is("GET") 145 } 146 147 // IsPost Is this a POST method request? 148 func (input *BeegoInput) IsPost() bool { 149 return input.Is("POST") 150 } 151 152 // IsHead Is this a Head method request? 153 func (input *BeegoInput) IsHead() bool { 154 return input.Is("HEAD") 155 } 156 157 // IsOptions Is this a OPTIONS method request? 158 func (input *BeegoInput) IsOptions() bool { 159 return input.Is("OPTIONS") 160 } 161 162 // IsPut Is this a PUT method request? 163 func (input *BeegoInput) IsPut() bool { 164 return input.Is("PUT") 165 } 166 167 // IsDelete Is this a DELETE method request? 168 func (input *BeegoInput) IsDelete() bool { 169 return input.Is("DELETE") 170 } 171 172 // IsPatch Is this a PATCH method request? 173 func (input *BeegoInput) IsPatch() bool { 174 return input.Is("PATCH") 175 } 176 177 // IsAjax returns boolean of this request is generated by ajax. 178 func (input *BeegoInput) IsAjax() bool { 179 return input.Header("X-Requested-With") == "XMLHttpRequest" 180 } 181 182 // IsSecure returns boolean of this request is in https. 183 func (input *BeegoInput) IsSecure() bool { 184 return input.Scheme() == "https" 185 } 186 187 // IsWebsocket returns boolean of this request is in webSocket. 188 func (input *BeegoInput) IsWebsocket() bool { 189 return input.Header("Upgrade") == "websocket" 190 } 191 192 // IsUpload returns boolean of whether file uploads in this request or not.. 193 func (input *BeegoInput) IsUpload() bool { 194 return strings.Contains(input.Header("Content-Type"), "multipart/form-data") 195 } 196 197 // AcceptsHTML Checks if request accepts html response 198 func (input *BeegoInput) AcceptsHTML() bool { 199 return acceptsHTMLRegex.MatchString(input.Header("Accept")) 200 } 201 202 // AcceptsXML Checks if request accepts xml response 203 func (input *BeegoInput) AcceptsXML() bool { 204 return acceptsXMLRegex.MatchString(input.Header("Accept")) 205 } 206 207 // AcceptsJSON Checks if request accepts json response 208 func (input *BeegoInput) AcceptsJSON() bool { 209 return acceptsJSONRegex.MatchString(input.Header("Accept")) 210 } 211 212 // AcceptsYAML Checks if request accepts json response 213 func (input *BeegoInput) AcceptsYAML() bool { 214 return acceptsYAMLRegex.MatchString(input.Header("Accept")) 215 } 216 217 // IP returns request client ip. 218 // if in proxy, return first proxy id. 219 // if error, return RemoteAddr. 220 func (input *BeegoInput) IP() string { 221 ips := input.Proxy() 222 if len(ips) > 0 && ips[0] != "" { 223 rip, _, err := net.SplitHostPort(ips[0]) 224 if err != nil { 225 rip = ips[0] 226 } 227 return rip 228 } 229 if ip, _, err := net.SplitHostPort(input.Context.Request.RemoteAddr); err == nil { 230 return ip 231 } 232 return input.Context.Request.RemoteAddr 233 } 234 235 // Proxy returns proxy client ips slice. 236 func (input *BeegoInput) Proxy() []string { 237 if ips := input.Header("X-Forwarded-For"); ips != "" { 238 return strings.Split(ips, ",") 239 } 240 return []string{} 241 } 242 243 // Referer returns http referer header. 244 func (input *BeegoInput) Referer() string { 245 return input.Header("Referer") 246 } 247 248 // Refer returns http referer header. 249 func (input *BeegoInput) Refer() string { 250 return input.Referer() 251 } 252 253 // SubDomains returns sub domain string. 254 // if aa.bb.domain.com, returns aa.bb . 255 func (input *BeegoInput) SubDomains() string { 256 parts := strings.Split(input.Host(), ".") 257 if len(parts) >= 3 { 258 return strings.Join(parts[:len(parts)-2], ".") 259 } 260 return "" 261 } 262 263 // Port returns request client port. 264 // when error or empty, return 80. 265 func (input *BeegoInput) Port() int { 266 if _, portPart, err := net.SplitHostPort(input.Context.Request.Host); err == nil { 267 port, _ := strconv.Atoi(portPart) 268 return port 269 } 270 return 80 271 } 272 273 // UserAgent returns request client user agent string. 274 func (input *BeegoInput) UserAgent() string { 275 return input.Header("User-Agent") 276 } 277 278 // ParamsLen return the length of the params 279 func (input *BeegoInput) ParamsLen() int { 280 return len(input.pnames) 281 } 282 283 // Param returns router param by a given key. 284 func (input *BeegoInput) Param(key string) string { 285 for i, v := range input.pnames { 286 if v == key && i <= len(input.pvalues) { 287 // we cannot use url.PathEscape(input.pvalues[i]) 288 // for example, if the value is /a/b 289 // after url.PathEscape(input.pvalues[i]), the value is %2Fa%2Fb 290 // However, the value is used in ControllerRegister.ServeHTTP 291 // and split by "/", so function crash... 292 return input.pvalues[i] 293 } 294 } 295 return "" 296 } 297 298 // Params returns the map[key]value. 299 func (input *BeegoInput) Params() map[string]string { 300 m := make(map[string]string) 301 for i, v := range input.pnames { 302 if i <= len(input.pvalues) { 303 m[v] = input.pvalues[i] 304 } 305 } 306 return m 307 } 308 309 // SetParam will set the param with key and value 310 func (input *BeegoInput) SetParam(key, val string) { 311 // check if already exists 312 for i, v := range input.pnames { 313 if v == key && i <= len(input.pvalues) { 314 input.pvalues[i] = val 315 return 316 } 317 } 318 input.pvalues = append(input.pvalues, val) 319 input.pnames = append(input.pnames, key) 320 } 321 322 // ResetParams clears any of the input's Params 323 // This function is used to clear parameters so they may be reset between filter 324 // passes. 325 func (input *BeegoInput) ResetParams() { 326 input.pnames = input.pnames[:0] 327 input.pvalues = input.pvalues[:0] 328 } 329 330 // Query returns input data item string by a given string. 331 func (input *BeegoInput) Query(key string) string { 332 if val := input.Param(key); val != "" { 333 return val 334 } 335 if input.Context.Request.Form == nil { 336 input.dataLock.Lock() 337 if input.Context.Request.Form == nil { 338 input.Context.Request.ParseForm() 339 } 340 input.dataLock.Unlock() 341 } 342 input.dataLock.RLock() 343 defer input.dataLock.RUnlock() 344 return input.Context.Request.Form.Get(key) 345 } 346 347 // Header returns request header item string by a given string. 348 // if non-existed, return empty string. 349 func (input *BeegoInput) Header(key string) string { 350 return input.Context.Request.Header.Get(key) 351 } 352 353 // Cookie returns request cookie item string by a given key. 354 // if non-existed, return empty string. 355 func (input *BeegoInput) Cookie(key string) string { 356 ck, err := input.Context.Request.Cookie(key) 357 if err != nil { 358 return "" 359 } 360 return ck.Value 361 } 362 363 // Session returns current session item value by a given key. 364 // if non-existed, return nil. 365 func (input *BeegoInput) Session(key interface{}) interface{} { 366 return input.CruSession.Get(key) 367 } 368 369 // CopyBody returns the raw request body data as bytes. 370 func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { 371 if input.Context.Request.Body == nil { 372 return []byte{} 373 } 374 375 var requestbody []byte 376 safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory} 377 if input.Header("Content-Encoding") == "gzip" { 378 reader, err := gzip.NewReader(safe) 379 if err != nil { 380 return nil 381 } 382 requestbody, _ = ioutil.ReadAll(reader) 383 } else { 384 requestbody, _ = ioutil.ReadAll(safe) 385 } 386 387 input.Context.Request.Body.Close() 388 bf := bytes.NewBuffer(requestbody) 389 input.Context.Request.Body = http.MaxBytesReader(input.Context.ResponseWriter, ioutil.NopCloser(bf), MaxMemory) 390 input.RequestBody = requestbody 391 return requestbody 392 } 393 394 // Data return the implicit data in the input 395 func (input *BeegoInput) Data() map[interface{}]interface{} { 396 input.dataLock.Lock() 397 defer input.dataLock.Unlock() 398 if input.data == nil { 399 input.data = make(map[interface{}]interface{}) 400 } 401 return input.data 402 } 403 404 // GetData returns the stored data in this context. 405 func (input *BeegoInput) GetData(key interface{}) interface{} { 406 input.dataLock.Lock() 407 defer input.dataLock.Unlock() 408 if v, ok := input.data[key]; ok { 409 return v 410 } 411 return nil 412 } 413 414 // SetData stores data with given key in this context. 415 // This data are only available in this context. 416 func (input *BeegoInput) SetData(key, val interface{}) { 417 input.dataLock.Lock() 418 defer input.dataLock.Unlock() 419 if input.data == nil { 420 input.data = make(map[interface{}]interface{}) 421 } 422 input.data[key] = val 423 } 424 425 // ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type 426 func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { 427 // Parse the body depending on the content type. 428 if strings.Contains(input.Header("Content-Type"), "multipart/form-data") { 429 if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil { 430 return errors.New("Error parsing request body:" + err.Error()) 431 } 432 } else if err := input.Context.Request.ParseForm(); err != nil { 433 return errors.New("Error parsing request body:" + err.Error()) 434 } 435 return nil 436 } 437 438 // Bind data from request.Form[key] to dest 439 // like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie 440 // var id int beegoInput.Bind(&id, "id") id ==123 441 // var isok bool beegoInput.Bind(&isok, "isok") isok ==true 442 // var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2 443 // ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2] 444 // ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array] 445 // user struct{Name} beegoInput.Bind(&user, "user") user == {Name:"astaxie"} 446 func (input *BeegoInput) Bind(dest interface{}, key string) error { 447 value := reflect.ValueOf(dest) 448 if value.Kind() != reflect.Ptr { 449 return errors.New("beego: non-pointer passed to Bind: " + key) 450 } 451 value = value.Elem() 452 if !value.CanSet() { 453 return errors.New("beego: non-settable variable passed to Bind: " + key) 454 } 455 typ := value.Type() 456 // Get real type if dest define with interface{}. 457 // e.g var dest interface{} dest=1.0 458 if value.Kind() == reflect.Interface { 459 typ = value.Elem().Type() 460 } 461 rv := input.bind(key, typ) 462 if !rv.IsValid() { 463 return errors.New("beego: reflect value is empty") 464 } 465 value.Set(rv) 466 return nil 467 } 468 469 func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value { 470 if input.Context.Request.Form == nil { 471 input.Context.Request.ParseForm() 472 } 473 rv := reflect.Zero(typ) 474 switch typ.Kind() { 475 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 476 val := input.Query(key) 477 if len(val) == 0 { 478 return rv 479 } 480 rv = input.bindInt(val, typ) 481 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 482 val := input.Query(key) 483 if len(val) == 0 { 484 return rv 485 } 486 rv = input.bindUint(val, typ) 487 case reflect.Float32, reflect.Float64: 488 val := input.Query(key) 489 if len(val) == 0 { 490 return rv 491 } 492 rv = input.bindFloat(val, typ) 493 case reflect.String: 494 val := input.Query(key) 495 if len(val) == 0 { 496 return rv 497 } 498 rv = input.bindString(val, typ) 499 case reflect.Bool: 500 val := input.Query(key) 501 if len(val) == 0 { 502 return rv 503 } 504 rv = input.bindBool(val, typ) 505 case reflect.Slice: 506 rv = input.bindSlice(&input.Context.Request.Form, key, typ) 507 case reflect.Struct: 508 rv = input.bindStruct(&input.Context.Request.Form, key, typ) 509 case reflect.Ptr: 510 rv = input.bindPoint(key, typ) 511 case reflect.Map: 512 rv = input.bindMap(&input.Context.Request.Form, key, typ) 513 } 514 return rv 515 } 516 517 func (input *BeegoInput) bindValue(val string, typ reflect.Type) reflect.Value { 518 rv := reflect.Zero(typ) 519 switch typ.Kind() { 520 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 521 rv = input.bindInt(val, typ) 522 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 523 rv = input.bindUint(val, typ) 524 case reflect.Float32, reflect.Float64: 525 rv = input.bindFloat(val, typ) 526 case reflect.String: 527 rv = input.bindString(val, typ) 528 case reflect.Bool: 529 rv = input.bindBool(val, typ) 530 case reflect.Slice: 531 rv = input.bindSlice(&url.Values{"": {val}}, "", typ) 532 case reflect.Struct: 533 rv = input.bindStruct(&url.Values{"": {val}}, "", typ) 534 case reflect.Ptr: 535 rv = input.bindPoint(val, typ) 536 case reflect.Map: 537 rv = input.bindMap(&url.Values{"": {val}}, "", typ) 538 } 539 return rv 540 } 541 542 func (input *BeegoInput) bindInt(val string, typ reflect.Type) reflect.Value { 543 intValue, err := strconv.ParseInt(val, 10, 64) 544 if err != nil { 545 return reflect.Zero(typ) 546 } 547 pValue := reflect.New(typ) 548 pValue.Elem().SetInt(intValue) 549 return pValue.Elem() 550 } 551 552 func (input *BeegoInput) bindUint(val string, typ reflect.Type) reflect.Value { 553 uintValue, err := strconv.ParseUint(val, 10, 64) 554 if err != nil { 555 return reflect.Zero(typ) 556 } 557 pValue := reflect.New(typ) 558 pValue.Elem().SetUint(uintValue) 559 return pValue.Elem() 560 } 561 562 func (input *BeegoInput) bindFloat(val string, typ reflect.Type) reflect.Value { 563 floatValue, err := strconv.ParseFloat(val, 64) 564 if err != nil { 565 return reflect.Zero(typ) 566 } 567 pValue := reflect.New(typ) 568 pValue.Elem().SetFloat(floatValue) 569 return pValue.Elem() 570 } 571 572 func (input *BeegoInput) bindString(val string, typ reflect.Type) reflect.Value { 573 return reflect.ValueOf(val) 574 } 575 576 func (input *BeegoInput) bindBool(val string, typ reflect.Type) reflect.Value { 577 val = strings.TrimSpace(strings.ToLower(val)) 578 switch val { 579 case "true", "on", "1": 580 return reflect.ValueOf(true) 581 } 582 return reflect.ValueOf(false) 583 } 584 585 type sliceValue struct { 586 index int // Index extracted from brackets. If -1, no index was provided. 587 value reflect.Value // the bound value for this slice element. 588 } 589 590 func (input *BeegoInput) bindSlice(params *url.Values, key string, typ reflect.Type) reflect.Value { 591 maxIndex := -1 592 numNoIndex := 0 593 sliceValues := []sliceValue{} 594 for reqKey, vals := range *params { 595 if !strings.HasPrefix(reqKey, key+"[") { 596 continue 597 } 598 // Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey) 599 index := -1 600 leftBracket, rightBracket := len(key), strings.Index(reqKey[len(key):], "]")+len(key) 601 if rightBracket > leftBracket+1 { 602 index, _ = strconv.Atoi(reqKey[leftBracket+1 : rightBracket]) 603 } 604 subKeyIndex := rightBracket + 1 605 606 // Handle the indexed case. 607 if index > -1 { 608 if index > maxIndex { 609 maxIndex = index 610 } 611 sliceValues = append(sliceValues, sliceValue{ 612 index: index, 613 value: input.bind(reqKey[:subKeyIndex], typ.Elem()), 614 }) 615 continue 616 } 617 618 // It's an un-indexed element. (e.g. element[]) 619 numNoIndex += len(vals) 620 for _, val := range vals { 621 // Unindexed values can only be direct-bound. 622 sliceValues = append(sliceValues, sliceValue{ 623 index: -1, 624 value: input.bindValue(val, typ.Elem()), 625 }) 626 } 627 } 628 resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex) 629 for _, sv := range sliceValues { 630 if sv.index != -1 { 631 resultArray.Index(sv.index).Set(sv.value) 632 } else { 633 resultArray = reflect.Append(resultArray, sv.value) 634 } 635 } 636 return resultArray 637 } 638 639 func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.Type) reflect.Value { 640 result := reflect.New(typ).Elem() 641 fieldValues := make(map[string]reflect.Value) 642 for reqKey, val := range *params { 643 var fieldName string 644 if strings.HasPrefix(reqKey, key+".") { 645 fieldName = reqKey[len(key)+1:] 646 } else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' { 647 fieldName = reqKey[len(key)+1 : len(reqKey)-1] 648 } else { 649 continue 650 } 651 652 if _, ok := fieldValues[fieldName]; !ok { 653 // Time to bind this field. Get it and make sure we can set it. 654 fieldValue := result.FieldByName(fieldName) 655 if !fieldValue.IsValid() { 656 continue 657 } 658 if !fieldValue.CanSet() { 659 continue 660 } 661 boundVal := input.bindValue(val[0], fieldValue.Type()) 662 fieldValue.Set(boundVal) 663 fieldValues[fieldName] = boundVal 664 } 665 } 666 667 return result 668 } 669 670 func (input *BeegoInput) bindPoint(key string, typ reflect.Type) reflect.Value { 671 return input.bind(key, typ.Elem()).Addr() 672 } 673 674 func (input *BeegoInput) bindMap(params *url.Values, key string, typ reflect.Type) reflect.Value { 675 var ( 676 result = reflect.MakeMap(typ) 677 keyType = typ.Key() 678 valueType = typ.Elem() 679 ) 680 for paramName, values := range *params { 681 if !strings.HasPrefix(paramName, key+"[") || paramName[len(paramName)-1] != ']' { 682 continue 683 } 684 685 key := paramName[len(key)+1 : len(paramName)-1] 686 result.SetMapIndex(input.bindValue(key, keyType), input.bindValue(values[0], valueType)) 687 } 688 return result 689 }