summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/couchdb/local_dev.ini26
-rw-r--r--share/www/script/couch.js181
-rw-r--r--share/www/script/couch_tests.js27
-rw-r--r--src/couchdb/couch_db.hrl3
-rw-r--r--src/couchdb/couch_db_updater.erl2
-rw-r--r--src/couchdb/couch_httpd.erl83
-rw-r--r--src/couchdb/couch_httpd_db.erl12
-rw-r--r--src/couchdb/couch_httpd_misc_handlers.erl20
-rw-r--r--src/couchdb/couch_rep.erl89
-rw-r--r--src/couchdb/couch_server.erl12
10 files changed, 271 insertions, 184 deletions
diff --git a/etc/couchdb/local_dev.ini b/etc/couchdb/local_dev.ini
index 4081062b..47751df4 100644
--- a/etc/couchdb/local_dev.ini
+++ b/etc/couchdb/local_dev.ini
@@ -12,7 +12,7 @@
;bind_address = 127.0.0.1
[log]
-;level = info
+level = debug
[update_notification]
;unique notifier name=/full/path/to/exe -with "cmd line arg"
@@ -24,3 +24,27 @@ foo = bar
[test]
foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index cae0abc9..934dacf8 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -13,58 +13,64 @@
// A simple class to represent a database. Uses XMLHttpRequest to interface with
// the CouchDB server.
-function CouchDB(name, options) {
+function CouchDB(name, httpHeaders) {
this.name = name;
this.uri = "/" + encodeURIComponent(name) + "/";
+
+ // The XMLHttpRequest object from the most recent request. Callers can
+ // use this to check result http status and headers.
+ this.last_req = null;
+
request = function(method, uri, requestOptions) {
- return CouchDB.request(method, uri, combine(requestOptions, options));
+ requestOptions = requestOptions || {}
+ requestOptions.headers = combine(requestOptions.headers, httpHeaders)
+ return CouchDB.request(method, uri, requestOptions);
}
// Creates the database on the server
this.createDb = function() {
- var req = request("PUT", this.uri);
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ this.last_req = request("PUT", this.uri);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
// Deletes the database on the server
this.deleteDb = function() {
- var req = request("DELETE", this.uri);
- if (req.status == 404)
+ this.last_req = request("DELETE", this.uri);
+ if (this.last_req.status == 404)
return false;
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
// Save a document to the database
this.save = function(doc, options) {
- var req;
if (doc._id == undefined)
doc._id = CouchDB.newUuids(1)[0];
- req = request("PUT", this.uri + encodeURIComponent(doc._id) + encodeOptions(options), {
- body: JSON.stringify(doc)
- });
- maybeThrowError(req);
- var result = JSON.parse(req.responseText);
+ this.last_req = request("PUT", this.uri +
+ encodeURIComponent(doc._id) + encodeOptions(options),
+ {body: JSON.stringify(doc)});
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
doc._rev = result.rev;
return result;
}
// Open a document from the database
this.open = function(docId, options) {
- var req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
- if (req.status == 404)
+ this.last_req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
+ if (this.last_req.status == 404)
return null;
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
// Deletes a document from the database
this.deleteDoc = function(doc) {
- var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
- maybeThrowError(req);
- var result = JSON.parse(req.responseText);
+ this.last_req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
doc._rev = result.rev; //record rev in input document
doc._deleted = true;
return result;
@@ -72,9 +78,9 @@ function CouchDB(name, options) {
// Deletes an attachment from a document
this.deleteDocAttachment = function(doc, attachment_name) {
- var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
- maybeThrowError(req);
- var result = JSON.parse(req.responseText);
+ this.last_req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
doc._rev = result.rev; //record rev in input document
return result;
}
@@ -92,11 +98,11 @@ function CouchDB(name, options) {
if (docs[i]._id == undefined)
docs[i]._id = newUuids.pop();
}
- var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
+ this.last_req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
body: JSON.stringify({"docs": docs})
});
- maybeThrowError(req);
- var result = JSON.parse(req.responseText);
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
for (var i = 0; i < docs.length; i++) {
docs[i]._rev = result.new_revs[i].rev;
}
@@ -117,49 +123,49 @@ function CouchDB(name, options) {
reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
body.reduce = reduceFun;
}
- var req = request("POST", this.uri + "_temp_view" + encodeOptions(options), {
+ this.last_req = request("POST", this.uri + "_temp_view" + encodeOptions(options), {
headers: {"Content-Type": "application/json"},
body: JSON.stringify(body)
});
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
this.view = function(viewname, options, keys) {
- var req = null ;
if(!keys) {
- req = request("GET", this.uri + "_view/" + viewname + encodeOptions(options));
+ this.last_req = request("GET", this.uri + "_view/" +
+ viewname + encodeOptions(options));
} else {
- req = request("POST", this.uri + "_view/" + viewname + encodeOptions(options), {
+ this.last_req = request("POST", this.uri + "_view/" +
+ viewname + encodeOptions(options), {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({keys:keys})
});
}
- if (req.status == 404)
+ if (this.last_req.status == 404)
return null;
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
// gets information about the database
this.info = function() {
- var req = request("GET", this.uri);
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ this.last_req = request("GET", this.uri);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
this.allDocs = function(options,keys) {
- var req = null;
if(!keys) {
- req = request("GET", this.uri + "_all_docs" + encodeOptions(options));
+ this.last_req = request("GET", this.uri + "_all_docs" + encodeOptions(options));
} else {
- req = request("POST", this.uri + "_all_docs" + encodeOptions(options), {
+ this.last_req = request("POST", this.uri + "_all_docs" + encodeOptions(options), {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({keys:keys})
});
}
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
this.allDocsBySeq = function(options,keys) {
@@ -172,14 +178,14 @@ function CouchDB(name, options) {
body: JSON.stringify({keys:keys})
});
}
- maybeThrowError(req);
+ CouchDB.maybeThrowError(req);
return JSON.parse(req.responseText);
}
this.compact = function() {
- var req = request("POST", this.uri + "_compact");
- maybeThrowError(req);
- return JSON.parse(req.responseText);
+ this.last_req = request("POST", this.uri + "_compact");
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
// Convert a options object to an url query string.
@@ -207,59 +213,44 @@ function CouchDB(name, options) {
}
function combine(object1, object2) {
- if (!object2) {
+ if (!object2)
return object1;
- }
- if (!object1) {
+ if (!object1)
return object2;
- }
- for (var name in object2) {
+
+ for (var name in object2)
object1[name] = object2[name];
- }
+
return object1;
}
- function maybeThrowError(req) {
- if (req.status >= 400) {
- if (req.responseText) {
- try {
- var result = JSON.parse(req.responseText);
- } catch (ParseError) {
- var result = {error:"unknown", reason:req.responseText};
- }
- } else {
- var result = {};
- }
- result.http_status = req.status;
- throw result;
- }
- }
+
}
+// this is the XMLHttpRequest object from last request made by the following
+// CouchDB.* functions (except for calls to request itself).
+// Use this from callers to check HTTP status or header values of requests.
+CouchDB.last_req = null;
+
+
CouchDB.allDbs = function() {
- var req = CouchDB.request("GET", "/_all_dbs");
- var result = JSON.parse(req.responseText);
- if (req.status != 200)
- throw result;
- return result;
+ CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText);
}
CouchDB.getVersion = function() {
- var req = CouchDB.request("GET", "/");
- var result = JSON.parse(req.responseText);
- if (req.status != 200)
- throw result;
- return result.version;
+ CouchDB.last_req = CouchDB.request("GET", "/");
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText).version;
}
CouchDB.replicate = function(source, target) {
- var req = CouchDB.request("POST", "/_replicate", {
+ CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
body: JSON.stringify({source: source, target: target})
});
- var result = JSON.parse(req.responseText);
- if (req.status != 200)
- throw result;
- return result;
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText);
}
CouchDB.request = function(method, uri, options) {
@@ -272,7 +263,7 @@ CouchDB.request = function(method, uri, options) {
} else {
throw new Error("No XMLHTTPRequest support detected");
}
- req.open(method, uri, false, options.username, options.password);
+ req.open(method, uri, false);
if (options.headers) {
var headers = options.headers;
for (var headerName in headers) {
@@ -297,12 +288,22 @@ CouchDB.newUuids = function(n) {
}
return uuids;
} else {
- var req = CouchDB.request("POST", "/_uuids?count=" + (100 + n));
- var result = JSON.parse(req.responseText);
- if (req.status != 200)
- throw result;
+ CouchDB.last_req = CouchDB.request("POST", "/_uuids?count=" + (100 + n));
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ var result = JSON.parse(CouchDB.last_req.responseText);
CouchDB.uuids_cache =
CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
return result.uuids.slice(100);
}
}
+
+CouchDB.maybeThrowError = function(req) {
+ if (req.status >= 400) {
+ try {
+ var result = JSON.parse(req.responseText);
+ } catch (ParseError) {
+ var result = {error:"unknown", reason:req.responseText};
+ }
+ throw result;
+ }
+}
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 8575da6f..3c14b761 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -1991,11 +1991,25 @@ var tests = {
reason:"Documents must have an author field",
http_status:403};
}
- if (oldDoc && oldDoc.author != userCtx.name) {
+
+ // Note, the next line could be:
+ //
+ // if (oldDoc && oldDoc.author != userCtx.name) {
+ //
+ // when name is the result of basic authentication, and added:
+ //
+ // headers:
+ // {"WWW-Authenticate": "Basic realm=\"" + userCtx.db + "\""},
+ //
+ // to the thrown exception. But when trying to authenticate in this
+ // manner, most browsers have weird behaviors that make testing it
+ // in the browser difficult. So instead we use special header values
+ // a proof of concept.
+ if (oldDoc && oldDoc.author != userCtx["X-Couch-Username"]) {
throw {error:"unauthorized",
reason:"You are not the author of this document. You jerk.",
headers:
- {"WWW-Authenticate": "Basic realm=\"" + userCtx.db + "\""},
+ {"X-Couch-Foo": "bar"},
http_status:401};
}
}).toString() + ")"
@@ -2003,14 +2017,14 @@ var tests = {
db.save(designDoc);
- var userDb = new CouchDB("test_suite_db", {username:"test user", password:"foo"});
+ var userDb = new CouchDB("test_suite_db", {"X-Couch-Username":"test user"});
try {
userDb.save({foo:1});
T(false && "Can't get here. Should have thrown an error");
} catch (e) {
T(e.error == "forbidden");
- T(e.http_status == 403);
+ T(userDb.last_req.status == 403);
}
userDb.save({_id:"testdoc", foo:1, author:"test user"});
@@ -2019,7 +2033,7 @@ var tests = {
doc.foo=2;
userDb.save(doc);
- var user2Db = new CouchDB("test_suite_db", {username:"test user2"});
+ var user2Db = new CouchDB("test_suite_db", {"X-Couch-Username":"test user2"});
var doc = user2Db.open("testdoc");
doc.foo=3;
@@ -2028,7 +2042,8 @@ var tests = {
T(false && "Can't get here. Should have thrown an error 2");
} catch (e) {
T(e.error == "unauthorized");
- T(e.http_status == 401);
+ T(user2Db.last_req.status == 401);
+ T(user2Db.last_req.getResponseHeader("X-Couch-Foo") == "bar");
}
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index 51f9b311..6769c840 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -59,7 +59,8 @@
{mochi_req,
method,
path_parts,
- db_url_handlers
+ db_url_handlers,
+ user_ctx
}).
diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl
index b3df910f..fecc5a14 100644
--- a/src/couchdb/couch_db_updater.erl
+++ b/src/couchdb/couch_db_updater.erl
@@ -35,7 +35,7 @@ init({MainPid, DbName, Filepath, Fd, Options}) ->
Db = init_db(DbName, Filepath, Fd, Header),
Db2 = refresh_validate_doc_funs(Db),
- {ok, Db#db{main_pid=MainPid}}.
+ {ok, Db2#db{main_pid=MainPid}}.
terminate(_Reason, Db) ->
close_db(Db).
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index c58255e3..6c8873dd 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -13,16 +13,17 @@
-module(couch_httpd).
-include("couch_db.hrl").
--export([start_link/0, stop/0, handle_request/3]).
+-export([start_link/0, stop/0, handle_request/4]).
-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1]).
--export([check_is_admin/1,unquote/1,creds/1]).
+-export([check_is_admin/1,unquote/1]).
-export([parse_form/1,json_body/1,body/1,doc_etag/1]).
-export([primary_header_value/2,partition/1,serve_file/3]).
-export([start_chunked_response/3,send_chunk/2]).
-export([start_json_response/2, start_json_response/3, end_json_response/1]).
-export([send_response/4,send_method_not_allowed/2,send_error/4]).
-export([send_json/2,send_json/3,send_json/4]).
+-export([default_authenticate/1]).
% Maximum size of document PUT request body (4GB)
@@ -36,32 +37,24 @@ start_link() ->
BindAddress = couch_config:get("httpd", "bind_address", any),
Port = couch_config:get("httpd", "port", "5984"),
-
+ AuthenticationFun = make_arity_1_fun(
+ couch_config:get("httpd", "authentication",
+ "{couch_httpd, default_authenticate}")),
+
UrlHandlersList = lists:map(
fun({UrlKey, SpecStr}) ->
- case couch_util:parse_term(SpecStr) of
- {ok, {M, F, A}} ->
- {list_to_binary(UrlKey), fun(Req) -> apply(M, F, [Req, A]) end};
- {ok, {M, F}} ->
- {list_to_binary(UrlKey), fun(Req) -> apply(M, F, [Req]) end}
- end
+ {list_to_binary(UrlKey), make_arity_1_fun(SpecStr)}
end, couch_config:get("httpd_global_handlers")),
DbUrlHandlersList = lists:map(
fun({UrlKey, SpecStr}) ->
- case couch_util:parse_term(SpecStr) of
- {ok, {M, F, A}} ->
- {list_to_binary(UrlKey),
- fun(Req, Db) -> apply(M, F, [Req, Db, A]) end};
- {ok, {M, F}} ->
- {list_to_binary(UrlKey),
- fun(Req, Db) -> apply(M, F, [Req, Db]) end}
- end
+ {list_to_binary(UrlKey), make_arity_2_fun(SpecStr)}
end, couch_config:get("httpd_db_handlers")),
UrlHandlers = dict:from_list(UrlHandlersList),
DbUrlHandlers = dict:from_list(DbUrlHandlersList),
Loop = fun(Req)->
- apply(?MODULE, handle_request, [Req, UrlHandlers, DbUrlHandlers])
+ apply(?MODULE, handle_request,
+ [Req, UrlHandlers, DbUrlHandlers, AuthenticationFun])
end,
% and off we go
@@ -76,6 +69,8 @@ start_link() ->
?MODULE:stop();
("httpd", "port") ->
?MODULE:stop();
+ ("httpd", "authentication") ->
+ ?MODULE:stop();
("httpd_global_handlers", _) ->
?MODULE:stop();
("httpd_db_handlers", _) ->
@@ -84,11 +79,29 @@ start_link() ->
{ok, Pid}.
+% SpecStr is a string like "{my_module, my_fun}" or "{my_module, my_fun, foo}"
+make_arity_1_fun(SpecStr) ->
+ case couch_util:parse_term(SpecStr) of
+ {ok, {Mod, Fun, SpecArg}} ->
+ fun(Arg) -> apply(Mod, Fun, [Arg, SpecArg]) end;
+ {ok, {Mod, Fun}} ->
+ fun(Arg) -> apply(Mod, Fun, [Arg]) end
+ end.
+
+make_arity_2_fun(SpecStr) ->
+ case couch_util:parse_term(SpecStr) of
+ {ok, {Mod, Fun, SpecArg}} ->
+ fun(Arg1, Arg2) -> apply(Mod, Fun, [Arg1, Arg2, SpecArg]) end;
+ {ok, {Mod, Fun}} ->
+ fun(Arg1, Arg2) -> apply(Mod, Fun, [Arg1, Arg2]) end
+ end.
+
+
stop() ->
mochiweb_http:stop(?MODULE).
-handle_request(MochiReq, UrlHandlers, DbUrlHandlers) ->
+handle_request(MochiReq, UrlHandlers, DbUrlHandlers, AuthenticationFun) ->
% for the path, use the raw path with the query string and fragment
% removed, but URL quoting left intact
@@ -128,13 +141,16 @@ handle_request(MochiReq, UrlHandlers, DbUrlHandlers) ->
|| Part <- string:tokens(Path, "/")],
db_url_handlers = DbUrlHandlers
},
-
DefaultFun = fun couch_httpd_db:handle_request/1,
HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun),
+ CouchHeaders = [{?l2b(K), ?l2b(V)}
+ || {"X-Couch-" ++ _= K,V}
+ <- mochiweb_headers:to_list(MochiReq:get(headers))],
{ok, Resp} =
try
- HandlerFun(HttpReq)
+ {UserCtxProps} = AuthenticationFun(HttpReq),
+ HandlerFun(HttpReq#httpd{user_ctx={UserCtxProps ++ CouchHeaders}})
catch
Error ->
send_error(HttpReq, Error)
@@ -150,6 +166,17 @@ handle_request(MochiReq, UrlHandlers, DbUrlHandlers) ->
+default_authenticate(Req) ->
+ % by default, we just assume the users credentials for basic authentication
+ % are correct.
+ case basic_username_pw(Req) of
+ {Username, _Pw} ->
+ {[{<<"name">>, ?l2b(Username)}]};
+ nil ->
+ {[]}
+ end.
+
+
% Utilities
partition(Path) ->
@@ -197,17 +224,7 @@ json_body(#httpd{mochi_req=MochiReq}) ->
doc_etag(#doc{revs=[DiskRev|_]}) ->
"\"" ++ binary_to_list(DiskRev) ++ "\"".
-
-% user credentials
-creds(Req) ->
- case username_pw(Req) of
- {User, _Pw} ->
- {[{<<"name">>, ?l2b(User)}]};
- nil ->
- {[]}
- end.
-
-username_pw(Req) ->
+basic_username_pw(Req) ->
case header_value(Req, "Authorization") of
"Basic " ++ Base64Value ->
case string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),":") of
@@ -224,7 +241,7 @@ username_pw(Req) ->
check_is_admin(Req) ->
IsNamedAdmin =
- case username_pw(Req) of
+ case basic_username_pw(Req) of
{User, Pass} ->
couch_server:is_admin(User, Pass);
nil ->
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index bc8e2019..a239ceb5 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -41,9 +41,9 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
do_db_req(Req, Handler)
end.
-create_db_req(Req, DbName) ->
+create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
ok = couch_httpd:check_is_admin(Req),
- case couch_server:create(DbName, [{creds, couch_httpd:creds(Req)}]) of
+ case couch_server:create(DbName, [{user_ctx, UserCtx}]) of
{ok, Db} ->
couch_db:close(Db),
send_json(Req, 201, {[{ok, true}]});
@@ -51,17 +51,17 @@ create_db_req(Req, DbName) ->
throw(Error)
end.
-delete_db_req(Req, DbName) ->
+delete_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
ok = couch_httpd:check_is_admin(Req),
- case couch_server:delete(DbName, [{creds, couch_httpd:creds(Req)}]) of
+ case couch_server:delete(DbName, [{user_ctx, UserCtx}]) of
ok ->
send_json(Req, 200, {[{ok, true}]});
Error ->
throw(Error)
end.
-do_db_req(#httpd{path_parts=[DbName|_]}=Req, Fun) ->
- case couch_db:open(DbName, [{creds, couch_httpd:creds(Req)}]) of
+do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) ->
+ case couch_db:open(DbName, [{user_ctx, UserCtx}]) of
{ok, Db} ->
try
Fun(Req, Db)
diff --git a/src/couchdb/couch_httpd_misc_handlers.erl b/src/couchdb/couch_httpd_misc_handlers.erl
index bcfe17c4..b62a4b85 100644
--- a/src/couchdb/couch_httpd_misc_handlers.erl
+++ b/src/couchdb/couch_httpd_misc_handlers.erl
@@ -62,12 +62,28 @@ handle_all_dbs_req(Req) ->
send_method_not_allowed(Req, "GET,HEAD").
-handle_replicate_req(#httpd{method='POST'}=Req) ->
+handle_replicate_req(#httpd{user_ctx=UserCtx,method='POST'}=Req) ->
{Props} = couch_httpd:json_body(Req),
Source = proplists:get_value(<<"source">>, Props),
Target = proplists:get_value(<<"target">>, Props),
+
+ {SrcOpts} = proplists:get_value(<<"source_options">>, Props, {[]}),
+ {SrcHeadersBinary} = proplists:get_value(<<"headers">>, SrcOpts, {[]}),
+ SrcHeaders = [{?b2l(K),(V)} || {K,V} <- SrcHeadersBinary],
+
+ {TgtOpts} = proplists:get_value(<<"target_options">>, Props, {[]}),
+ {TgtHeadersBinary} = proplists:get_value(<<"headers">>, TgtOpts, {[]}),
+ TgtHeaders = [{?b2l(K),(V)} || {K,V} <- TgtHeadersBinary],
+
{Options} = proplists:get_value(<<"options">>, Props, {[]}),
- {ok, {JsonResults}} = couch_rep:replicate(Source, Target, Options),
+ Options2 = [{source_options,
+ [{headers, SrcHeaders},
+ {user_ctx, UserCtx}]},
+ {target_options,
+ [{headers, TgtHeaders},
+ {user_ctx, UserCtx}]}
+ | Options],
+ {ok, {JsonResults}} = couch_rep:replicate(Source, Target, Options2),
send_json(Req, {[{ok, true} | JsonResults]});
handle_replicate_req(Req) ->
send_method_not_allowed(Req, "POST").
diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl
index d0a12fc7..eda62c86 100644
--- a/src/couchdb/couch_rep.erl
+++ b/src/couchdb/couch_rep.erl
@@ -14,6 +14,11 @@
-include("couch_db.hrl").
+-record(http_db, {
+ uri,
+ headers
+}).
+
-export([replicate/2, replicate/3]).
url_encode(Bin) when is_binary(Bin) ->
@@ -44,9 +49,11 @@ replicate(DbNameA, DbNameB) ->
replicate(DbNameA, DbNameB, []).
replicate(Source, Target, Options) ->
- {ok, DbSrc} = open_db(Source),
+ {ok, DbSrc} = open_db(Source,
+ proplists:get_value(source_options, Options, [])),
try
- {ok, DbTgt} = open_db(Target),
+ {ok, DbTgt} = open_db(Target,
+ proplists:get_value(target_options, Options, [])),
try
replicate2(Source, DbSrc, Target, DbTgt, Options)
after
@@ -216,20 +223,19 @@ save_docs_loop(DbTarget, DocsWritten) ->
end.
-do_http_request(Url, Action) ->
- do_http_request(Url, Action, []).
+do_http_request(Url, Action, Headers) ->
+ do_http_request(Url, Action, Headers, []).
-do_http_request(Url, Action, JsonBody) ->
+do_http_request(Url, Action, Headers, JsonBody) ->
?LOG_DEBUG("couch_rep HTTP client request:", []),
?LOG_DEBUG("\tAction: ~p", [Action]),
?LOG_DEBUG("\tUrl: ~p", [Url]),
-
Request =
case JsonBody of
[] ->
- {Url, []};
+ {Url, Headers};
_ ->
- {Url, [], "application/json; charset=utf-8", iolist_to_binary(?JSON_ENCODE(JsonBody))}
+ {Url, Headers, "application/json; charset=utf-8", iolist_to_binary(?JSON_ENCODE(JsonBody))}
end,
{ok, {{_, ResponseCode,_},_Headers, ResponseBody}} = http:request(Action, Request, [], []),
if
@@ -249,29 +255,32 @@ fix_url(UrlBin) ->
Url = binary_to_list(UrlBin),
case lists:last(Url) of
$/ ->
- {ok, Url};
+ Url;
_ ->
- {ok, Url ++ "/"}
+ Url ++ "/"
end.
-open_db(<<"http://", _/binary>>=UrlBin)->
- fix_url(UrlBin);
-open_db(<<"https://", _/binary>>=UrlBin)->
- fix_url(UrlBin);
-open_db(DbName)->
- couch_db:open(DbName, []).
-
-close_db("http://" ++ _)->
- ok;
-close_db("https://" ++ _)->
+open_http_db(UrlBin, Options) ->
+ Headers = proplists:get_value(headers, Options, {[]}),
+ {ok, #http_db{uri=fix_url(UrlBin), headers=Headers}}.
+
+open_db(<<"http://", _/binary>>=Url, Options)->
+ open_http_db(Url, Options);
+open_db(<<"https://", _/binary>>=Url, Options)->
+ open_http_db(Url, Options);
+open_db(DbName, Options)->
+ couch_db:open(DbName, Options).
+
+close_db(#http_db{})->
ok;
-close_db(DbName)->
- couch_db:close(DbName).
+close_db(Db)->
+ couch_db:close(Db).
-enum_docs_since(DbUrl, StartSeq, InFun, InAcc) when is_list(DbUrl) ->
- Url = DbUrl ++ "_all_docs_by_seq?count=100&startkey=" ++ integer_to_list(StartSeq),
- {Results} = do_http_request(Url, get),
+enum_docs_since(#http_db{uri=DbUrl, headers=Headers}=Db, Start, InFun, InAcc)->
+ Url = DbUrl ++ "_all_docs_by_seq?count=100&startkey="
+ ++ integer_to_list(Start),
+ {Results} = do_http_request(Url, get, Headers),
DocInfoList=
lists:map(fun({RowInfoList}) ->
{RowValueProps} = proplists:get_value(<<"value">>, RowInfoList),
@@ -291,23 +300,25 @@ enum_docs_since(DbUrl, StartSeq, InFun, InAcc) when is_list(DbUrl) ->
_ ->
Acc2 = enum_docs0(InFun, DocInfoList, InAcc),
#doc_info{update_seq=LastSeq} = lists:last(DocInfoList),
- enum_docs_since(DbUrl, LastSeq, InFun, Acc2)
+ enum_docs_since(Db, LastSeq, InFun, Acc2)
end;
enum_docs_since(DbSource, StartSeq, Fun, Acc) ->
couch_db:enum_docs_since(DbSource, StartSeq, Fun, Acc).
-get_missing_revs(DbUrl, DocIdRevsList) when is_list(DbUrl) ->
- {ResponseMembers} = do_http_request(DbUrl ++ "_missing_revs", post, {DocIdRevsList}),
+get_missing_revs(#http_db{uri=DbUrl, headers=Headers}, DocIdRevsList) ->
+ {ResponseMembers} = do_http_request(DbUrl ++ "_missing_revs", post, Headers,
+ {DocIdRevsList}),
{DocMissingRevsList} = proplists:get_value(<<"missing_revs">>, ResponseMembers),
{ok, DocMissingRevsList};
get_missing_revs(Db, DocId) ->
couch_db:get_missing_revs(Db, DocId).
-update_doc(DbUrl, #doc{id=DocId}=Doc, _Options) when is_list(DbUrl) ->
+update_doc(#http_db{uri=DbUrl, headers=Headers}, #doc{id=DocId}=Doc, Options) ->
+ [] = Options,
Url = DbUrl ++ url_encode(DocId),
- {ResponseMembers} =
- do_http_request(Url, put, couch_doc:to_json_obj(Doc, [revs,attachments])),
+ {ResponseMembers} = do_http_request(Url, put, Headers,
+ couch_doc:to_json_obj(Doc, [revs,attachments])),
RevId = proplists:get_value(<<"_rev">>, ResponseMembers),
{ok, RevId};
update_doc(Db, Doc, Options) ->
@@ -315,28 +326,30 @@ update_doc(Db, Doc, Options) ->
update_docs(_, [], _, _) ->
ok;
-update_docs(DbUrl, Docs, [], NewEdits) when is_list(DbUrl) ->
+update_docs(#http_db{uri=DbUrl, headers=Headers}, Docs, [], NewEdits) ->
JsonDocs = [couch_doc:to_json_obj(Doc, [revs,attachments]) || Doc <- Docs],
{Returned} =
- do_http_request(DbUrl ++ "_bulk_docs", post, {[{new_edits, NewEdits}, {docs, JsonDocs}]}),
+ do_http_request(DbUrl ++ "_bulk_docs", post, Headers,
+ {[{new_edits, NewEdits}, {docs, JsonDocs}]}),
true = proplists:get_value(<<"ok">>, Returned),
ok;
update_docs(Db, Docs, Options, NewEdits) ->
couch_db:update_docs(Db, Docs, Options, NewEdits).
-open_doc(DbUrl, DocId, []) when is_list(DbUrl) ->
- case do_http_request(DbUrl ++ url_encode(DocId), get) of
+open_doc(#http_db{uri=DbUrl, headers=Headers}, DocId, Options) ->
+ [] = Options,
+ case do_http_request(DbUrl ++ url_encode(DocId), get, Headers) of
{[{<<"error">>, ErrId}, {<<"reason">>, Reason}]} -> % binaries?
{list_to_atom(binary_to_list(ErrId)), Reason};
Doc ->
{ok, couch_doc:from_json_obj(Doc)}
end;
-open_doc(Db, DocId, Options) when not is_list(Db) ->
+open_doc(Db, DocId, Options) ->
couch_db:open_doc(Db, DocId, Options).
-open_doc_revs(DbUrl, DocId, Revs, Options) when is_list(DbUrl) ->
+open_doc_revs(#http_db{uri=DbUrl, headers=Headers}, DocId, Revs, Options) ->
QueryOptionStrs =
lists:map(fun(latest) ->
% latest is only option right now
@@ -344,7 +357,7 @@ open_doc_revs(DbUrl, DocId, Revs, Options) when is_list(DbUrl) ->
end, Options),
RevsQueryStrs = lists:flatten(?JSON_ENCODE(Revs)),
Url = DbUrl ++ url_encode(DocId) ++ "?" ++ couch_util:implode(["revs=true", "attachments=true", "open_revs=" ++ RevsQueryStrs ] ++ QueryOptionStrs, "&"),
- JsonResults = do_http_request(Url, get, []),
+ JsonResults = do_http_request(Url, get, Headers),
Results =
lists:map(
fun({[{<<"missing">>, Rev}]}) ->
diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl
index b0d03a53..a81ca5e1 100644
--- a/src/couchdb/couch_server.erl
+++ b/src/couchdb/couch_server.erl
@@ -202,7 +202,7 @@ handle_call(get_root, _From, #server{root_dir=Root}=Server) ->
{reply, {ok, Root}, Server};
handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
DbNameList = binary_to_list(DbName),
- UserCreds = proplists:get_value(creds, Options, nil),
+ UserCtx = proplists:get_value(user_ctx, Options, nil),
case check_dbname(Server, DbNameList) of
ok ->
Filepath = get_full_filename(Server, DbNameList),
@@ -218,7 +218,7 @@ handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
DbsOpen = Server2#server.current_dbs_open + 1,
{reply,
- couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
+ couch_db:open_ref_counted(MainPid, FromPid, UserCtx),
Server2#server{current_dbs_open=DbsOpen}};
Error ->
{reply, Error, Server2}
@@ -231,7 +231,7 @@ handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
true = ets:delete(couch_dbs_by_lru, PrevLruTime),
true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
{reply,
- couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
+ couch_db:open_ref_counted(MainPid, FromPid, UserCtx),
Server}
end;
Error ->
@@ -239,7 +239,7 @@ handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
end;
handle_call({create, DbName, Options}, {FromPid,_}, Server) ->
DbNameList = binary_to_list(DbName),
- UserCreds = proplists:get_value(creds, Options, nil),
+ UserCtx = proplists:get_value(user_ctx, Options, nil),
case check_dbname(Server, DbNameList) of
ok ->
Filepath = get_full_filename(Server, DbNameList),
@@ -255,7 +255,7 @@ handle_call({create, DbName, Options}, {FromPid,_}, Server) ->
DbsOpen = Server#server.current_dbs_open + 1,
couch_db_update_notifier:notify({created, DbName}),
{reply,
- couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
+ couch_db:open_ref_counted(MainPid, FromPid, UserCtx),
Server#server{current_dbs_open=DbsOpen}};
Error ->
{reply, Error, Server}
@@ -268,7 +268,7 @@ handle_call({create, DbName, Options}, {FromPid,_}, Server) ->
end;
handle_call({delete, DbName, Options}, _From, Server) ->
DbNameList = binary_to_list(DbName),
- _UserCreds = proplists:get_value(creds, Options, nil),
+ _UserCtx = proplists:get_value(user_ctx, Options, nil),
case check_dbname(Server, DbNameList) of
ok ->
FullFilepath = get_full_filename(Server, DbNameList),