diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 2d5fd5d0053be1ede119e3462d8024c660e7064e..bc84f57a44c6b73bbdc9dd3a1c6acb7981a87996 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -233,6 +233,10 @@
 			"ImportPath": "github.com/mtchavez/jenkins",
 			"Rev": "5a816af6ef21ef401bff5e4b7dd255d63400f497"
 		},
+		{
+			"ImportPath": "github.com/rs/cors",
+			"Rev": "5e4ce6bc0ecd3472f6f943666d84876691be2ced"
+		},
 		{
 			"ImportPath": "github.com/steakknife/hamming",
 			"Comment": "0.0.10",
diff --git a/Godeps/_workspace/src/github.com/rs/cors/.travis.yml b/Godeps/_workspace/src/github.com/rs/cors/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bbb5185a2edc87ed46f15fd234ea3243e3aebfad
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/.travis.yml
@@ -0,0 +1,4 @@
+language: go
+go:
+- 1.3
+- 1.4
diff --git a/Godeps/_workspace/src/github.com/rs/cors/LICENSE b/Godeps/_workspace/src/github.com/rs/cors/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..d8e2df5a479a47e6afe311efdda70e2b541ad7a5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Godeps/_workspace/src/github.com/rs/cors/README.md b/Godeps/_workspace/src/github.com/rs/cors/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4fe320cf3429354fe3df8beeab81610ee24d602d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/README.md
@@ -0,0 +1,96 @@
+# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors)
+
+CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang.
+
+## Getting Started
+
+After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`.
+
+```go
+package main
+
+import (
+    "net/http"
+
+    "github.com/rs/cors"
+)
+
+func main() {
+    h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+        w.Header().Set("Content-Type", "application/json")
+        w.Write([]byte("{\"hello\": \"world\"}"))
+    })
+
+    // cors.Default() setup the middleware with default options being
+    // all origins accepted with simple methods (GET, POST). See
+    // documentation below for more options.
+    handler := cors.Default().Handler(h)
+    http.ListenAndServe(":8080", handler)
+}
+```
+
+Install `cors`:
+
+    go get github.com/rs/cors
+
+Then run your server:
+
+    go run server.go
+
+The server now runs on `localhost:8080`:
+
+    $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/
+    HTTP/1.1 200 OK
+    Access-Control-Allow-Origin: foo.com
+    Content-Type: application/json
+    Date: Sat, 25 Oct 2014 03:43:57 GMT
+    Content-Length: 18
+
+    {"hello": "world"}
+
+### More Examples
+
+* `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go)
+* [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go)
+* [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go)
+* [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go)
+* [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go)
+
+## Parameters
+
+Parameters are passed to the middleware thru the `cors.New` method as follow:
+
+```go
+c := cors.New(cors.Options{
+    AllowedOrigins: []string{"http://foo.com"},
+    AllowCredentials: true,
+})
+
+// Insert the middleware
+handler = c.Handler(handler)
+```
+
+* **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. The default value is `*`.
+* **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It take the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored
+* **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests.
+* **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`)
+* **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification
+* **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`.
+* **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age.
+
+See [API documentation](http://godoc.org/github.com/rs/cors) for more info.
+
+## Benchmarks
+
+    BenchmarkWithout          20000000    64.6 ns/op      8 B/op    1 allocs/op
+    BenchmarkDefault          3000000      469 ns/op    114 B/op    2 allocs/op
+    BenchmarkAllowedOrigin    3000000      608 ns/op    114 B/op    2 allocs/op
+    BenchmarkPreflight        20000000    73.2 ns/op      0 B/op    0 allocs/op
+    BenchmarkPreflightHeader  20000000    73.6 ns/op      0 B/op    0 allocs/op
+    BenchmarkParseHeaderList  2000000      847 ns/op    184 B/op    6 allocs/op
+    BenchmarkParse…Single     5000000      290 ns/op     32 B/op    3 allocs/op
+    BenchmarkParse…Normalized 2000000      776 ns/op    160 B/op    6 allocs/op
+
+## Licenses
+
+All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE).
diff --git a/Godeps/_workspace/src/github.com/rs/cors/bench_test.go b/Godeps/_workspace/src/github.com/rs/cors/bench_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b6e3721decccc2b4dd74f272f6a663c78c786b93
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/bench_test.go
@@ -0,0 +1,88 @@
+package cors
+
+import (
+	"net/http"
+	"testing"
+)
+
+type FakeResponse struct {
+	header http.Header
+}
+
+func (r FakeResponse) Header() http.Header {
+	return r.header
+}
+
+func (r FakeResponse) WriteHeader(n int) {
+}
+
+func (r FakeResponse) Write(b []byte) (n int, err error) {
+	return len(b), nil
+}
+
+func BenchmarkWithout(b *testing.B) {
+	res := FakeResponse{http.Header{}}
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		testHandler.ServeHTTP(res, req)
+	}
+}
+
+func BenchmarkDefault(b *testing.B) {
+	res := FakeResponse{http.Header{}}
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "somedomain.com")
+	handler := Default().Handler(testHandler)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		handler.ServeHTTP(res, req)
+	}
+}
+
+func BenchmarkAllowedOrigin(b *testing.B) {
+	res := FakeResponse{http.Header{}}
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "somedomain.com")
+	c := New(Options{
+		AllowedOrigins: []string{"somedomain.com"},
+	})
+	handler := c.Handler(testHandler)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		handler.ServeHTTP(res, req)
+	}
+}
+
+func BenchmarkPreflight(b *testing.B) {
+	res := FakeResponse{http.Header{}}
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Access-Control-Request-Method", "GET")
+	handler := Default().Handler(testHandler)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		handler.ServeHTTP(res, req)
+	}
+}
+
+func BenchmarkPreflightHeader(b *testing.B) {
+	res := FakeResponse{http.Header{}}
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Access-Control-Request-Method", "GET")
+	req.Header.Add("Access-Control-Request-Headers", "Accept")
+	handler := Default().Handler(testHandler)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		handler.ServeHTTP(res, req)
+	}
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/cors.go b/Godeps/_workspace/src/github.com/rs/cors/cors.go
new file mode 100644
index 0000000000000000000000000000000000000000..1ec676e3a9addf7e8fb3d17281df35c295499293
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/cors.go
@@ -0,0 +1,345 @@
+/*
+Package cors is net/http handler to handle CORS related requests
+as defined by http://www.w3.org/TR/cors/
+
+You can configure it by passing an option struct to cors.New:
+
+    c := cors.New(cors.Options{
+        AllowedOrigins: []string{"foo.com"},
+        AllowedMethods: []string{"GET", "POST", "DELETE"},
+        AllowCredentials: true,
+    })
+
+Then insert the handler in the chain:
+
+    handler = c.Handler(handler)
+
+See Options documentation for more options.
+
+The resulting handler is a standard net/http handler.
+*/
+package cors
+
+import (
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+)
+
+// Options is a configuration container to setup the CORS middleware.
+type Options struct {
+	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
+	// If the special "*" value is present in the list, all origins will be allowed.
+	// Default value is ["*"]
+	AllowedOrigins []string
+	// AllowOriginFunc is a custom function to validate the origin. It take the origin
+	// as argument and returns true if allowed or false otherwise. If this option is
+	// set, the content of AllowedOrigins is ignored.
+	AllowOriginFunc func(origin string) bool
+	// AllowedMethods is a list of methods the client is allowed to use with
+	// cross-domain requests. Default value is simple methods (GET and POST)
+	AllowedMethods []string
+	// AllowedHeaders is list of non simple headers the client is allowed to use with
+	// cross-domain requests.
+	// If the special "*" value is present in the list, all headers will be allowed.
+	// Default value is [] but "Origin" is always appended to the list.
+	AllowedHeaders []string
+	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
+	// API specification
+	ExposedHeaders []string
+	// AllowCredentials indicates whether the request can include user credentials like
+	// cookies, HTTP authentication or client side SSL certificates.
+	AllowCredentials bool
+	// MaxAge indicates how long (in seconds) the results of a preflight request
+	// can be cached
+	MaxAge int
+	// Debugging flag adds additional output to debug server side CORS issues
+	Debug bool
+}
+
+type Cors struct {
+	// Debug logger
+	log *log.Logger
+	// Set to true when allowed origins contains a "*"
+	allowedOriginsAll bool
+	// Normalized list of allowed origins
+	allowedOrigins []string
+	// Optional origin validator function
+	allowOriginFunc func(origin string) bool
+	// Set to true when allowed headers contains a "*"
+	allowedHeadersAll bool
+	// Normalized list of allowed headers
+	allowedHeaders []string
+	// Normalized list of allowed methods
+	allowedMethods []string
+	// Normalized list of exposed headers
+	exposedHeaders   []string
+	allowCredentials bool
+	maxAge           int
+}
+
+// New creates a new Cors handler with the provided options.
+func New(options Options) *Cors {
+	c := &Cors{
+		exposedHeaders:   convert(options.ExposedHeaders, http.CanonicalHeaderKey),
+		allowOriginFunc:  options.AllowOriginFunc,
+		allowCredentials: options.AllowCredentials,
+		maxAge:           options.MaxAge,
+	}
+	if options.Debug {
+		c.log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
+	}
+
+	// Normalize options
+	// Note: for origins and methods matching, the spec requires a case-sensitive matching.
+	// As it may error prone, we chose to ignore the spec here.
+
+	// Allowed Origins
+	if len(options.AllowedOrigins) == 0 {
+		// Default is all origins
+		c.allowedOriginsAll = true
+	} else {
+		c.allowedOrigins = convert(options.AllowedOrigins, strings.ToLower)
+		for _, o := range c.allowedOrigins {
+			if o == "*" {
+				c.allowedOriginsAll = true
+				c.allowedOrigins = nil
+				break
+			}
+		}
+	}
+
+	// Allowed Headers
+	if len(options.AllowedHeaders) == 0 {
+		// Use sensible defaults
+		c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"}
+	} else {
+		// Origin is always appended as some browsers will always request for this header at preflight
+		c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey)
+		for _, h := range options.AllowedHeaders {
+			if h == "*" {
+				c.allowedHeadersAll = true
+				c.allowedHeaders = nil
+				break
+			}
+		}
+	}
+
+	// Allowed Methods
+	if len(options.AllowedMethods) == 0 {
+		// Default is spec's "simple" methods
+		c.allowedMethods = []string{"GET", "POST"}
+	} else {
+		c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper)
+	}
+
+	return c
+}
+
+// Default creates a new Cors handler with default options
+func Default() *Cors {
+	return New(Options{})
+}
+
+// Handler apply the CORS specification on the request, and add relevant CORS headers
+// as necessary.
+func (c *Cors) Handler(h http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Method == "OPTIONS" {
+			c.logf("Handler: Preflight request")
+			c.handlePreflight(w, r)
+			// Preflight requests are standalone and should stop the chain as some other
+			// middleware may not handle OPTIONS requests correctly. One typical example
+			// is authentication middleware ; OPTIONS requests won't carry authentication
+			// headers (see #1)
+		} else {
+			c.logf("Handler: Actual request")
+			c.handleActualRequest(w, r)
+			h.ServeHTTP(w, r)
+		}
+	})
+}
+
+// Martini compatible handler
+func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) {
+	if r.Method == "OPTIONS" {
+		c.logf("HandlerFunc: Preflight request")
+		c.handlePreflight(w, r)
+	} else {
+		c.logf("HandlerFunc: Actual request")
+		c.handleActualRequest(w, r)
+	}
+}
+
+// Negroni compatible interface
+func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+	if r.Method == "OPTIONS" {
+		c.logf("ServeHTTP: Preflight request")
+		c.handlePreflight(w, r)
+		// Preflight requests are standalone and should stop the chain as some other
+		// middleware may not handle OPTIONS requests correctly. One typical example
+		// is authentication middleware ; OPTIONS requests won't carry authentication
+		// headers (see #1)
+	} else {
+		c.logf("ServeHTTP: Actual request")
+		c.handleActualRequest(w, r)
+		next(w, r)
+	}
+}
+
+// handlePreflight handles pre-flight CORS requests
+func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
+	headers := w.Header()
+	origin := r.Header.Get("Origin")
+
+	if r.Method != "OPTIONS" {
+		c.logf("  Preflight aborted: %s!=OPTIONS", r.Method)
+		return
+	}
+	if origin == "" {
+		c.logf("  Preflight aborted: empty origin")
+		return
+	}
+	if !c.isOriginAllowed(origin) {
+		c.logf("  Preflight aborted: origin '%s' not allowed", origin)
+		return
+	}
+
+	reqMethod := r.Header.Get("Access-Control-Request-Method")
+	if !c.isMethodAllowed(reqMethod) {
+		c.logf("  Preflight aborted: method '%s' not allowed", reqMethod)
+		return
+	}
+	reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers"))
+	if !c.areHeadersAllowed(reqHeaders) {
+		c.logf("  Preflight aborted: headers '%v' not allowed", reqHeaders)
+		return
+	}
+	headers.Set("Access-Control-Allow-Origin", origin)
+	headers.Add("Vary", "Origin")
+	// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
+	// by Access-Control-Request-Method (if supported) can be enough
+	headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
+	if len(reqHeaders) > 0 {
+
+		// Spec says: Since the list of headers can be unbounded, simply returning supported headers
+		// from Access-Control-Request-Headers can be enough
+		headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", "))
+	}
+	if c.allowCredentials {
+		headers.Set("Access-Control-Allow-Credentials", "true")
+	}
+	if c.maxAge > 0 {
+		headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge))
+	}
+	c.logf("  Preflight response headers: %v", headers)
+}
+
+// handleActualRequest handles simple cross-origin requests, actual request or redirects
+func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
+	headers := w.Header()
+	origin := r.Header.Get("Origin")
+
+	if r.Method == "OPTIONS" {
+		c.logf("  Actual request no headers added: method == %s", r.Method)
+		return
+	}
+	if origin == "" {
+		c.logf("  Actual request no headers added: missing origin")
+		return
+	}
+	if !c.isOriginAllowed(origin) {
+		c.logf("  Actual request no headers added: origin '%s' not allowed", origin)
+		return
+	}
+
+	// Note that spec does define a way to specifically disallow a simple method like GET or
+	// POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
+	// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
+	// We think it's a nice feature to be able to have control on those methods though.
+	if !c.isMethodAllowed(r.Method) {
+		if c.log != nil {
+			c.logf("  Actual request no headers added: method '%s' not allowed",
+				r.Method)
+		}
+
+		return
+	}
+	headers.Set("Access-Control-Allow-Origin", origin)
+	headers.Add("Vary", "Origin")
+	if len(c.exposedHeaders) > 0 {
+		headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", "))
+	}
+	if c.allowCredentials {
+		headers.Set("Access-Control-Allow-Credentials", "true")
+	}
+	c.logf("  Actual response added headers: %v", headers)
+}
+
+// convenience method. checks if debugging is turned on before printing
+func (c *Cors) logf(format string, a ...interface{}) {
+	if c.log != nil {
+		c.log.Printf(format, a...)
+	}
+}
+
+// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
+// on the endpoint
+func (c *Cors) isOriginAllowed(origin string) bool {
+	if c.allowOriginFunc != nil {
+		return c.allowOriginFunc(origin)
+	}
+	if c.allowedOriginsAll {
+		return true
+	}
+	origin = strings.ToLower(origin)
+	for _, o := range c.allowedOrigins {
+		if o == origin {
+			return true
+		}
+	}
+	return false
+}
+
+// isMethodAllowed checks if a given method can be used as part of a cross-domain request
+// on the endpoing
+func (c *Cors) isMethodAllowed(method string) bool {
+	if len(c.allowedMethods) == 0 {
+		// If no method allowed, always return false, even for preflight request
+		return false
+	}
+	method = strings.ToUpper(method)
+	if method == "OPTIONS" {
+		// Always allow preflight requests
+		return true
+	}
+	for _, m := range c.allowedMethods {
+		if m == method {
+			return true
+		}
+	}
+	return false
+}
+
+// areHeadersAllowed checks if a given list of headers are allowed to used within
+// a cross-domain request.
+func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool {
+	if c.allowedHeadersAll || len(requestedHeaders) == 0 {
+		return true
+	}
+	for _, header := range requestedHeaders {
+		header = http.CanonicalHeaderKey(header)
+		found := false
+		for _, h := range c.allowedHeaders {
+			if h == header {
+				found = true
+			}
+		}
+		if !found {
+			return false
+		}
+	}
+	return true
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/cors_test.go b/Godeps/_workspace/src/github.com/rs/cors/cors_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1a7f0e4a4b8a18c7a2e257d1530e2dd0aa9d9c3c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/cors_test.go
@@ -0,0 +1,315 @@
+package cors
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"regexp"
+	"testing"
+)
+
+var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	w.Write([]byte("bar"))
+})
+
+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))
+		}
+	}
+}
+
+func TestNoConfig(t *testing.T) {
+	s := New(Options{
+	// Intentionally left blank.
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "",
+		"Access-Control-Allow-Methods":     "",
+		"Access-Control-Allow-Headers":     "",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestWildcardOrigin(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"*"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	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":    "",
+	})
+}
+
+func TestAllowedOrigin(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	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":    "",
+	})
+}
+
+func TestDisallowedOrigin(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://barbaz.com")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "",
+		"Access-Control-Allow-Methods":     "",
+		"Access-Control-Allow-Headers":     "",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestAllowedOriginFunc(t *testing.T) {
+	r, _ := regexp.Compile("^http://foo")
+	s := New(Options{
+		AllowOriginFunc: func(o string) bool {
+			println(r.MatchString(o))
+			return r.MatchString(o)
+		},
+	})
+
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+
+	res := httptest.NewRecorder()
+	req.Header.Set("Origin", "http://foobar.com")
+	s.Handler(testHandler).ServeHTTP(res, req)
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin": "http://foobar.com",
+	})
+
+	res = httptest.NewRecorder()
+	req.Header.Set("Origin", "http://barfoo.com")
+	s.Handler(testHandler).ServeHTTP(res, req)
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin": "",
+	})
+}
+
+func TestAllowedMethod(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+		AllowedMethods: []string{"PUT", "DELETE"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "PUT")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "http://foobar.com",
+		"Access-Control-Allow-Methods":     "PUT",
+		"Access-Control-Allow-Headers":     "",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestDisallowedMethod(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+		AllowedMethods: []string{"PUT", "DELETE"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "PATCH")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "",
+		"Access-Control-Allow-Methods":     "",
+		"Access-Control-Allow-Headers":     "",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestAllowedHeader(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+		AllowedHeaders: []string{"X-Header-1", "x-header-2"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "GET")
+	req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "http://foobar.com",
+		"Access-Control-Allow-Methods":     "GET",
+		"Access-Control-Allow-Headers":     "X-Header-2, X-Header-1",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestAllowedWildcardHeader(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+		AllowedHeaders: []string{"*"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "GET")
+	req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "http://foobar.com",
+		"Access-Control-Allow-Methods":     "GET",
+		"Access-Control-Allow-Headers":     "X-Header-2, X-Header-1",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestDisallowedHeader(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+		AllowedHeaders: []string{"X-Header-1", "x-header-2"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "GET")
+	req.Header.Add("Access-Control-Request-Headers", "X-Header-3, X-Header-1")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "",
+		"Access-Control-Allow-Methods":     "",
+		"Access-Control-Allow-Headers":     "",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestOriginHeader(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "GET")
+	req.Header.Add("Access-Control-Request-Headers", "origin")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "http://foobar.com",
+		"Access-Control-Allow-Methods":     "GET",
+		"Access-Control-Allow-Headers":     "Origin",
+		"Access-Control-Allow-Credentials": "",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
+
+func TestExposedHeader(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins: []string{"http://foobar.com"},
+		ExposedHeaders: []string{"X-Header-1", "x-header-2"},
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	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":    "X-Header-1, X-Header-2",
+	})
+}
+
+func TestAllowedCredentials(t *testing.T) {
+	s := New(Options{
+		AllowedOrigins:   []string{"http://foobar.com"},
+		AllowCredentials: true,
+	})
+
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+	req.Header.Add("Access-Control-Request-Method", "GET")
+
+	s.Handler(testHandler).ServeHTTP(res, req)
+
+	assertHeaders(t, res.Header(), map[string]string{
+		"Access-Control-Allow-Origin":      "http://foobar.com",
+		"Access-Control-Allow-Methods":     "GET",
+		"Access-Control-Allow-Headers":     "",
+		"Access-Control-Allow-Credentials": "true",
+		"Access-Control-Max-Age":           "",
+		"Access-Control-Expose-Headers":    "",
+	})
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..00a6a0bf1bdf794c529cec6469f0296798a9cb85
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+	"github.com/justinas/alice"
+)
+
+func main() {
+	c := cors.New(cors.Options{
+		AllowedOrigins: []string{"http://foo.com"},
+	})
+
+	mux := http.NewServeMux()
+
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte("{\"hello\": \"world\"}"))
+	})
+
+	chain := alice.New(c.Handler).Then(mux)
+	http.ListenAndServe(":8080", chain)
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..94e49bb5b0e9c60cb32250a5be5488e537a7a00b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+)
+
+func main() {
+	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte("{\"hello\": \"world\"}"))
+	})
+
+	// Use default options
+	handler := cors.Default().Handler(h)
+	http.ListenAndServe(":8080", handler)
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..96f63cfd9e8cd539cc27360c9cd9b2ecce350b27
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+	"github.com/zenazn/goji"
+)
+
+func main() {
+	c := cors.New(cors.Options{
+		AllowedOrigins: []string{"http://foo.com"},
+	})
+	goji.Use(c.Handler)
+
+	goji.Get("/", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte("{\"hello\": \"world\"}"))
+	})
+
+	goji.Serve()
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..199d989e6b3516b068f97d568a3a806d49094181
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+	"github.com/go-martini/martini"
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+	"github.com/martini-contrib/render"
+)
+
+func main() {
+	c := cors.New(cors.Options{
+		AllowedOrigins: []string{"http://foo.com"},
+	})
+
+	m := martini.Classic()
+	m.Use(render.Renderer())
+	m.Use(c.HandlerFunc)
+
+	m.Get("/", func(r render.Render) {
+		r.JSON(200, map[string]interface{}{"hello": "world"})
+	})
+
+	m.Run()
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..b9e6f386a98b645d3d6de0a12fc0dd03698b21a5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/codegangsta/negroni"
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+)
+
+func main() {
+	c := cors.New(cors.Options{
+		AllowedOrigins: []string{"http://foo.com"},
+	})
+
+	mux := http.NewServeMux()
+
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte("{\"hello\": \"world\"}"))
+	})
+
+	n := negroni.Classic()
+	n.Use(c)
+	n.UseHandler(mux)
+	n.Run(":3000")
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..a11fe064cb7a941e67782e8ac5e6c13466698124
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+)
+
+func main() {
+	c := cors.New(cors.Options{
+		AllowedOrigins: []string{"http://foo.com"},
+	})
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte("{\"hello\": \"world\"}"))
+	})
+
+	http.ListenAndServe(":8080", c.Handler(handler))
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..746a15a1663d4659723d60ae56fae8a7b8e43370
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
+)
+
+func main() {
+	c := cors.New(cors.Options{
+		AllowedOrigins:   []string{"*"},
+		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
+		AllowCredentials: true,
+	})
+
+	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte("{\"hello\": \"world\"}"))
+	})
+
+	http.ListenAndServe(":8080", c.Handler(h))
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/utils.go b/Godeps/_workspace/src/github.com/rs/cors/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8823a639c5016551fefcda16ab2d1b4074ca159
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/utils.go
@@ -0,0 +1,59 @@
+package cors
+
+const toLower = 'a' - 'A'
+
+type converter func(string) string
+
+// convert converts a list of string using the passed converter function
+func convert(s []string, c converter) []string {
+	out := []string{}
+	for _, i := range s {
+		out = append(out, c(i))
+	}
+	return out
+}
+
+// parseHeaderList tokenize + normalize a string containing a list of headers
+func parseHeaderList(headerList string) []string {
+	l := len(headerList)
+	h := make([]byte, 0, l)
+	upper := true
+	// Estimate the number headers in order to allocate the right splice size
+	t := 0
+	for i := 0; i < l; i++ {
+		if headerList[i] == ',' {
+			t++
+		}
+	}
+	headers := make([]string, 0, t)
+	for i := 0; i < l; i++ {
+		b := headerList[i]
+		if b >= 'a' && b <= 'z' {
+			if upper {
+				h = append(h, b-toLower)
+			} else {
+				h = append(h, b)
+			}
+		} else if b >= 'A' && b <= 'Z' {
+			if !upper {
+				h = append(h, b+toLower)
+			} else {
+				h = append(h, b)
+			}
+		} else if b == '-' || (b >= '0' && b <= '9') {
+			h = append(h, b)
+		}
+
+		if b == ' ' || b == ',' || i == l-1 {
+			if len(h) > 0 {
+				// Flush the found header
+				headers = append(headers, string(h))
+				h = h[:0]
+				upper = true
+			}
+		} else {
+			upper = b == '-'
+		}
+	}
+	return headers
+}
diff --git a/Godeps/_workspace/src/github.com/rs/cors/utils_test.go b/Godeps/_workspace/src/github.com/rs/cors/utils_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..de562b772e8629da839bcf8c3ffced6c9eaae0b9
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rs/cors/utils_test.go
@@ -0,0 +1,52 @@
+package cors
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestConvert(t *testing.T) {
+	s := convert([]string{"A", "b", "C"}, strings.ToLower)
+	e := []string{"a", "b", "c"}
+	if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] {
+		t.Errorf("%v != %v", s, e)
+	}
+}
+
+func TestParseHeaderList(t *testing.T) {
+	h := parseHeaderList("header, second-header, THIRD-HEADER, Numb3r3d-H34d3r")
+	e := []string{"Header", "Second-Header", "Third-Header", "Numb3r3d-H34d3r"}
+	if h[0] != e[0] || h[1] != e[1] || h[2] != e[2] {
+		t.Errorf("%v != %v", h, e)
+	}
+}
+
+func TestParseHeaderListEmpty(t *testing.T) {
+	if len(parseHeaderList("")) != 0 {
+		t.Error("should be empty sclice")
+	}
+	if len(parseHeaderList(" , ")) != 0 {
+		t.Error("should be empty sclice")
+	}
+}
+
+func BenchmarkParseHeaderList(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		parseHeaderList("header, second-header, THIRD-HEADER")
+	}
+}
+
+func BenchmarkParseHeaderListSingle(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		parseHeaderList("header")
+	}
+}
+
+func BenchmarkParseHeaderListNormalized(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		parseHeaderList("Header1, Header2, Third-Header")
+	}
+}
diff --git a/commands/http/handler.go b/commands/http/handler.go
index 17b782526e3ff867fec79045770c2598ff4b59b1..774c5ca4a6238601cb31e1ba8de2d760584c1f56 100644
--- a/commands/http/handler.go
+++ b/commands/http/handler.go
@@ -8,6 +8,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
 	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
 
 	cmds "github.com/ipfs/go-ipfs/commands"
