diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 52addcc072c..7f1c3d44e46 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -67,7 +67,6 @@ const ( routingOptionAutoClientKwd = "autoclient" unencryptTransportKwd = "disable-transport-encryption" unrestrictedAPIAccessKwd = "unrestricted-api" - writableKwd = "writable" enablePubSubKwd = "enable-pubsub-experiment" enableIPNSPubSubKwd = "enable-namesys-pubsub" enableMultiplexKwd = "enable-mplex-experiment" @@ -163,7 +162,6 @@ Headers. cmds.StringOption(initProfileOptionKwd, "Configuration profiles to apply for --init. See ipfs init --help for more"), cmds.StringOption(routingOptionKwd, "Overrides the routing option").WithDefault(routingOptionDefaultKwd), cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem using FUSE (experimental)"), - cmds.BoolOption(writableKwd, "Enable legacy Gateway.Writable (deprecated)"), cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount). Defaults to config setting."), cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount). Defaults to config setting."), cmds.BoolOption(unrestrictedAPIAccessKwd, "Allow API access to unlisted hashes"), @@ -692,9 +690,9 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error // only the webui objects are allowed. // if you know what you're doing, go ahead and pass --unrestricted-api. unrestricted, _ := req.Options[unrestrictedAPIAccessKwd].(bool) - gatewayOpt := corehttp.GatewayOption(false, corehttp.WebUIPaths...) + gatewayOpt := corehttp.GatewayOption(corehttp.WebUIPaths...) if unrestricted { - gatewayOpt = corehttp.GatewayOption(true, "/ipfs", "/ipns") + gatewayOpt = corehttp.GatewayOption("/ipfs", "/ipns") } var opts = []corehttp.ServeOption{ @@ -798,15 +796,6 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e return nil, fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err) } - writable, writableOptionFound := req.Options[writableKwd].(bool) - if !writableOptionFound { - writable = cfg.Gateway.Writable.WithDefault(false) - } - - if writable { - log.Error("serveHTTPGateway: legacy Gateway.Writable is DEPRECATED and will be removed or changed in future versions. If you are still using this, provide feedback in https://github.com/ipfs/specs/issues/375") - } - listeners, err := sockets.TakeListeners("io.ipfs.gateway") if err != nil { return nil, fmt.Errorf("serveHTTPGateway: socket activation failed: %s", err) @@ -837,13 +826,8 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e } // we might have listened to /tcp/0 - let's see what we are listing on - gwType := "readonly" - if writable { - gwType = "writable" - } - for _, listener := range listeners { - fmt.Printf("Gateway (%s) server listening on %s\n", gwType, listener.Multiaddr()) + fmt.Printf("Gateway (readonly) server listening on %s\n", listener.Multiaddr()) } cmdctx := *cctx @@ -852,7 +836,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e var opts = []corehttp.ServeOption{ corehttp.MetricsCollectionOption("gateway"), corehttp.HostnameOption(), - corehttp.GatewayOption(writable, "/ipfs", "/ipns"), + corehttp.GatewayOption("/ipfs", "/ipns"), corehttp.VersionOption(), corehttp.CheckVersionOption(), corehttp.CommandsROOption(cmdctx), diff --git a/cmd/ipfswatch/main.go b/cmd/ipfswatch/main.go index 06215687c05..2c333fc2336 100644 --- a/cmd/ipfswatch/main.go +++ b/cmd/ipfswatch/main.go @@ -94,7 +94,7 @@ func run(ipfsPath, watchPath string) error { if *http { addr := "/ip4/127.0.0.1/tcp/5001" var opts = []corehttp.ServeOption{ - corehttp.GatewayOption(true, "/ipfs", "/ipns"), + corehttp.GatewayOption("/ipfs", "/ipns"), corehttp.WebUIOption, corehttp.CommandsOption(cmdCtx(node, ipfsPath)), } diff --git a/config/gateway.go b/config/gateway.go index de43dd39e11..5fce4be6724 100644 --- a/config/gateway.go +++ b/config/gateway.go @@ -38,10 +38,6 @@ type Gateway struct { // should be redirected. RootRedirect string - // DEPRECATED: Enables legacy PUT/POST request handling. - // Modern replacement tracked in https://github.com/ipfs/specs/issues/375 - Writable Flag `json:",omitempty"` - // PathPrefixes was removed: https://github.com/ipfs/go-ipfs/issues/7702 PathPrefixes []string diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index c20ab6e4a4f..f77e941d9ee 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -24,18 +24,13 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -func GatewayOption(writable bool, paths ...string) ServeOption { +func GatewayOption(paths ...string) ServeOption { return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { cfg, err := n.Repo.Config() if err != nil { return nil, err } - api, err := coreapi.NewCoreAPI(n, options.Api.FetchBlocks(!cfg.Gateway.NoFetch)) - if err != nil { - return nil, err - } - headers := make(map[string][]string, len(cfg.Gateway.HTTPHeaders)) for h, v := range cfg.Gateway.HTTPHeaders { headers[http.CanonicalHeaderKey(h)] = v @@ -58,28 +53,6 @@ func GatewayOption(writable bool, paths ...string) ServeOption { // By default, our HTTP handler is the gateway handler. handler := gw.ServeHTTP - // If we have the writable gateway enabled, we have to replace our - // http handler by a handler that takes care of the different methods. - if writable { - writableGw := &writableGatewayHandler{ - config: &gwConfig, - api: api, - } - - handler = func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodPost: - writableGw.postHandler(w, r) - case http.MethodDelete: - writableGw.deleteHandler(w, r) - case http.MethodPut: - writableGw.putHandler(w, r) - default: - gw.ServeHTTP(w, r) - } - } - } - for _, p := range paths { mux.HandleFunc(p+"/", handler) } diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index d4e357740bd..03b04350dbd 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -124,7 +124,7 @@ func newTestServerAndNode(t *testing.T, ns mockNamesys) (*httptest.Server, iface dh.Handler, err = makeHandler(n, ts.Listener, HostnameOption(), - GatewayOption(false, "/ipfs", "/ipns"), + GatewayOption("/ipfs", "/ipns"), VersionOption(), ) if err != nil { diff --git a/core/corehttp/gateway_writable.go b/core/corehttp/gateway_writable.go deleted file mode 100644 index 34cd8438ea7..00000000000 --- a/core/corehttp/gateway_writable.go +++ /dev/null @@ -1,265 +0,0 @@ -package corehttp - -import ( - "context" - "fmt" - "net/http" - "os" - gopath "path" - - cid "github.com/ipfs/go-cid" - ipld "github.com/ipfs/go-ipld-format" - "github.com/ipfs/go-libipfs/files" - "github.com/ipfs/go-libipfs/gateway" - dag "github.com/ipfs/go-merkledag" - "github.com/ipfs/go-mfs" - path "github.com/ipfs/go-path" - "github.com/ipfs/go-path/resolver" - iface "github.com/ipfs/interface-go-ipfs-core" - routing "github.com/libp2p/go-libp2p/core/routing" -) - -const ( - ipfsPathPrefix = "/ipfs/" -) - -type writableGatewayHandler struct { - api iface.CoreAPI - config *gateway.Config -} - -func (i *writableGatewayHandler) addUserHeaders(w http.ResponseWriter) { - for k, v := range i.config.Headers { - w.Header()[http.CanonicalHeaderKey(k)] = v - } -} - -func (i *writableGatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) { - p, err := i.api.Unixfs().Add(r.Context(), files.NewReaderFile(r.Body)) - if err != nil { - internalWebError(w, err) - return - } - - i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", p.Cid().String()) - log.Debugw("CID created, http redirect", "from", r.URL, "to", p, "status", http.StatusCreated) - http.Redirect(w, r, p.String(), http.StatusCreated) -} - -func (i *writableGatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - ds := i.api.Dag() - - // Parse the path - rootCid, newPath, err := parseIpfsPath(r.URL.Path) - if err != nil { - webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest) - return - } - if newPath == "" || newPath == "/" { - http.Error(w, "WritableGateway: empty path", http.StatusBadRequest) - return - } - newDirectory, newFileName := gopath.Split(newPath) - - // Resolve the old root. - - rnode, err := ds.Get(ctx, rootCid) - if err != nil { - webError(w, "WritableGateway: Could not create DAG from request", err, http.StatusInternalServerError) - return - } - - pbnd, ok := rnode.(*dag.ProtoNode) - if !ok { - webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest) - return - } - - // Create the new file. - newFilePath, err := i.api.Unixfs().Add(ctx, files.NewReaderFile(r.Body)) - if err != nil { - webError(w, "WritableGateway: could not create DAG from request", err, http.StatusInternalServerError) - return - } - - newFile, err := ds.Get(ctx, newFilePath.Cid()) - if err != nil { - webError(w, "WritableGateway: failed to resolve new file", err, http.StatusInternalServerError) - return - } - - // Patch the new file into the old root. - - root, err := mfs.NewRoot(ctx, ds, pbnd, nil) - if err != nil { - webError(w, "WritableGateway: failed to create MFS root", err, http.StatusBadRequest) - return - } - - if newDirectory != "" { - err := mfs.Mkdir(root, newDirectory, mfs.MkdirOpts{Mkparents: true, Flush: false}) - if err != nil { - webError(w, "WritableGateway: failed to create MFS directory", err, http.StatusInternalServerError) - return - } - } - dirNode, err := mfs.Lookup(root, newDirectory) - if err != nil { - webError(w, "WritableGateway: failed to lookup directory", err, http.StatusInternalServerError) - return - } - dir, ok := dirNode.(*mfs.Directory) - if !ok { - http.Error(w, "WritableGateway: target directory is not a directory", http.StatusBadRequest) - return - } - err = dir.Unlink(newFileName) - switch err { - case os.ErrNotExist, nil: - default: - webError(w, "WritableGateway: failed to replace existing file", err, http.StatusBadRequest) - return - } - err = dir.AddChild(newFileName, newFile) - if err != nil { - webError(w, "WritableGateway: failed to link file into directory", err, http.StatusInternalServerError) - return - } - nnode, err := root.GetDirectory().GetNode() - if err != nil { - webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError) - return - } - newcid := nnode.Cid() - - i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", newcid.String()) - - redirectURL := gopath.Join(ipfsPathPrefix, newcid.String(), newPath) - log.Debugw("CID replaced, redirect", "from", r.URL, "to", redirectURL, "status", http.StatusCreated) - http.Redirect(w, r, redirectURL, http.StatusCreated) -} - -func (i *writableGatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // parse the path - - rootCid, newPath, err := parseIpfsPath(r.URL.Path) - if err != nil { - webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest) - return - } - if newPath == "" || newPath == "/" { - http.Error(w, "WritableGateway: empty path", http.StatusBadRequest) - return - } - directory, filename := gopath.Split(newPath) - - // lookup the root - - rootNodeIPLD, err := i.api.Dag().Get(ctx, rootCid) - if err != nil { - webError(w, "WritableGateway: failed to resolve root CID", err, http.StatusInternalServerError) - return - } - rootNode, ok := rootNodeIPLD.(*dag.ProtoNode) - if !ok { - http.Error(w, "WritableGateway: empty path", http.StatusInternalServerError) - return - } - - // construct the mfs root - - root, err := mfs.NewRoot(ctx, i.api.Dag(), rootNode, nil) - if err != nil { - webError(w, "WritableGateway: failed to construct the MFS root", err, http.StatusBadRequest) - return - } - - // lookup the parent directory - - parentNode, err := mfs.Lookup(root, directory) - if err != nil { - webError(w, "WritableGateway: failed to look up parent", err, http.StatusInternalServerError) - return - } - - parent, ok := parentNode.(*mfs.Directory) - if !ok { - http.Error(w, "WritableGateway: parent is not a directory", http.StatusInternalServerError) - return - } - - // delete the file - - switch parent.Unlink(filename) { - case nil, os.ErrNotExist: - default: - webError(w, "WritableGateway: failed to remove file", err, http.StatusInternalServerError) - return - } - - nnode, err := root.GetDirectory().GetNode() - if err != nil { - webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError) - return - } - ncid := nnode.Cid() - - i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", ncid.String()) - - redirectURL := gopath.Join(ipfsPathPrefix+ncid.String(), directory) - // note: StatusCreated is technically correct here as we created a new resource. - log.Debugw("CID deleted, redirect", "from", r.RequestURI, "to", redirectURL, "status", http.StatusCreated) - http.Redirect(w, r, redirectURL, http.StatusCreated) -} - -func parseIpfsPath(p string) (cid.Cid, string, error) { - rootPath, err := path.ParsePath(p) - if err != nil { - return cid.Cid{}, "", err - } - - // Check the path. - rsegs := rootPath.Segments() - if rsegs[0] != "ipfs" { - return cid.Cid{}, "", fmt.Errorf("WritableGateway: only ipfs paths supported") - } - - rootCid, err := cid.Decode(rsegs[1]) - if err != nil { - return cid.Cid{}, "", err - } - - return rootCid, path.Join(rsegs[2:]), nil -} - -func webError(w http.ResponseWriter, message string, err error, defaultCode int) { - if _, ok := err.(resolver.ErrNoLink); ok { - webErrorWithCode(w, message, err, http.StatusNotFound) - } else if err == routing.ErrNotFound { - webErrorWithCode(w, message, err, http.StatusNotFound) - } else if ipld.IsNotFound(err) { - webErrorWithCode(w, message, err, http.StatusNotFound) - } else if err == context.DeadlineExceeded { - webErrorWithCode(w, message, err, http.StatusRequestTimeout) - } else { - webErrorWithCode(w, message, err, defaultCode) - } -} - -func webErrorWithCode(w http.ResponseWriter, message string, err error, code int) { - http.Error(w, fmt.Sprintf("%s: %s", message, err), code) - if code >= 500 { - log.Warnf("server error: %s: %s", message, err) - } -} - -// return a 500 error and log -func internalWebError(w http.ResponseWriter, err error) { - webErrorWithCode(w, "internalWebError", err, http.StatusInternalServerError) -} diff --git a/docs/config.md b/docs/config.md index 2e9fff51265..74d3600b962 100644 --- a/docs/config.md +++ b/docs/config.md @@ -681,14 +681,7 @@ Type: `string` (url) ### `Gateway.Writable` -**DEPRECATED**: Enables legacy PUT/POST request handling. - -This API is not standardized, and should not be used for new projects. -We are working on a modern replacement. IPIP can be tracked in [ipfs/specs#375](https://github.com/ipfs/specs/issues/375). - -Default: `false` - -Type: `bool` +**REMOVED**: this option has been removed. We are working on a modern replacement. IPIP can be tracked in [ipfs/specs#375](https://github.com/ipfs/specs/issues/375). ### `Gateway.PathPrefixes` diff --git a/test/sharness/lib/test-lib.sh b/test/sharness/lib/test-lib.sh index 3aecaec994b..8b57fb1a1bd 100644 --- a/test/sharness/lib/test-lib.sh +++ b/test/sharness/lib/test-lib.sh @@ -214,13 +214,6 @@ test_init_ipfs() { } -test_config_ipfs_gateway_writable() { - test_expect_success "prepare config -- gateway writable" ' - test_config_set --bool Gateway.Writable true || - test_fsh cat "\"$IPFS_PATH/config\"" - ' -} - test_wait_for_file() { loops=$1 delay=$2 diff --git a/test/sharness/t0111-gateway-writeable.sh b/test/sharness/t0111-gateway-writeable.sh deleted file mode 100755 index 115114c890a..00000000000 --- a/test/sharness/t0111-gateway-writeable.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2014 Christian Couder -# MIT Licensed; see the LICENSE file in this repository. -# - -test_description="Test HTTP Gateway (Writable)" - -. lib/test-lib.sh - -test_init_ipfs - -test_launch_ipfs_daemon --writable -test_expect_success "ipfs daemon --writable overrides config" ' - curl -v -X POST http://$GWAY_ADDR/ipfs/ 2> outfile && - grep "HTTP/1.1 201 Created" outfile && - grep "Location: /ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH" outfile -' -test_kill_ipfs_daemon - -test_config_ipfs_gateway_writable -test_launch_ipfs_daemon --writable=false -test_expect_success "ipfs daemon --writable=false overrides Writable=true config" ' - curl -v -X POST http://$GWAY_ADDR/ipfs/ 2> outfile && - grep "HTTP/1.1 405 Method Not Allowed" outfile -' -test_kill_ipfs_daemon -test_launch_ipfs_daemon - -port=$GWAY_PORT - -test_expect_success "ipfs daemon up" ' - pollEndpoint -host $GWAY_MADDR -v -tout=1s -tries=60 2>poll_apierr > poll_apiout || - test_fsh cat poll_apierr || test_fsh cat poll_apiout -' - -test_expect_success "deprecation notice is printed when Gateway.Writable=true" ' - test_should_contain "legacy Gateway.Writable is DEPRECATED and will be removed or changed in future versions. If you are still using this, provide feedback in https://github.com/ipfs/specs/issues/375" daemon_err -' - -test_expect_success "HTTP gateway gives access to sample file" ' - curl -s -o welcome "http://$GWAY_ADDR/ipfs/$HASH_WELCOME_DOCS/readme" && - grep "Hello and Welcome to IPFS!" welcome -' - -test_expect_success "HTTP POST file gives Hash" ' - echo "$RANDOM" >infile && - URL="http://127.0.0.1:$port/ipfs/" && - curl -svX POST --data-binary @infile "$URL" 2>curl_post.out && - grep "HTTP/1.1 201 Created" curl_post.out && - LOCATION=$(grep Location curl_post.out) && - HASH=$(echo $LOCATION | cut -d":" -f2- |tr -d " \n\r") -' - -test_expect_success "We can HTTP GET file just created" ' - URL="http://127.0.0.1:${port}${HASH}" && - curl -so outfile "$URL" && - test_cmp infile outfile -' - -test_expect_success "We got the correct hash" ' - ADD_HASH="/ipfs/$(ipfs add -q infile)" && - test "x$ADD_HASH" = "x$HASH" || test_fsh echo "$ADD_HASH != $HASH" -' - -test_expect_success "HTTP GET empty directory" ' - URL="http://127.0.0.1:$port/ipfs/$HASH_EMPTY_DIR/" && - echo "GET $URL" && - curl -so outfile "$URL" 2>curl_getEmpty.out && - cat outfile | tr -s "\n" " " | grep "Index of /ipfs/$HASH_EMPTY_DIR" -' - -test_expect_success "HTTP PUT file to construct a hierarchy" ' - echo "$RANDOM" >infile && - URL="http://127.0.0.1:$port/ipfs/$HASH_EMPTY_DIR/test.txt" && - echo "PUT $URL" && - curl -svX PUT --data-binary @infile "$URL" 2>curl_put.out && - grep "HTTP/1.1 201 Created" curl_put.out && - LOCATION=$(grep Location curl_put.out) && - HASH=$(expr "$LOCATION" : "< Location: /ipfs/\(.*\)/test.txt") -' - -test_expect_success "We can HTTP GET file just created" ' - URL="http://127.0.0.1:$port/ipfs/$HASH/test.txt" && - echo "GET $URL" && - curl -so outfile "$URL" && - test_cmp infile outfile -' - -test_expect_success "HTTP PUT file to append to existing hierarchy" ' - echo "$RANDOM" >infile2 && - URL="http://127.0.0.1:$port/ipfs/$HASH/test/test.txt" && - echo "PUT $URL" && - curl -svX PUT --data-binary @infile2 "$URL" 2>curl_putAgain.out && - grep "HTTP/1.1 201 Created" curl_putAgain.out && - LOCATION=$(grep Location curl_putAgain.out) && - HASH=$(expr "$LOCATION" : "< Location: /ipfs/\(.*\)/test/test.txt") -' - - -test_expect_success "We can HTTP GET file just updated" ' - URL="http://127.0.0.1:$port/ipfs/$HASH/test/test.txt" && - echo "GET $URL" && - curl -svo outfile2 "$URL" 2>curl_getAgain.out && - test_cmp infile2 outfile2 -' - -test_expect_success "HTTP PUT to replace a directory" ' - echo "$RANDOM" >infile3 && - URL="http://127.0.0.1:$port/ipfs/$HASH/test" && - echo "PUT $URL" && - curl -svX PUT --data-binary @infile3 "$URL" 2>curl_putOverDirectory.out && - grep "HTTP/1.1 201 Created" curl_putOverDirectory.out && - LOCATION=$(grep Location curl_putOverDirectory.out) && - HASH=$(expr "$LOCATION" : "< Location: /ipfs/\(.*\)/test") -' - -test_expect_success "We can HTTP GET file just put over a directory" ' - URL="http://127.0.0.1:$port/ipfs/$HASH/test" && - echo "GET $URL" && - curl -svo outfile3 "$URL" 2>curl_getOverDirectory.out && - test_cmp infile3 outfile3 -' - -test_expect_success "HTTP PUT to /ipns fails" ' - PEERID=`ipfs id --format=""` && - URL="http://127.0.0.1:$port/ipns/$PEERID/test.txt" && - echo "PUT $URL" && - curl -svX PUT --data-binary @infile1 "$URL" 2>curl_putIpns.out && - grep "HTTP/1.1 400 Bad Request" curl_putIpns.out -' - - -test_kill_ipfs_daemon - -test_done