@cryptotaxi247 / kubo / commits / c1d6230bc

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>

keks committed Nov 27, 2017 at 17:42 UTC c1d6230bc0e03f25b04f607f8d8ed9158656338c
3 files changed +164
cmd/ipfs/daemon.go
+4
@@ -17,6 +17,7 @@ import (
17 corehttp "github.com/ipfs/go-ipfs/core/corehttp"
18 corerepo "github.com/ipfs/go-ipfs/core/corerepo"
19 nodeMount "github.com/ipfs/go-ipfs/fuse/node"
20 + config "github.com/ipfs/go-ipfs/repo/config"
21 fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
22 migrate "github.com/ipfs/go-ipfs/repo/fsrepo/migrations"
23
@@ -432,6 +433,8 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (error, <-chan error
433 var opts = []corehttp.ServeOption{
434 corehttp.MetricsCollectionOption("api"),
435 corehttp.CommandsOption(*cctx),
436 + corehttp.CheckVersionOption(),
437 + corehttp.ServerNameOption("go-ipfs/" + config.CurrentVersionNumber),
438 corehttp.WebUIOption,
439 gatewayOpt,
440 corehttp.VersionOption(),
@@ -529,6 +532,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (error, <-chan e
532 corehttp.VersionOption(),
533 corehttp.IPNSHostnameOption(),
534 corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
535 + corehttp.CheckVersionOption(),
536 }
537
538 if len(cfg.Gateway.RootRedirect) > 0 {
core/corehttp/commands.go
+47
@@ -1,6 +1,8 @@
1 package corehttp
2
3 import (
4 + "errors"
5 + "fmt"
6 "net"
7 "net/http"
8 "os"
@@ -10,12 +12,18 @@ import (
12 oldcmds "github.com/ipfs/go-ipfs/commands"
13 core "github.com/ipfs/go-ipfs/core"
14 corecommands "github.com/ipfs/go-ipfs/core/commands"
15 + path "github.com/ipfs/go-ipfs/path"
16 config "github.com/ipfs/go-ipfs/repo/config"
17
18 cmds "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds"
19 cmdsHttp "gx/ipfs/QmTwKPLyeRKuDawuy6CAn1kRj1FVoqBEM8sviAUWN7NW9K/go-ipfs-cmds/http"
20 )
21
22 +var (
23 + errApiVersionMismatch = errors.New("api version mismatch")
24 +)
25 +
26 +const apiPath = "/api/v0"
27 const originEnvKey = "API_ORIGIN"
28 const originEnvKeyDeprecate = `You are using the ` + originEnvKey + `ENV Variable.
29 This functionality is deprecated, and will be removed in future versions.
@@ -131,3 +139,42 @@ func CommandsOption(cctx oldcmds.Context) ServeOption {
139 func CommandsROOption(cctx oldcmds.Context) ServeOption {
140 return commandsOption(cctx, corecommands.RootRO)
141 }
142 +
143 +// CheckVersionOption returns a ServeOption that checks whether the client ipfs version matches. Does nothing when the user agent string does not contain `/go-ipfs/`
144 +func CheckVersionOption() ServeOption {
145 + daemonVersion := config.ApiVersion
146 +
147 + return ServeOption(func(n *core.IpfsNode, l net.Listener, next *http.ServeMux) (*http.ServeMux, error) {
148 + mux := http.NewServeMux()
149 + mux.HandleFunc(APIPath+"/", func(w http.ResponseWriter, r *http.Request) {
150 + pth := path.SplitList(r.URL.Path[len(APIPath):])
151 + // backwards compatibility to previous version check
152 + if pth[1] != "version" {
153 + clientVersion := r.UserAgent()
154 + // skips check if client is not go-ipfs
155 + if clientVersion != "" && strings.Contains(clientVersion, "/go-ipfs/") && daemonVersion != clientVersion {
156 + http.Error(w, fmt.Sprintf("%s (%s != %s)", errApiVersionMismatch, daemonVersion, clientVersion), http.StatusBadRequest)
157 + return
158 + }
159 + }
160 +
161 + next.ServeHTTP(w, r)
162 + })
163 + mux.HandleFunc("/", next.ServeHTTP)
164 +
165 + return mux, nil
166 + })
167 +}
168 +
169 +// ServerNameOption returns a ServeOption that makes the http server set the Server HTTP header.
170 +func ServerNameOption(name string) ServeOption {
171 + return ServeOption(func(n *core.IpfsNode, l net.Listener, next *http.ServeMux) (*http.ServeMux, error) {
172 + mux := http.NewServeMux()
173 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
174 + w.Header().Set("Server", name)
175 + next.ServeHTTP(w, r)
176 + })
177 +
178 + return mux, nil
179 + })
180 +}
core/corehttp/option_test.go new
+113
@@ -0,0 +1,113 @@
1 +package corehttp
2 +
3 +import (
4 + "fmt"
5 + "io"
6 + "net/http"
7 + "net/http/httptest"
8 + "testing"
9 +
10 + config "github.com/ipfs/go-ipfs/repo/config"
11 +)
12 +
13 +type testcasecheckversion struct {
14 + userAgent string
15 + uri string
16 + shouldHandle bool
17 + responseBody string
18 + responseCode int
19 +}
20 +
21 +func (tc testcasecheckversion) body() string {
22 + if !tc.shouldHandle && tc.responseBody == "" {
23 + return fmt.Sprintf("%s (%s != %s)\n", errApiVersionMismatch, config.ApiVersion, tc.userAgent)
24 + }
25 +
26 + return tc.responseBody
27 +}
28 +
29 +func TestCheckVersionOption(t *testing.T) {
30 + tcs := []testcasecheckversion{
31 + {"/go-ipfs/0.1/", APIPath + "/test/", false, "", http.StatusBadRequest},
32 + {"/go-ipfs/0.1/", APIPath + "/version", true, "check!", http.StatusOK},
33 + {config.ApiVersion, APIPath + "/test", true, "check!", http.StatusOK},
34 + {"Mozilla Firefox/no go-ipfs node", APIPath + "/test", true, "check!", http.StatusOK},
35 + {"/go-ipfs/0.1/", "/webui", true, "check!", http.StatusOK},
36 + }
37 +
38 + for _, tc := range tcs {
39 + t.Logf("%#v", tc)
40 + r := httptest.NewRequest("POST", tc.uri, nil)
41 + r.Header.Add("User-Agent", tc.userAgent) // old version, should fail
42 +
43 + called := false
44 + inner := http.NewServeMux()
45 + inner.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
46 + called = true
47 + if !tc.shouldHandle {
48 + t.Error("handler was called even though version didn't match")
49 + } else {
50 + io.WriteString(w, "check!")
51 + }
52 + })
53 +
54 + mux, err := CheckVersionOption()(nil, nil, inner)
55 + if err != nil {
56 + t.Fatal(err)
57 + }
58 +
59 + w := httptest.NewRecorder()
60 +
61 + mux.ServeHTTP(w, r)
62 +
63 + if tc.shouldHandle && !called {
64 + t.Error("handler wasn't called even though it should have")
65 + }
66 +
67 + if w.Code != tc.responseCode {
68 + t.Errorf("expected code %d but got %d", tc.responseCode, w.Code)
69 + }
70 +
71 + if w.Body.String() != tc.body() {
72 + t.Errorf("expected error message %q, got %q", tc.body(), w.Body.String())
73 + }
74 + }
75 +}
76 +
77 +func TestServerNameOption(t *testing.T) {
78 + type testcase struct {
79 + name string
80 + }
81 +
82 + tcs := []testcase{
83 + {"go-ipfs/0.4.13"},
84 + {"go-ipfs/" + config.CurrentVersionNumber},
85 + }
86 +
87 + assert := func(name string, exp, got interface{}) {
88 + if got != exp {
89 + t.Errorf("%s: got %q, expected %q", name, got, exp)
90 + }
91 + }
92 +
93 + for _, tc := range tcs {
94 + t.Logf("%#v", tc)
95 + r := httptest.NewRequest("POST", "/", nil)
96 +
97 + inner := http.NewServeMux()
98 + inner.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
99 + // this block is intentionally left blank.
100 + })
101 +
102 + mux, err := ServerNameOption(tc.name)(nil, nil, inner)
103 + if err != nil {
104 + t.Fatal(err)
105 + }
106 +
107 + w := httptest.NewRecorder()
108 +
109 + mux.ServeHTTP(w, r)
110 + srvHdr := w.Header().Get("Server")
111 + assert("Server header", tc.name, srvHdr)
112 + }
113 +}