mirror of
https://github.com/ipfs/kubo.git
synced 2025-08-06 19:44:01 +08:00
check api version in corehttp
- add comments, trim api path prefix - corehttp: add option to set HTTP header "Server" - daemon: use new corehttp options License: MIT Signed-off-by: keks <keks@cryptoscope.co>
This commit is contained in:
@ -17,6 +17,7 @@ import (
|
|||||||
corehttp "github.com/ipfs/go-ipfs/core/corehttp"
|
corehttp "github.com/ipfs/go-ipfs/core/corehttp"
|
||||||
corerepo "github.com/ipfs/go-ipfs/core/corerepo"
|
corerepo "github.com/ipfs/go-ipfs/core/corerepo"
|
||||||
nodeMount "github.com/ipfs/go-ipfs/fuse/node"
|
nodeMount "github.com/ipfs/go-ipfs/fuse/node"
|
||||||
|
config "github.com/ipfs/go-ipfs/repo/config"
|
||||||
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
|
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
|
||||||
migrate "github.com/ipfs/go-ipfs/repo/fsrepo/migrations"
|
migrate "github.com/ipfs/go-ipfs/repo/fsrepo/migrations"
|
||||||
|
|
||||||
@ -432,6 +433,8 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (error, <-chan error
|
|||||||
var opts = []corehttp.ServeOption{
|
var opts = []corehttp.ServeOption{
|
||||||
corehttp.MetricsCollectionOption("api"),
|
corehttp.MetricsCollectionOption("api"),
|
||||||
corehttp.CommandsOption(*cctx),
|
corehttp.CommandsOption(*cctx),
|
||||||
|
corehttp.CheckVersionOption(),
|
||||||
|
corehttp.ServerNameOption("go-ipfs/" + config.CurrentVersionNumber),
|
||||||
corehttp.WebUIOption,
|
corehttp.WebUIOption,
|
||||||
gatewayOpt,
|
gatewayOpt,
|
||||||
corehttp.VersionOption(),
|
corehttp.VersionOption(),
|
||||||
@ -529,6 +532,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (error, <-chan e
|
|||||||
corehttp.VersionOption(),
|
corehttp.VersionOption(),
|
||||||
corehttp.IPNSHostnameOption(),
|
corehttp.IPNSHostnameOption(),
|
||||||
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
|
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
|
||||||
|
corehttp.CheckVersionOption(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Gateway.RootRedirect) > 0 {
|
if len(cfg.Gateway.RootRedirect) > 0 {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package corehttp
|
package corehttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -10,12 +12,18 @@ import (
|
|||||||
oldcmds "github.com/ipfs/go-ipfs/commands"
|
oldcmds "github.com/ipfs/go-ipfs/commands"
|
||||||
core "github.com/ipfs/go-ipfs/core"
|
core "github.com/ipfs/go-ipfs/core"
|
||||||
corecommands "github.com/ipfs/go-ipfs/core/commands"
|
corecommands "github.com/ipfs/go-ipfs/core/commands"
|
||||||
|
path "github.com/ipfs/go-ipfs/path"
|
||||||
config "github.com/ipfs/go-ipfs/repo/config"
|
config "github.com/ipfs/go-ipfs/repo/config"
|
||||||
|
|
||||||
cmds "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds"
|
cmds "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds"
|
||||||
cmdsHttp "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds/http"
|
cmdsHttp "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errApiVersionMismatch = errors.New("api version mismatch")
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiPath = "/api/v0"
|
||||||
const originEnvKey = "API_ORIGIN"
|
const originEnvKey = "API_ORIGIN"
|
||||||
const originEnvKeyDeprecate = `You are using the ` + originEnvKey + `ENV Variable.
|
const originEnvKeyDeprecate = `You are using the ` + originEnvKey + `ENV Variable.
|
||||||
This functionality is deprecated, and will be removed in future versions.
|
This functionality is deprecated, and will be removed in future versions.
|
||||||
@ -131,3 +139,42 @@ func CommandsOption(cctx oldcmds.Context) ServeOption {
|
|||||||
func CommandsROOption(cctx oldcmds.Context) ServeOption {
|
func CommandsROOption(cctx oldcmds.Context) ServeOption {
|
||||||
return commandsOption(cctx, corecommands.RootRO)
|
return commandsOption(cctx, corecommands.RootRO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckVersionOption returns a ServeOption that checks whether the client ipfs version matches. Does nothing when the user agent string does not contain `/go-ipfs/`
|
||||||
|
func CheckVersionOption() ServeOption {
|
||||||
|
daemonVersion := config.ApiVersion
|
||||||
|
|
||||||
|
return ServeOption(func(n *core.IpfsNode, l net.Listener, next *http.ServeMux) (*http.ServeMux, error) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc(APIPath+"/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pth := path.SplitList(r.URL.Path[len(APIPath):])
|
||||||
|
// backwards compatibility to previous version check
|
||||||
|
if pth[1] != "version" {
|
||||||
|
clientVersion := r.UserAgent()
|
||||||
|
// skips check if client is not go-ipfs
|
||||||
|
if clientVersion != "" && strings.Contains(clientVersion, "/go-ipfs/") && daemonVersion != clientVersion {
|
||||||
|
http.Error(w, fmt.Sprintf("%s (%s != %s)", errApiVersionMismatch, daemonVersion, clientVersion), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/", next.ServeHTTP)
|
||||||
|
|
||||||
|
return mux, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerNameOption returns a ServeOption that makes the http server set the Server HTTP header.
|
||||||
|
func ServerNameOption(name string) ServeOption {
|
||||||
|
return ServeOption(func(n *core.IpfsNode, l net.Listener, next *http.ServeMux) (*http.ServeMux, error) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Server", name)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
return mux, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
113
core/corehttp/option_test.go
Normal file
113
core/corehttp/option_test.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package corehttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
config "github.com/ipfs/go-ipfs/repo/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testcasecheckversion struct {
|
||||||
|
userAgent string
|
||||||
|
uri string
|
||||||
|
shouldHandle bool
|
||||||
|
responseBody string
|
||||||
|
responseCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc testcasecheckversion) body() string {
|
||||||
|
if !tc.shouldHandle && tc.responseBody == "" {
|
||||||
|
return fmt.Sprintf("%s (%s != %s)\n", errApiVersionMismatch, config.ApiVersion, tc.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc.responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckVersionOption(t *testing.T) {
|
||||||
|
tcs := []testcasecheckversion{
|
||||||
|
{"/go-ipfs/0.1/", APIPath + "/test/", false, "", http.StatusBadRequest},
|
||||||
|
{"/go-ipfs/0.1/", APIPath + "/version", true, "check!", http.StatusOK},
|
||||||
|
{config.ApiVersion, APIPath + "/test", true, "check!", http.StatusOK},
|
||||||
|
{"Mozilla Firefox/no go-ipfs node", APIPath + "/test", true, "check!", http.StatusOK},
|
||||||
|
{"/go-ipfs/0.1/", "/webui", true, "check!", http.StatusOK},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Logf("%#v", tc)
|
||||||
|
r := httptest.NewRequest("POST", tc.uri, nil)
|
||||||
|
r.Header.Add("User-Agent", tc.userAgent) // old version, should fail
|
||||||
|
|
||||||
|
called := false
|
||||||
|
inner := http.NewServeMux()
|
||||||
|
inner.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
called = true
|
||||||
|
if !tc.shouldHandle {
|
||||||
|
t.Error("handler was called even though version didn't match")
|
||||||
|
} else {
|
||||||
|
io.WriteString(w, "check!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mux, err := CheckVersionOption()(nil, nil, inner)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
if tc.shouldHandle && !called {
|
||||||
|
t.Error("handler wasn't called even though it should have")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Code != tc.responseCode {
|
||||||
|
t.Errorf("expected code %d but got %d", tc.responseCode, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Body.String() != tc.body() {
|
||||||
|
t.Errorf("expected error message %q, got %q", tc.body(), w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerNameOption(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
tcs := []testcase{
|
||||||
|
{"go-ipfs/0.4.13"},
|
||||||
|
{"go-ipfs/" + config.CurrentVersionNumber},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert := func(name string, exp, got interface{}) {
|
||||||
|
if got != exp {
|
||||||
|
t.Errorf("%s: got %q, expected %q", name, got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Logf("%#v", tc)
|
||||||
|
r := httptest.NewRequest("POST", "/", nil)
|
||||||
|
|
||||||
|
inner := http.NewServeMux()
|
||||||
|
inner.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// this block is intentionally left blank.
|
||||||
|
})
|
||||||
|
|
||||||
|
mux, err := ServerNameOption(tc.name)(nil, nil, inner)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(w, r)
|
||||||
|
srvHdr := w.Header().Get("Server")
|
||||||
|
assert("Server header", tc.name, srvHdr)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user