config.go 5.18 KB
Newer Older
1 2 3 4 5
package http

import (
	"net/http"
	"net/url"
6
	"strings"
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
	"sync"

	cors "github.com/rs/cors"
)

const (
	ACAOrigin      = "Access-Control-Allow-Origin"
	ACAMethods     = "Access-Control-Allow-Methods"
	ACACredentials = "Access-Control-Allow-Credentials"
)

type ServerConfig struct {
	// APIPath is the prefix of all request paths.
	// Example: host:port/api/v0/add. Here the APIPath is /api/v0
	APIPath string

	// Headers is an optional map of headers that is written out.
	Headers map[string][]string

26 27 28 29 30 31 32
	// AllowGet indicates whether or not this server accepts GET requests.
	// When unset, the server only accepts POST, HEAD, and OPTIONS.
	//
	// This is different from CORS AllowedMethods. The API may allow GET
	// requests in general, but reject them in CORS. That will allow
	// websites to include resources from the API but not _read_ them.
	AllowGet bool
33

34 35 36 37 38 39 40 41 42 43 44 45 46
	// corsOpts is a set of options for CORS headers.
	corsOpts *cors.Options

	// corsOptsRWMutex is a RWMutex for read/write CORSOpts
	corsOptsRWMutex sync.RWMutex
}

func NewServerConfig() *ServerConfig {
	cfg := new(ServerConfig)
	cfg.corsOpts = new(cors.Options)
	return cfg
}

Steven Allen's avatar
Steven Allen committed
47
func (cfg *ServerConfig) AllowedOrigins() []string {
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
	cfg.corsOptsRWMutex.RLock()
	defer cfg.corsOptsRWMutex.RUnlock()
	return cfg.corsOpts.AllowedOrigins
}

func (cfg *ServerConfig) SetAllowedOrigins(origins ...string) {
	cfg.corsOptsRWMutex.Lock()
	defer cfg.corsOptsRWMutex.Unlock()
	o := make([]string, len(origins))
	copy(o, origins)
	cfg.corsOpts.AllowedOrigins = o
}

func (cfg *ServerConfig) AppendAllowedOrigins(origins ...string) {
	cfg.corsOptsRWMutex.Lock()
	defer cfg.corsOptsRWMutex.Unlock()
	cfg.corsOpts.AllowedOrigins = append(cfg.corsOpts.AllowedOrigins, origins...)
}

Steven Allen's avatar
Steven Allen committed
67
func (cfg *ServerConfig) AllowedMethods() []string {
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
	cfg.corsOptsRWMutex.RLock()
	defer cfg.corsOptsRWMutex.RUnlock()
	return []string(cfg.corsOpts.AllowedMethods)
}

func (cfg *ServerConfig) SetAllowedMethods(methods ...string) {
	cfg.corsOptsRWMutex.Lock()
	defer cfg.corsOptsRWMutex.Unlock()
	if cfg.corsOpts == nil {
		cfg.corsOpts = new(cors.Options)
	}
	cfg.corsOpts.AllowedMethods = methods
}

func (cfg *ServerConfig) SetAllowCredentials(flag bool) {
	cfg.corsOptsRWMutex.Lock()
	defer cfg.corsOptsRWMutex.Unlock()
	cfg.corsOpts.AllowCredentials = flag
}

// 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
	}
	origins := cfg.AllowedOrigins()
	for _, o := range origins {
		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
keks's avatar
keks committed
116
// work similarly in the normal uses of the API).
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
// 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
	}

	u, err := url.Parse(referer)
	if err != nil {
		// bad referer. but there _is_ something, so bail.
		// debug because referer comes straight from the client. dont want to
		// let people DOS by putting a huge referer that gets stored in log files.
		return false
	}
	origin := u.Scheme + "://" + u.Host

	// 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.
	origins := cfg.AllowedOrigins()
	for _, o := range origins {
		if o == "*" { // ok! you asked for it!
			return true
		}

		// referer is allowed explicitly
		if o == origin {
			return true
		}
	}

	return false
}
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

// allowUserAgent checks the request's user-agent against the list
// of DisallowUserAgents for requests with no origin nor referer set.
func allowUserAgent(r *http.Request, cfg *ServerConfig) bool {
	// This check affects POST as we should never get POST requests from a
	// browser without Origin or Referer, but we might:
	// https://bugzilla.mozilla.org/show_bug.cgi?id=429594.
	if r.Method != http.MethodPost {
		return true
	}

	origin := r.Header.Get("Origin")
	referer := r.Referer()

	// If these are set, we leave up to CORS and CSRF checks.
	if origin != "" || referer != "" {
		return true
	}

Hector Sanjuan's avatar
Hector Sanjuan committed
173
	// Allow if the user agent does not start with Mozilla... (i.e. curl).
174 175 176 177
	// Disallow otherwise.
	//
	// This means the request probably came from a browser and thus, it
	// should have included Origin or referer headers.
Hector Sanjuan's avatar
Hector Sanjuan committed
178
	ua := r.Header.Get("User-agent")
179 180 181 182 183 184 185

	// The fetch API in the Electron Renderer process sends no referer or
	// origin but should be allowed
	if strings.Contains(ua, "Electron") {
		return true
	}

Hector Sanjuan's avatar
Hector Sanjuan committed
186
	return !strings.HasPrefix(ua, "Mozilla")
187
}