From a2ecae7fee6125f594c2e0c1de891170347a94df Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 30 Mar 2021 10:26:53 +0100 Subject: [PATCH 1/3] feat: serve CBOR encoded DAG nodes from the gateway --- core/corehttp/gateway_handler.go | 63 ++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 0275c873d1e..2338ec342dc 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -1,7 +1,9 @@ package corehttp import ( + "bytes" "context" + "encoding/json" "fmt" "html/template" "io" @@ -21,6 +23,7 @@ import ( "github.com/ipfs/go-cid" files "github.com/ipfs/go-ipfs-files" assets "github.com/ipfs/go-ipfs/assets" + format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" mfs "github.com/ipfs/go-mfs" path "github.com/ipfs/go-path" @@ -264,6 +267,16 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request return } + if resolvedPath.Cid().Prefix().Codec == cid.DagCBOR { + n, err := i.api.Dag().Get(r.Context(), resolvedPath.Cid()) + if err != nil { + webError(w, "ipfs dag get "+escapedURLPath, err, http.StatusNotFound) + return + } + i.serveCBOR(w, r, n) + return + } + dr, err := i.api.Unixfs().Get(r.Context(), resolvedPath) if err != nil { webError(w, "ipfs cat "+escapedURLPath, err, http.StatusNotFound) @@ -309,18 +322,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request modtime = time.Unix(1, 0) } - urlFilename := r.URL.Query().Get("filename") - var name string - if urlFilename != "" { - disposition := "inline" - if r.URL.Query().Get("download") == "true" { - disposition = "attachment" - } - utf8Name := url.PathEscape(urlFilename) - asciiName := url.PathEscape(onlyAscii.ReplaceAllLiteralString(urlFilename, "_")) - w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"; filename*=UTF-8''%s", disposition, asciiName, utf8Name)) - name = urlFilename - } else { + setContentDispositionHeader(r.URL.Query(), w.Header()) + name := r.URL.Query().Get("filename") + if name == "" { name = getFilename(urlPath) } i.serveFile(w, r, name, modtime, f) @@ -523,6 +527,25 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam http.ServeContent(w, req, name, modtime, content) } +func (i *gatewayHandler) serveCBOR(w http.ResponseWriter, r *http.Request, n format.Node) { + w.Header().Set("Cache-Control", "public, max-age=29030400, immutable") + w.Header().Set("Content-Type", "application/json") + setContentDispositionHeader(r.URL.Query(), w.Header()) + + name := r.URL.Query().Get("filename") + if name == "" { + name = getFilename(r.URL.Path) + } + modtime := time.Unix(1, 0) + + b, err := json.Marshal(n) + if err != nil { + internalWebError(w, err) + return + } + http.ServeContent(w, r, name, modtime, bytes.NewReader(b)) +} + func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.Request, parsedPath ipath.Path) bool { resolved404Path, ctype, err := i.searchUpTreeFor404(r, parsedPath) if err != nil { @@ -838,3 +861,19 @@ func fixupSuperfluousNamespace(w http.ResponseWriter, urlPath string, urlQuery s ErrorMsg: fmt.Sprintf("invalid path: %q should be %q", urlPath, intendedPath.String()), }) == nil } + +// setContentDispositionHeader sets the Content-Disposition header if a +// "filename" was included in the URL querystring. +func setContentDispositionHeader(qs url.Values, h http.Header) { + urlFilename := qs.Get("filename") + if urlFilename == "" { + return + } + disposition := "inline" + if qs.Get("download") == "true" { + disposition = "attachment" + } + utf8Name := url.PathEscape(urlFilename) + asciiName := url.PathEscape(onlyAscii.ReplaceAllLiteralString(urlFilename, "_")) + h.Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"; filename*=UTF-8''%s", disposition, asciiName, utf8Name)) +} From ea2bb34a7ad0704077180936f1ec3fe7ec25c6d2 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 30 Mar 2021 13:44:00 +0100 Subject: [PATCH 2/3] refactor: maybe to indicate header not always set --- core/corehttp/gateway_handler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 2338ec342dc..e24160e67fa 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -322,7 +322,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request modtime = time.Unix(1, 0) } - setContentDispositionHeader(r.URL.Query(), w.Header()) + maybeSetContentDispositionHeader(r.URL.Query(), w.Header()) name := r.URL.Query().Get("filename") if name == "" { name = getFilename(urlPath) @@ -530,7 +530,7 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam func (i *gatewayHandler) serveCBOR(w http.ResponseWriter, r *http.Request, n format.Node) { w.Header().Set("Cache-Control", "public, max-age=29030400, immutable") w.Header().Set("Content-Type", "application/json") - setContentDispositionHeader(r.URL.Query(), w.Header()) + maybeSetContentDispositionHeader(r.URL.Query(), w.Header()) name := r.URL.Query().Get("filename") if name == "" { @@ -862,9 +862,9 @@ func fixupSuperfluousNamespace(w http.ResponseWriter, urlPath string, urlQuery s }) == nil } -// setContentDispositionHeader sets the Content-Disposition header if a +// maybeSetContentDispositionHeader sets the Content-Disposition header if a // "filename" was included in the URL querystring. -func setContentDispositionHeader(qs url.Values, h http.Header) { +func maybeSetContentDispositionHeader(qs url.Values, h http.Header) { urlFilename := qs.Get("filename") if urlFilename == "" { return From 0d58d3aa037d114ac9a65cb426156c2e4f3d9adf Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 2 Apr 2021 14:19:01 +0100 Subject: [PATCH 3/3] refactor: serve CBOR bytes or JSON if ?enc=json or Content-Type header set --- core/corehttp/gateway_handler.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index e24160e67fa..11e5db67121 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -529,7 +529,6 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam func (i *gatewayHandler) serveCBOR(w http.ResponseWriter, r *http.Request, n format.Node) { w.Header().Set("Cache-Control", "public, max-age=29030400, immutable") - w.Header().Set("Content-Type", "application/json") maybeSetContentDispositionHeader(r.URL.Query(), w.Header()) name := r.URL.Query().Get("filename") @@ -538,12 +537,23 @@ func (i *gatewayHandler) serveCBOR(w http.ResponseWriter, r *http.Request, n for } modtime := time.Unix(1, 0) - b, err := json.Marshal(n) - if err != nil { - internalWebError(w, err) - return + var contentType string + var data []byte + if r.URL.Query().Get("enc") == "json" || r.Header.Get("Content-Type") == "application/json" { + contentType = "application/json" + b, err := json.Marshal(n) + if err != nil { + internalWebError(w, err) + return + } + data = b + } else { + contentType = "application/cbor" + data = n.RawData() } - http.ServeContent(w, r, name, modtime, bytes.NewReader(b)) + + w.Header().Set("Content-Type", contentType) + http.ServeContent(w, r, name, modtime, bytes.NewReader(data)) } func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.Request, parsedPath ipath.Path) bool {