Skip to content

Commit 90182ce

Browse files
authored
Merge pull request #190 from ipfs/fix/api-post
http: configurable allowed request methods for the API.
2 parents 59c18d0 + 2fbebbe commit 90182ce

File tree

6 files changed

+74
-17
lines changed

6 files changed

+74
-17
lines changed

http/config.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ type ServerConfig struct {
2222
// Headers is an optional map of headers that is written out.
2323
Headers map[string][]string
2424

25+
// HandledMethods set which methods will be handled for the HTTP
26+
// requests. Other methods will return 405. This is different from CORS
27+
// AllowedMethods (the API may handle GET and POST, but only allow GETs
28+
// for CORS-enabled requests via AllowedMethods).
29+
HandledMethods []string
30+
2531
// corsOpts is a set of options for CORS headers.
2632
corsOpts *cors.Options
2733

@@ -32,6 +38,7 @@ type ServerConfig struct {
3238
func NewServerConfig() *ServerConfig {
3339
cfg := new(ServerConfig)
3440
cfg.corsOpts = new(cors.Options)
41+
cfg.HandledMethods = []string{http.MethodPost}
3542
return cfg
3643
}
3744

@@ -142,3 +149,16 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool {
142149

143150
return false
144151
}
152+
153+
// handleRequestMethod returns true if the request method is among
154+
// HandledMethods.
155+
func handleRequestMethod(r *http.Request, cfg *ServerConfig) bool {
156+
// For very small slices as these, this should be faster than
157+
// a map lookup.
158+
for _, m := range cfg.HandledMethods {
159+
if r.Method == m {
160+
return true
161+
}
162+
}
163+
return false
164+
}

http/errors_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"strings"
1010
"testing"
1111

12-
"github.com/ipfs/go-ipfs-cmds"
12+
cmds "github.com/ipfs/go-ipfs-cmds"
1313
)
1414

1515
func TestErrors(t *testing.T) {
@@ -116,7 +116,7 @@ func TestErrors(t *testing.T) {
116116

117117
mkTest := func(tc testcase) func(*testing.T) {
118118
return func(t *testing.T) {
119-
_, srv := getTestServer(t, nil) // handler_test:/^func getTestServer/
119+
_, srv := getTestServer(t, nil, nil) // handler_test:/^func getTestServer/
120120
c := NewClient(srv.URL)
121121
req, err := cmds.NewRequest(context.Background(), tc.path, tc.opts, nil, nil, cmdRoot)
122122
if err != nil {
@@ -158,3 +158,15 @@ func TestErrors(t *testing.T) {
158158
t.Run(fmt.Sprintf("%d-%s", i, strings.Join(tc.path, "/")), mkTest(tc))
159159
}
160160
}
161+
162+
func TestUnhandledMethod(t *testing.T) {
163+
tc := httpTestCase{
164+
Method: "GET",
165+
HandledMethods: []string{"POST"},
166+
Code: http.StatusMethodNotAllowed,
167+
ResHeaders: map[string]string{
168+
"Allow": "POST",
169+
},
170+
}
171+
tc.test(t)
172+
}

http/handler.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
9595
}
9696
}()
9797

98+
// First of all, check if we are allowed to handle the request method
99+
// or we are configured not to.
100+
if !handleRequestMethod(r, h.cfg) {
101+
setAllowedHeaders(w, h.cfg.HandledMethods)
102+
http.Error(w, "405 - Method Not Allowed", http.StatusMethodNotAllowed)
103+
log.Warningf("The IPFS API does not support %s requests. All requests must use %s", h.cfg.HandledMethods)
104+
return
105+
}
106+
98107
if !allowOrigin(r, h.cfg) || !allowReferer(r, h.cfg) {
99108
http.Error(w, "403 - Forbidden", http.StatusForbidden)
100109
log.Warningf("API blocked request to %s. (possible CSRF)", r.URL)
@@ -170,3 +179,9 @@ func sanitizedErrStr(err error) string {
170179
s = strings.Split(s, "\r")[0]
171180
return s
172181
}
182+
183+
func setAllowedHeaders(w http.ResponseWriter, methods []string) {
184+
for _, m := range methods {
185+
w.Header().Add("Allow", m)
186+
}
187+
}

http/handler_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ var (
292292
}
293293
)
294294

295-
func getTestServer(t *testing.T, origins []string) (cmds.Environment, *httptest.Server) {
295+
func getTestServer(t *testing.T, origins []string, handledMethods []string) (cmds.Environment, *httptest.Server) {
296296
if len(origins) == 0 {
297297
origins = defaultOrigins
298298
}
@@ -305,7 +305,15 @@ func getTestServer(t *testing.T, origins []string) (cmds.Environment, *httptest.
305305
wait: make(chan struct{}),
306306
}
307307

308-
return env, httptest.NewServer(NewHandler(env, cmdRoot, originCfg(origins)))
308+
srvCfg := originCfg(origins)
309+
310+
if len(handledMethods) == 0 {
311+
srvCfg.HandledMethods = []string{"GET", "POST"}
312+
} else {
313+
srvCfg.HandledMethods = handledMethods
314+
}
315+
316+
return env, httptest.NewServer(NewHandler(env, cmdRoot, srvCfg))
309317
}
310318

311319
func errEq(err1, err2 error) bool {

http/http_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import (
1212
"strings"
1313
"testing"
1414

15-
"github.com/ipfs/go-ipfs-cmds"
15+
cmds "github.com/ipfs/go-ipfs-cmds"
1616

17-
"github.com/ipfs/go-ipfs-files"
17+
files "github.com/ipfs/go-ipfs-files"
1818
)
1919

2020
func newReaderPathFile(t *testing.T, path string, reader io.ReadCloser, stat os.FileInfo) files.File {
@@ -88,7 +88,7 @@ func TestHTTP(t *testing.T) {
8888

8989
mkTest := func(tc testcase) func(*testing.T) {
9090
return func(t *testing.T) {
91-
env, srv := getTestServer(t, nil) // handler_test:/^func getTestServer/
91+
env, srv := getTestServer(t, nil, nil) // handler_test:/^func getTestServer/
9292
c := NewClient(srv.URL)
9393
req, err := cmds.NewRequest(context.Background(), tc.path, nil, nil, nil, cmdRoot)
9494
if err != nil {

http/reforigin_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"net/url"
77
"testing"
88

9-
"github.com/ipfs/go-ipfs-cmds"
9+
cmds "github.com/ipfs/go-ipfs-cmds"
1010
)
1111

1212
func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
@@ -27,6 +27,7 @@ func originCfg(origins []string) *ServerConfig {
2727
cfg := NewServerConfig()
2828
cfg.SetAllowedOrigins(origins...)
2929
cfg.SetAllowedMethods("GET", "PUT", "POST")
30+
cfg.HandledMethods = []string{"GET", "POST"}
3031
return cfg
3132
}
3233

@@ -38,14 +39,15 @@ var defaultOrigins = []string{
3839
}
3940

4041
type httpTestCase struct {
41-
Method string
42-
Path string
43-
Code int
44-
Origin string
45-
Referer string
46-
AllowOrigins []string
47-
ReqHeaders map[string]string
48-
ResHeaders map[string]string
42+
Method string
43+
Path string
44+
Code int
45+
Origin string
46+
Referer string
47+
AllowOrigins []string
48+
HandledMethods []string
49+
ReqHeaders map[string]string
50+
ResHeaders map[string]string
4951
}
5052

5153
func (tc *httpTestCase) test(t *testing.T) {
@@ -83,7 +85,7 @@ func (tc *httpTestCase) test(t *testing.T) {
8385
}
8486

8587
// server
86-
_, server := getTestServer(t, tc.AllowOrigins)
88+
_, server := getTestServer(t, tc.AllowOrigins, tc.HandledMethods)
8789
if server == nil {
8890
return
8991
}

0 commit comments

Comments
 (0)