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.

520 lines
13 KiB

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