github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/commands/http/handler.go (about) 1 package http 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "os" 11 "runtime" 12 "strconv" 13 "strings" 14 15 cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors" 16 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" 17 18 cmds "github.com/ipfs/go-ipfs/commands" 19 u "github.com/ipfs/go-ipfs/util" 20 ) 21 22 var log = u.Logger("commands/http") 23 24 // the internal handler for the API 25 type internalHandler struct { 26 ctx cmds.Context 27 root *cmds.Command 28 cfg *ServerConfig 29 } 30 31 // The Handler struct is funny because we want to wrap our internal handler 32 // with CORS while keeping our fields. 33 type Handler struct { 34 internalHandler 35 corsHandler http.Handler 36 } 37 38 var ErrNotFound = errors.New("404 page not found") 39 40 const ( 41 StreamErrHeader = "X-Stream-Error" 42 streamHeader = "X-Stream-Output" 43 channelHeader = "X-Chunked-Output" 44 uaHeader = "User-Agent" 45 contentTypeHeader = "Content-Type" 46 contentLengthHeader = "Content-Length" 47 contentDispHeader = "Content-Disposition" 48 transferEncodingHeader = "Transfer-Encoding" 49 applicationJson = "application/json" 50 applicationOctetStream = "application/octet-stream" 51 plainText = "text/plain" 52 originHeader = "origin" 53 ) 54 55 const ( 56 ACAOrigin = "Access-Control-Allow-Origin" 57 ACAMethods = "Access-Control-Allow-Methods" 58 ACACredentials = "Access-Control-Allow-Credentials" 59 ) 60 61 var mimeTypes = map[string]string{ 62 cmds.JSON: "application/json", 63 cmds.XML: "application/xml", 64 cmds.Text: "text/plain", 65 } 66 67 type ServerConfig struct { 68 // Headers is an optional map of headers that is written out. 69 Headers map[string][]string 70 71 // CORSOpts is a set of options for CORS headers. 72 CORSOpts *cors.Options 73 } 74 75 func skipAPIHeader(h string) bool { 76 switch h { 77 case "Access-Control-Allow-Origin": 78 return true 79 case "Access-Control-Allow-Methods": 80 return true 81 case "Access-Control-Allow-Credentials": 82 return true 83 default: 84 return false 85 } 86 } 87 88 func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) *Handler { 89 if cfg == nil { 90 panic("must provide a valid ServerConfig") 91 } 92 93 // Wrap the internal handler with CORS handling-middleware. 94 // Create a handler for the API. 95 internal := internalHandler{ctx, root, cfg} 96 c := cors.New(*cfg.CORSOpts) 97 return &Handler{internal, c.Handler(internal)} 98 } 99 100 func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 101 // Call the CORS handler which wraps the internal handler. 102 i.corsHandler.ServeHTTP(w, r) 103 } 104 105 func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 106 log.Debug("Incoming API request: ", r.URL) 107 108 defer func() { 109 if r := recover(); r != nil { 110 log.Error(r) 111 112 buf := make([]byte, 4096) 113 n := runtime.Stack(buf, false) 114 fmt.Fprintln(os.Stderr, string(buf[:n])) 115 } 116 }() 117 118 if !allowOrigin(r, i.cfg) || !allowReferer(r, i.cfg) { 119 w.WriteHeader(http.StatusForbidden) 120 w.Write([]byte("403 - Forbidden")) 121 log.Warningf("API blocked request to %s. (possible CSRF)", r.URL) 122 return 123 } 124 125 req, err := Parse(r, i.root) 126 if err != nil { 127 if err == ErrNotFound { 128 w.WriteHeader(http.StatusNotFound) 129 } else { 130 w.WriteHeader(http.StatusBadRequest) 131 } 132 w.Write([]byte(err.Error())) 133 return 134 } 135 136 // get the node's context to pass into the commands. 137 node, err := i.ctx.GetNode() 138 if err != nil { 139 s := fmt.Sprintf("cmds/http: couldn't GetNode(): %s", err) 140 http.Error(w, s, http.StatusInternalServerError) 141 return 142 } 143 144 //ps: take note of the name clash - commands.Context != context.Context 145 req.SetInvocContext(i.ctx) 146 147 ctx, cancel := context.WithCancel(node.Context()) 148 defer cancel() 149 150 err = req.SetRootContext(ctx) 151 if err != nil { 152 http.Error(w, err.Error(), http.StatusInternalServerError) 153 return 154 } 155 156 // call the command 157 res := i.root.Call(req) 158 159 // set user's headers first. 160 for k, v := range i.cfg.Headers { 161 if !skipAPIHeader(k) { 162 w.Header()[k] = v 163 } 164 } 165 166 // now handle responding to the client properly 167 sendResponse(w, r, res, req) 168 } 169 170 func guessMimeType(res cmds.Response) (string, error) { 171 // Try to guess mimeType from the encoding option 172 enc, found, err := res.Request().Option(cmds.EncShort).String() 173 if err != nil { 174 return "", err 175 } 176 if !found { 177 return "", errors.New("no encoding option set") 178 } 179 180 return mimeTypes[enc], nil 181 } 182 183 func sendResponse(w http.ResponseWriter, r *http.Request, res cmds.Response, req cmds.Request) { 184 mime, err := guessMimeType(res) 185 if err != nil { 186 http.Error(w, err.Error(), http.StatusInternalServerError) 187 return 188 } 189 190 status := http.StatusOK 191 // if response contains an error, write an HTTP error status code 192 if e := res.Error(); e != nil { 193 if e.Code == cmds.ErrClient { 194 status = http.StatusBadRequest 195 } else { 196 status = http.StatusInternalServerError 197 } 198 // NOTE: The error will actually be written out by the reader below 199 } 200 201 out, err := res.Reader() 202 if err != nil { 203 http.Error(w, err.Error(), http.StatusInternalServerError) 204 return 205 } 206 207 h := w.Header() 208 if res.Length() > 0 { 209 h.Set(contentLengthHeader, strconv.FormatUint(res.Length(), 10)) 210 } 211 212 if _, ok := res.Output().(io.Reader); ok { 213 // we don't set the Content-Type for streams, so that browsers can MIME-sniff the type themselves 214 // we set this header so clients have a way to know this is an output stream 215 // (not marshalled command output) 216 mime = "" 217 h.Set(streamHeader, "1") 218 } 219 220 // if output is a channel and user requested streaming channels, 221 // use chunk copier for the output 222 _, isChan := res.Output().(chan interface{}) 223 if !isChan { 224 _, isChan = res.Output().(<-chan interface{}) 225 } 226 227 streamChans, _, _ := req.Option("stream-channels").Bool() 228 if isChan { 229 h.Set(channelHeader, "1") 230 if streamChans { 231 // streaming output from a channel will always be json objects 232 mime = applicationJson 233 } 234 } 235 236 if mime != "" { 237 h.Set(contentTypeHeader, mime) 238 } 239 h.Set(transferEncodingHeader, "chunked") 240 241 if r.Method == "HEAD" { // after all the headers. 242 return 243 } 244 245 if err := writeResponse(status, w, out); err != nil { 246 if strings.Contains(err.Error(), "broken pipe") { 247 log.Info("client disconnect while writing stream ", err) 248 return 249 } 250 251 log.Error("error while writing stream ", err) 252 } 253 } 254 255 // Copies from an io.Reader to a http.ResponseWriter. 256 // Flushes chunks over HTTP stream as they are read (if supported by transport). 257 func writeResponse(status int, w http.ResponseWriter, out io.Reader) error { 258 // hijack the connection so we can write our own chunked output and trailers 259 hijacker, ok := w.(http.Hijacker) 260 if !ok { 261 log.Error("Failed to create hijacker! cannot continue!") 262 return errors.New("Could not create hijacker") 263 } 264 conn, writer, err := hijacker.Hijack() 265 if err != nil { 266 return err 267 } 268 defer conn.Close() 269 270 // write status 271 writer.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\r\n", status, http.StatusText(status))) 272 273 // Write out headers 274 w.Header().Write(writer) 275 276 // end of headers 277 writer.WriteString("\r\n") 278 279 // write body 280 streamErr := writeChunks(out, writer) 281 282 // close body 283 writer.WriteString("0\r\n") 284 285 // if there was a stream error, write out an error trailer. hopefully 286 // the client will pick it up! 287 if streamErr != nil { 288 writer.WriteString(StreamErrHeader + ": " + sanitizedErrStr(streamErr) + "\r\n") 289 } 290 writer.WriteString("\r\n") // close response 291 writer.Flush() 292 return streamErr 293 } 294 295 func writeChunks(r io.Reader, w *bufio.ReadWriter) error { 296 buf := make([]byte, 32*1024) 297 for { 298 n, err := r.Read(buf) 299 300 if n > 0 { 301 length := fmt.Sprintf("%x\r\n", n) 302 w.WriteString(length) 303 304 _, err := w.Write(buf[0:n]) 305 if err != nil { 306 return err 307 } 308 309 w.WriteString("\r\n") 310 w.Flush() 311 } 312 313 if err != nil && err != io.EOF { 314 return err 315 } 316 if err == io.EOF { 317 break 318 } 319 } 320 return nil 321 } 322 323 func sanitizedErrStr(err error) string { 324 s := err.Error() 325 s = strings.Split(s, "\n")[0] 326 s = strings.Split(s, "\r")[0] 327 return s 328 } 329 330 // allowOrigin just stops the request if the origin is not allowed. 331 // the CORS middleware apparently does not do this for us... 332 func allowOrigin(r *http.Request, cfg *ServerConfig) bool { 333 origin := r.Header.Get("Origin") 334 335 // curl, or ipfs shell, typing it in manually, or clicking link 336 // NOT in a browser. this opens up a hole. we should close it, 337 // but right now it would break things. TODO 338 if origin == "" { 339 return true 340 } 341 342 for _, o := range cfg.CORSOpts.AllowedOrigins { 343 if o == "*" { // ok! you asked for it! 344 return true 345 } 346 347 if o == origin { // allowed explicitly 348 return true 349 } 350 } 351 352 return false 353 } 354 355 // allowReferer this is here to prevent some CSRF attacks that 356 // the API would be vulnerable to. We check that the Referer 357 // is allowed by CORS Origin (origins and referrers here will 358 // work similarly in the normla uses of the API). 359 // See discussion at https://github.com/ipfs/go-ipfs/issues/1532 360 func allowReferer(r *http.Request, cfg *ServerConfig) bool { 361 referer := r.Referer() 362 363 // curl, or ipfs shell, typing it in manually, or clicking link 364 // NOT in a browser. this opens up a hole. we should close it, 365 // but right now it would break things. TODO 366 if referer == "" { 367 return true 368 } 369 370 u, err := url.Parse(referer) 371 if err != nil { 372 // bad referer. but there _is_ something, so bail. 373 log.Debug("failed to parse referer: ", referer) 374 // debug because referer comes straight from the client. dont want to 375 // let people DOS by putting a huge referer that gets stored in log files. 376 return false 377 } 378 origin := u.Scheme + "://" + u.Host 379 380 // check CORS ACAOs and pretend Referer works like an origin. 381 // this is valid for many (most?) sane uses of the API in 382 // other applications, and will have the desired effect. 383 for _, o := range cfg.CORSOpts.AllowedOrigins { 384 if o == "*" { // ok! you asked for it! 385 return true 386 } 387 388 // referer is allowed explicitly 389 if o == origin { 390 return true 391 } 392 } 393 394 return false 395 }