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
+}