github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/corehttp/gateway_handler.go (about) 1 package corehttp 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/http" 8 gopath "path" 9 "strings" 10 "time" 11 12 humanize "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/dustin/go-humanize" 13 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" 14 15 key "github.com/ipfs/go-ipfs/blocks/key" 16 core "github.com/ipfs/go-ipfs/core" 17 "github.com/ipfs/go-ipfs/importer" 18 chunk "github.com/ipfs/go-ipfs/importer/chunk" 19 dag "github.com/ipfs/go-ipfs/merkledag" 20 path "github.com/ipfs/go-ipfs/path" 21 "github.com/ipfs/go-ipfs/routing" 22 uio "github.com/ipfs/go-ipfs/unixfs/io" 23 ) 24 25 const ( 26 ipfsPathPrefix = "/ipfs/" 27 ipnsPathPrefix = "/ipns/" 28 ) 29 30 // gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>) 31 // (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link) 32 type gatewayHandler struct { 33 node *core.IpfsNode 34 config GatewayConfig 35 } 36 37 func newGatewayHandler(node *core.IpfsNode, conf GatewayConfig) (*gatewayHandler, error) { 38 i := &gatewayHandler{ 39 node: node, 40 config: conf, 41 } 42 return i, nil 43 } 44 45 // TODO(cryptix): find these helpers somewhere else 46 func (i *gatewayHandler) newDagFromReader(r io.Reader) (*dag.Node, error) { 47 // TODO(cryptix): change and remove this helper once PR1136 is merged 48 // return ufs.AddFromReader(i.node, r.Body) 49 return importer.BuildDagFromReader( 50 i.node.DAG, 51 chunk.DefaultSplitter(r), 52 importer.BasicPinnerCB(i.node.Pinning.GetManual())) 53 } 54 55 // TODO(btc): break this apart into separate handlers using a more expressive muxer 56 func (i *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 57 if i.config.Writable { 58 switch r.Method { 59 case "POST": 60 i.postHandler(w, r) 61 return 62 case "PUT": 63 i.putHandler(w, r) 64 return 65 case "DELETE": 66 i.deleteHandler(w, r) 67 return 68 } 69 } 70 71 if r.Method == "GET" || r.Method == "HEAD" { 72 i.getOrHeadHandler(w, r) 73 return 74 } 75 76 errmsg := "Method " + r.Method + " not allowed: " 77 if !i.config.Writable { 78 w.WriteHeader(http.StatusMethodNotAllowed) 79 errmsg = errmsg + "read only access" 80 } else { 81 w.WriteHeader(http.StatusBadRequest) 82 errmsg = errmsg + "bad request for " + r.URL.Path 83 } 84 fmt.Fprint(w, errmsg) 85 log.Error(errmsg) // TODO(cryptix): log errors until we have a better way to expose these (counter metrics maybe) 86 } 87 88 func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { 89 ctx, cancel := context.WithCancel(i.node.Context()) 90 defer cancel() 91 92 urlPath := r.URL.Path 93 94 // IPNSHostnameOption might have constructed an IPNS path using the Host header. 95 // In this case, we need the original path for constructing redirects 96 // and links that match the requested URL. 97 // For example, http://example.net would become /ipns/example.net, and 98 // the redirects and links would end up as http://example.net/ipns/example.net 99 originalUrlPath := urlPath 100 ipnsHostname := false 101 hdr := r.Header["X-IPNS-Original-Path"] 102 if len(hdr) > 0 { 103 originalUrlPath = hdr[0] 104 ipnsHostname = true 105 } 106 107 if i.config.BlockList != nil && i.config.BlockList.ShouldBlock(urlPath) { 108 w.WriteHeader(http.StatusForbidden) 109 w.Write([]byte("403 - Forbidden")) 110 return 111 } 112 113 nd, err := core.Resolve(ctx, i.node, path.Path(urlPath)) 114 if err != nil { 115 webError(w, "Path Resolve error", err, http.StatusBadRequest) 116 return 117 } 118 119 etag := gopath.Base(urlPath) 120 if r.Header.Get("If-None-Match") == etag { 121 w.WriteHeader(http.StatusNotModified) 122 return 123 } 124 125 i.addUserHeaders(w) // ok, _now_ write user's headers. 126 w.Header().Set("X-IPFS-Path", urlPath) 127 128 // Suborigin header, sandboxes apps from each other in the browser (even 129 // though they are served from the same gateway domain). 130 // 131 // Omited if the path was treated by IPNSHostnameOption(), for example 132 // a request for http://example.net/ would be changed to /ipns/example.net/, 133 // which would turn into an incorrect Suborigin: example.net header. 134 // 135 // NOTE: This is not yet widely supported by browsers. 136 if !ipnsHostname { 137 pathRoot := strings.SplitN(urlPath, "/", 4)[2] 138 w.Header().Set("Suborigin", pathRoot) 139 } 140 141 dr, err := uio.NewDagReader(ctx, nd, i.node.DAG) 142 if err != nil && err != uio.ErrIsDir { 143 // not a directory and still an error 144 internalWebError(w, err) 145 return 146 } 147 148 // set these headers _after_ the error, for we may just not have it 149 // and dont want the client to cache a 500 response... 150 // and only if it's /ipfs! 151 // TODO: break this out when we split /ipfs /ipns routes. 152 modtime := time.Now() 153 if strings.HasPrefix(urlPath, ipfsPathPrefix) { 154 w.Header().Set("Etag", etag) 155 w.Header().Set("Cache-Control", "public, max-age=29030400") 156 157 // set modtime to a really long time ago, since files are immutable and should stay cached 158 modtime = time.Unix(1, 0) 159 } 160 161 if err == nil { 162 defer dr.Close() 163 _, name := gopath.Split(urlPath) 164 http.ServeContent(w, r, name, modtime, dr) 165 return 166 } 167 168 // storage for directory listing 169 var dirListing []directoryItem 170 // loop through files 171 foundIndex := false 172 for _, link := range nd.Links { 173 if link.Name == "index.html" { 174 log.Debugf("found index.html link for %s", urlPath) 175 foundIndex = true 176 177 if urlPath[len(urlPath)-1] != '/' { 178 // See comment above where originalUrlPath is declared. 179 http.Redirect(w, r, originalUrlPath+"/", 302) 180 log.Debugf("redirect to %s", originalUrlPath+"/") 181 return 182 } 183 184 // return index page instead. 185 nd, err := core.Resolve(ctx, i.node, path.Path(urlPath+"/index.html")) 186 if err != nil { 187 internalWebError(w, err) 188 return 189 } 190 dr, err := uio.NewDagReader(ctx, nd, i.node.DAG) 191 if err != nil { 192 internalWebError(w, err) 193 return 194 } 195 defer dr.Close() 196 197 // write to request 198 if r.Method != "HEAD" { 199 io.Copy(w, dr) 200 } 201 break 202 } 203 204 // See comment above where originalUrlPath is declared. 205 di := directoryItem{humanize.Bytes(link.Size), link.Name, gopath.Join(originalUrlPath, link.Name)} 206 dirListing = append(dirListing, di) 207 } 208 209 if !foundIndex { 210 if r.Method != "HEAD" { 211 // construct the correct back link 212 // https://github.com/ipfs/go-ipfs/issues/1365 213 var backLink string = urlPath 214 215 // don't go further up than /ipfs/$hash/ 216 pathSplit := strings.Split(backLink, "/") 217 switch { 218 // keep backlink 219 case len(pathSplit) == 3: // url: /ipfs/$hash 220 221 // keep backlink 222 case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/ 223 224 // add the correct link depending on wether the path ends with a slash 225 default: 226 if strings.HasSuffix(backLink, "/") { 227 backLink += "./.." 228 } else { 229 backLink += "/.." 230 } 231 } 232 233 // strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path. 234 if ipnsHostname { 235 backLink = "/" 236 if len(pathSplit) > 5 { 237 // also strip the trailing segment, because it's a backlink 238 backLinkParts := pathSplit[3 : len(pathSplit)-2] 239 backLink += strings.Join(backLinkParts, "/") + "/" 240 } 241 } 242 243 // See comment above where originalUrlPath is declared. 244 tplData := listingTemplateData{ 245 Listing: dirListing, 246 Path: originalUrlPath, 247 BackLink: backLink, 248 } 249 err := listingTemplate.Execute(w, tplData) 250 if err != nil { 251 internalWebError(w, err) 252 return 253 } 254 } 255 } 256 } 257 258 func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) { 259 nd, err := i.newDagFromReader(r.Body) 260 if err != nil { 261 internalWebError(w, err) 262 return 263 } 264 265 k, err := i.node.DAG.Add(nd) 266 if err != nil { 267 internalWebError(w, err) 268 return 269 } 270 271 i.addUserHeaders(w) // ok, _now_ write user's headers. 272 w.Header().Set("IPFS-Hash", k.String()) 273 http.Redirect(w, r, ipfsPathPrefix+k.String(), http.StatusCreated) 274 } 275 276 func (i *gatewayHandler) putEmptyDirHandler(w http.ResponseWriter, r *http.Request) { 277 newnode := uio.NewEmptyDirectory() 278 279 key, err := i.node.DAG.Add(newnode) 280 if err != nil { 281 webError(w, "Could not recursively add new node", err, http.StatusInternalServerError) 282 return 283 } 284 285 i.addUserHeaders(w) // ok, _now_ write user's headers. 286 w.Header().Set("IPFS-Hash", key.String()) 287 http.Redirect(w, r, ipfsPathPrefix+key.String()+"/", http.StatusCreated) 288 } 289 290 func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) { 291 // TODO(cryptix): either ask mildred about the flow of this or rewrite it 292 webErrorWithCode(w, "Sorry, PUT is bugged right now, closing request", errors.New("handler disabled"), http.StatusInternalServerError) 293 return 294 urlPath := r.URL.Path 295 pathext := urlPath[5:] 296 var err error 297 if urlPath == ipfsPathPrefix+"QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn/" { 298 i.putEmptyDirHandler(w, r) 299 return 300 } 301 302 var newnode *dag.Node 303 if pathext[len(pathext)-1] == '/' { 304 newnode = uio.NewEmptyDirectory() 305 } else { 306 newnode, err = i.newDagFromReader(r.Body) 307 if err != nil { 308 webError(w, "Could not create DAG from request", err, http.StatusInternalServerError) 309 return 310 } 311 } 312 313 ctx, cancel := context.WithCancel(i.node.Context()) 314 defer cancel() 315 316 ipfsNode, err := core.Resolve(ctx, i.node, path.Path(urlPath)) 317 if err != nil { 318 // FIXME HTTP error code 319 webError(w, "Could not resolve name", err, http.StatusInternalServerError) 320 return 321 } 322 323 k, err := ipfsNode.Key() 324 if err != nil { 325 webError(w, "Could not get key from resolved node", err, http.StatusInternalServerError) 326 return 327 } 328 329 h, components, err := path.SplitAbsPath(path.FromKey(k)) 330 if err != nil { 331 webError(w, "Could not split path", err, http.StatusInternalServerError) 332 return 333 } 334 335 if len(components) < 1 { 336 err = fmt.Errorf("Cannot override existing object") 337 webError(w, "http gateway", err, http.StatusBadRequest) 338 return 339 } 340 341 tctx, cancel := context.WithTimeout(ctx, time.Minute) 342 defer cancel() 343 // TODO(cryptix): could this be core.Resolve() too? 344 rootnd, err := i.node.Resolver.DAG.Get(tctx, key.Key(h)) 345 if err != nil { 346 webError(w, "Could not resolve root object", err, http.StatusBadRequest) 347 return 348 } 349 350 // resolving path components into merkledag nodes. if a component does not 351 // resolve, create empty directories (which will be linked and populated below.) 352 pathNodes, err := i.node.Resolver.ResolveLinks(tctx, rootnd, components[:len(components)-1]) 353 if _, ok := err.(path.ErrNoLink); ok { 354 // Create empty directories, links will be made further down the code 355 for len(pathNodes) < len(components) { 356 pathNodes = append(pathNodes, uio.NewDirectory(i.node.DAG).GetNode()) 357 } 358 } else if err != nil { 359 webError(w, "Could not resolve parent object", err, http.StatusBadRequest) 360 return 361 } 362 363 for i := len(pathNodes) - 1; i >= 0; i-- { 364 newnode, err = pathNodes[i].UpdateNodeLink(components[i], newnode) 365 if err != nil { 366 webError(w, "Could not update node links", err, http.StatusInternalServerError) 367 return 368 } 369 } 370 371 if err := i.node.DAG.AddRecursive(newnode); err != nil { 372 webError(w, "Could not add recursively new node", err, http.StatusInternalServerError) 373 return 374 } 375 376 // Redirect to new path 377 key, err := newnode.Key() 378 if err != nil { 379 webError(w, "Could not get key of new node", err, http.StatusInternalServerError) 380 return 381 } 382 383 i.addUserHeaders(w) // ok, _now_ write user's headers. 384 w.Header().Set("IPFS-Hash", key.String()) 385 http.Redirect(w, r, ipfsPathPrefix+key.String()+"/"+strings.Join(components, "/"), http.StatusCreated) 386 } 387 388 func (i *gatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { 389 urlPath := r.URL.Path 390 ctx, cancel := context.WithCancel(i.node.Context()) 391 defer cancel() 392 393 ipfsNode, err := core.Resolve(ctx, i.node, path.Path(urlPath)) 394 if err != nil { 395 // FIXME HTTP error code 396 webError(w, "Could not resolve name", err, http.StatusInternalServerError) 397 return 398 } 399 400 k, err := ipfsNode.Key() 401 if err != nil { 402 webError(w, "Could not get key from resolved node", err, http.StatusInternalServerError) 403 return 404 } 405 406 h, components, err := path.SplitAbsPath(path.FromKey(k)) 407 if err != nil { 408 webError(w, "Could not split path", err, http.StatusInternalServerError) 409 return 410 } 411 412 tctx, cancel := context.WithTimeout(ctx, time.Minute) 413 defer cancel() 414 rootnd, err := i.node.Resolver.DAG.Get(tctx, key.Key(h)) 415 if err != nil { 416 webError(w, "Could not resolve root object", err, http.StatusBadRequest) 417 return 418 } 419 420 pathNodes, err := i.node.Resolver.ResolveLinks(tctx, rootnd, components[:len(components)-1]) 421 if err != nil { 422 webError(w, "Could not resolve parent object", err, http.StatusBadRequest) 423 return 424 } 425 426 // TODO(cyrptix): assumes len(pathNodes) > 1 - not found is an error above? 427 err = pathNodes[len(pathNodes)-1].RemoveNodeLink(components[len(components)-1]) 428 if err != nil { 429 webError(w, "Could not delete link", err, http.StatusBadRequest) 430 return 431 } 432 433 newnode := pathNodes[len(pathNodes)-1] 434 for i := len(pathNodes) - 2; i >= 0; i-- { 435 newnode, err = pathNodes[i].UpdateNodeLink(components[i], newnode) 436 if err != nil { 437 webError(w, "Could not update node links", err, http.StatusInternalServerError) 438 return 439 } 440 } 441 442 if err := i.node.DAG.AddRecursive(newnode); err != nil { 443 webError(w, "Could not add recursively new node", err, http.StatusInternalServerError) 444 return 445 } 446 447 // Redirect to new path 448 key, err := newnode.Key() 449 if err != nil { 450 webError(w, "Could not get key of new node", err, http.StatusInternalServerError) 451 return 452 } 453 454 i.addUserHeaders(w) // ok, _now_ write user's headers. 455 w.Header().Set("IPFS-Hash", key.String()) 456 http.Redirect(w, r, ipfsPathPrefix+key.String()+"/"+strings.Join(components[:len(components)-1], "/"), http.StatusCreated) 457 } 458 459 func (i *gatewayHandler) addUserHeaders(w http.ResponseWriter) { 460 for k, v := range i.config.Headers { 461 w.Header()[k] = v 462 } 463 } 464 465 func webError(w http.ResponseWriter, message string, err error, defaultCode int) { 466 if _, ok := err.(path.ErrNoLink); ok { 467 webErrorWithCode(w, message, err, http.StatusNotFound) 468 } else if err == routing.ErrNotFound { 469 webErrorWithCode(w, message, err, http.StatusNotFound) 470 } else if err == context.DeadlineExceeded { 471 webErrorWithCode(w, message, err, http.StatusRequestTimeout) 472 } else { 473 webErrorWithCode(w, message, err, defaultCode) 474 } 475 } 476 477 func webErrorWithCode(w http.ResponseWriter, message string, err error, code int) { 478 w.WriteHeader(code) 479 log.Errorf("%s: %s", message, err) // TODO(cryptix): log errors until we have a better way to expose these (counter metrics maybe) 480 fmt.Fprintf(w, "%s: %s", message, err) 481 } 482 483 // return a 500 error and log 484 func internalWebError(w http.ResponseWriter, err error) { 485 webErrorWithCode(w, "internalWebError", err, http.StatusInternalServerError) 486 }