Skip to content

Commit c068ac8

Browse files
committed
fix and improve the writable gateway
1. Fix handling of PUT. The simple implementation was the correct implementation, I have no idea what was going on here. 2. Use MFS everywhere to reduce code duplication and add support for sharded directories. 3. _Correctly_ block IPNS. 4. Remove the dependency on `core.IpfsNode`.
1 parent 876d5ba commit c068ac8

File tree

2 files changed

+109
-154
lines changed

2 files changed

+109
-154
lines changed

core/corehttp/gateway.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
8787
"X-Stream-Output",
8888
}, headers[ACEHeadersName]...))
8989

90-
gateway := newGatewayHandler(n, GatewayConfig{
90+
gateway := newGatewayHandler(GatewayConfig{
9191
Headers: headers,
9292
Writable: writable,
9393
PathPrefixes: cfg.Gateway.PathPrefixes,

core/corehttp/gateway_handler.go

+108-153
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package corehttp
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"io"
87
"net/http"
@@ -12,20 +11,13 @@ import (
1211
"strings"
1312
"time"
1413

15-
"github.com/ipfs/go-ipfs/core"
16-
"github.com/ipfs/go-ipfs/dagutils"
17-
"github.com/ipfs/go-ipfs/namesys/resolve"
18-
1914
"github.com/dustin/go-humanize"
2015
"github.com/ipfs/go-cid"
21-
chunker "github.com/ipfs/go-ipfs-chunker"
2216
files "github.com/ipfs/go-ipfs-files"
23-
ipld "github.com/ipfs/go-ipld-format"
2417
dag "github.com/ipfs/go-merkledag"
18+
"github.com/ipfs/go-mfs"
2519
"github.com/ipfs/go-path"
2620
"github.com/ipfs/go-path/resolver"
27-
ft "github.com/ipfs/go-unixfs"
28-
"github.com/ipfs/go-unixfs/importer"
2921
coreiface "github.com/ipfs/interface-go-ipfs-core"
3022
ipath "github.com/ipfs/interface-go-ipfs-core/path"
3123
routing "github.com/libp2p/go-libp2p-core/routing"
@@ -40,27 +32,36 @@ const (
4032
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
4133
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
4234
type gatewayHandler struct {
43-
node *core.IpfsNode
4435
config GatewayConfig
4536
api coreiface.CoreAPI
4637
}
4738

48-
func newGatewayHandler(n *core.IpfsNode, c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler {
39+
func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler {
4940
i := &gatewayHandler{
50-
node: n,
5141
config: c,
5242
api: api,
5343
}
5444
return i
5545
}
5646

57-
// TODO(cryptix): find these helpers somewhere else
58-
func (i *gatewayHandler) newDagFromReader(r io.Reader) (ipld.Node, error) {
59-
// TODO(cryptix): change and remove this helper once PR1136 is merged
60-
// return ufs.AddFromReader(i.node, r.Body)
61-
return importer.BuildDagFromReader(
62-
i.node.DAG,
63-
chunker.DefaultSplitter(r))
47+
func parseIpfsPath(p string) (cid.Cid, string, error) {
48+
rootPath, err := path.ParsePath(p)
49+
if err != nil {
50+
return cid.Cid{}, "", err
51+
}
52+
53+
// Check the path.
54+
rsegs := rootPath.Segments()
55+
if rsegs[0] != "ipfs" {
56+
return cid.Cid{}, "", fmt.Errorf("WritableGateway: only ipfs paths supported", rsegs[0])
57+
}
58+
59+
rootCid, err := cid.Decode(rsegs[1])
60+
if err != nil {
61+
return cid.Cid{}, "", err
62+
}
63+
64+
return rootCid, path.Join(rsegs[2:]), nil
6465
}
6566

6667
func (i *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -160,10 +161,12 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
160161

161162
// Resolve path to the final DAG node for the ETag
162163
resolvedPath, err := i.api.ResolvePath(r.Context(), parsedPath)
163-
if err == coreiface.ErrOffline && !i.node.IsOnline {
164+
switch err {
165+
case nil:
166+
case coreiface.ErrOffline:
164167
webError(w, "ipfs resolve -r "+escapedURLPath, err, http.StatusServiceUnavailable)
165168
return
166-
} else if err != nil {
169+
default:
167170
webError(w, "ipfs resolve -r "+escapedURLPath, err, http.StatusNotFound)
168171
return
169172
}
@@ -395,194 +398,146 @@ func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
395398
}
396399

397400
func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) {
398-
rootPath, err := path.ParsePath(r.URL.Path)
401+
ctx := r.Context()
402+
ds := i.api.Dag()
403+
404+
// Parse the path
405+
rootCid, newPath, err := parseIpfsPath(r.URL.Path)
399406
if err != nil {
400-
webError(w, "putHandler: IPFS path not valid", err, http.StatusBadRequest)
407+
webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest)
401408
return
402409
}
403-
404-
rsegs := rootPath.Segments()
405-
if rsegs[0] == ipnsPathPrefix {
406-
webError(w, "putHandler: updating named entries not supported", errors.New("WritableGateway: ipns put not supported"), http.StatusBadRequest)
410+
if newPath == "" || newPath == "/" {
411+
http.Error(w, "WritableGateway: empty path", http.StatusBadRequest)
407412
return
408413
}
414+
newDirectory, _ := gopath.Split(newPath)
409415

410-
var newnode ipld.Node
411-
if rsegs[len(rsegs)-1] == "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" {
412-
newnode = ft.EmptyDirNode()
413-
} else {
414-
putNode, err := i.newDagFromReader(r.Body)
415-
if err != nil {
416-
webError(w, "putHandler: Could not create DAG from request", err, http.StatusInternalServerError)
417-
return
418-
}
419-
newnode = putNode
420-
}
416+
// Resolve the old root.
421417

422-
var newPath string
423-
if len(rsegs) > 1 {
424-
newPath = path.Join(rsegs[2:])
418+
rnode, err := ds.Get(ctx, rootCid)
419+
if err != nil {
420+
webError(w, "WritableGateway: Could not create DAG from request", err, http.StatusInternalServerError)
421+
return
425422
}
426423

427-
var newcid cid.Cid
428-
rnode, err := resolve.Resolve(r.Context(), i.node.Namesys, i.node.Resolver, rootPath)
429-
switch ev := err.(type) {
430-
case resolver.ErrNoLink:
431-
// ev.Node < node where resolve failed
432-
// ev.Name < new link
433-
// but we need to patch from the root
434-
c, err := cid.Decode(rsegs[1])
435-
if err != nil {
436-
webError(w, "putHandler: bad input path", err, http.StatusBadRequest)
437-
return
438-
}
439-
440-
rnode, err := i.node.DAG.Get(r.Context(), c)
441-
if err != nil {
442-
webError(w, "putHandler: Could not create DAG from request", err, http.StatusInternalServerError)
443-
return
444-
}
445-
446-
pbnd, ok := rnode.(*dag.ProtoNode)
447-
if !ok {
448-
webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
449-
return
450-
}
451-
452-
e := dagutils.NewDagEditor(pbnd, i.node.DAG)
453-
err = e.InsertNodeAtPath(r.Context(), newPath, newnode, ft.EmptyDirNode)
454-
if err != nil {
455-
webError(w, "putHandler: InsertNodeAtPath failed", err, http.StatusInternalServerError)
456-
return
457-
}
458-
459-
nnode, err := e.Finalize(r.Context(), i.node.DAG)
460-
if err != nil {
461-
webError(w, "putHandler: could not get node", err, http.StatusInternalServerError)
462-
return
463-
}
424+
pbnd, ok := rnode.(*dag.ProtoNode)
425+
if !ok {
426+
webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
427+
return
428+
}
464429

465-
newcid = nnode.Cid()
430+
// Create the new file.
431+
newFilePath, err := i.api.Unixfs().Add(ctx, files.NewReaderFile(r.Body))
432+
if err != nil {
433+
webError(w, "WritableGateway: could not create DAG from request", err, http.StatusInternalServerError)
434+
return
435+
}
466436

467-
case nil:
468-
pbnd, ok := rnode.(*dag.ProtoNode)
469-
if !ok {
470-
webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
471-
return
472-
}
437+
newFile, err := ds.Get(ctx, newFilePath.Cid())
438+
if err != nil {
439+
webError(w, "WritableGateway: failed to resolve new file", err, http.StatusInternalServerError)
440+
return
441+
}
473442

474-
pbnewnode, ok := newnode.(*dag.ProtoNode)
475-
if !ok {
476-
webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
477-
return
478-
}
443+
// Patch the new file into the old root.
479444

480-
// object set-data case
481-
pbnd.SetData(pbnewnode.Data())
445+
root, err := mfs.NewRoot(ctx, ds, pbnd, nil)
446+
if err != nil {
447+
webError(w, "WritableGateway: failed to create MFS root", err, http.StatusBadRequest)
448+
return
449+
}
482450

483-
newcid = pbnd.Cid()
484-
err = i.node.DAG.Add(r.Context(), pbnd)
451+
if newDirectory != "" {
452+
err := mfs.Mkdir(root, newDirectory, mfs.MkdirOpts{Mkparents: true, Flush: false})
485453
if err != nil {
486-
nnk := newnode.Cid()
487-
webError(w, fmt.Sprintf("putHandler: Could not add newnode(%q) to root(%q)", nnk.String(), newcid.String()), err, http.StatusInternalServerError)
454+
webError(w, "WritableGateway: failed to create MFS directory", err, http.StatusInternalServerError)
488455
return
489456
}
490-
default:
491-
webError(w, "could not resolve root DAG", ev, http.StatusInternalServerError)
492-
return
493457
}
458+
err = mfs.PutNode(root, newPath, newFile)
459+
if err != nil {
460+
webError(w, "WritableGateway: failed to link file into directory", err, http.StatusInternalServerError)
461+
}
462+
nnode, err := root.GetDirectory().GetNode()
463+
if err != nil {
464+
webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError)
465+
}
466+
newcid := nnode.Cid()
494467

495468
i.addUserHeaders(w) // ok, _now_ write user's headers.
496469
w.Header().Set("IPFS-Hash", newcid.String())
497470
http.Redirect(w, r, gopath.Join(ipfsPathPrefix, newcid.String(), newPath), http.StatusCreated)
498471
}
499472

500473
func (i *gatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
501-
urlPath := r.URL.Path
474+
ctx := r.Context()
475+
476+
// parse the path
502477

503-
p, err := path.ParsePath(urlPath)
478+
rootCid, newPath, err := parseIpfsPath(r.URL.Path)
504479
if err != nil {
505-
webError(w, "failed to parse path", err, http.StatusBadRequest)
480+
webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest)
506481
return
507482
}
508-
509-
c, components, err := path.SplitAbsPath(p)
510-
if err != nil {
511-
webError(w, "Could not split path", err, http.StatusInternalServerError)
483+
if newPath == "" || newPath == "/" {
484+
http.Error(w, "WritableGateway: empty path", http.StatusBadRequest)
512485
return
513486
}
487+
directory, filename := gopath.Split(newPath)
488+
489+
// lookup the root
514490

515-
pathNodes, err := i.resolvePathComponents(r.Context(), c, components)
491+
rootNodeIPLD, err := i.api.Dag().Get(ctx, rootCid)
516492
if err != nil {
517-
webError(w, "Could not resolve path components", err, http.StatusBadRequest)
493+
webError(w, "WritableGateway: failed to resolve root CID", err, http.StatusInternalServerError)
518494
return
519495
}
520-
521-
pbnd, ok := pathNodes[len(pathNodes)-1].(*dag.ProtoNode)
496+
rootNode, ok := rootNodeIPLD.(*dag.ProtoNode)
522497
if !ok {
523-
webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
498+
http.Error(w, "WritableGateway: empty path", http.StatusInternalServerError)
524499
return
525500
}
526501

527-
// TODO(cyrptix): assumes len(pathNodes) > 1 - not found is an error above?
528-
err = pbnd.RemoveNodeLink(components[len(components)-1])
502+
// construct the mfs root
503+
504+
root, err := mfs.NewRoot(ctx, i.api.Dag(), rootNode, nil)
529505
if err != nil {
530-
webError(w, "Could not delete link", err, http.StatusBadRequest)
506+
webError(w, "WritableGateway: failed to construct the MFS root", err, http.StatusBadRequest)
531507
return
532508
}
533509

534-
var newnode *dag.ProtoNode = pbnd
535-
for j := len(pathNodes) - 2; j >= 0; j-- {
536-
if err := i.node.DAG.Add(r.Context(), newnode); err != nil {
537-
webError(w, "Could not add node", err, http.StatusInternalServerError)
538-
return
539-
}
540-
541-
pathpb, ok := pathNodes[j].(*dag.ProtoNode)
542-
if !ok {
543-
webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
544-
return
545-
}
510+
// lookup the parent directory
546511

547-
newnode, err = pathpb.UpdateNodeLink(components[j], newnode)
548-
if err != nil {
549-
webError(w, "Could not update node links", err, http.StatusInternalServerError)
550-
return
551-
}
512+
parentNode, err := mfs.Lookup(root, directory)
513+
if err != nil {
514+
webError(w, "WritableGateway: failed to look up parent", err, http.StatusInternalServerError)
515+
return
552516
}
553517

554-
if err := i.node.DAG.Add(r.Context(), newnode); err != nil {
555-
webError(w, "Could not add root node", err, http.StatusInternalServerError)
518+
parent, ok := parentNode.(*mfs.Directory)
519+
if !ok {
520+
http.Error(w, "WritableGateway: parent is not a directory", http.StatusInternalServerError)
556521
return
557522
}
558523

559-
// Redirect to new path
560-
ncid := newnode.Cid()
561-
562-
i.addUserHeaders(w) // ok, _now_ write user's headers.
563-
w.Header().Set("IPFS-Hash", ncid.String())
564-
http.Redirect(w, r, gopath.Join(ipfsPathPrefix+ncid.String(), path.Join(components[:len(components)-1])), http.StatusCreated)
565-
}
566-
567-
func (i *gatewayHandler) resolvePathComponents(
568-
ctx context.Context,
569-
c cid.Cid,
570-
components []string,
571-
) ([]ipld.Node, error) {
572-
tctx, cancel := context.WithTimeout(ctx, time.Minute)
573-
defer cancel()
524+
// delete the file
574525

575-
rootnd, err := i.node.Resolver.DAG.Get(tctx, c)
526+
err = parent.Unlink(filename)
576527
if err != nil {
577-
return nil, fmt.Errorf("Could not resolve root object: %s", err)
528+
webError(w, "WritableGateway: failed to remove file", err, http.StatusInternalServerError)
529+
return
578530
}
579531

580-
pathNodes, err := i.node.Resolver.ResolveLinks(tctx, rootnd, components[:len(components)-1])
532+
nnode, err := root.GetDirectory().GetNode()
581533
if err != nil {
582-
return nil, fmt.Errorf("Could not resolve parent object: %s", err)
534+
webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError)
583535
}
536+
ncid := nnode.Cid()
584537

585-
return pathNodes, nil
538+
i.addUserHeaders(w) // ok, _now_ write user's headers.
539+
w.Header().Set("IPFS-Hash", ncid.String())
540+
http.Redirect(w, r, gopath.Join(ipfsPathPrefix+ncid.String(), directory), http.StatusCreated)
586541
}
587542

588543
func (i *gatewayHandler) addUserHeaders(w http.ResponseWriter) {

0 commit comments

Comments
 (0)