You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
14KB

  1. // Copyright 2014 The Macaron Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // 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, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package macaron
  15. import (
  16. "crypto/sha256"
  17. "encoding/hex"
  18. "html/template"
  19. "io"
  20. "io/ioutil"
  21. "mime/multipart"
  22. "net/http"
  23. "net/url"
  24. "os"
  25. "path"
  26. "path/filepath"
  27. "reflect"
  28. "strconv"
  29. "strings"
  30. "time"
  31. "gitea.com/macaron/inject"
  32. "github.com/unknwon/com"
  33. "golang.org/x/crypto/pbkdf2"
  34. )
  35. // Locale reprents a localization interface.
  36. type Locale interface {
  37. Language() string
  38. Tr(string, ...interface{}) string
  39. }
  40. // RequestBody represents a request body.
  41. type RequestBody struct {
  42. reader io.ReadCloser
  43. }
  44. // Bytes reads and returns content of request body in bytes.
  45. func (rb *RequestBody) Bytes() ([]byte, error) {
  46. return ioutil.ReadAll(rb.reader)
  47. }
  48. // String reads and returns content of request body in string.
  49. func (rb *RequestBody) String() (string, error) {
  50. data, err := rb.Bytes()
  51. return string(data), err
  52. }
  53. // ReadCloser returns a ReadCloser for request body.
  54. func (rb *RequestBody) ReadCloser() io.ReadCloser {
  55. return rb.reader
  56. }
  57. // Request represents an HTTP request received by a server or to be sent by a client.
  58. type Request struct {
  59. *http.Request
  60. }
  61. func (r *Request) Body() *RequestBody {
  62. return &RequestBody{r.Request.Body}
  63. }
  64. // ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context).
  65. type ContextInvoker func(ctx *Context)
  66. func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
  67. invoke(params[0].(*Context))
  68. return nil, nil
  69. }
  70. // Context represents the runtime context of current request of Macaron instance.
  71. // It is the integration of most frequently used middlewares and helper methods.
  72. type Context struct {
  73. inject.Injector
  74. handlers []Handler
  75. action Handler
  76. index int
  77. *Router
  78. Req Request
  79. Resp ResponseWriter
  80. params Params
  81. Render
  82. Locale
  83. Data map[string]interface{}
  84. }
  85. func (c *Context) handler() Handler {
  86. if c.index < len(c.handlers) {
  87. return c.handlers[c.index]
  88. }
  89. if c.index == len(c.handlers) {
  90. return c.action
  91. }
  92. panic("invalid index for context handler")
  93. }
  94. func (c *Context) Next() {
  95. c.index += 1
  96. c.run()
  97. }
  98. func (c *Context) Written() bool {
  99. return c.Resp.Written()
  100. }
  101. func (c *Context) run() {
  102. for c.index <= len(c.handlers) {
  103. vals, err := c.Invoke(c.handler())
  104. if err != nil {
  105. panic(err)
  106. }
  107. c.index += 1
  108. // if the handler returned something, write it to the http response
  109. if len(vals) > 0 {
  110. ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil)))
  111. handleReturn := ev.Interface().(ReturnHandler)
  112. handleReturn(c, vals)
  113. }
  114. if c.Written() {
  115. return
  116. }
  117. }
  118. }
  119. // RemoteAddr returns more real IP address.
  120. func (ctx *Context) RemoteAddr() string {
  121. addr := ctx.Req.Header.Get("X-Real-IP")
  122. if len(addr) == 0 {
  123. addr = ctx.Req.Header.Get("X-Forwarded-For")
  124. if addr == "" {
  125. addr = ctx.Req.RemoteAddr
  126. if i := strings.LastIndex(addr, ":"); i > -1 {
  127. addr = addr[:i]
  128. }
  129. }
  130. }
  131. return addr
  132. }
  133. func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {
  134. if len(data) <= 0 {
  135. ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)
  136. } else if len(data) == 1 {
  137. ctx.Render.HTMLSet(status, setName, tplName, data[0])
  138. } else {
  139. ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))
  140. }
  141. }
  142. // HTML renders the HTML with default template set.
  143. func (ctx *Context) HTML(status int, name string, data ...interface{}) {
  144. ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...)
  145. }
  146. // HTMLSet renders the HTML with given template set name.
  147. func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) {
  148. ctx.renderHTML(status, setName, tplName, data...)
  149. }
  150. func (ctx *Context) Redirect(location string, status ...int) {
  151. code := http.StatusFound
  152. if len(status) == 1 {
  153. code = status[0]
  154. }
  155. http.Redirect(ctx.Resp, ctx.Req.Request, location, code)
  156. }
  157. // Maximum amount of memory to use when parsing a multipart form.
  158. // Set this to whatever value you prefer; default is 10 MB.
  159. var MaxMemory = int64(1024 * 1024 * 10)
  160. func (ctx *Context) parseForm() {
  161. if ctx.Req.Form != nil {
  162. return
  163. }
  164. contentType := ctx.Req.Header.Get(_CONTENT_TYPE)
  165. if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&
  166. len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {
  167. ctx.Req.ParseMultipartForm(MaxMemory)
  168. } else {
  169. ctx.Req.ParseForm()
  170. }
  171. }
  172. // Query querys form parameter.
  173. func (ctx *Context) Query(name string) string {
  174. ctx.parseForm()
  175. return ctx.Req.Form.Get(name)
  176. }
  177. // QueryTrim querys and trims spaces form parameter.
  178. func (ctx *Context) QueryTrim(name string) string {
  179. return strings.TrimSpace(ctx.Query(name))
  180. }
  181. // QueryStrings returns a list of results by given query name.
  182. func (ctx *Context) QueryStrings(name string) []string {
  183. ctx.parseForm()
  184. vals, ok := ctx.Req.Form[name]
  185. if !ok {
  186. return []string{}
  187. }
  188. return vals
  189. }
  190. // QueryEscape returns escapred query result.
  191. func (ctx *Context) QueryEscape(name string) string {
  192. return template.HTMLEscapeString(ctx.Query(name))
  193. }
  194. // QueryBool returns query result in bool type.
  195. func (ctx *Context) QueryBool(name string) bool {
  196. v, _ := strconv.ParseBool(ctx.Query(name))
  197. return v
  198. }
  199. // QueryInt returns query result in int type.
  200. func (ctx *Context) QueryInt(name string) int {
  201. return com.StrTo(ctx.Query(name)).MustInt()
  202. }
  203. // QueryInt64 returns query result in int64 type.
  204. func (ctx *Context) QueryInt64(name string) int64 {
  205. return com.StrTo(ctx.Query(name)).MustInt64()
  206. }
  207. // QueryFloat64 returns query result in float64 type.
  208. func (ctx *Context) QueryFloat64(name string) float64 {
  209. v, _ := strconv.ParseFloat(ctx.Query(name), 64)
  210. return v
  211. }
  212. // Params returns value of given param name.
  213. // e.g. ctx.Params(":uid") or ctx.Params("uid")
  214. func (ctx *Context) Params(name string) string {
  215. if len(name) == 0 {
  216. return ""
  217. }
  218. if len(name) > 1 && name[0] != ':' {
  219. name = ":" + name
  220. }
  221. return ctx.params[name]
  222. }
  223. // SetParams sets value of param with given name.
  224. func (ctx *Context) SetParams(name, val string) {
  225. if name != "*" && !strings.HasPrefix(name, ":") {
  226. name = ":" + name
  227. }
  228. ctx.params[name] = val
  229. }
  230. // ReplaceAllParams replace all current params with given params
  231. func (ctx *Context) ReplaceAllParams(params Params) {
  232. ctx.params = params
  233. }
  234. // ParamsEscape returns escapred params result.
  235. // e.g. ctx.ParamsEscape(":uname")
  236. func (ctx *Context) ParamsEscape(name string) string {
  237. return template.HTMLEscapeString(ctx.Params(name))
  238. }
  239. // ParamsInt returns params result in int type.
  240. // e.g. ctx.ParamsInt(":uid")
  241. func (ctx *Context) ParamsInt(name string) int {
  242. return com.StrTo(ctx.Params(name)).MustInt()
  243. }
  244. // ParamsInt64 returns params result in int64 type.
  245. // e.g. ctx.ParamsInt64(":uid")
  246. func (ctx *Context) ParamsInt64(name string) int64 {
  247. return com.StrTo(ctx.Params(name)).MustInt64()
  248. }
  249. // ParamsFloat64 returns params result in int64 type.
  250. // e.g. ctx.ParamsFloat64(":uid")
  251. func (ctx *Context) ParamsFloat64(name string) float64 {
  252. v, _ := strconv.ParseFloat(ctx.Params(name), 64)
  253. return v
  254. }
  255. // GetFile returns information about user upload file by given form field name.
  256. func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {
  257. return ctx.Req.FormFile(name)
  258. }
  259. // SaveToFile reads a file from request by field name and saves to given path.
  260. func (ctx *Context) SaveToFile(name, savePath string) error {
  261. fr, _, err := ctx.GetFile(name)
  262. if err != nil {
  263. return err
  264. }
  265. defer fr.Close()
  266. fw, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  267. if err != nil {
  268. return err
  269. }
  270. defer fw.Close()
  271. _, err = io.Copy(fw, fr)
  272. return err
  273. }
  274. // SetCookie sets given cookie value to response header.
  275. // FIXME: IE support? http://golanghome.com/post/620#reply2
  276. func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
  277. cookie := http.Cookie{}
  278. cookie.Name = name
  279. cookie.Value = url.QueryEscape(value)
  280. if len(others) > 0 {
  281. switch v := others[0].(type) {
  282. case int:
  283. cookie.MaxAge = v
  284. case int64:
  285. cookie.MaxAge = int(v)
  286. case int32:
  287. cookie.MaxAge = int(v)
  288. }
  289. }
  290. cookie.Path = "/"
  291. if len(others) > 1 {
  292. if v, ok := others[1].(string); ok && len(v) > 0 {
  293. cookie.Path = v
  294. }
  295. }
  296. if len(others) > 2 {
  297. if v, ok := others[2].(string); ok && len(v) > 0 {
  298. cookie.Domain = v
  299. }
  300. }
  301. if len(others) > 3 {
  302. switch v := others[3].(type) {
  303. case bool:
  304. cookie.Secure = v
  305. default:
  306. if others[3] != nil {
  307. cookie.Secure = true
  308. }
  309. }
  310. }
  311. if len(others) > 4 {
  312. if v, ok := others[4].(bool); ok && v {
  313. cookie.HttpOnly = true
  314. }
  315. }
  316. if len(others) > 5 {
  317. if v, ok := others[5].(time.Time); ok {
  318. cookie.Expires = v
  319. cookie.RawExpires = v.Format(time.UnixDate)
  320. }
  321. }
  322. ctx.Resp.Header().Add("Set-Cookie", cookie.String())
  323. }
  324. // GetCookie returns given cookie value from request header.
  325. func (ctx *Context) GetCookie(name string) string {
  326. cookie, err := ctx.Req.Cookie(name)
  327. if err != nil {
  328. return ""
  329. }
  330. val, _ := url.QueryUnescape(cookie.Value)
  331. return val
  332. }
  333. // GetCookieInt returns cookie result in int type.
  334. func (ctx *Context) GetCookieInt(name string) int {
  335. return com.StrTo(ctx.GetCookie(name)).MustInt()
  336. }
  337. // GetCookieInt64 returns cookie result in int64 type.
  338. func (ctx *Context) GetCookieInt64(name string) int64 {
  339. return com.StrTo(ctx.GetCookie(name)).MustInt64()
  340. }
  341. // GetCookieFloat64 returns cookie result in float64 type.
  342. func (ctx *Context) GetCookieFloat64(name string) float64 {
  343. v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
  344. return v
  345. }
  346. var defaultCookieSecret string
  347. // SetDefaultCookieSecret sets global default secure cookie secret.
  348. func (m *Macaron) SetDefaultCookieSecret(secret string) {
  349. defaultCookieSecret = secret
  350. }
  351. // SetSecureCookie sets given cookie value to response header with default secret string.
  352. func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) {
  353. ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...)
  354. }
  355. // GetSecureCookie returns given cookie value from request header with default secret string.
  356. func (ctx *Context) GetSecureCookie(key string) (string, bool) {
  357. return ctx.GetSuperSecureCookie(defaultCookieSecret, key)
  358. }
  359. // SetSuperSecureCookie sets given cookie value to response header with secret string.
  360. func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
  361. key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
  362. text, err := com.AESGCMEncrypt(key, []byte(value))
  363. if err != nil {
  364. panic("error encrypting cookie: " + err.Error())
  365. }
  366. ctx.SetCookie(name, hex.EncodeToString(text), others...)
  367. }
  368. // GetSuperSecureCookie returns given cookie value from request header with secret string.
  369. func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
  370. val := ctx.GetCookie(name)
  371. if val == "" {
  372. return "", false
  373. }
  374. text, err := hex.DecodeString(val)
  375. if err != nil {
  376. return "", false
  377. }
  378. key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
  379. text, err = com.AESGCMDecrypt(key, text)
  380. return string(text), err == nil
  381. }
  382. func (ctx *Context) setRawContentHeader() {
  383. ctx.Resp.Header().Set("Content-Description", "Raw content")
  384. ctx.Resp.Header().Set("Content-Type", "text/plain")
  385. ctx.Resp.Header().Set("Expires", "0")
  386. ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
  387. ctx.Resp.Header().Set("Pragma", "public")
  388. }
  389. // ServeContent serves given content to response.
  390. func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
  391. modtime := time.Now()
  392. for _, p := range params {
  393. switch v := p.(type) {
  394. case time.Time:
  395. modtime = v
  396. }
  397. }
  398. ctx.setRawContentHeader()
  399. http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
  400. }
  401. // ServeFileContent serves given file as content to response.
  402. func (ctx *Context) ServeFileContent(file string, names ...string) {
  403. var name string
  404. if len(names) > 0 {
  405. name = names[0]
  406. } else {
  407. name = path.Base(file)
  408. }
  409. f, err := os.Open(file)
  410. if err != nil {
  411. if Env == PROD {
  412. http.Error(ctx.Resp, "Internal Server Error", 500)
  413. } else {
  414. http.Error(ctx.Resp, err.Error(), 500)
  415. }
  416. return
  417. }
  418. defer f.Close()
  419. ctx.setRawContentHeader()
  420. http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f)
  421. }
  422. // ServeFile serves given file to response.
  423. func (ctx *Context) ServeFile(file string, names ...string) {
  424. var name string
  425. if len(names) > 0 {
  426. name = names[0]
  427. } else {
  428. name = path.Base(file)
  429. }
  430. ctx.Resp.Header().Set("Content-Description", "File Transfer")
  431. ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
  432. ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
  433. ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
  434. ctx.Resp.Header().Set("Expires", "0")
  435. ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
  436. ctx.Resp.Header().Set("Pragma", "public")
  437. http.ServeFile(ctx.Resp, ctx.Req.Request, file)
  438. }
  439. // ChangeStaticPath changes static path from old to new one.
  440. func (ctx *Context) ChangeStaticPath(oldPath, newPath string) {
  441. if !filepath.IsAbs(oldPath) {
  442. oldPath = filepath.Join(Root, oldPath)
  443. }
  444. dir := statics.Get(oldPath)
  445. if dir != nil {
  446. statics.Delete(oldPath)
  447. if !filepath.IsAbs(newPath) {
  448. newPath = filepath.Join(Root, newPath)
  449. }
  450. *dir = http.Dir(newPath)
  451. statics.Set(dir)
  452. }
  453. }