1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-29 09:20:02 +08:00

Merge pull request #1529 from ipfs/cors-fix

Fix CORS Support
This commit is contained in:
Juan Benet
2015-07-28 23:34:07 -07:00
10 changed files with 585 additions and 85 deletions

View File

@ -61,20 +61,47 @@ The API address can be changed the same way:
Make sure to restart the daemon after changing addresses.
By default, the gateway is only accessible locally. To expose it to other computers
in the network, use 0.0.0.0 as the ip address:
By default, the gateway is only accessible locally. To expose it to
other computers in the network, use 0.0.0.0 as the ip address:
ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
Be careful if you expose the API. It is a security risk, as anyone could control
your node remotely. If you need to control the node remotely, make sure to protect
the port as you would other services or database (firewall, authenticated proxy, etc).
Be careful if you expose the API. It is a security risk, as anyone could
control your node remotely. If you need to control the node remotely,
make sure to protect the port as you would other services or database
(firewall, authenticated proxy, etc).
In order to explicitly allow Cross-Origin requests, export the root url as
environment variable API_ORIGIN. For example, to allow a local server at port 8888,
run this then restart the daemon:
HTTP Headers
export API_ORIGIN="http://localhost:8888/`,
IPFS supports passing arbitrary headers to the API and Gateway. You can
do this by setting headers on the API.HTTPHeaders and Gateway.HTTPHeaders
keys:
ipfs config --json API.HTTPHeaders.X-Special-Header '["so special :)"]'
ipfs config --json Gateway.HTTPHeaders.X-Special-Header '["so special :)"]'
Note that the value of the keys is an _array_ of strings. This is because
headers can have more than one value, and it is convenient to pass through
to other libraries.
CORS Headers (for API)
You can setup CORS headers the same way:
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
DEPRECATION NOTICE
Previously, IPFS used an environment variable as seen below:
export API_ORIGIN="http://localhost:8888/"
This is deprecated. It is still honored in this version, but will be removed in a
future version, along with this notice. Please move to setting the HTTP Headers.
`,
},
Options: []cmds.Option{

View File

@ -9,7 +9,7 @@ import (
"strconv"
"strings"
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
cmds "github.com/ipfs/go-ipfs/commands"
u "github.com/ipfs/go-ipfs/util"
@ -21,6 +21,7 @@ var log = u.Logger("commands/http")
type internalHandler struct {
ctx cmds.Context
root *cmds.Command
cfg *ServerConfig
}
// The Handler struct is funny because we want to wrap our internal handler
@ -44,35 +45,72 @@ const (
applicationJson = "application/json"
applicationOctetStream = "application/octet-stream"
plainText = "text/plain"
originHeader = "origin"
)
const (
ACAOrigin = "Access-Control-Allow-Origin"
ACAMethods = "Access-Control-Allow-Methods"
ACACredentials = "Access-Control-Allow-Credentials"
)
var localhostOrigins = []string{
"http://127.0.0.1",
"https://127.0.0.1",
"http://localhost",
"https://localhost",
}
var mimeTypes = map[string]string{
cmds.JSON: "application/json",
cmds.XML: "application/xml",
cmds.Text: "text/plain",
}
func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
// allow whitelisted origins (so we can make API requests from the browser)
if len(allowedOrigin) > 0 {
log.Info("Allowing API requests from origin: " + allowedOrigin)
type ServerConfig struct {
// Headers is an optional map of headers that is written out.
Headers map[string][]string
// CORSOpts is a set of options for CORS headers.
CORSOpts *cors.Options
}
func skipAPIHeader(h string) bool {
switch h {
case "Access-Control-Allow-Origin":
return true
case "Access-Control-Allow-Methods":
return true
case "Access-Control-Allow-Credentials":
return true
default:
return false
}
}
func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) *Handler {
if cfg == nil {
cfg = &ServerConfig{}
}
// Create a handler for the API.
internal := internalHandler{ctx, root}
if cfg.CORSOpts == nil {
cfg.CORSOpts = new(cors.Options)
}
// Create a CORS object for wrapping the internal handler.
c := cors.New(cors.Options{
AllowedMethods: []string{"GET", "POST", "PUT"},
// by default, use GET, PUT, POST
if cfg.CORSOpts.AllowedMethods == nil {
cfg.CORSOpts.AllowedMethods = []string{"GET", "POST", "PUT"}
}
// use AllowOriginFunc instead of AllowedOrigins because we want to be
// restrictive by default.
AllowOriginFunc: func(origin string) bool {
return (allowedOrigin == "*") || (origin == allowedOrigin)
},
})
// by default, only let 127.0.0.1 through.
if cfg.CORSOpts.AllowedOrigins == nil {
cfg.CORSOpts.AllowedOrigins = localhostOrigins
}
// Wrap the internal handler with CORS handling-middleware.
// Create a handler for the API.
internal := internalHandler{ctx, root, cfg}
c := cors.New(*cfg.CORSOpts)
return &Handler{internal, c.Handler(internal)}
}
@ -84,17 +122,10 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Debug("Incoming API request: ", r.URL)
// error on external referers (to prevent CSRF attacks)
referer := r.Referer()
scheme := r.URL.Scheme
if len(scheme) == 0 {
scheme = "http"
}
host := fmt.Sprintf("%s://%s/", scheme, r.Host)
// empty string means the user isn't following a link (they are directly typing in the url)
if referer != "" && !strings.HasPrefix(referer, host) {
if !allowOrigin(r, i.cfg) || !allowReferer(r, i.cfg) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("403 - Forbidden"))
log.Warningf("API blocked request to %s. (possible CSRF)", r.URL)
return
}
@ -128,8 +159,15 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// call the command
res := i.root.Call(req)
// set user's headers first.
for k, v := range i.cfg.Headers {
if !skipAPIHeader(k) {
w.Header()[k] = v
}
}
// now handle responding to the client properly
sendResponse(w, req, res)
sendResponse(w, r, res, req)
}
func guessMimeType(res cmds.Response) (string, error) {
@ -145,7 +183,7 @@ func guessMimeType(res cmds.Response) (string, error) {
return mimeTypes[enc], nil
}
func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
func sendResponse(w http.ResponseWriter, r *http.Request, res cmds.Response, req cmds.Request) {
mime, err := guessMimeType(res)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@ -203,6 +241,10 @@ func sendResponse(w http.ResponseWriter, req cmds.Request, res cmds.Response) {
}
h.Set(transferEncodingHeader, "chunked")
if r.Method == "HEAD" { // after all the headers.
return
}
if err := writeResponse(status, w, out); err != nil {
log.Error("error while writing stream", err)
}
@ -282,3 +324,60 @@ func sanitizedErrStr(err error) string {
s = strings.Split(s, "\r")[0]
return s
}
// allowOrigin just stops the request if the origin is not allowed.
// the CORS middleware apparently does not do this for us...
func allowOrigin(r *http.Request, cfg *ServerConfig) bool {
origin := r.Header.Get("Origin")
// curl, or ipfs shell, typing it in manually, or clicking link
// NOT in a browser. this opens up a hole. we should close it,
// but right now it would break things. TODO
if origin == "" {
return true
}
for _, o := range cfg.CORSOpts.AllowedOrigins {
if o == "*" { // ok! you asked for it!
return true
}
if o == origin { // allowed explicitly
return true
}
}
return false
}
// allowReferer this is here to prevent some CSRF attacks that
// the API would be vulnerable to. We check that the Referer
// is allowed by CORS Origin (origins and referrers here will
// work similarly in the normla uses of the API).
// See discussion at https://github.com/ipfs/go-ipfs/issues/1532
func allowReferer(r *http.Request, cfg *ServerConfig) bool {
referer := r.Referer()
// curl, or ipfs shell, typing it in manually, or clicking link
// NOT in a browser. this opens up a hole. we should close it,
// but right now it would break things. TODO
if referer == "" {
return true
}
// check CORS ACAOs and pretend Referer works like an origin.
// this is valid for many (most?) sane uses of the API in
// other applications, and will have the desired effect.
for _, o := range cfg.CORSOpts.AllowedOrigins {
if o == "*" { // ok! you asked for it!
return true
}
// referer is allowed explicitly
if o == referer {
return true
}
}
return false
}

View File

@ -3,69 +3,338 @@ package http
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/ipfs/go-ipfs/commands"
cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
cmds "github.com/ipfs/go-ipfs/commands"
ipfscmd "github.com/ipfs/go-ipfs/core/commands"
coremock "github.com/ipfs/go-ipfs/core/mock"
)
func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
for name, value := range reqHeaders {
if resHeaders.Get(name) != value {
t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, resHeaders.Get(name))
t.Errorf("Invalid header '%s', wanted '%s', got '%s'", name, value, resHeaders.Get(name))
}
}
}
func TestDisallowedOrigin(t *testing.T) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://barbaz.com")
func assertStatus(t *testing.T, actual, expected int) {
if actual != expected {
t.Errorf("Expected status: %d got: %d", expected, actual)
}
}
handler := NewHandler(commands.Context{}, nil, "")
handler.ServeHTTP(res, req)
func originCfg(origins []string) *ServerConfig {
return &ServerConfig{
CORSOpts: &cors.Options{
AllowedOrigins: origins,
},
}
}
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
type testCase struct {
Method string
Path string
Code int
Origin string
Referer string
AllowOrigins []string
ReqHeaders map[string]string
ResHeaders map[string]string
}
func getTestServer(t *testing.T, origins []string) *httptest.Server {
cmdsCtx, err := coremock.MockCmdsCtx()
if err != nil {
t.Error("failure to initialize mock cmds ctx", err)
return nil
}
cmdRoot := &cmds.Command{
Subcommands: map[string]*cmds.Command{
"version": ipfscmd.VersionCmd,
},
}
handler := NewHandler(cmdsCtx, cmdRoot, originCfg(origins))
return httptest.NewServer(handler)
}
func (tc *testCase) test(t *testing.T) {
// defaults
method := tc.Method
if method == "" {
method = "GET"
}
path := tc.Path
if path == "" {
path = "/api/v0/version"
}
expectCode := tc.Code
if expectCode == 0 {
expectCode = 200
}
// request
req, err := http.NewRequest(method, path, nil)
if err != nil {
t.Error(err)
return
}
for k, v := range tc.ReqHeaders {
req.Header.Add(k, v)
}
if tc.Origin != "" {
req.Header.Add("Origin", tc.Origin)
}
if tc.Referer != "" {
req.Header.Add("Referer", tc.Referer)
}
// server
server := getTestServer(t, tc.AllowOrigins)
if server == nil {
return
}
defer server.Close()
req.URL, err = url.Parse(server.URL + path)
if err != nil {
t.Error(err)
return
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Error(err)
return
}
// checks
t.Log("GET", server.URL+path, req.Header, res.Header)
assertHeaders(t, res.Header, tc.ResHeaders)
assertStatus(t, res.StatusCode, expectCode)
}
func TestDisallowedOrigins(t *testing.T) {
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: "",
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusForbidden,
}
}
tcs := []testCase{
gtc("http://barbaz.com", nil),
gtc("http://barbaz.com", []string{"http://localhost"}),
gtc("http://127.0.0.1", []string{"http://localhost"}),
gtc("http://localhost", []string{"http://127.0.0.1"}),
gtc("http://127.0.0.1:1234", nil),
gtc("http://localhost:1234", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestAllowedOrigins(t *testing.T) {
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: origin,
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
tcs := []testCase{
gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", nil),
gtc("http://127.0.0.1", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestWildcardOrigin(t *testing.T) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: origin,
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
handler := NewHandler(commands.Context{}, nil, "*")
handler.ServeHTTP(res, req)
tcs := []testCase{
gtc("http://barbaz.com", []string{"*"}),
gtc("http://barbaz.com", []string{"http://localhost", "*"}),
gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
gtc("http://127.0.0.1", []string{"*"}),
gtc("http://localhost", []string{"*"}),
gtc("http://127.0.0.1:1234", []string{"*"}),
gtc("http://localhost:1234", []string{"*"}),
}
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
for _, tc := range tcs {
tc.test(t)
}
}
func TestDisallowedReferer(t *testing.T) {
gtc := func(referer string, allowedOrigins []string) testCase {
return testCase{
Origin: "http://localhost",
Referer: referer,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: "http://localhost",
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusForbidden,
}
}
tcs := []testCase{
gtc("http://foobar.com", nil),
gtc("http://localhost:1234", nil),
gtc("http://127.0.0.1:1234", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestAllowedReferer(t *testing.T) {
gtc := func(referer string, allowedOrigins []string) testCase {
return testCase{
Origin: "http://localhost",
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: "http://localhost",
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
tcs := []testCase{
gtc("http://barbaz.com", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", []string{"http://barbaz.com", "http://localhost"}),
gtc("http://localhost", nil),
gtc("http://127.0.0.1", nil),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestWildcardReferer(t *testing.T) {
gtc := func(origin string, allowedOrigins []string) testCase {
return testCase{
Origin: origin,
AllowOrigins: allowedOrigins,
ResHeaders: map[string]string{
ACAOrigin: origin,
ACAMethods: "",
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
},
Code: http.StatusOK,
}
}
tcs := []testCase{
gtc("http://barbaz.com", []string{"*"}),
gtc("http://barbaz.com", []string{"http://localhost", "*"}),
gtc("http://127.0.0.1", []string{"http://localhost", "*"}),
gtc("http://localhost", []string{"http://127.0.0.1", "*"}),
gtc("http://127.0.0.1", []string{"*"}),
gtc("http://localhost", []string{"*"}),
gtc("http://127.0.0.1:1234", []string{"*"}),
gtc("http://localhost:1234", []string{"*"}),
}
for _, tc := range tcs {
tc.test(t)
}
}
func TestAllowedMethod(t *testing.T) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://www.foobar.com")
req.Header.Add("Access-Control-Request-Method", "PUT")
gtc := func(method string, ok bool) testCase {
code := http.StatusOK
hdrs := map[string]string{
ACAOrigin: "http://localhost",
ACAMethods: method,
ACACredentials: "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
}
handler := NewHandler(commands.Context{}, nil, "http://www.foobar.com")
handler.ServeHTTP(res, req)
if !ok {
hdrs[ACAOrigin] = ""
hdrs[ACAMethods] = ""
}
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://www.foobar.com",
"Access-Control-Allow-Methods": "PUT",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
return testCase{
Method: "OPTIONS",
Origin: "http://localhost",
AllowOrigins: []string{"*"},
ReqHeaders: map[string]string{
"Access-Control-Request-Method": method,
},
ResHeaders: hdrs,
Code: code,
}
}
tcs := []testCase{
gtc("PUT", true),
gtc("GET", true),
gtc("FOOBAR", false),
}
for _, tc := range tcs {
tc.test(t)
}
}

View File

@ -3,22 +3,73 @@ package corehttp
import (
"net/http"
"os"
"strings"
cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
commands "github.com/ipfs/go-ipfs/commands"
cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
core "github.com/ipfs/go-ipfs/core"
corecommands "github.com/ipfs/go-ipfs/core/commands"
config "github.com/ipfs/go-ipfs/repo/config"
)
const (
// TODO rename
originEnvKey = "API_ORIGIN"
)
const originEnvKey = "API_ORIGIN"
const originEnvKeyDeprecate = `You are using the ` + originEnvKey + `ENV Variable.
This functionality is deprecated, and will be removed in future versions.
Instead, try either adding headers to the config, or passing them via
cli arguments:
ipfs config API.HTTPHeaders 'Access-Control-Allow-Origin' '*'
ipfs daemon
or
ipfs daemon --api-http-header 'Access-Control-Allow-Origin: *'
`
func addCORSFromEnv(c *cmdsHttp.ServerConfig) {
origin := os.Getenv(originEnvKey)
if origin != "" {
log.Warning(originEnvKeyDeprecate)
if c.CORSOpts == nil {
c.CORSOpts.AllowedOrigins = []string{origin}
}
c.CORSOpts.AllowedOrigins = append(c.CORSOpts.AllowedOrigins, origin)
}
}
func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) {
log.Info("Using API.HTTPHeaders:", nc.API.HTTPHeaders)
if acao := nc.API.HTTPHeaders[cmdsHttp.ACAOrigin]; acao != nil {
c.CORSOpts.AllowedOrigins = acao
}
if acam := nc.API.HTTPHeaders[cmdsHttp.ACAMethods]; acam != nil {
c.CORSOpts.AllowedMethods = acam
}
if acac := nc.API.HTTPHeaders[cmdsHttp.ACACredentials]; acac != nil {
for _, v := range acac {
c.CORSOpts.AllowCredentials = (strings.ToLower(v) == "true")
}
}
c.Headers = nc.API.HTTPHeaders
}
func CommandsOption(cctx commands.Context) ServeOption {
return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
origin := os.Getenv(originEnvKey)
cmdHandler := cmdsHttp.NewHandler(cctx, corecommands.Root, origin)
cfg := &cmdsHttp.ServerConfig{
CORSOpts: &cors.Options{
AllowedMethods: []string{"GET", "POST", "PUT"},
},
}
addHeadersFromConfig(cfg, n.Repo.Config())
addCORSFromEnv(cfg)
cmdHandler := cmdsHttp.NewHandler(cctx, corecommands.Root, cfg)
mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
return mux, nil
}

View File

@ -15,6 +15,7 @@ type Gateway struct {
}
type GatewayConfig struct {
Headers map[string][]string
BlockList *BlockList
Writable bool
}
@ -27,6 +28,9 @@ func NewGateway(conf GatewayConfig) *Gateway {
func (g *Gateway) ServeOption() ServeOption {
return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
// pass user's HTTP headers
g.Config.Headers = n.Repo.Config().Gateway.HTTPHeaders
gateway, err := newGatewayHandler(n, g.Config)
if err != nil {
return nil, err

View File

@ -106,6 +106,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
return
}
i.addUserHeaders(w) // ok, _now_ write user's headers.
w.Header().Set("X-IPFS-Path", urlPath)
// Suborigin header, sandboxes apps from each other in the browser (even
@ -229,6 +230,7 @@ func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
return
}
i.addUserHeaders(w) // ok, _now_ write user's headers.
w.Header().Set("IPFS-Hash", k.String())
http.Redirect(w, r, ipfsPathPrefix+k.String(), http.StatusCreated)
}
@ -242,6 +244,7 @@ func (i *gatewayHandler) putEmptyDirHandler(w http.ResponseWriter, r *http.Reque
return
}
i.addUserHeaders(w) // ok, _now_ write user's headers.
w.Header().Set("IPFS-Hash", key.String())
http.Redirect(w, r, ipfsPathPrefix+key.String()+"/", http.StatusCreated)
}
@ -340,6 +343,7 @@ func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) {
return
}
i.addUserHeaders(w) // ok, _now_ write user's headers.
w.Header().Set("IPFS-Hash", key.String())
http.Redirect(w, r, ipfsPathPrefix+key.String()+"/"+strings.Join(components, "/"), http.StatusCreated)
}
@ -411,10 +415,17 @@ func (i *gatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
return
}
i.addUserHeaders(w) // ok, _now_ write user's headers.
w.Header().Set("IPFS-Hash", key.String())
http.Redirect(w, r, ipfsPathPrefix+key.String()+"/"+strings.Join(components[:len(components)-1], "/"), http.StatusCreated)
}
func (i *gatewayHandler) addUserHeaders(w http.ResponseWriter) {
for k, v := range i.config.Headers {
w.Header()[k] = v
}
}
func webError(w http.ResponseWriter, message string, err error, defaultCode int) {
if _, ok := err.(path.ErrNoLink); ok {
webErrorWithCode(w, message, err, http.StatusNotFound)

View File

@ -6,6 +6,7 @@ import (
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/ipfs/go-ipfs/blocks/blockstore"
blockservice "github.com/ipfs/go-ipfs/blockservice"
commands "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/exchange/offline"
mdag "github.com/ipfs/go-ipfs/merkledag"
@ -27,7 +28,7 @@ import (
// NewMockNode constructs an IpfsNode for use in tests.
func NewMockNode() (*core.IpfsNode, error) {
ctx := context.TODO()
ctx := context.Background()
// Generate Identity
ident, err := testutil.RandIdentity()
@ -82,3 +83,34 @@ func NewMockNode() (*core.IpfsNode, error) {
return nd, nil
}
func MockCmdsCtx() (commands.Context, error) {
// Generate Identity
ident, err := testutil.RandIdentity()
if err != nil {
return commands.Context{}, err
}
p := ident.ID()
conf := config.Config{
Identity: config.Identity{
PeerID: p.String(),
},
}
node, err := core.NewIPFSNode(context.Background(), core.Offline(&repo.Mock{
D: ds2.CloserWrap(syncds.MutexWrap(datastore.NewMapDatastore())),
C: conf,
}))
return commands.Context{
Online: true,
ConfigRoot: "/tmp/.mockipfsconfig",
LoadConfig: func(path string) (*config.Config, error) {
return &conf, nil
},
ConstructNode: func() (*core.IpfsNode, error) {
return node, nil
},
}, nil
}

5
repo/config/api.go Normal file
View File

@ -0,0 +1,5 @@
package config
type API struct {
HTTPHeaders map[string][]string // HTTP headers to return with the API.
}

View File

@ -26,6 +26,7 @@ type Config struct {
Tour Tour // local node's tour position
Gateway Gateway // local node's gateway server options
SupernodeRouting SupernodeClientConfig // local node's routing servers (if SupernodeRouting enabled)
API API // local node's API settings
Swarm SwarmConfig
Log Log
}

View File

@ -2,6 +2,7 @@ package config
// Gateway contains options for the HTTP gateway server.
type Gateway struct {
HTTPHeaders map[string][]string // HTTP headers to return with the gateway
RootRedirect string
Writable bool
}