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  }