@@ -16,10 +17,17 @@ import (
 
 var log = u.Logger("commands/http")
 
+// the internal handler for the API
+type internalHandler struct {
+	ctx  cmds.Context
+	root *cmds.Command
+}
+
+// The Handler struct is funny because we want to wrap our internal handler
+// with CORS while keeping our fields.
 type Handler struct {
-	ctx    cmds.Context
-	root   *cmds.Command
-	origin string
+	internalHandler
+	corsHandler http.Handler
 }
 
 var ErrNotFound = errors.New("404 page not found")
@@ -39,16 +47,31 @@ var mimeTypes = map[string]string{
 	cmds.Text: "text/plain",
 }
 
-func NewHandler(ctx cmds.Context, root *cmds.Command, origin string) *Handler {
+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(origin) > 0 {
-		log.Info("Allowing API requests from origin: " + origin)
+	if len(allowedOrigin) > 0 {
+		log.Info("Allowing API requests from origin: " + allowedOrigin)
 	}
 
-	return &Handler{ctx, root, origin}
+	// Create a handler for the API.
+	internal := internalHandler{ctx, root}
+
+	// Create a CORS object for wrapping the internal handler.
+	c := cors.New(cors.Options{
+		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)
+		},
+	})
+
+	// Wrap the internal handler with CORS handling-middleware.
+	return &Handler{internal, c.Handler(internal)}
 }
 
