2016-04-03 17:59:27 +00:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2016-04-08 16:06:26 +00:00
|
|
|
"encoding/json"
|
2016-06-16 11:49:35 +00:00
|
|
|
"fmt"
|
2017-02-02 14:13:17 +00:00
|
|
|
"reflect"
|
2016-06-13 19:17:43 +00:00
|
|
|
"regexp"
|
2017-06-17 16:11:10 +00:00
|
|
|
"strings"
|
2017-02-02 12:40:28 +00:00
|
|
|
"unsafe"
|
2016-04-03 17:59:27 +00:00
|
|
|
|
2017-02-02 12:40:28 +00:00
|
|
|
"github.com/valyala/fasthttp"
|
2019-02-25 21:04:55 +00:00
|
|
|
"github.com/osuyozora/api/common"
|
2016-04-03 17:59:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Method wraps an API method to a HandlerFunc.
|
2017-02-02 12:40:28 +00:00
|
|
|
func Method(f func(md common.MethodData) common.CodeMessager, privilegesNeeded ...int) fasthttp.RequestHandler {
|
|
|
|
return func(c *fasthttp.RequestCtx) {
|
2016-05-15 11:57:04 +00:00
|
|
|
initialCaretaker(c, f, privilegesNeeded...)
|
2016-04-08 15:27:55 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-03 17:59:27 +00:00
|
|
|
|
2017-02-02 12:40:28 +00:00
|
|
|
func initialCaretaker(c *fasthttp.RequestCtx, f func(md common.MethodData) common.CodeMessager, privilegesNeeded ...int) {
|
2016-10-02 18:07:38 +00:00
|
|
|
var doggoTags []string
|
|
|
|
|
2017-02-02 12:40:28 +00:00
|
|
|
qa := c.Request.URI().QueryArgs()
|
|
|
|
var token string
|
2017-06-17 16:11:10 +00:00
|
|
|
var bearerToken bool
|
2016-04-08 15:27:55 +00:00
|
|
|
switch {
|
2017-02-02 12:40:28 +00:00
|
|
|
case len(c.Request.Header.Peek("X-Ripple-Token")) > 0:
|
|
|
|
token = string(c.Request.Header.Peek("X-Ripple-Token"))
|
2017-06-17 16:11:10 +00:00
|
|
|
case strings.HasPrefix(string(c.Request.Header.Peek("Authorization")), "Bearer "):
|
|
|
|
token = strings.TrimPrefix(string(c.Request.Header.Peek("Authorization")), "Bearer ")
|
|
|
|
bearerToken = true
|
2017-02-02 12:40:28 +00:00
|
|
|
case len(qa.Peek("token")) > 0:
|
|
|
|
token = string(qa.Peek("token"))
|
|
|
|
case len(qa.Peek("k")) > 0:
|
|
|
|
token = string(qa.Peek("k"))
|
2016-06-08 16:55:48 +00:00
|
|
|
default:
|
2017-02-02 12:40:28 +00:00
|
|
|
token = string(c.Request.Header.Cookie("rt"))
|
2016-04-08 15:27:55 +00:00
|
|
|
}
|
2016-04-03 17:59:27 +00:00
|
|
|
|
2016-04-08 15:27:55 +00:00
|
|
|
md := common.MethodData{
|
2017-02-02 12:40:28 +00:00
|
|
|
DB: db,
|
|
|
|
Ctx: c,
|
|
|
|
Doggo: doggo,
|
|
|
|
R: red,
|
2016-04-08 15:27:55 +00:00
|
|
|
}
|
|
|
|
if token != "" {
|
2017-06-17 16:11:10 +00:00
|
|
|
var (
|
|
|
|
tokenReal common.Token
|
|
|
|
exists bool
|
|
|
|
)
|
|
|
|
if bearerToken {
|
|
|
|
tokenReal, exists = BearerToken(token, db)
|
|
|
|
} else {
|
|
|
|
tokenReal, exists = GetTokenFull(token, db)
|
|
|
|
}
|
2016-04-08 15:27:55 +00:00
|
|
|
if exists {
|
|
|
|
md.User = tokenReal
|
2016-10-02 18:07:38 +00:00
|
|
|
doggoTags = append(doggoTags, "authorised")
|
2016-04-03 17:59:27 +00:00
|
|
|
}
|
2016-04-08 15:27:55 +00:00
|
|
|
}
|
2016-04-03 17:59:27 +00:00
|
|
|
|
2017-02-02 12:40:28 +00:00
|
|
|
// log into datadog that this is an hanayo request
|
|
|
|
if b2s(c.Request.Header.Peek("H-Key")) == cf.HanayoKey && b2s(c.UserAgent()) == "hanayo" {
|
2016-10-02 18:07:38 +00:00
|
|
|
doggoTags = append(doggoTags, "hanayo")
|
2016-07-12 13:49:02 +00:00
|
|
|
}
|
2016-07-06 14:33:58 +00:00
|
|
|
|
2016-10-02 19:59:04 +00:00
|
|
|
doggo.Incr("requests.v1", doggoTags, 1)
|
2016-10-02 18:07:38 +00:00
|
|
|
|
2016-04-08 15:27:55 +00:00
|
|
|
missingPrivileges := 0
|
|
|
|
for _, privilege := range privilegesNeeded {
|
2016-08-27 10:04:12 +00:00
|
|
|
if uint64(md.User.TokenPrivileges)&uint64(privilege) == 0 {
|
2016-04-08 15:27:55 +00:00
|
|
|
missingPrivileges |= privilege
|
2016-04-03 17:59:27 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-08 15:27:55 +00:00
|
|
|
if missingPrivileges != 0 {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.SetStatusCode(401)
|
|
|
|
mkjson(c, common.SimpleResponse(401, "You don't have the privilege(s): "+common.Privileges(missingPrivileges).String()+"."))
|
2016-04-08 15:27:55 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
resp := f(md)
|
2016-08-27 10:04:12 +00:00
|
|
|
if md.HasQuery("pls200") {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.SetStatusCode(200)
|
2016-04-08 16:06:26 +00:00
|
|
|
} else {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.SetStatusCode(resp.GetCode())
|
2016-04-08 16:06:26 +00:00
|
|
|
}
|
|
|
|
|
2016-08-27 10:04:12 +00:00
|
|
|
if md.HasQuery("callback") {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.Response.Header.Add("Content-Type", "application/javascript; charset=utf-8")
|
2016-04-08 15:27:55 +00:00
|
|
|
} else {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.Response.Header.Add("Content-Type", "application/json; charset=utf-8")
|
2016-04-08 16:06:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mkjson(c, resp)
|
|
|
|
}
|
|
|
|
|
2016-06-13 19:17:43 +00:00
|
|
|
// Very restrictive, but this way it shouldn't completely fuck up.
|
|
|
|
var callbackJSONP = regexp.MustCompile(`^[a-zA-Z_\$][a-zA-Z0-9_\$]*$`)
|
|
|
|
|
2016-04-08 16:06:26 +00:00
|
|
|
// mkjson auto indents json, and wraps json into a jsonp callback if specified by the request.
|
2017-02-02 12:40:28 +00:00
|
|
|
// then writes to the RequestCtx the data.
|
|
|
|
func mkjson(c *fasthttp.RequestCtx, data interface{}) {
|
2016-04-08 16:06:26 +00:00
|
|
|
exported, err := json.MarshalIndent(data, "", "\t")
|
|
|
|
if err != nil {
|
2017-02-02 12:40:28 +00:00
|
|
|
fmt.Println(err)
|
2016-07-06 14:33:58 +00:00
|
|
|
exported = []byte(`{ "code": 500, "message": "something has gone really really really really really really wrong." }`)
|
2016-04-08 16:06:26 +00:00
|
|
|
}
|
2017-02-02 12:40:28 +00:00
|
|
|
cb := string(c.URI().QueryArgs().Peek("callback"))
|
2016-06-13 19:17:43 +00:00
|
|
|
willcb := cb != "" &&
|
|
|
|
len(cb) < 100 &&
|
|
|
|
callbackJSONP.MatchString(cb)
|
2016-04-08 16:06:26 +00:00
|
|
|
if willcb {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.Write([]byte("/**/ typeof " + cb + " === 'function' && " + cb + "("))
|
2016-04-08 16:06:26 +00:00
|
|
|
}
|
2017-02-02 12:40:28 +00:00
|
|
|
c.Write(exported)
|
2016-04-08 16:06:26 +00:00
|
|
|
if willcb {
|
2017-02-02 12:40:28 +00:00
|
|
|
c.Write([]byte(");"))
|
2016-04-08 15:27:55 +00:00
|
|
|
}
|
2016-04-03 17:59:27 +00:00
|
|
|
}
|
2017-02-02 12:40:28 +00:00
|
|
|
|
|
|
|
// b2s converts byte slice to a string without memory allocation.
|
|
|
|
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
|
|
|
|
//
|
|
|
|
// Note it may break if string and/or slice header will change
|
|
|
|
// in the future go versions.
|
|
|
|
func b2s(b []byte) string {
|
|
|
|
return *(*string)(unsafe.Pointer(&b))
|
|
|
|
}
|
2017-02-02 14:13:17 +00:00
|
|
|
|
|
|
|
// s2b converts string to a byte slice without memory allocation.
|
|
|
|
//
|
|
|
|
// Note it may break if string and/or slice header will change
|
|
|
|
// in the future go versions.
|
|
|
|
func s2b(s string) []byte {
|
|
|
|
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
|
|
|
bh := reflect.SliceHeader{
|
|
|
|
Data: sh.Data,
|
|
|
|
Len: sh.Len,
|
|
|
|
Cap: sh.Len,
|
|
|
|
}
|
|
|
|
return *(*[]byte)(unsafe.Pointer(&bh))
|
|
|
|
}
|