output.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. package context
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "encoding/xml"
  19. "errors"
  20. "fmt"
  21. "html/template"
  22. "io"
  23. "mime"
  24. "net/http"
  25. "net/url"
  26. "os"
  27. "path/filepath"
  28. "strconv"
  29. "strings"
  30. "time"
  31. )
  32. // BeegoOutput does work for sending response header.
  33. type BeegoOutput struct {
  34. Context *Context
  35. Status int
  36. EnableGzip bool
  37. }
  38. // NewOutput returns new BeegoOutput.
  39. // it contains nothing now.
  40. func NewOutput() *BeegoOutput {
  41. return &BeegoOutput{}
  42. }
  43. // Reset init BeegoOutput
  44. func (output *BeegoOutput) Reset(ctx *Context) {
  45. output.Context = ctx
  46. output.Status = 0
  47. }
  48. // Header sets response header item string via given key.
  49. func (output *BeegoOutput) Header(key, val string) {
  50. output.Context.ResponseWriter.Header().Set(key, val)
  51. }
  52. // Body sets response body content.
  53. // if EnableGzip, compress content string.
  54. // it sends out response body directly.
  55. func (output *BeegoOutput) Body(content []byte) error {
  56. var encoding string
  57. var buf = &bytes.Buffer{}
  58. if output.EnableGzip {
  59. encoding = ParseEncoding(output.Context.Request)
  60. }
  61. if b, n, _ := WriteBody(encoding, buf, content); b {
  62. output.Header("Content-Encoding", n)
  63. output.Header("Content-Length", strconv.Itoa(buf.Len()))
  64. } else {
  65. output.Header("Content-Length", strconv.Itoa(len(content)))
  66. }
  67. // Write status code if it has been set manually
  68. // Set it to 0 afterwards to prevent "multiple response.WriteHeader calls"
  69. if output.Status != 0 {
  70. output.Context.ResponseWriter.WriteHeader(output.Status)
  71. output.Status = 0
  72. } else {
  73. output.Context.ResponseWriter.Started = true
  74. }
  75. io.Copy(output.Context.ResponseWriter, buf)
  76. return nil
  77. }
  78. // Cookie sets cookie value via given key.
  79. // others are ordered as cookie's max age time, path,domain, secure and httponly.
  80. func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
  81. var b bytes.Buffer
  82. fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
  83. //fix cookie not work in IE
  84. if len(others) > 0 {
  85. var maxAge int64
  86. switch v := others[0].(type) {
  87. case int:
  88. maxAge = int64(v)
  89. case int32:
  90. maxAge = int64(v)
  91. case int64:
  92. maxAge = v
  93. }
  94. switch {
  95. case maxAge > 0:
  96. fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge)
  97. case maxAge < 0:
  98. fmt.Fprintf(&b, "; Max-Age=0")
  99. }
  100. }
  101. // the settings below
  102. // Path, Domain, Secure, HttpOnly
  103. // can use nil skip set
  104. // default "/"
  105. if len(others) > 1 {
  106. if v, ok := others[1].(string); ok && len(v) > 0 {
  107. fmt.Fprintf(&b, "; Path=%s", sanitizeValue(v))
  108. }
  109. } else {
  110. fmt.Fprintf(&b, "; Path=%s", "/")
  111. }
  112. // default empty
  113. if len(others) > 2 {
  114. if v, ok := others[2].(string); ok && len(v) > 0 {
  115. fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(v))
  116. }
  117. }
  118. // default empty
  119. if len(others) > 3 {
  120. var secure bool
  121. switch v := others[3].(type) {
  122. case bool:
  123. secure = v
  124. default:
  125. if others[3] != nil {
  126. secure = true
  127. }
  128. }
  129. if secure {
  130. fmt.Fprintf(&b, "; Secure")
  131. }
  132. }
  133. // default false. for session cookie default true
  134. if len(others) > 4 {
  135. if v, ok := others[4].(bool); ok && v {
  136. fmt.Fprintf(&b, "; HttpOnly")
  137. }
  138. }
  139. output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
  140. }
  141. var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
  142. func sanitizeName(n string) string {
  143. return cookieNameSanitizer.Replace(n)
  144. }
  145. var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
  146. func sanitizeValue(v string) string {
  147. return cookieValueSanitizer.Replace(v)
  148. }
  149. // JSON writes json to response body.
  150. // if coding is true, it converts utf-8 to \u0000 type.
  151. func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
  152. output.Header("Content-Type", "application/json; charset=utf-8")
  153. var content []byte
  154. var err error
  155. if hasIndent {
  156. content, err = json.MarshalIndent(data, "", " ")
  157. } else {
  158. content, err = json.Marshal(data)
  159. }
  160. if err != nil {
  161. http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
  162. return err
  163. }
  164. if coding {
  165. content = []byte(stringsToJSON(string(content)))
  166. }
  167. return output.Body(content)
  168. }
  169. // JSONP writes jsonp to response body.
  170. func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
  171. output.Header("Content-Type", "application/javascript; charset=utf-8")
  172. var content []byte
  173. var err error
  174. if hasIndent {
  175. content, err = json.MarshalIndent(data, "", " ")
  176. } else {
  177. content, err = json.Marshal(data)
  178. }
  179. if err != nil {
  180. http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
  181. return err
  182. }
  183. callback := output.Context.Input.Query("callback")
  184. if callback == "" {
  185. return errors.New(`"callback" parameter required`)
  186. }
  187. callback = template.JSEscapeString(callback)
  188. callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback)
  189. callbackContent.WriteString("(")
  190. callbackContent.Write(content)
  191. callbackContent.WriteString(");\r\n")
  192. return output.Body(callbackContent.Bytes())
  193. }
  194. // XML writes xml string to response body.
  195. func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
  196. output.Header("Content-Type", "application/xml; charset=utf-8")
  197. var content []byte
  198. var err error
  199. if hasIndent {
  200. content, err = xml.MarshalIndent(data, "", " ")
  201. } else {
  202. content, err = xml.Marshal(data)
  203. }
  204. if err != nil {
  205. http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
  206. return err
  207. }
  208. return output.Body(content)
  209. }
  210. // Download forces response for download file.
  211. // it prepares the download response header automatically.
  212. func (output *BeegoOutput) Download(file string, filename ...string) {
  213. // check get file error, file not found or other error.
  214. if _, err := os.Stat(file); err != nil {
  215. http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
  216. return
  217. }
  218. var fName string
  219. if len(filename) > 0 && filename[0] != "" {
  220. fName = filename[0]
  221. } else {
  222. fName = filepath.Base(file)
  223. }
  224. output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName))
  225. output.Header("Content-Description", "File Transfer")
  226. output.Header("Content-Type", "application/octet-stream")
  227. output.Header("Content-Transfer-Encoding", "binary")
  228. output.Header("Expires", "0")
  229. output.Header("Cache-Control", "must-revalidate")
  230. output.Header("Pragma", "public")
  231. http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
  232. }
  233. // ContentType sets the content type from ext string.
  234. // MIME type is given in mime package.
  235. func (output *BeegoOutput) ContentType(ext string) {
  236. if !strings.HasPrefix(ext, ".") {
  237. ext = "." + ext
  238. }
  239. ctype := mime.TypeByExtension(ext)
  240. if ctype != "" {
  241. output.Header("Content-Type", ctype)
  242. }
  243. }
  244. // SetStatus sets response status code.
  245. // It writes response header directly.
  246. func (output *BeegoOutput) SetStatus(status int) {
  247. output.Status = status
  248. }
  249. // IsCachable returns boolean of this request is cached.
  250. // HTTP 304 means cached.
  251. func (output *BeegoOutput) IsCachable() bool {
  252. return output.Status >= 200 && output.Status < 300 || output.Status == 304
  253. }
  254. // IsEmpty returns boolean of this request is empty.
  255. // HTTP 201,204 and 304 means empty.
  256. func (output *BeegoOutput) IsEmpty() bool {
  257. return output.Status == 201 || output.Status == 204 || output.Status == 304
  258. }
  259. // IsOk returns boolean of this request runs well.
  260. // HTTP 200 means ok.
  261. func (output *BeegoOutput) IsOk() bool {
  262. return output.Status == 200
  263. }
  264. // IsSuccessful returns boolean of this request runs successfully.
  265. // HTTP 2xx means ok.
  266. func (output *BeegoOutput) IsSuccessful() bool {
  267. return output.Status >= 200 && output.Status < 300
  268. }
  269. // IsRedirect returns boolean of this request is redirection header.
  270. // HTTP 301,302,307 means redirection.
  271. func (output *BeegoOutput) IsRedirect() bool {
  272. return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
  273. }
  274. // IsForbidden returns boolean of this request is forbidden.
  275. // HTTP 403 means forbidden.
  276. func (output *BeegoOutput) IsForbidden() bool {
  277. return output.Status == 403
  278. }
  279. // IsNotFound returns boolean of this request is not found.
  280. // HTTP 404 means forbidden.
  281. func (output *BeegoOutput) IsNotFound() bool {
  282. return output.Status == 404
  283. }
  284. // IsClientError returns boolean of this request client sends error data.
  285. // HTTP 4xx means forbidden.
  286. func (output *BeegoOutput) IsClientError() bool {
  287. return output.Status >= 400 && output.Status < 500
  288. }
  289. // IsServerError returns boolean of this server handler errors.
  290. // HTTP 5xx means server internal error.
  291. func (output *BeegoOutput) IsServerError() bool {
  292. return output.Status >= 500 && output.Status < 600
  293. }
  294. func stringsToJSON(str string) string {
  295. rs := []rune(str)
  296. var jsons bytes.Buffer
  297. for _, r := range rs {
  298. rint := int(r)
  299. if rint < 128 {
  300. jsons.WriteRune(r)
  301. } else {
  302. jsons.WriteString("\\u")
  303. jsons.WriteString(strconv.FormatInt(int64(rint), 16))
  304. }
  305. }
  306. return jsons.String()
  307. }
  308. // Session sets session item value with given key.
  309. func (output *BeegoOutput) Session(name interface{}, value interface{}) {
  310. output.Context.Input.CruSession.Set(name, value)
  311. }