Skip to content

Commit

Permalink
feat: upgrade deps, expose http utility methods
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg authored and hannahhoward committed Jun 22, 2023
1 parent 0dff3ca commit 1526265
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 148 deletions.
151 changes: 3 additions & 148 deletions pkg/server/http/ipfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import (
"errors"
"fmt"
"net/http"
"path/filepath"
"strconv"
"strings"

lassie "github.com/filecoin-project/lassie/pkg/lassie"
"github.com/filecoin-project/lassie/pkg/retriever"
Expand Down Expand Up @@ -57,13 +55,13 @@ func ipfsHandler(lassie *lassie.Lassie, cfg HttpServerConfig) func(http.Response
return
}

includeDupes, err := checkFormat(req)
includeDupes, err := CheckFormat(req)
if err != nil {
errorResponse(res, statusLogger, http.StatusBadRequest, err)
return
}

fileName, err := parseFilename(req)
fileName, err := ParseFilename(req)
if err != nil {
errorResponse(res, statusLogger, http.StatusBadRequest, err)
return
Expand All @@ -78,7 +76,7 @@ func ipfsHandler(lassie *lassie.Lassie, cfg HttpServerConfig) func(http.Response
return
}

dagScope, err := parseScope(req)
dagScope, err := ParseScope(req)
if err != nil {
errorResponse(res, statusLogger, http.StatusBadRequest, err)
return
Expand Down Expand Up @@ -258,156 +256,13 @@ func (l statusLogger) logStatus(statusCode int, message string) {
logger.Infof("%s\t%s\t%d: %s\n", l.method, l.path, statusCode, message)
}

// checkFormat validates that the data being requested is of the type CAR.
// We do this validation because the http gateway path spec allows for additional
// response formats that Lassie does not currently support, so we throw an error in
// the cases where the request is requesting one of Lassie's unsupported response
// formats. Lassie only supports returning CAR data.
//
// The spec outlines that the requesting format can be provided
// via the Accept header or the format query parameter.
//
// Lassie only allows the application/vnd.ipld.car Accept header
// https://specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header
//
// Lassie only allows the "car" format query parameter
// https://specs.ipfs.tech/http-gateways/path-gateway/#format-request-query-parameter
func checkFormat(req *http.Request) (bool, error) {
hasAccept := req.Header.Get("Accept") != ""
// check if Accept header includes application/vnd.ipld.car
validAccept, includeDupes := parceAccept(req.Header.Get("Accept"))
if hasAccept && !validAccept {
return false, fmt.Errorf("no acceptable content type")
}

// check if format is "car"
hasFormat := req.URL.Query().Has("format")
if hasFormat && req.URL.Query().Get("format") != FormatParameterCar {
return false, fmt.Errorf("requested non-supported format %s", req.URL.Query().Get("format"))
}

// if neither are provided return
// one of them has to be given with a CAR type since we only return CAR data
if !validAccept && !hasFormat {
return false, fmt.Errorf("neither a valid accept header or format parameter were provided")
}

return includeDupes, nil
}

// parseAccept validates that the request Accept header is of the type CAR and
// returns whether or not duplicate blocks are allowed in the response via
// IPIP-412: https://github.com/ipfs/specs/pull/412.
func parceAccept(acceptHeader string) (validAccept bool, includeDupes bool) {
acceptTypes := strings.Split(acceptHeader, ",")
validAccept = false
includeDupes = DefaultIncludeDupes
for _, acceptType := range acceptTypes {
typeParts := strings.Split(acceptType, ";")
if typeParts[0] == "*/*" || typeParts[0] == "application/*" || typeParts[0] == MimeTypeCar {
validAccept = true
if typeParts[0] == MimeTypeCar {
// parse additional car attributes outlined in IPIP-412: https://github.com/ipfs/specs/pull/412
for _, nextPart := range typeParts[1:] {
pair := strings.Split(nextPart, "=")
if len(pair) == 2 {
attr := strings.TrimSpace(pair[0])
value := strings.TrimSpace(pair[1])
switch attr {
case "dups":
switch value {
case "y":
includeDupes = true
case "n":
includeDupes = false
default:
// don't accept unexpected values
validAccept = false
}
case "version":
switch value {
case MimeTypeCarVersion:
default:
validAccept = false
}
case "order":
switch value {
case "dfs":
case "unk":
default:
// we only do dfs, which also satisfies unk, future extensions are not yet supported
validAccept = false
}
default:
// ignore others
}
}
}
}
// only break if further validation didn't fail
if validAccept {
break
}
}
}
return
}

// parseFilename returns the filename query parameter or an error if the filename
// extension is not ".car". Lassie only supports returning CAR data.
// See https://specs.ipfs.tech/http-gateways/path-gateway/#filename-request-query-parameter
func parseFilename(req *http.Request) (string, error) {
// check if provided filename query parameter has .car extension
if req.URL.Query().Has("filename") {
filename := req.URL.Query().Get("filename")
ext := filepath.Ext(filename)
if ext == "" {
return "", errors.New("filename missing extension")
}
if ext != FilenameExtCar {
return "", fmt.Errorf("filename uses non-supported extension %s", ext)
}
return filename, nil
}
return "", nil
}

func parseProtocols(req *http.Request) ([]multicodec.Code, error) {
if req.URL.Query().Has("protocols") {
return types.ParseProtocolsString(req.URL.Query().Get("protocols"))
}
return nil, nil
}

func parseScope(req *http.Request) (types.DagScope, error) {
if req.URL.Query().Has("dag-scope") {
switch req.URL.Query().Get("dag-scope") {
case "all":
return types.DagScopeAll, nil
case "entity":
return types.DagScopeEntity, nil
case "block":
return types.DagScopeBlock, nil
default:
return types.DagScopeAll, errors.New("invalid dag-scope parameter")
}
}
// check for legacy param name -- to do -- delete once we confirm this isn't used any more
if req.URL.Query().Has("car-scope") {
switch req.URL.Query().Get("car-scope") {
case "all":
return types.DagScopeAll, nil
case "file":
return types.DagScopeEntity, nil
case "block":
return types.DagScopeBlock, nil
default:
return types.DagScopeAll, errors.New("invalid car-scope parameter")
}
}
return types.DagScopeAll, nil
}

func parseProviders(req *http.Request) ([]peer.AddrInfo, error) {
if req.URL.Query().Has("providers") {
fixedPeers, err := types.ParseProviderStrings(req.URL.Query().Get("providers"))
Expand Down
156 changes: 156 additions & 0 deletions pkg/server/http/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package httpserver

import (
"errors"
"fmt"
"net/http"
"path/filepath"
"strings"

"github.com/filecoin-project/lassie/pkg/types"
)

// ParseScope returns the dag-scope query parameter or an error if the dag-scope
// parameter is not one of the supported values.
func ParseScope(req *http.Request) (types.DagScope, error) {
if req.URL.Query().Has("dag-scope") {
switch req.URL.Query().Get("dag-scope") {
case "all":
return types.DagScopeAll, nil
case "entity":
return types.DagScopeEntity, nil
case "block":
return types.DagScopeBlock, nil
default:
return types.DagScopeAll, errors.New("invalid dag-scope parameter")
}
}
// check for legacy param name -- to do -- delete once we confirm this isn't used any more
if req.URL.Query().Has("car-scope") {
switch req.URL.Query().Get("car-scope") {
case "all":
return types.DagScopeAll, nil
case "file":
return types.DagScopeEntity, nil
case "block":
return types.DagScopeBlock, nil
default:
return types.DagScopeAll, errors.New("invalid car-scope parameter")
}
}
return types.DagScopeAll, nil
}

// ParseFilename returns the filename query parameter or an error if the filename
// extension is not ".car". Lassie only supports returning CAR data.
// See https://specs.ipfs.tech/http-gateways/path-gateway/#filename-request-query-parameter
func ParseFilename(req *http.Request) (string, error) {
// check if provided filename query parameter has .car extension
if req.URL.Query().Has("filename") {
filename := req.URL.Query().Get("filename")
ext := filepath.Ext(filename)
if ext == "" {
return "", errors.New("filename missing extension")
}
if ext != FilenameExtCar {
return "", fmt.Errorf("filename uses non-supported extension %s", ext)
}
return filename, nil
}
return "", nil
}

// CheckFormat validates that the data being requested is of the type CAR.
// We do this validation because the http gateway path spec allows for additional
// response formats that Lassie does not currently support, so we throw an error in
// the cases where the request is requesting one of Lassie's unsupported response
// formats. Lassie only supports returning CAR data.
//
// The spec outlines that the requesting format can be provided
// via the Accept header or the format query parameter.
//
// Lassie only allows the application/vnd.ipld.car Accept header
// https://specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header
//
// Lassie only allows the "car" format query parameter
// https://specs.ipfs.tech/http-gateways/path-gateway/#format-request-query-parameter
func CheckFormat(req *http.Request) (bool, error) {
hasAccept := req.Header.Get("Accept") != ""
// check if Accept header includes application/vnd.ipld.car
validAccept, includeDupes := ParseAccept(req.Header.Get("Accept"))
if hasAccept && !validAccept {
return false, fmt.Errorf("no acceptable content type")
}

// check if format is "car"
hasFormat := req.URL.Query().Has("format")
if hasFormat && req.URL.Query().Get("format") != FormatParameterCar {
return false, fmt.Errorf("requested non-supported format %s", req.URL.Query().Get("format"))
}

// if neither are provided return
// one of them has to be given with a CAR type since we only return CAR data
if !validAccept && !hasFormat {
return false, fmt.Errorf("neither a valid accept header or format parameter were provided")
}

return includeDupes, nil
}

// ParseAccept validates that the request Accept header is of the type CAR and
// returns whether or not duplicate blocks are allowed in the response via
// IPIP-412: https://github.com/ipfs/specs/pull/412.
func ParseAccept(acceptHeader string) (validAccept bool, includeDupes bool) {
acceptTypes := strings.Split(acceptHeader, ",")
validAccept = false
includeDupes = DefaultIncludeDupes
for _, acceptType := range acceptTypes {
typeParts := strings.Split(acceptType, ";")
if typeParts[0] == "*/*" || typeParts[0] == "application/*" || typeParts[0] == MimeTypeCar {
validAccept = true
if typeParts[0] == MimeTypeCar {
// parse additional car attributes outlined in IPIP-412: https://github.com/ipfs/specs/pull/412
for _, nextPart := range typeParts[1:] {
pair := strings.Split(nextPart, "=")
if len(pair) == 2 {
attr := strings.TrimSpace(pair[0])
value := strings.TrimSpace(pair[1])
switch attr {
case "dups":
switch value {
case "y":
includeDupes = true
case "n":
includeDupes = false
default:
// don't accept unexpected values
validAccept = false
}
case "version":
switch value {
case MimeTypeCarVersion:
default:
validAccept = false
}
case "order":
switch value {
case "dfs":
case "unk":
default:
// we only do dfs, which also satisfies unk, future extensions are not yet supported
validAccept = false
}
default:
// ignore others
}
}
}
}
// only break if further validation didn't fail
if validAccept {
break
}
}
}
return
}

0 comments on commit 1526265

Please sign in to comment.