From 88d431c8129aeef2ea5076336412cb54161342ce Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 27 Mar 2023 15:19:55 +0200 Subject: [PATCH] feat: remove writable gateway (#9743) Co-authored-by: Marcin Rataj --- cmd/ipfs/daemon.go | 19 +- cmd/ipfswatch/main.go | 2 +- config/gateway.go | 3 +- core/corehttp/corehttp.go | 2 +- core/corehttp/gateway.go | 29 +-- core/corehttp/gateway_test.go | 2 +- core/corehttp/gateway_writable.go | 265 ----------------------- docs/config.md | 9 +- test/cli/gateway_test.go | 2 +- test/sharness/lib/test-lib.sh | 9 +- test/sharness/t0060-daemon.sh | 4 +- test/sharness/t0111-gateway-writeable.sh | 136 ------------ 12 files changed, 18 insertions(+), 464 deletions(-) delete mode 100644 core/corehttp/gateway_writable.go delete mode 100755 test/sharness/t0111-gateway-writeable.sh diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 52addcc072c..85ca79246cb 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -163,7 +163,7 @@ 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.BoolOption(writableKwd, "Enable legacy Gateway.Writable (REMOVED)"), 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"), @@ -679,7 +679,7 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error for _, listener := range listeners { // we might have listened to /tcp/0 - let's see what we are listing on - fmt.Printf("API server listening on %s\n", listener.Multiaddr()) + fmt.Printf("RPC API server listening on %s\n", listener.Multiaddr()) // Browsers require TCP. switch listener.Addr().Network() { case "tcp", "tcp4", "tcp6": @@ -692,9 +692,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{ @@ -804,7 +804,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e } 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") + log.Fatalf("Support for Gateway.Writable and --writable has been REMOVED. Please remove it from your config file or CLI. Modern replacement tracked in https://github.com/ipfs/specs/issues/375") } listeners, err := sockets.TakeListeners("io.ipfs.gateway") @@ -837,13 +837,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 server listening on %s\n", listener.Multiaddr()) } cmdctx := *cctx @@ -852,7 +847,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..8ae312b59ae 100644 --- a/config/gateway.go +++ b/config/gateway.go @@ -38,8 +38,7 @@ 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 + // REMOVED: 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 diff --git a/core/corehttp/corehttp.go b/core/corehttp/corehttp.go index fe9f1b1dba5..b1a317f3c2a 100644 --- a/core/corehttp/corehttp.go +++ b/core/corehttp/corehttp.go @@ -75,7 +75,7 @@ func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...Serv // we might have listened to /tcp/0 - let's see what we are listing on addr = list.Multiaddr() - fmt.Printf("API server listening on %s\n", addr) + fmt.Printf("RPC API server listening on %s\n", addr) return Serve(n, manet.NetListener(list), options...) } 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..bb2d8b7022f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -681,14 +681,9 @@ Type: `string` (url) ### `Gateway.Writable` -**DEPRECATED**: Enables legacy PUT/POST request handling. +**REMOVED**: this option no longer available as of [Kubo 0.20](https://github.com/ipfs/kubo/blob/master/docs/changelogs/v0.20.md). -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` +We are working on developing a modern replacement. To support our efforts, please leave a comment describing your use case in [ipfs/specs#375](https://github.com/ipfs/specs/issues/375). ### `Gateway.PathPrefixes` diff --git a/test/cli/gateway_test.go b/test/cli/gateway_test.go index 4c40729c708..1d6ac45b965 100644 --- a/test/cli/gateway_test.go +++ b/test/cli/gateway_test.go @@ -436,7 +436,7 @@ func TestGateway(t *testing.T) { t.Run("verify gateway file", func(t *testing.T) { t.Parallel() - r := regexp.MustCompile(`Gateway \(readonly\) server listening on (?P.+)\s`) + r := regexp.MustCompile(`Gateway server listening on (?P.+)\s`) matches := r.FindStringSubmatch(node.Daemon.Stdout.String()) ma, err := multiaddr.NewMultiaddr(matches[1]) require.NoError(t, err) diff --git a/test/sharness/lib/test-lib.sh b/test/sharness/lib/test-lib.sh index 3aecaec994b..1f1491f9ebf 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 @@ -247,7 +240,7 @@ test_set_address_vars() { API_ADDR=$(convert_tcp_maddr $API_MADDR) && API_PORT=$(port_from_maddr $API_MADDR) && - GWAY_MADDR=$(sed -n "s/^Gateway (.*) server listening on //p" "$daemon_output") && + GWAY_MADDR=$(sed -n "s/^Gateway server listening on //p" "$daemon_output") && GWAY_ADDR=$(convert_tcp_maddr $GWAY_MADDR) && GWAY_PORT=$(port_from_maddr $GWAY_MADDR) ' diff --git a/test/sharness/t0060-daemon.sh b/test/sharness/t0060-daemon.sh index b237e143f66..f43708b1df5 100755 --- a/test/sharness/t0060-daemon.sh +++ b/test/sharness/t0060-daemon.sh @@ -89,9 +89,9 @@ test_expect_success "ipfs daemon output looks good" ' echo "" >>expected_daemon && sed "s/^/Swarm listening on /" listen_addrs >>expected_daemon && sed "s/^/Swarm announcing /" local_addrs >>expected_daemon && - echo "API server listening on '$API_MADDR'" >>expected_daemon && + echo "RPC API server listening on '$API_MADDR'" >>expected_daemon && echo "WebUI: http://'$API_ADDR'/webui" >>expected_daemon && - echo "Gateway (readonly) server listening on '$GWAY_MADDR'" >>expected_daemon && + echo "Gateway server listening on '$GWAY_MADDR'" >>expected_daemon && echo "Daemon is ready" >>expected_daemon && test_cmp expected_daemon actual_daemon ' 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