Commit c1d6230b authored by keks's avatar keks Committed by Jeromy

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: default avatarkeks <keks@cryptoscope.co>
parent bd9576fa
......@@ -17,6 +17,7 @@ import (
corehttp "github.com/ipfs/go-ipfs/core/corehttp"
corerepo "github.com/ipfs/go-ipfs/core/corerepo"
nodeMount "github.com/ipfs/go-ipfs/fuse/node"
config "github.com/ipfs/go-ipfs/repo/config"
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
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{
corehttp.MetricsCollectionOption("api"),
corehttp.CommandsOption(*cctx),
corehttp.CheckVersionOption(),
corehttp.ServerNameOption("go-ipfs/" + config.CurrentVersionNumber),
corehttp.WebUIOption,
gatewayOpt,
corehttp.VersionOption(),
......@@ -529,6 +532,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (error, <-chan e
corehttp.VersionOption(),
corehttp.IPNSHostnameOption(),
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
corehttp.CheckVersionOption(),
}
if len(cfg.Gateway.RootRedirect) > 0 {
......
package corehttp
import (
"errors"
"fmt"
"net"
"net/http"
"os"
......@@ -10,12 +12,18 @@ import (
oldcmds "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core"
corecommands "github.com/ipfs/go-ipfs/core/commands"
path "github.com/ipfs/go-ipfs/path"
config "github.com/ipfs/go-ipfs/repo/config"
cmds "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds"
cmdsHttp "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds/http"
)
var (
errApiVersionMismatch = errors.New("api version mismatch")
)
const apiPath = "/api/v0"
const originEnvKey = "API_ORIGIN"
const originEnvKeyDeprecate = `You are using the ` + originEnvKey + `ENV Variable.
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 {
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
})
}
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)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment