Skip to content

Commit

Permalink
Experimental support for Cross-Origin Resource Sharing (CORS).
Browse files Browse the repository at this point in the history
Closes COUCHDB-431

Patch by:

 - Dale Harvey
 - Benoit Chesneau
 - Jan Lehnardt
 - Robert Newson

See `etc/couchdb/default.ini.tpl.in` for configuration examples.
  • Loading branch information
janl committed Dec 5, 2012
1 parent 56f969b commit b90e402
Show file tree
Hide file tree
Showing 9 changed files with 825 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ HTTP Interface:
See http://www.w3.org/TR/eventsource/ for details.
* Make password hashing synchronous when using the /_config/admins API.
* Include user name in show/list ETags.
* Experimental support for Cross-Origin Resource Sharing (CORS).
See http://www.w3.org/TR/cors/ for details.

Replicator:

Expand Down
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This version has not been released yet.
* Server-wide UUID in some replication ids.
* E4X support in views is now deprecated and will be removed
in a future version.
* Experimental support for Cross-Origin Resource Sharing (CORS).

Version 1.2.1
-------------
Expand Down
25 changes: 25 additions & 0 deletions etc/couchdb/default.ini.tpl.in
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ allow_jsonp = false
; For more socket options, consult Erlang's module 'inet' man page.
;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}]
log_max_chunk_size = 1000000
enable_cors = false

[ssl]
port = 6984
Expand All @@ -67,6 +68,30 @@ auth_cache_size = 50 ; size is number of cache entries
allow_persistent_cookies = false ; set to true to allow persistent cookies
iterations = 10000 ; iterations for password hashing

[cors]
credentials = false
; List of origins separated by a comma, * means accept all
; Origins must include the scheme: http://example.com
; You can’t set origins: * and credentials = true at the same time.
;origins = *
; List of accepted headers separated by a comma
; headers =
; List of accepted methods
; methods =


; Configuration for a vhost
;[cors:http://example.com]
; credentials = false
; List of origins separated by a comma
; Origins must include the scheme: http://example.com
; You can’t set origins: * and credentials = true at the same time.
;origins =
; List of accepted headers separated by a comma
; headers =
; List of accepted methods
; methods =

[couch_httpd_oauth]
; If set to 'true', oauth token and consumer secrets will be looked up
; in the authentication database (_users). These secrets are stored in
Expand Down
2 changes: 2 additions & 0 deletions src/couchdb/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ source_files = \
couch_httpd.erl \
couch_httpd_db.erl \
couch_httpd_auth.erl \
couch_httpd_cors.erl \
couch_httpd_oauth.erl \
couch_httpd_external.erl \
couch_httpd_misc_handlers.erl \
Expand Down Expand Up @@ -106,6 +107,7 @@ compiled_files = \
couch_httpd_db.beam \
couch_httpd_auth.beam \
couch_httpd_oauth.beam \
couch_httpd_cors.beam \
couch_httpd_proxy.beam \
couch_httpd_external.beam \
couch_httpd_misc_handlers.beam \
Expand Down
42 changes: 33 additions & 9 deletions src/couchdb/couch_httpd.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
-export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
-export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]).
-export([accepted_encodings/1,handle_request_int/5,validate_referer/1,validate_ctype/2]).
-export([http_1_0_keep_alive/2]).

start_link() ->
start_link(http).
Expand Down Expand Up @@ -279,7 +280,10 @@ handle_request_int(MochiReq, DefaultFun,

% allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header
MethodOverride = MochiReq:get_primary_header_value("X-HTTP-Method-Override"),
Method2 = case lists:member(MethodOverride, ["GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", "COPY"]) of
Method2 = case lists:member(MethodOverride, ["GET", "HEAD", "POST",
"PUT", "DELETE",
"TRACE", "CONNECT",
"COPY"]) of
true ->
?LOG_INFO("MethodOverride: ~s (real method was ~s)", [MethodOverride, Method1]),
case Method1 of
Expand Down Expand Up @@ -318,9 +322,14 @@ handle_request_int(MochiReq, DefaultFun,

{ok, Resp} =
try
case couch_httpd_cors:is_preflight_request(HttpReq) of
#httpd{} ->
case authenticate_request(HttpReq, AuthHandlers) of
#httpd{} = Req ->
HandlerFun(Req);
Response ->
Response
end;
Response ->
Response
end
Expand Down Expand Up @@ -454,10 +463,14 @@ accepted_encodings(#httpd{mochi_req=MochiReq}) ->
serve_file(Req, RelativePath, DocumentRoot) ->
serve_file(Req, RelativePath, DocumentRoot, []).

serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot, ExtraHeaders) ->
serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot,
ExtraHeaders) ->
log_request(Req, 200),
ResponseHeaders = server_header()
++ couch_httpd_auth:cookie_auth_header(Req, [])
++ ExtraHeaders,
{ok, MochiReq:serve_file(RelativePath, DocumentRoot,
server_header() ++ couch_httpd_auth:cookie_auth_header(Req, []) ++ ExtraHeaders)}.
couch_httpd_cors:cors_headers(Req, ResponseHeaders))}.

qs_value(Req, Key) ->
qs_value(Req, Key, undefined).
Expand Down Expand Up @@ -607,7 +620,10 @@ log_request(#httpd{mochi_req=MochiReq,peer=Peer}, Code) ->
start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Length) ->
log_request(Req, Code),
couch_stats_collector:increment({httpd_status_codes, Code}),
Resp = MochiReq:start_response_length({Code, Headers ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers), Length}),
Headers1 = Headers ++ server_header() ++
couch_httpd_auth:cookie_auth_header(Req, Headers),
Headers2 = couch_httpd_cors:cors_headers(Req, Headers1),
Resp = MochiReq:start_response_length({Code, Headers2, Length}),
case MochiReq:get(method) of
'HEAD' -> throw({http_head_abort, Resp});
_ -> ok
Expand All @@ -618,7 +634,8 @@ start_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
log_request(Req, Code),
couch_stats_collector:increment({httpd_status_codes, Code}),
CookieHeader = couch_httpd_auth:cookie_auth_header(Req, Headers),
Headers2 = Headers ++ server_header() ++ CookieHeader,
Headers1 = Headers ++ server_header() ++ CookieHeader,
Headers2 = couch_httpd_cors:cors_headers(Req, Headers1),
Resp = MochiReq:start_response({Code, Headers2}),
case MochiReq:get(method) of
'HEAD' -> throw({http_head_abort, Resp});
Expand Down Expand Up @@ -650,8 +667,11 @@ http_1_0_keep_alive(Req, Headers) ->
start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
log_request(Req, Code),
couch_stats_collector:increment({httpd_status_codes, Code}),
Headers2 = http_1_0_keep_alive(MochiReq, Headers),
Resp = MochiReq:respond({Code, Headers2 ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers2), chunked}),
Headers1 = http_1_0_keep_alive(MochiReq, Headers),
Headers2 = Headers1 ++ server_header() ++
couch_httpd_auth:cookie_auth_header(Req, Headers1),
Headers3 = couch_httpd_cors:cors_headers(Req, Headers2),
Resp = MochiReq:respond({Code, Headers3, chunked}),
case MochiReq:get(method) of
'HEAD' -> throw({http_head_abort, Resp});
_ -> ok
Expand All @@ -672,14 +692,18 @@ last_chunk(Resp) ->
send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) ->
log_request(Req, Code),
couch_stats_collector:increment({httpd_status_codes, Code}),
Headers2 = http_1_0_keep_alive(MochiReq, Headers),
Headers1 = http_1_0_keep_alive(MochiReq, Headers),
if Code >= 500 ->
?LOG_ERROR("httpd ~p error response:~n ~s", [Code, Body]);
Code >= 400 ->
?LOG_DEBUG("httpd ~p error response:~n ~s", [Code, Body]);
true -> ok
end,
{ok, MochiReq:respond({Code, Headers2 ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers2), Body})}.
Headers2 = Headers1 ++ server_header() ++
couch_httpd_auth:cookie_auth_header(Req, Headers1),
Headers3 = couch_httpd_cors:cors_headers(Req, Headers2),

{ok, MochiReq:respond({Code, Headers3, Body})}.

send_method_not_allowed(Req, Methods) ->
send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>, ?l2b("Only " ++ Methods ++ " allowed")).
Expand Down
Loading

0 comments on commit b90e402

Please sign in to comment.