-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)
@@ -65,11 +88,6 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if len(i.origin) > 0 {
-		w.Header().Set("Access-Control-Allow-Origin", i.origin)
-	}
-	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
 	req, err := Parse(r, i.root)
 	if err != nil {
 		if err == ErrNotFound {
@@ -168,6 +186,11 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	flushCopy(w, out)
 }
 
+func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// Call the CORS handler which wraps the internal handler.
+	i.corsHandler.ServeHTTP(w, r)
+}
+
 // flushCopy Copies from an io.Reader to a http.ResponseWriter.
 // Flushes chunks over HTTP stream as they are read (if supported by transport).
 func flushCopy(w http.ResponseWriter, out io.Reader) error {
diff --git a/commands/http/handler_test.go b/commands/http/handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d622e048c42aa7071c991f2ac4fdf2c7fbc502a
--- /dev/null
+++ b/commands/http/handler_test.go
@@ -0,0 +1,71 @@
+package http
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/ipfs/go-ipfs/commands"
+)
+
+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))
+		}
+	}
+}
+
+func TestDisallowedOrigin(t *testing.T) {
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://barbaz.com")
+
+	handler := NewHandler(commands.Context{}, nil, "")
+	handler.ServeHTTP(res, req)
+
+	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":    "",
+	})
+}
+
+func TestWildcardOrigin(t *testing.T) {
+	res := httptest.NewRecorder()
+	req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
+	req.Header.Add("Origin", "http://foobar.com")
+
+	handler := NewHandler(commands.Context{}, nil, "*")
+	handler.ServeHTTP(res, req)
+
+	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":    "",
+	})
+}
+
+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")
+
+	handler := NewHandler(commands.Context{}, nil, "http://www.foobar.com")
+	handler.ServeHTTP(res, req)
+
+	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":    "",
+	})
+}