Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge CouchDB 1.1

  • Loading branch information...
commit e9a5a6f90a021db1db8a7e55ec797a4c86edcad6 2 parents 266ba88 + 3c1a0d7
Robert Newson authored
Showing with 5,998 additions and 1,486 deletions.
  1. +3 −0  .gitignore
  2. +1 −1  apps/couch/AUTHORS
  3. +60 −0 apps/couch/CHANGES
  4. +1 −1  apps/couch/INSTALL.Unix
  5. +12 −7 apps/couch/INSTALL.Windows
  6. +33 −3 apps/couch/NEWS
  7. +5 −1 apps/couch/NOTICE
  8. +11 −0 apps/couch/THANKS
  9. +11 −21 apps/couch/include/couch_db.hrl
  10. +4 −0 apps/couch/license.skip
  11. +1 −1  apps/couch/src/couch_btree.erl
  12. +161 −79 apps/couch/src/couch_changes.erl
  13. +25 −14 apps/couch/src/couch_config.erl
  14. +8 −1 apps/couch/src/couch_config_writer.erl
  15. +10 −3 apps/couch/src/couch_db.erl
  16. +9 −6 apps/couch/src/couch_db_updater.erl
  17. +46 −10 apps/couch/src/couch_doc.erl
  18. +6 −2 apps/couch/src/couch_event_sup.erl
  19. +42 −13 apps/couch/src/couch_file.erl
  20. +122 −114 apps/couch/src/couch_httpd.erl
  21. +20 −9 apps/couch/src/couch_httpd_auth.erl
  22. +187 −100 apps/couch/src/couch_httpd_db.erl
  23. +10 −3 apps/couch/src/couch_httpd_external.erl
  24. +72 −14 apps/couch/src/couch_httpd_misc_handlers.erl
  25. +431 −0 apps/couch/src/couch_httpd_proxy.erl
  26. +112 −77 apps/couch/src/couch_httpd_rewrite.erl
  27. +18 −13 apps/couch/src/couch_httpd_show.erl
  28. +403 −0 apps/couch/src/couch_httpd_vhost.erl
  29. +97 −36 apps/couch/src/couch_httpd_view.erl
  30. +141 −0 apps/couch/src/couch_js_functions.hrl
  31. +38 −17 apps/couch/src/couch_key_tree.erl
  32. +58 −21 apps/couch/src/couch_log.erl
  33. +364 −0 apps/couch/src/couch_os_daemons.erl
  34. +1 −1  apps/couch/src/couch_proc_manager.erl
  35. +28 −6 apps/couch/src/couch_query_servers.erl
  36. +315 −135 apps/couch/src/couch_rep.erl
  37. +1 −2  apps/couch/src/couch_rep_att.erl
  38. +174 −73 apps/couch/src/couch_rep_changes_feed.erl
  39. +33 −11 apps/couch/src/couch_rep_httpc.erl
  40. +9 −71 apps/couch/src/couch_rep_reader.erl
  41. +6 −12 apps/couch/src/couch_rep_writer.erl
  42. +387 −0 apps/couch/src/couch_replication_manager.erl
  43. +2 −1  apps/couch/src/couch_server.erl
  44. +32 −6 apps/couch/src/couch_server_sup.erl
  45. +41 −3 apps/couch/src/couch_stream.erl
  46. +48 −31 apps/couch/src/couch_util.erl
  47. +58 −27 apps/couch/src/couch_view.erl
  48. +79 −46 apps/couch/src/couch_view_group.erl
  49. +21 −8 apps/couch/src/couch_view_updater.erl
  50. +87 −47 apps/couch/src/couch_work_queue.erl
  51. +10 −2 apps/couch/src/test_util.erl
  52. +2 −1  apps/couch/test/etap/010-file-basics.t
  53. +2 −2 apps/couch/test/etap/021-btree-reductions.t
  54. +3 −0  apps/couch/test/etap/030-doc-from-json.t
  55. +3 −0  apps/couch/test/etap/031-doc-to-json.t
  56. +3 −3 apps/couch/test/etap/050-stream.t
  57. +1 −1  apps/couch/test/etap/083-config-no-files.t
  58. +19 −0 apps/couch/test/etap/random_port.ini
  59. +30 −0 apps/couch/test/etap/test_cfg_register.c
  60. +99 −0 apps/couch/test/etap/test_web.erl
  61. +2 −1  couchjs/js/json2.js
  62. +1 −0  couchjs/js/loop.js
  63. +4 −4 couchjs/js/render.js
  64. +6 −1 couchjs/js/state.js
  65. +23 −12 couchjs/js/util.js
  66. +4 −4 rebar.config
  67. +27 −2 rel/overlay/etc/default.ini
  68. +6 −7 rel/overlay/share/www/_sidebar.html
  69. +4 −1 rel/overlay/share/www/couch_tests.html
  70. +1 −1  rel/overlay/share/www/custom_test.html
  71. +40 −11 rel/overlay/share/www/replicator.html
  72. +2 −2 rel/overlay/share/www/script/couch.js
  73. +4 −0 rel/overlay/share/www/script/couch_tests.js
  74. +6 −4 rel/overlay/share/www/script/futon.browse.js
  75. +2 −2 rel/overlay/share/www/script/futon.format.js
  76. +55 −0 rel/overlay/share/www/script/futon.js
  77. +66 −29 rel/overlay/share/www/script/jquery.couch.js
  78. +2 −1  rel/overlay/share/www/script/json2.js
  79. +1 −1  rel/overlay/share/www/script/jspec/jspec.js
  80. +41 −0 rel/overlay/share/www/script/test/all_docs.js
  81. +26 −15 rel/overlay/share/www/script/test/attachment_names.js
  82. +2 −0  rel/overlay/share/www/script/test/attachments.js
  83. +167 −2 rel/overlay/share/www/script/test/attachments_multipart.js
  84. +6 −8 rel/overlay/share/www/script/test/basics.js
  85. +134 −23 rel/overlay/share/www/script/test/changes.js
  86. +101 −5 rel/overlay/share/www/script/test/config.js
  87. +25 −0 rel/overlay/share/www/script/test/conflicts.js
  88. +12 −0 rel/overlay/share/www/script/test/cookie_auth.js
  89. +3 −0  rel/overlay/share/www/script/test/copy_doc.js
  90. +385 −150 rel/overlay/share/www/script/test/design_docs.js
  91. +92 −6 rel/overlay/share/www/script/test/etags_views.js
  92. +3 −3 rel/overlay/share/www/script/test/http.js
  93. +1 −1  rel/overlay/share/www/script/test/jsonp.js
  94. +47 −0 rel/overlay/share/www/script/test/list_views.js
  95. +14 −14 rel/overlay/share/www/script/test/oauth.js
  96. +1 −1  rel/overlay/share/www/script/test/proxyauth.js
  97. +32 −0 rel/overlay/share/www/script/test/purge.js
  98. +25 −0 rel/overlay/share/www/script/test/reduce_builtin.js
  99. +244 −59 rel/overlay/share/www/script/test/replication.js
  100. +60 −0 rel/overlay/share/www/script/test/rewrite.js
  101. +4 −4 rel/overlay/share/www/script/test/security_validation.js
  102. +22 −0 rel/overlay/share/www/script/test/show_documents.js
  103. +4 −0 rel/overlay/share/www/script/test/update_documents.js
  104. +8 −0 rel/overlay/share/www/script/test/view_errors.js
  105. +54 −0 rel/overlay/share/www/script/test/view_include_docs.js
  106. +37 −0 rel/overlay/share/www/script/test/view_multi_key_all_docs.js
  107. +78 −0 rel/overlay/share/www/script/test/view_multi_key_design.js
  108. +74 −49 rel/overlay/share/www/script/test/view_pagination.js
  109. +21 −4 rel/overlay/share/www/script/test/view_update_seq.js
  110. +3 −3 rel/overlay/share/www/style/layout.css
View
3  .gitignore
@@ -2,6 +2,9 @@
*.so
*.Tpo
*.beam
+*~
+*.orig
+*.rej
erl_crash.dump
# building
View
2  apps/couch/AUTHORS
@@ -15,6 +15,6 @@ documentation or developing software. Some of these people are:
* Mark Hammond <mhammond@skippinet.com.au>
* Benoît Chesneau <benoitc@apache.org>
* Filipe Manana <fdmanana@apache.org>
- * Robert Newson <robert.newson@gmail.com>
+ * Robert Newson <rnewson@apache.org>
For a list of other credits see the `THANKS` file.
View
60 apps/couch/CHANGES
@@ -1,6 +1,66 @@
Apache CouchDB CHANGES
======================
+Version 1.1.1
+-------------
+
+This version has not been released yet.
+
+Version 1.1.0
+-------------
+
+All CHANGES for 1.0.2 and 1.0.3 also apply to 1.1.0.
+
+HTTP Interface:
+
+ * Native SSL support.
+ * Added support for HTTP range requests for attachments.
+ * Added built-in filters for `_changes`: `_doc_ids` and `_design`.
+ * Added configuration option for TCP_NODELAY aka "Nagle".
+ * Allow POSTing arguments to `_changes`.
+ * Allow `keys` parameter for GET requests to views.
+ * Allow wildcards in vhosts definitions.
+ * More granular ETag support for views.
+ * More flexible URL rewriter.
+ * Added support for recognizing "Q values" and media parameters in
+ HTTP Accept headers.
+ * Validate doc ids that come from a PUT to a URL.
+
+Externals:
+
+ * Added OS Process module to manage daemons outside of CouchDB.
+ * Added HTTP Proxy handler for more scalable externals.
+
+Replicator:
+
+ * Added `_replicator` database to manage replications.
+ * Fixed issues when an endpoint is a remote database accessible via SSL.
+ * Added support for continuous by-doc-IDs replication.
+ * Fix issue where revision info was omitted when replicating attachments.
+ * Integrity of attachment replication is now verified by MD5.
+
+Storage System:
+
+ * Multiple micro-optimizations when reading data.
+
+View Server:
+
+ * Added CommonJS support to map functions.
+ * Added `stale=update_after` query option that triggers a view update after
+ returning a `stale=ok` response.
+ * Warn about empty result caused by `startkey` and `endkey` limiting.
+ * Built-in reduce function `_sum` now accepts lists of integers as input.
+ * Added view query aliases start_key, end_key, start_key_doc_id and
+ end_key_doc_id.
+
+Futon:
+
+ * Added a "change password"-feature to Futon.
+
+URL Rewriter & Vhosts:
+
+ * Fix for variable substituion
+
Version 1.0.2
-------------
View
2  apps/couch/INSTALL.Unix
@@ -8,7 +8,7 @@ Dependencies
You will need the following installed:
- * Erlang OTP (>=R12B5) (http://erlang.org/)
+ * Erlang OTP (>=R13B2) (http://erlang.org/)
* ICU (http://icu.sourceforge.net/)
* OpenSSL (http://www.openssl.org/)
* Mozilla SpiderMonkey (1.8) (http://www.mozilla.org/js/spidermonkey/)
View
19 apps/couch/INSTALL.Windows
@@ -8,7 +8,7 @@ Dependencies
You will need the following installed:
- * Erlang OTP (>=R12B5) (http://erlang.org/)
+ * Erlang OTP (=14B01) (http://erlang.org/)
* ICU (http://icu.sourceforge.net/)
* OpenSSL (http://www.openssl.org/)
* Mozilla SpiderMonkey (1.8) (http://www.mozilla.org/js/spidermonkey/)
@@ -50,12 +50,17 @@ You must check that:
* The `which cl` command points to the Microsoft compiler.
-If you do not do this, the ones found in `/usr/bin` may be used instead.
+ * The `which mc` command points to the Microsoft message compiler.
+
+ * The `which mt` command points to the Microsoft manifest tool.
+
+If you do not do this, the build may fail due to Cygwin ones found in `/usr/bin`
+being used instead.
Building Erlang
---------------
-You must include OpenSSL.
+You must include Win32 OpenSSL.
However, you can skip the GUI tools by running:
@@ -89,7 +94,7 @@ Remember to use `/cygdrive/c/` instead of `c:/` as the directory prefix.
To set up your path, run:
- export PATH=$ERL_TOP/release/win32/erts-5.7.2/bin:$PATH
+ export PATH=$ERL_TOP/release/win32/erts-5.8.2/bin:$PATH
If everything was successful, you should be ready to build CouchDB.
@@ -101,8 +106,8 @@ Building CouchDB
Once you have satisfied the dependencies you should run:
./configure \
- --with-js-include=/cygdrive/c/path_to_seamonkey_include \
- --with-js-lib=/cygdrive/c/path_to_seamonkey_lib \
+ --with-js-include=/cygdrive/c/path_to_spidermonkey_include \
+ --with-js-lib=/cygdrive/c/path_to_spidermonkey_lib \
--with-win32-icu-binaries=/cygdrive/c/path_to_icu_binaries_root \
--with-erlang=$ERL_TOP/release/win32/usr/include \
--with-win32-curl=/cygdrive/c/path/to/curl/root/directory \
@@ -145,4 +150,4 @@ To check that everything has worked, point your web browser to:
http://127.0.0.1:5984/_utils/index.html
-From here you should run the test suite.
+From here you should run the test suite in either Firefox 3.6+ or Safari 4+.
View
36 apps/couch/NEWS
@@ -7,6 +7,36 @@ For details about backwards incompatible changes, see:
Each release section notes when backwards incompatible changes have been made.
+Version 1.1.1
+-------------
+
+This version has not been released yet.
+
+Version 1.1.0
+-------------
+
+All NEWS for 1.0.2 also apply to 1.1.0.
+
+This release contains backwards incompatible changes.
+
+ * Native SSL support.
+ * Added support for HTTP range requests for attachments.
+ * Added built-in filters for `_changes`: `_doc_ids` and `_design`.
+ * Added configuration option for TCP_NODELAY aka "Nagle".
+ * Allow wildcards in vhosts definitions.
+ * More granular ETag support for views.
+ * More flexible URL rewriter.
+ * Added OS Process module to manage daemons outside of CouchDB.
+ * Added HTTP Proxy handler for more scalable externals.
+ * Added `_replicator` database to manage replications.
+ * Multiple micro-optimizations when reading data.
+ * Added CommonJS support to map functions.
+ * Added `stale=update_after` query option that triggers a view update after
+ returning a `stale=ok` response.
+ * More explicit error messages when it's not possible to access a file due
+ to lack of permissions.
+ * Added a "change password"-feature to Futon.
+
Version 1.0.2
-------------
@@ -166,7 +196,7 @@ Version 0.10.1
Version 0.10.0
--------------
-This release contains backwards incompatible changes, please see above for help.
+This release contains backwards incompatible changes.
* General performance improvements.
* View index generation speedups.
@@ -198,7 +228,7 @@ Version 0.9.1
Version 0.9.0
-------------
-This release contains backwards incompatible changes, please see above for help.
+This release contains backwards incompatible changes.
* Modular configuration.
* Performance enhancements for document and view access.
@@ -222,7 +252,7 @@ Version 0.8.1-incubating
Version 0.8.0-incubating
------------------------
-This release contains backwards incompatible changes, please see above for help.
+This release contains backwards incompatible changes.
* Changed core licensing to the Apache Software License 2.0.
* Refactoring of the core view and storage engines.
View
6 apps/couch/NOTICE
@@ -17,6 +17,10 @@ This product also includes the following third-party components:
* jQuery (http://jquery.com/)
Copyright 2010, John Resig
+
+ * jQuery UI (http://jqueryui.com)
+
+ Copyright 2011, Paul Bakaus
* json2.js (http://www.json.org/)
@@ -46,6 +50,6 @@ This product also includes the following third-party components:
Copyright 1999, Masanao Izumo <iz@onicos.co.jp>
-* jspec.js (http://visionmedia.github.com/jspec/)
+ * jspec.js (http://visionmedia.github.com/jspec/)
Copyright 2010 TJ Holowaychuk <tj@vision-media.ca>
View
11 apps/couch/THANKS
@@ -63,12 +63,23 @@ suggesting improvements or submitting changes. Some of these people are:
* Paul Bonser <pib@paulbonser.com>
* Caleb Land <caleb.land@gmail.com>
* Juhani Ränkimies <juhani@juranki.com>
+ * Kev Jackson <foamdino@gmail.com>
+ * Jonathan D. Knezek <jdknezek@gmail.com>
+ * David Rose <doppler@gmail.com>
* Lim Yue Chuan <shasderias@gmail.com>
* David Davis <xantus@xantus.org>
+ * Klaus Trainer <klaus.trainer@web.de>
* Dale Harvey <dale@arandomurl.com>
* Juuso Väänänen <juuso@vaananen.org>
+ * Jeff Zellner <jeff.zellner@gmail.com>
* Benjamin Young <byoung@bigbluehat.com>
* Gabriel Farrell <gsf747@gmail.com>
* Mike Leddy <mike@loop.com.br>
+ * Felix Hummel <apache@felixhummel.de>
+ * Tim Smith <tim@couchbase.com>
+ * Sam Bisbee <sam@sbisbee.com>
+ * Nathan Vander Wilt <natevw@yahoo.com>
+ * Caolan McMahon <caolan.mcmahon@googlemail.com>
+
For a list of authors see the `AUTHORS` file.
View
32 apps/couch/include/couch_db.hrl
@@ -25,26 +25,9 @@
-define(DEFAULT_ATTACHMENT_CONTENT_TYPE, <<"application/octet-stream">>).
--define(LOG_DEBUG(Format, Args),
- case couch_log:debug_on() of
- true ->
- gen_event:sync_notify(error_logger,
- {self(), couch_debug, erlang:get(nonce), {Format, Args}});
- false -> ok
- end).
-
--define(LOG_INFO(Format, Args),
- case couch_log:info_on() of
- true ->
- gen_event:sync_notify(error_logger,
- {self(), couch_info, erlang:get(nonce), {Format, Args}});
- false -> ok
- end).
-
--define(LOG_ERROR(Format, Args),
- gen_event:sync_notify(error_logger,
- {self(), couch_error, erlang:get(nonce), {Format, Args}})).
-
+-define(LOG_DEBUG(Format, Args), couch_log:debug(Format, Args)).
+-define(LOG_INFO(Format, Args), couch_log:info(Format, Args)).
+-define(LOG_ERROR(Format, Args), couch_log:error(Format, Args)).
-record(rev_info,
{
@@ -73,6 +56,7 @@
{mochi_req,
peer,
method,
+ requested_path_parts,
path_parts,
db_url_handlers,
user_ctx,
@@ -194,6 +178,7 @@
view_type = nil,
include_docs = false,
+ conflicts = false,
stale = false,
multi_get = false,
callback = nil,
@@ -231,6 +216,7 @@
def_lang,
design_options=[],
views,
+ lib,
id_btree=nil,
current_seq=0,
purge_seq=0,
@@ -240,6 +226,8 @@
-record(view,
{id_num,
+ update_seq=0,
+ purge_seq=0,
map_names=[],
def,
btree=nil,
@@ -287,7 +275,9 @@
heartbeat,
timeout,
filter = "",
- include_docs = false
+ include_docs = false,
+ conflicts = false,
+ db_open_options = []
}).
-record(proc, {
View
4 apps/couch/license.skip
@@ -66,6 +66,7 @@
^share/www/script/sha1.js
^share/www/script/base64.js
^share/www/script/test/lorem*
+^share/www/style/jquery-ui-1.8.11.custom.css
^src/Makefile
^src/Makefile.in
^src/couchdb/.*beam
@@ -92,6 +93,9 @@
^test/bench/Makefile
^test/bench/Makefile.in
^test/etap/.*beam
+^test/etap/.*\.o
+^test/etap/.deps/*
+^test/etap/test_cfg_register
^test/etap/Makefile
^test/etap/Makefile.in
^test/etap/temp.*
View
2  apps/couch/src/couch_btree.erl
@@ -198,7 +198,7 @@ query_modify(Bt, LookupKeys, InsertValues, RemoveKeys) ->
{ok, NewRoot, Bt3} = complete_root(Bt2, KeyPointers),
{ok, QueryResults, Bt3#btree{root=NewRoot}}.
-% for ordering different operatations with the same key.
+% for ordering different operations with the same key.
% fetch < remove < insert
op_order(fetch) -> 1;
op_order(remove) -> 2;
View
240 apps/couch/src/couch_changes.erl
@@ -17,17 +17,19 @@
configure_filter/4, filter/2]).
%% @spec handle_changes(#changes_args{}, #httpd{} | {json_req, {[any()]}}, #db{}) -> any()
-handle_changes(#changes_args{filter=Raw, style=Style}=Args1, Req, Db) ->
- Args = Args1#changes_args{filter=make_filter_fun(Raw, Style, Req, Db)},
+handle_changes(#changes_args{style=Style}=Args1, Req, Db) ->
+ #changes_args{feed = Feed} = Args = Args1#changes_args{
+ filter = make_filter_fun(Args1#changes_args.filter, Style, Req, Db)
+ },
StartSeq = case Args#changes_args.dir of
rev ->
couch_db:get_update_seq(Db);
fwd ->
Args#changes_args.since
end,
- if Args#changes_args.feed == "continuous" orelse
- Args#changes_args.feed == "longpoll" ->
- fun(Callback) ->
+ if Feed == "continuous" orelse Feed == "longpoll" ->
+ fun(CallbackAcc) ->
+ {Callback, UserAcc} = get_callback_acc(CallbackAcc),
Self = self(),
{ok, Notify} = couch_db_update_notifier:start_link(
fun({_, DbName}) when DbName == Db#db.name ->
@@ -36,12 +38,13 @@ handle_changes(#changes_args{filter=Raw, style=Style}=Args1, Req, Db) ->
ok
end
),
- start_sending_changes(Callback, Args#changes_args.feed),
+ UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
{Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
try
keep_sending_changes(
Args,
Callback,
+ UserAcc2,
Db,
StartSeq,
<<"">>,
@@ -50,37 +53,52 @@ handle_changes(#changes_args{filter=Raw, style=Style}=Args1, Req, Db) ->
)
after
couch_db_update_notifier:stop(Notify),
- get_rest_db_updated() % clean out any remaining update messages
+ get_rest_db_updated(ok) % clean out any remaining update messages
end
end;
true ->
- fun(Callback) ->
- start_sending_changes(Callback, Args#changes_args.feed),
- {ok, {_, LastSeq, _Prepend, _, _, _, _, _}} =
+ fun(CallbackAcc) ->
+ {Callback, UserAcc} = get_callback_acc(CallbackAcc),
+ UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
+ {ok, {_, LastSeq, _Prepend, _, _, UserAcc3, _, _, _, _}} =
send_changes(
Args#changes_args{feed="normal"},
Callback,
+ UserAcc2,
Db,
StartSeq,
- <<"">>
+ <<>>
),
- end_sending_changes(Callback, LastSeq, Args#changes_args.feed)
+ end_sending_changes(Callback, UserAcc3, LastSeq, Feed)
end
end.
+get_callback_acc({Callback, _UserAcc} = Pair) when is_function(Callback, 3) ->
+ Pair;
+get_callback_acc(Callback) when is_function(Callback, 2) ->
+ {fun(Ev, Data, _) -> Callback(Ev, Data) end, ok}.
+
%% @spec make_filter_fun(string(), main_only|all_docs, #httpd{} | {json_req,
%% {[any()]}}, #db{}) -> fun()
-make_filter_fun(Filter, Style, Req, Db) when is_list(Filter) ->
- case [?l2b(couch_httpd:unquote(X)) || X <- string:tokens(Filter, "/")] of
+make_filter_fun([$_ | _] = FilterName, Style, Req, Db) ->
+ builtin_filter_fun(FilterName, Style, Req, Db);
+make_filter_fun(FilterName, Style, Req, Db) ->
+ os_filter_fun(FilterName, Style, Req, Db).
+
+os_filter_fun(FilterName, Style, Req, Db) ->
+ case [list_to_binary(couch_httpd:unquote(Part))
+ || Part <- string:tokens(FilterName, "/")] of
[] ->
- make_filter_fun(nil, Style, Req, Db);
+ fun(_Db2, #doc_info{revs=Revs}) ->
+ builtin_results(Style, Revs)
+ end;
[DName, FName] ->
DesignId = <<"_design/", DName/binary>>,
DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
% validate that the ddoc has the filter fun
#doc{body={Props}} = DDoc,
couch_util:get_nested_json_value({Props}, [<<"filters">>, FName]),
- fun(DocInfo) ->
+ fun(Db2, DocInfo) ->
DocInfos =
case Style of
main_only ->
@@ -89,10 +107,10 @@ make_filter_fun(Filter, Style, Req, Db) when is_list(Filter) ->
[DocInfo#doc_info{revs=[Rev]}|| Rev <- DocInfo#doc_info.revs]
end,
Docs = [Doc || {ok, Doc} <- [
- couch_db:open_doc(Db, DocInfo2, [deleted, conflicts])
+ couch_db:open_doc(Db2, DocInfo2, [deleted, conflicts])
|| DocInfo2 <- DocInfos]],
{ok, Passes} = couch_query_servers:filter_docs(
- Req, Db, DDoc, FName, Docs
+ Req, Db2, DDoc, FName, Docs
),
[{[{<<"rev">>, couch_doc:rev_to_str({RevPos,RevId})}]}
|| {Pass, #doc{revs={RevPos,[RevId|_]}}}
@@ -101,9 +119,50 @@ make_filter_fun(Filter, Style, Req, Db) when is_list(Filter) ->
_Else ->
throw({bad_request,
"filter parameter must be of the form `designname/filtername`"})
+ end.
+
+builtin_filter_fun("_doc_ids", Style, {json_req, {Props}}, _Db) ->
+ filter_docids(couch_util:get_value(<<"doc_ids">>, Props), Style);
+builtin_filter_fun("_doc_ids", Style, #httpd{method='POST'}=Req, _Db) ->
+ {Props} = couch_httpd:json_body_obj(Req),
+ DocIds = couch_util:get_value(<<"doc_ids">>, Props, nil),
+ filter_docids(DocIds, Style);
+builtin_filter_fun("_doc_ids", Style, #httpd{method='GET'}=Req, _Db) ->
+ DocIds = ?JSON_DECODE(couch_httpd:qs_value(Req, "doc_ids", "null")),
+ filter_docids(DocIds, Style);
+builtin_filter_fun("_design", Style, _Req, _Db) ->
+ filter_designdoc(Style);
+builtin_filter_fun(_FilterName, _Style, _Req, _Db) ->
+ throw({bad_request, "unknown builtin filter name"}).
+
+filter_docids(DocIds, Style) when is_list(DocIds)->
+ fun(_Db, #doc_info{id=DocId, revs=Revs}) ->
+ case lists:member(DocId, DocIds) of
+ true ->
+ builtin_results(Style, Revs);
+ _ -> []
+ end
end;
-make_filter_fun(_, Style, _, _) ->
- fun(DI) -> ?MODULE:filter(DI, Style) end.
+filter_docids(_, _) ->
+ throw({bad_request, "`doc_ids` filter parameter is not a list."}).
+
+filter_designdoc(Style) ->
+ fun(_Db, #doc_info{id=DocId, revs=Revs}) ->
+ case DocId of
+ <<"_design", _/binary>> ->
+ builtin_results(Style, Revs);
+ _ -> []
+ end
+ end.
+
+builtin_results(Style, [#rev_info{rev=Rev}|_]=Revs) ->
+ case Style of
+ main_only ->
+ [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
+ all_docs ->
+ [{[{<<"rev">>, couch_doc:rev_to_str(R)}]}
+ || #rev_info{rev=R} <- Revs]
+ end.
configure_filter(Filter, Style, Req, Db) when is_list(Filter) ->
case [?l2b(couch_httpd:unquote(X)) || X <- string:tokens(Filter, "/")] of
@@ -157,28 +216,31 @@ get_changes_timeout(Args, Callback) ->
undefined ->
case Timeout of
undefined ->
- {DefaultTimeout, fun() -> stop end};
+ {DefaultTimeout, fun(UserAcc) -> {stop, UserAcc} end};
infinity ->
- {infinity, fun() -> stop end};
+ {infinity, fun(UserAcc) -> {stop, UserAcc} end};
_ ->
- {lists:min([DefaultTimeout, Timeout]), fun() -> stop end}
+ {lists:min([DefaultTimeout, Timeout]),
+ fun(UserAcc) -> {stop, UserAcc} end}
end;
true ->
- {DefaultTimeout, fun() -> Callback(timeout, ResponseType), ok end};
+ {DefaultTimeout,
+ fun(UserAcc) -> {ok, Callback(timeout, ResponseType, UserAcc)} end};
_ ->
{lists:min([DefaultTimeout, Heartbeat]),
- fun() -> Callback(timeout, ResponseType), ok end}
+ fun(UserAcc) -> {ok, Callback(timeout, ResponseType, UserAcc)} end}
end.
-start_sending_changes(_Callback, "continuous") ->
- ok;
-start_sending_changes(Callback, ResponseType) ->
- Callback(start, ResponseType).
+start_sending_changes(_Callback, UserAcc, "continuous") ->
+ UserAcc;
+start_sending_changes(Callback, UserAcc, ResponseType) ->
+ Callback(start, ResponseType, UserAcc).
-send_changes(Args, Callback, Db, StartSeq, Prepend) ->
+send_changes(Args, Callback, UserAcc, Db, StartSeq, Prepend) ->
#changes_args{
style = Style,
include_docs = IncludeDocs,
+ conflicts = Conflicts,
limit = Limit,
feed = ResponseType,
dir = Dir,
@@ -190,33 +252,36 @@ send_changes(Args, Callback, Db, StartSeq, Prepend) ->
StartSeq,
fun changes_enumerator/2,
[{dir, Dir}],
- {Db, StartSeq, Prepend, FilterFun, Callback, ResponseType, Limit,
- IncludeDocs}
+ {Db, StartSeq, Prepend, FilterFun, Callback, UserAcc, ResponseType,
+ Limit, IncludeDocs, Conflicts}
).
-keep_sending_changes(Args, Callback, Db, StartSeq, Prepend, Timeout,
+keep_sending_changes(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout,
TimeoutFun) ->
#changes_args{
feed = ResponseType,
- limit = Limit
+ limit = Limit,
+ db_open_options = DbOptions
} = Args,
% ?LOG_INFO("send_changes start ~p",[StartSeq]),
- {ok, {_, EndSeq, Prepend2, _, _, _, NewLimit, _}} = send_changes(
- Args#changes_args{dir=fwd}, Callback, Db, StartSeq, Prepend
+ {ok, {_, EndSeq, Prepend2, _, _, UserAcc2, _, NewLimit, _, _}} = send_changes(
+ Args#changes_args{dir=fwd}, Callback, UserAcc, Db, StartSeq, Prepend
),
% ?LOG_INFO("send_changes last ~p",[EndSeq]),
couch_db:close(Db),
if Limit > NewLimit, ResponseType == "longpoll" ->
- end_sending_changes(Callback, EndSeq, ResponseType);
+ end_sending_changes(Callback, UserAcc2, EndSeq, ResponseType);
true ->
- case wait_db_updated(Timeout, TimeoutFun) of
- updated ->
+ case wait_db_updated(Timeout, TimeoutFun, UserAcc2) of
+ {updated, UserAcc3} ->
% ?LOG_INFO("wait_db_updated updated ~p",[{Db#db.name, EndSeq}]),
- case couch_db:open(Db#db.name, [{user_ctx, Db#db.user_ctx}]) of
+ DbOptions1 = [{user_ctx, Db#db.user_ctx} | DbOptions],
+ case couch_db:open(Db#db.name, DbOptions1) of
{ok, Db2} ->
keep_sending_changes(
Args#changes_args{limit=NewLimit},
Callback,
+ UserAcc3,
Db2,
EndSeq,
Prepend2,
@@ -224,79 +289,96 @@ keep_sending_changes(Args, Callback, Db, StartSeq, Prepend, Timeout,
TimeoutFun
);
_Else ->
- end_sending_changes(Callback, EndSeq, ResponseType)
+ end_sending_changes(Callback, UserAcc2, EndSeq, ResponseType)
end;
- stop ->
+ {stop, UserAcc3} ->
% ?LOG_INFO("wait_db_updated stop ~p",[{Db#db.name, EndSeq}]),
- end_sending_changes(Callback, EndSeq, ResponseType)
+ end_sending_changes(Callback, UserAcc3, EndSeq, ResponseType)
end
end.
-end_sending_changes(Callback, EndSeq, ResponseType) ->
- Callback({stop, EndSeq}, ResponseType).
+end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
+ Callback({stop, EndSeq}, ResponseType, UserAcc).
-changes_enumerator(DocInfo, {Db, _, _, FilterFun, Callback, "continuous",
- Limit, IncludeDocs}) ->
+changes_enumerator(DocInfo, {Db, _, _, FilterFun, Callback, UserAcc,
+ "continuous", Limit, IncludeDocs, Conflicts}) ->
- #doc_info{id=Id, high_seq=Seq, revs=[#rev_info{deleted=Del,rev=Rev}|_]}
- = DocInfo,
- Results0 = FilterFun(DocInfo),
+ #doc_info{high_seq = Seq} = DocInfo,
+ Results0 = FilterFun(Db, DocInfo),
Results = [Result || Result <- Results0, Result /= null],
Go = if Limit =< 1 -> stop; true -> ok end,
case Results of
[] ->
- {Go, {Db, Seq, nil, FilterFun, Callback, "continuous", Limit,
- IncludeDocs}
+ {Go, {Db, Seq, nil, FilterFun, Callback, UserAcc, "continuous", Limit,
+ IncludeDocs, Conflicts}
};
_ ->
- ChangesRow = changes_row(Db, Seq, Id, Del, Results, Rev, IncludeDocs),
- Callback({change, ChangesRow, <<"">>}, "continuous"),
- {Go, {Db, Seq, nil, FilterFun, Callback, "continuous", Limit - 1,
- IncludeDocs}
+ ChangesRow = changes_row(Db, Results, DocInfo, IncludeDocs, Conflicts),
+ UserAcc2 = Callback({change, ChangesRow, <<>>}, "continuous", UserAcc),
+ {Go, {Db, Seq, nil, FilterFun, Callback, UserAcc2, "continuous",
+ Limit - 1, IncludeDocs, Conflicts}
}
end;
-changes_enumerator(DocInfo, {Db, _, Prepend, FilterFun, Callback, ResponseType,
- Limit, IncludeDocs}) ->
+changes_enumerator(DocInfo, {Db, _, Prepend, FilterFun, Callback, UserAcc,
+ ResponseType, Limit, IncludeDocs, Conflicts}) ->
- #doc_info{id=Id, high_seq=Seq, revs=[#rev_info{deleted=Del,rev=Rev}|_]}
- = DocInfo,
- Results0 = FilterFun(DocInfo),
+ #doc_info{high_seq = Seq} = DocInfo,
+ Results0 = FilterFun(Db, DocInfo),
Results = [Result || Result <- Results0, Result /= null],
- Go = if Limit =< 1 -> stop; true -> ok end,
+ Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
case Results of
[] ->
- {Go, {Db, Seq, Prepend, FilterFun, Callback, ResponseType, Limit,
- IncludeDocs}
+ {Go, {Db, Seq, Prepend, FilterFun, Callback, UserAcc, ResponseType,
+ Limit, IncludeDocs, Conflicts}
};
_ ->
- ChangesRow = changes_row(Db, Seq, Id, Del, Results, Rev, IncludeDocs),
- Callback({change, ChangesRow, Prepend}, ResponseType),
- {Go, {Db, Seq, <<",\n">>, FilterFun, Callback, ResponseType, Limit - 1,
- IncludeDocs}
+ ChangesRow = changes_row(Db, Results, DocInfo, IncludeDocs, Conflicts),
+ UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+ {Go, {Db, Seq, <<",\n">>, FilterFun, Callback, UserAcc2, ResponseType,
+ Limit - 1, IncludeDocs, Conflicts}
}
end.
-changes_row(Db, Seq, Id, Del, Results, Rev, true) ->
+changes_row(Db, Results, DocInfo, IncludeDoc, Conflicts) ->
+ #doc_info{
+ id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
+ } = DocInfo,
{[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
- deleted_item(Del) ++ couch_httpd_view:doc_member(Db, {Id, Rev})};
-changes_row(_, Seq, Id, Del, Results, _, false) ->
- {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
- deleted_item(Del)}.
+ deleted_item(Del) ++ case IncludeDoc of
+ true ->
+ Options = if Conflicts -> [conflicts]; true -> [] end,
+ couch_httpd_view:doc_member(Db, DocInfo, Options);
+ false ->
+ []
+ end}.
-deleted_item(true) -> [{deleted, true}];
+deleted_item(true) -> [{<<"deleted">>, true}];
deleted_item(_) -> [].
% waits for a db_updated msg, if there are multiple msgs, collects them.
-wait_db_updated(Timeout, TimeoutFun) ->
- receive db_updated -> get_rest_db_updated()
+wait_db_updated(Timeout, TimeoutFun, UserAcc) ->
+ receive
+ db_updated ->
+ get_rest_db_updated(UserAcc)
after Timeout ->
- case TimeoutFun() of
- ok -> wait_db_updated(Timeout, TimeoutFun);
- stop -> stop
+ {Go, UserAcc2} = TimeoutFun(UserAcc),
+ case Go of
+ ok ->
+ wait_db_updated(Timeout, TimeoutFun, UserAcc2);
+ stop ->
+ {stop, UserAcc2}
end
end.
+get_rest_db_updated(UserAcc) ->
+ receive
+ db_updated ->
+ get_rest_db_updated(UserAcc)
+ after 0 ->
+ {updated, UserAcc}
+ end.
+
get_rest_db_updated() ->
receive db_updated -> get_rest_db_updated()
after 0 -> updated
View
39 apps/couch/src/couch_config.erl
@@ -93,15 +93,19 @@ register(Fun, Pid) ->
init(IniFiles) ->
ets:new(?MODULE, [named_table, set, protected]),
- lists:map(fun(IniFile) ->
- {ok, ParsedIniValues} = parse_ini_file(IniFile),
- ets:insert(?MODULE, ParsedIniValues)
- end, IniFiles),
- WriteFile = case IniFiles of
- [_|_] -> lists:last(IniFiles);
- _ -> undefined
- end,
- {ok, #config{write_filename=WriteFile}}.
+ try
+ lists:map(fun(IniFile) ->
+ {ok, ParsedIniValues} = parse_ini_file(IniFile),
+ ets:insert(?MODULE, ParsedIniValues)
+ end, IniFiles),
+ WriteFile = case IniFiles of
+ [_|_] -> lists:last(IniFiles);
+ _ -> undefined
+ end,
+ {ok, #config{write_filename = WriteFile}}
+ catch _Tag:Error ->
+ {stop, Error}
+ end.
terminate(_Reason, _State) ->
@@ -112,8 +116,7 @@ handle_call(all, _From, Config) ->
Resp = lists:sort((ets:tab2list(?MODULE))),
{reply, Resp, Config};
handle_call({set, Sec, Key, Val, Persist}, _From, Config) ->
- true = ets:insert(?MODULE, {{Sec, Key}, Val}),
- case {Persist, Config#config.write_filename} of
+ Result = case {Persist, Config#config.write_filename} of
{true, undefined} ->
ok;
{true, FileName} ->
@@ -121,9 +124,15 @@ handle_call({set, Sec, Key, Val, Persist}, _From, Config) ->
_ ->
ok
end,
- Event = {config_change, Sec, Key, Val, Persist},
- gen_event:sync_notify(couch_config_event, Event),
- {reply, ok, Config};
+ case Result of
+ ok ->
+ true = ets:insert(?MODULE, {{Sec, Key}, Val}),
+ Event = {config_change, Sec, Key, Val, Persist},
+ gen_event:sync_notify(couch_config_event, Event),
+ {reply, ok, Config};
+ _Error ->
+ {reply, Result, Config}
+ end;
handle_call({delete, Sec, Key, Persist}, _From, Config) ->
true = ets:delete(?MODULE, {Sec,Key}),
case {Persist, Config#config.write_filename} of
@@ -158,6 +167,8 @@ parse_ini_file(IniFile) ->
case file:read_file(IniFilename) of
{ok, IniBin0} ->
IniBin0;
+ {error, eacces} ->
+ throw({file_permission_error, IniFile});
{error, enoent} ->
Fmt = "Couldn't find server configuration file ~s.",
Msg = ?l2b(io_lib:format(Fmt, [IniFilename])),
View
9 apps/couch/src/couch_config_writer.erl
@@ -35,7 +35,14 @@ save_to_file({{Section, Key}, Value}, File) ->
NewLines = process_file_lines(Lines, [], SectionLine, Pattern, Key, Value),
NewFileContents = reverse_and_add_newline(strip_empty_lines(NewLines), []),
- ok = file:write_file(File, NewFileContents).
+ case file:write_file(File, NewFileContents) of
+ ok ->
+ ok;
+ {error, eacces} ->
+ {file_permission_error, File};
+ Error ->
+ Error
+ end.
process_file_lines([Section|Rest], SeenLines, Section, Pattern, Key, Value) ->
View
13 apps/couch/src/couch_db.erl
@@ -24,7 +24,7 @@
-export([start_link/3,open_doc_int/3,ensure_full_commit/1,ensure_full_commit/2]).
-export([set_security/2,get_security/1]).
-export([changes_since/5,changes_since/6,read_doc/2,new_revid/1]).
--export([check_is_admin/1, check_is_reader/1, get_doc_count/1, load_validation_funs/1]).
+-export([check_is_admin/1, check_is_reader/1, get_doc_count/1]).
-export([reopen/1, make_doc/5]).
-include("couch_db.hrl").
@@ -775,6 +775,8 @@ update_docs(Db, Docs, Options, interactive_edit) ->
% for the doc.
make_first_doc_on_disk(_Db, _Id, _Pos, []) ->
nil;
+make_first_doc_on_disk(Db, Id, Pos, [{_Rev, #doc{}} | RestPath]) ->
+ make_first_doc_on_disk(Db, Id, Pos-1, RestPath);
make_first_doc_on_disk(Db, Id, Pos, [{_Rev, ?REV_MISSING}|RestPath]) ->
make_first_doc_on_disk(Db, Id, Pos - 1, RestPath);
make_first_doc_on_disk(Db, Id, Pos, [{_, #leaf{deleted=IsDel, ptr=Sp}} |_]=DocPath) ->
@@ -852,7 +854,7 @@ doc_flush_atts(Doc, Fd) ->
Doc#doc{atts=[flush_att(Fd, Att) || Att <- Doc#doc.atts]}.
check_md5(_NewSig, <<>>) -> ok;
-check_md5(Sig1, Sig2) when Sig1 == Sig2 -> ok;
+check_md5(Sig, Sig) -> ok;
check_md5(_, _) -> throw(md5_mismatch).
flush_att(Fd, #att{data={Fd0, _}}=Att) when Fd0 == Fd ->
@@ -963,10 +965,15 @@ with_stream(Fd, #att{md5=InMd5,type=Type,encoding=Enc}=Att, Fun) ->
write_streamed_attachment(_Stream, _F, 0) ->
ok;
write_streamed_attachment(Stream, F, LenLeft) when LenLeft > 0 ->
- Bin = F(),
+ Bin = read_next_chunk(F, LenLeft),
ok = couch_stream:write(Stream, Bin),
write_streamed_attachment(Stream, F, LenLeft - size(Bin)).
+read_next_chunk(F, _) when is_function(F, 0) ->
+ F();
+read_next_chunk(F, LenLeft) when is_function(F, 1) ->
+ F(lists:min([LenLeft, 16#2000])).
+
enum_docs_since_reduce_to_count(Reds) ->
couch_btree:final_reduce(
fun couch_db_updater:btree_by_seq_reduce/2, Reds).
View
15 apps/couch/src/couch_db_updater.erl
@@ -133,8 +133,9 @@ handle_call({purge_docs, IdRevs}, _From, Db) ->
{DocInfoToUpdate, NewSeq} = lists:mapfoldl(
fun(#full_doc_info{rev_tree=Tree}=FullInfo, SeqAcc) ->
- Tree2 = couch_key_tree:map_leafs( fun(RevInfo) ->
- RevInfo#rev_info{seq=SeqAcc + 1}
+ Tree2 = couch_key_tree:map_leafs(
+ fun(_RevId, {IsDeleted, BodyPointer, _UpdateSeq}) ->
+ {IsDeleted, BodyPointer, SeqAcc + 1}
end, Tree),
{couch_doc:to_doc_info(FullInfo#full_doc_info{rev_tree=Tree2}),
SeqAcc + 1}
@@ -666,10 +667,11 @@ update_docs_int(Db, DocsList, NonRepDocs, MergeConflicts, FullCommit) ->
% Check if we just updated any design documents, and update the validation
% funs if we did.
- case [1 || <<"_design/",_/binary>> <- Ids] of
- [] ->
+ case lists:any(
+ fun(<<"_design/", _/binary>>) -> true; (_) -> false end, Ids) of
+ false ->
Db4 = Db3;
- _ ->
+ true ->
Db4 = refresh_validate_doc_funs(Db3)
end,
@@ -687,7 +689,8 @@ compute_data_sizes([FullDocInfo | RestDocInfos], Acc) ->
-
+update_local_docs(Db, []) ->
+ {ok, Db};
update_local_docs(#db{local_tree=Btree}=Db, Docs) ->
Ids = [Id || {_Client, #doc{id=Id}} <- Docs],
OldDocLookups = couch_btree:lookup(Btree, Ids),
View
56 apps/couch/src/couch_doc.erl
@@ -13,7 +13,7 @@
-module(couch_doc).
-export([to_doc_info/1,to_doc_info_path/1,parse_rev/1,parse_revs/1,rev_to_str/1,revs_to_strs/1]).
--export([att_foldl/3,att_foldl_decode/3,get_validate_doc_fun/1]).
+-export([att_foldl/3,range_att_foldl/5,att_foldl_decode/3,get_validate_doc_fun/1]).
-export([from_json_obj/1,to_json_obj/2,has_stubs/1, merge_stubs/2]).
-export([validate_docid/1]).
-export([doc_from_multi_part_stream/2]).
@@ -87,8 +87,14 @@ to_json_attachments(Atts, OutputData, DataToFollow, ShowEncInfo) ->
fun(#att{disk_len=DiskLen, att_len=AttLen, encoding=Enc}=Att) ->
{Att#att.name, {[
{<<"content_type">>, Att#att.type},
- {<<"revpos">>, Att#att.revpos}
- ] ++
+ {<<"revpos">>, Att#att.revpos}] ++
+ case Att#att.md5 of
+ <<>> ->
+ [];
+ Md5 ->
+ EncodedMd5 = base64:encode(Md5),
+ [{<<"digest">>, <<"md5-",EncodedMd5/binary>>}]
+ end ++
if not OutputData orelse Att#att.data == stub ->
[{<<"length">>, DiskLen}, {<<"stub">>, true}];
true ->
@@ -165,6 +171,10 @@ parse_revs([Rev | Rest]) ->
validate_docid(Id) when is_binary(Id) ->
+ case couch_util:validate_utf8(Id) of
+ false -> throw({bad_request, <<"Document id must be valid UTF-8">>});
+ true -> ok
+ end,
case Id of
<<"_design/", _/binary>> -> ok;
<<"_local/", _/binary>> -> ok;
@@ -195,6 +205,12 @@ transfer_fields([{<<"_rev">>, _Rev} | Rest], Doc) ->
transfer_fields([{<<"_attachments">>, {JsonBins}} | Rest], Doc) ->
Atts = lists:map(fun({Name, {BinProps}}) ->
+ Md5 = case couch_util:get_value(<<"digest">>, BinProps) of
+ <<"md5-",EncodedMd5/binary>> ->
+ base64:decode(EncodedMd5);
+ _ ->
+ <<>>
+ end,
case couch_util:get_value(<<"stub">>, BinProps) of
true ->
Type = couch_util:get_value(<<"content_type">>, BinProps),
@@ -202,7 +218,7 @@ transfer_fields([{<<"_attachments">>, {JsonBins}} | Rest], Doc) ->
DiskLen = couch_util:get_value(<<"length">>, BinProps),
{Enc, EncLen} = att_encoding_info(BinProps),
#att{name=Name, data=stub, type=Type, att_len=EncLen,
- disk_len=DiskLen, encoding=Enc, revpos=RevPos};
+ disk_len=DiskLen, encoding=Enc, revpos=RevPos, md5=Md5};
_ ->
Type = couch_util:get_value(<<"content_type">>, BinProps,
?DEFAULT_ATTACHMENT_CONTENT_TYPE),
@@ -212,7 +228,7 @@ transfer_fields([{<<"_attachments">>, {JsonBins}} | Rest], Doc) ->
DiskLen = couch_util:get_value(<<"length">>, BinProps),
{Enc, EncLen} = att_encoding_info(BinProps),
#att{name=Name, data=follows, type=Type, encoding=Enc,
- att_len=EncLen, disk_len=DiskLen, revpos=RevPos};
+ att_len=EncLen, disk_len=DiskLen, revpos=RevPos, md5=Md5};
_ ->
Value = couch_util:get_value(<<"data">>, BinProps),
Bin = base64:decode(Value),
@@ -252,6 +268,17 @@ transfer_fields([{<<"_conflicts">>, _} | Rest], Doc) ->
transfer_fields([{<<"_deleted_conflicts">>, _} | Rest], Doc) ->
transfer_fields(Rest, Doc);
+% special fields for replication documents
+transfer_fields([{<<"_replication_state">>, _} = Field | Rest],
+ #doc{body=Fields} = Doc) ->
+ transfer_fields(Rest, Doc#doc{body=[Field|Fields]});
+transfer_fields([{<<"_replication_state_time">>, _} = Field | Rest],
+ #doc{body=Fields} = Doc) ->
+ transfer_fields(Rest, Doc#doc{body=[Field|Fields]});
+transfer_fields([{<<"_replication_id">>, _} = Field | Rest],
+ #doc{body=Fields} = Doc) ->
+ transfer_fields(Rest, Doc#doc{body=[Field|Fields]});
+
% unknown special field
transfer_fields([{<<"_",Name/binary>>, _} | _], _) ->
throw({doc_validation,
@@ -307,6 +334,9 @@ att_foldl(#att{data={Fd,Sp},md5=Md5}, Fun, Acc) ->
att_foldl(#att{data=DataFun,att_len=Len}, Fun, Acc) when is_function(DataFun) ->
fold_streamed_data(DataFun, Len, Fun, Acc).
+range_att_foldl(#att{data={Fd,Sp}}, From, To, Fun, Acc) ->
+ couch_stream:range_foldl(Fd, Sp, From, To, Fun, Acc).
+
att_foldl_decode(#att{data={Fd,Sp},md5=Md5,encoding=Enc}, Fun, Acc) ->
couch_stream:foldl_decode(Fd, Sp, Md5, Enc, Fun, Acc);
att_foldl_decode(#att{data=Fun2,att_len=Len, encoding=identity}, Fun, Acc) ->
@@ -445,11 +475,13 @@ atts_to_mp([Att | RestAtts], Boundary, WriteFun,
doc_from_multi_part_stream(ContentType, DataFun) ->
- Self = self(),
+ Parent = self(),
Parser = spawn_link(fun() ->
- couch_httpd:parse_multipart_request(ContentType, DataFun,
- fun(Next)-> mp_parse_doc(Next, []) end),
- unlink(Self)
+ {<<"--">>, _, _} = couch_httpd:parse_multipart_request(
+ ContentType, DataFun,
+ fun(Next) -> mp_parse_doc(Next, []) end),
+ unlink(Parent),
+ Parent ! {self(), finished}
end),
Parser ! {get_doc_bytes, self()},
receive
@@ -463,7 +495,11 @@ doc_from_multi_part_stream(ContentType, DataFun) ->
(A) ->
A
end, Doc#doc.atts),
- {ok, Doc#doc{atts=Atts2}}
+ WaitFun = fun() ->
+ receive {Parser, finished} -> ok end,
+ erlang:put(mochiweb_request_recv, true)
+ end,
+ {ok, Doc#doc{atts=Atts2}, WaitFun}
end.
mp_parse_doc({headers, H}, []) ->
View
8 apps/couch/src/couch_event_sup.erl
@@ -50,8 +50,12 @@ stop(Pid) ->
gen_server:cast(Pid, stop).
init({EventMgr, EventHandler, Args}) ->
- ok = gen_event:add_sup_handler(EventMgr, EventHandler, Args),
- {ok, {EventMgr, EventHandler}}.
+ case gen_event:add_sup_handler(EventMgr, EventHandler, Args) of
+ ok ->
+ {ok, {EventMgr, EventHandler}};
+ {stop, Error} ->
+ {stop, Error}
+ end.
terminate(_Reason, _State) ->
ok.
View
55 apps/couch/src/couch_file.erl
@@ -53,7 +53,10 @@ open(Filepath, Options) ->
{trap_exit, true} -> receive {'EXIT', Pid, _} -> ok end;
{trap_exit, false} -> ok
end,
- Error
+ case Error of
+ {error, eacces} -> {file_permission_error, Filepath};
+ _ -> Error
+ end
end;
Error ->
Error
@@ -294,15 +297,23 @@ handle_call(close, _From, #file{fd=Fd}=File) ->
{stop, normal, file:close(Fd), File#file{fd = nil}};
handle_call({pread_iolist, Pos}, _From, File) ->
- {LenIolist, NextPos} = read_raw_iolist_int(File, Pos, 4),
- case iolist_to_binary(LenIolist) of
- <<1:1/integer,Len:31/integer>> -> % an MD5-prefixed term
- {Md5AndIoList, _} = read_raw_iolist_int(File, NextPos, Len+16),
- {Md5, IoList} = extract_md5(Md5AndIoList),
+ {RawData, NextPos} = try
+ % up to 8Kbs of read ahead
+ read_raw_iolist_int(File, Pos, 2 * ?SIZE_BLOCK - (Pos rem ?SIZE_BLOCK))
+ catch
+ _:_ ->
+ read_raw_iolist_int(File, Pos, 4)
+ end,
+ <<Prefix:1/integer, Len:31/integer, RestRawData/binary>> =
+ iolist_to_binary(RawData),
+ case Prefix of
+ 1 ->
+ {Md5, IoList} = extract_md5(
+ maybe_read_more_iolist(RestRawData, 16 + Len, NextPos, File)),
{reply, {ok, IoList, Md5}, File};
- <<0:1/integer,Len:31/integer>> ->
- {Iolist, _} = read_raw_iolist_int(File, NextPos, Len),
- {reply, {ok, Iolist, <<>>}, File}
+ 0 ->
+ IoList = maybe_read_more_iolist(RestRawData, Len, NextPos, File),
+ {reply, {ok, IoList, <<>>}, File}
end;
handle_call({pread, Pos, Bytes}, _From, #file{fd=Fd,tail_append_begin=TailAppendBegin}=File) ->
{ok, Bin} = file:pread(Fd, Pos, Bytes),
@@ -504,18 +515,36 @@ find_header(Fd, Block) ->
end.
load_header(Fd, Block) ->
- {ok, <<1>>} = file:pread(Fd, Block*?SIZE_BLOCK, 1),
- {ok, <<HeaderLen:32/integer>>} = file:pread(Fd, (Block*?SIZE_BLOCK) + 1, 4),
+ {ok, <<1, HeaderLen:32/integer, RestBlock/binary>>} =
+ file:pread(Fd, Block * ?SIZE_BLOCK, ?SIZE_BLOCK),
TotalBytes = calculate_total_read_len(1, HeaderLen),
- {ok, <<RawBin:TotalBytes/binary>>} =
- file:pread(Fd, (Block*?SIZE_BLOCK) + 5, TotalBytes),
+ case TotalBytes > byte_size(RestBlock) of
+ false ->
+ <<RawBin:TotalBytes/binary, _/binary>> = RestBlock;
+ true ->
+ {ok, Missing} = file:pread(
+ Fd, (Block * ?SIZE_BLOCK) + 5 + byte_size(RestBlock),
+ TotalBytes - byte_size(RestBlock)),
+ RawBin = <<RestBlock/binary, Missing/binary>>
+ end,
<<Md5Sig:16/binary, HeaderBin/binary>> =
iolist_to_binary(remove_block_prefixes(1, RawBin)),
Md5Sig = couch_util:md5(HeaderBin),
{ok, HeaderBin}.
+maybe_read_more_iolist(Buffer, DataSize, _, _)
+ when DataSize =< byte_size(Buffer) ->
+ <<Data:DataSize/binary, _/binary>> = Buffer,
+ [Data];
+maybe_read_more_iolist(Buffer, DataSize, NextPos, File) ->
+ {Missing, _} =
+ read_raw_iolist_int(File, NextPos, DataSize - byte_size(Buffer)),
+ [Buffer, Missing].
+
-spec read_raw_iolist_int(#file{}, Pos::non_neg_integer(), Len::non_neg_integer()) ->
{Data::iolist(), CurPos::non_neg_integer()}.
+read_raw_iolist_int(Fd, {Pos, _Size}, Len) -> % 0110 UPGRADE CODE
+ read_raw_iolist_int(Fd, Pos, Len);
read_raw_iolist_int(#file{fd=Fd, tail_append_begin=TAB}, Pos, Len) ->
BlockOffset = Pos rem ?SIZE_BLOCK,
TotalBytes = calculate_total_read_len(BlockOffset, Len),
View
236 apps/couch/src/couch_httpd.erl
@@ -13,35 +13,50 @@
-module(couch_httpd).
-include("couch_db.hrl").
--export([start_link/0, stop/0, handle_request/7]).
+-export([start_link/0, start_link/1, stop/0, handle_request/5]).
--export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1,absolute_uri/2,body_length/1]).
+-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,qs_json_value/3]).
+-export([path/1,absolute_uri/2,body_length/1]).
-export([verify_is_server_admin/1,unquote/1,quote/1,recv/2,recv_chunked/4,error_info/1]).
--export([make_fun_spec_strs/1, make_arity_1_fun/1]).
+-export([make_fun_spec_strs/1]).
+-export([make_arity_1_fun/1, make_arity_2_fun/1, make_arity_3_fun/1]).
-export([parse_form/1,json_body/1,json_body_obj/1,body/1,doc_etag/1, make_etag/1, etag_respond/3]).
-export([primary_header_value/2,partition/1,serve_file/3,serve_file/4, server_header/0]).
-export([start_chunked_response/3,send_chunk/2,log_request/2]).
--export([start_response_length/4, send/2]).
+-export([start_response_length/4, start_response/3, send/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, 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]).
start_link() ->
+ start_link(http).
+start_link(http) ->
+ Port = couch_config:get("httpd", "port", "5984"),
+ start_link(?MODULE, [{port, Port}]);
+start_link(https) ->
+ Port = couch_config:get("ssl", "port", "6984"),
+ CertFile = couch_config:get("ssl", "cert_file", nil),
+ KeyFile = couch_config:get("ssl", "key_file", nil),
+ Options = case CertFile /= nil andalso KeyFile /= nil of
+ true ->
+ [{port, Port},
+ {ssl, true},
+ {ssl_opts, [
+ {certfile, CertFile},
+ {keyfile, KeyFile}]}];
+ false ->
+ io:format("SSL enabled but PEM certificates are missing.", []),
+ throw({error, missing_certs})
+ end,
+ start_link(https, Options).
+start_link(Name, Options) ->
% read config and register for configuration changes
% just stop if one of the config settings change. couch_server_sup
% will restart us and then we will pick up the new settings.
BindAddress = couch_config:get("httpd", "bind_address", any),
- Port = couch_config:get("httpd", "port", "5984"),
- MaxConnections = couch_config:get("httpd", "max_connections", "2048"),
- VirtualHosts = couch_config:get("vhosts"),
- VhostGlobals = re:split(
- couch_config:get("httpd", "vhost_global_handlers", ""),
- ", ?",
- [{return, list}]
- ),
DefaultSpec = "{couch_httpd_db, handle_request}",
DefaultFun = make_arity_1_fun(
couch_config:get("httpd", "default_handler", DefaultSpec)
@@ -65,21 +80,28 @@ start_link() ->
UrlHandlers = dict:from_list(UrlHandlersList),
DbUrlHandlers = dict:from_list(DbUrlHandlersList),
DesignUrlHandlers = dict:from_list(DesignUrlHandlersList),
+ {ok, ServerOptions} = couch_util:parse_term(
+ couch_config:get("httpd", "server_options", "[]")),
+ {ok, SocketOptions} = couch_util:parse_term(
+ couch_config:get("httpd", "socket_options", "[]")),
Loop = fun(Req)->
+ case SocketOptions of
+ [] ->
+ ok;
+ _ ->
+ ok = mochiweb_socket:setopts(Req:get(socket), SocketOptions)
+ end,
apply(?MODULE, handle_request, [
- Req, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers,
- VirtualHosts, VhostGlobals
+ Req, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers
])
end,
% and off we go
- {ok, Pid} = case mochiweb_http:start([
+ {ok, Pid} = case mochiweb_http:start(Options ++ ServerOptions ++ [
{loop, Loop},
- {name, ?MODULE},
- {ip, BindAddress},
- {port, Port},
- {max, MaxConnections}
+ {name, Name},
+ {ip, BindAddress}
]) of
{ok, MochiPid} -> {ok, MochiPid};
{error, Reason} ->
@@ -92,15 +114,19 @@ start_link() ->
?MODULE:stop();
("httpd", "port") ->
?MODULE:stop();
- ("httpd", "max_connections") ->
- ?MODULE:stop();
("httpd", "default_handler") ->
?MODULE:stop();
+ ("httpd", "server_options") ->
+ ?MODULE:stop();
+ ("httpd", "socket_options") ->
+ ?MODULE:stop();
("httpd_global_handlers", _) ->
?MODULE:stop();
("httpd_db_handlers", _) ->
?MODULE:stop();
("vhosts", _) ->
+ ?MODULE:stop();
+ ("ssl", _) ->
?MODULE:stop()
end, Pid),
@@ -139,50 +165,13 @@ make_fun_spec_strs(SpecStr) ->
stop() ->
mochiweb_http:stop(?MODULE).
-%%
-% if there's a vhost definition that matches the request, redirect internally
-redirect_to_vhost(MochiReq, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers, VhostTarget) ->
-
- Path = MochiReq:get(raw_path),
- Target = VhostTarget ++ Path,
- ?LOG_DEBUG("Vhost Target: '~p'~n", [Target]),
- % build a new mochiweb request
- MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
- MochiReq:get(method),
- Target,
- MochiReq:get(version),
- MochiReq:get(headers)),
- % cleanup, It force mochiweb to reparse raw uri.
- MochiReq1:cleanup(),
+handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers,
+ DesignUrlHandlers) ->
+ MochiReq1 = couch_httpd_vhost:match_vhost(MochiReq),
handle_request_int(MochiReq1, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers).
-
-handle_request(MochiReq, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers, VirtualHosts, VhostGlobals) ->
-
- % grab Host from Req
- Vhost = MochiReq:get_header_value("Host"),
-
- % find Vhost in config
- case couch_util:get_value(Vhost, VirtualHosts) of
- undefined -> % business as usual
- handle_request_int(MochiReq, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers);
- VhostTarget ->
- case vhost_global(VhostGlobals, MochiReq) of
- true ->% global handler for vhosts
- handle_request_int(MochiReq, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers);
- _Else ->
- % do rewrite
- redirect_to_vhost(MochiReq, DefaultFun,
- UrlHandlers, DbUrlHandlers, DesignUrlHandlers, VhostTarget)
- end
- end.
-
+ UrlHandlers, DbUrlHandlers, DesignUrlHandlers).
handle_request_int(MochiReq, DefaultFun,
UrlHandlers, DbUrlHandlers, DesignUrlHandlers) ->
@@ -194,6 +183,14 @@ handle_request_int(MochiReq, DefaultFun,
RawUri = MochiReq:get(raw_path),
{"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
+ Headers = MochiReq:get(headers),
+
+ % get requested path
+ RequestedPath = case MochiReq:get_header_value("x-couchdb-vhost-path") of
+ undefined -> RawUri;
+ P -> P
+ end,
+
HandlerKey =
case mochiweb_util:partition(Path, "/") of
{"", "", ""} ->
@@ -201,10 +198,11 @@ handle_request_int(MochiReq, DefaultFun,
{FirstPart, _, _} ->
list_to_binary(FirstPart)
end,
- ?LOG_DEBUG("~p ~s ~p~nHeaders: ~p", [
+ ?LOG_DEBUG("~p ~s ~p from ~p~nHeaders: ~p", [
MochiReq:get(method),
RawUri,
MochiReq:get(version),
+ MochiReq:get(peer),
mochiweb_headers:to_list(MochiReq:get(headers))
]),
@@ -245,6 +243,8 @@ handle_request_int(MochiReq, DefaultFun,
mochi_req = MochiReq,
peer = MochiReq:get(peer),
method = Method,
+ requested_path_parts = [list_to_binary(couch_httpd:unquote(Part))
+ || Part <- string:tokens(RequestedPath, "/")],
path_parts = [list_to_binary(couch_httpd:unquote(Part))
|| Part <- string:tokens(Path, "/")],
db_url_handlers = DbUrlHandlers,
@@ -269,7 +269,7 @@ handle_request_int(MochiReq, DefaultFun,
throw:{invalid_json, S} ->
?LOG_ERROR("attempted upload of invalid JSON (set log_level to debug to log it)", []),
?LOG_DEBUG("Invalid JSON: ~p",[S]),
- send_error(HttpReq, {bad_request, "invalid UTF-8 JSON"});
+ send_error(HttpReq, {bad_request, io_lib:format("invalid UTF-8 JSON: ~p",[S])});
throw:unacceptable_encoding ->
?LOG_ERROR("unsupported encoding method for the response", []),
send_error(HttpReq, {not_acceptable, "unsupported encoding"});
@@ -326,18 +326,6 @@ authenticate_request(Response, _AuthSrcs) ->
increment_method_stats(Method) ->
couch_stats_collector:increment({httpd_request_methods, Method}).
-% if so, then it will not be rewritten, but will run as a normal couchdb request.
-% normally you'd use this for _uuids _utils and a few of the others you want to keep available on vhosts. You can also use it to make databases 'global'.
-vhost_global(VhostGlobals, MochiReq) ->
- "/" ++ Path = MochiReq:get(path),
- Front = case partition(Path) of
- {"", "", ""} ->
- "/"; % Special case the root url handler
- {FirstPart, _, _} ->
- FirstPart
- end,
- [true] == [true||V <- VhostGlobals, V == Front].
-
validate_referer(Req) ->
Host = host_for_request(Req),
Referer = header_value(Req, "Referer", fail),
@@ -406,6 +394,14 @@ qs_value(Req, Key) ->
qs_value(Req, Key, Default) ->
couch_util:get_value(Key, qs(Req), Default).
+qs_json_value(Req, Key, Default) ->
+ case qs_value(Req, Key, Default) of
+ Default ->
+ Default;
+ Result ->
+ ?JSON_DECODE(Result)
+ end.
+
qs(#httpd{mochi_req=MochiReq}) ->
MochiReq:parse_qs().
@@ -430,15 +426,18 @@ absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
Host = host_for_request(Req),
XSsl = couch_config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
Scheme = case MochiReq:get_header_value(XSsl) of
- "on" -> "https";
- _ ->
- XProto = couch_config:get("httpd", "x_forwarded_proto", "X-Forwarded-Proto"),
- case MochiReq:get_header_value(XProto) of
- % Restrict to "https" and "http" schemes only
- "https" -> "https";
- _ -> "http"
- end
- end,
+ "on" -> "https";
+ _ ->
+ XProto = couch_config:get("httpd", "x_forwarded_proto", "X-Forwarded-Proto"),
+ case MochiReq:get_header_value(XProto) of
+ %% Restrict to "https" and "http" schemes only
+ "https" -> "https";
+ _ -> case MochiReq:get(scheme) of
+ https -> "https";
+ http -> "http"
+ end
+ end
+ end,
Scheme ++ "://" ++ Host ++ Path.
unquote(UrlEncodedString) ->
@@ -545,6 +544,18 @@ start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Length) ->
end,
{ok, Resp}.
+start_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
+ log_request(Req, Code),
+ couch_stats_collector:increment({httpd_status_cdes, Code}),
+ CookieHeader = couch_httpd_auth:cookie_auth_header(Req, Headers),
+ Headers2 = Headers ++ server_header() ++ CookieHeader,
+ Resp = MochiReq:start_response({Code, Headers2}),
+ case MochiReq:get(method) of
+ 'HEAD' -> throw({http_head_abort, Resp});
+ _ -> ok
+ end,
+ {ok, Resp}.
+
send(Resp, Data) ->
Resp:send(Data),
{ok, Resp}.
@@ -612,9 +623,7 @@ send_json(Req, Code, Headers, Value) ->
{"Content-Type", negotiate_content_type(Req)},
{"Cache-Control", "must-revalidate"}
],
- Body = list_to_binary(
- [start_jsonp(Req), ?JSON_ENCODE(Value), end_jsonp(), $\n]
- ),
+ Body = [start_jsonp(Req), ?JSON_ENCODE(Value), end_jsonp(), $\n],
send_response(Req, Code, DefaultHeaders ++ Headers, Body).
start_json_response(Req, Code) ->
@@ -723,6 +732,8 @@ error_info(file_exists) ->
"created, the file already exists.">>};
error_info({bad_ctype, Reason}) ->
{415, <<"bad_content_type">>, Reason};
+error_info(requested_range_not_satisfiable) ->
+ {416, <<"requested_range_not_satisfiable">>, <<"Requested range not satisfiable">>};
error_info({error, illegal_database_name}) ->
{400, <<"illegal_database_name">>, <<"Only lowercase characters (a-z), "
"digits (0-9), and any of the characters _, $, (, ), +, -, and / "
@@ -753,31 +764,29 @@ error_headers(#httpd{mochi_req=MochiReq}=Req, Code, ErrorStr, ReasonStr) ->
% send the browser popup header no matter what if we are require_valid_user
{Code, [{"WWW-Authenticate", "Basic realm=\"server\""}]};
_False ->
- % if the accept header matches html, then do the redirect. else proceed as usual.
- Accepts = case MochiReq:get_header_value("Accept") of
- undefined ->
- % According to the HTTP 1.1 spec, if the Accept
- % header is missing, it means the client accepts
- % all media types.
- "html";
- Else ->
- Else
- end,
- case re:run(Accepts, "\\bhtml\\b",
- [{capture, none}, caseless]) of
- nomatch ->
+ case MochiReq:accepts_content_type("application/json") of
+ true ->
{Code, []};
- match ->
- AuthRedirectBin = ?l2b(AuthRedirect),
- % Redirect to the path the user requested, not
- % the one that is used internally.
- UrlReturnRaw = case MochiReq:get_header_value("x-couchdb-vhost-path") of
- undefined -> MochiReq:get(path);
- VHostPath -> VHostPath
- end,
- UrlReturn = ?l2b(couch_util:url_encode(UrlReturnRaw)),
- UrlReason = ?l2b(couch_util:url_encode(ReasonStr)),
- {302, [{"Location", couch_httpd:absolute_uri(Req, <<AuthRedirectBin/binary,"?return=",UrlReturn/binary,"&reason=",UrlReason/binary>>)}]}
+ false ->
+ case MochiReq:accepts_content_type("text/html") of
+ true ->
+ % Redirect to the path the user requested, not
+ % the one that is used internally.
+ UrlReturnRaw = case MochiReq:get_header_value("x-couchdb-vhost-path") of
+ undefined ->
+ MochiReq:get(path);
+ VHostPath ->
+ VHostPath
+ end,
+ RedirectLocation = lists:flatten([
+ AuthRedirect,
+ "?return=", couch_util:url_encode(UrlReturnRaw),
+ "&reason=", couch_util:url_encode(ReasonStr)
+ ]),
+ {302, [{"Location", absolute_uri(Req, RedirectLocation)}]};
+ false ->
+ {Code, []}
+ end
end
end
end;
@@ -842,9 +851,8 @@ negotiate_content_type(#httpd{mochi_req=MochiReq}) ->
end.
server_header() ->
- OTPVersion = "R" ++ integer_to_list(erlang:system_info(compat_rel)) ++ "B",
[{"Server", "CouchDB/" ++ couch:version() ++
- " (Erlang OTP/" ++ OTPVersion ++ ")"}].
+ " (Erlang OTP/" ++ erlang:system_info(otp_release) ++ ")"}].
-record(mp, {boundary, buffer, data_fun, callback}).
View
29 apps/couch/src/couch_httpd_auth.erl
@@ -173,7 +173,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) ->
CurrentTime = make_cookie_time(),
case couch_config:get("couch_httpd_auth", "secret", nil) of
nil ->
- ?LOG_ERROR("cookie auth secret is not set",[]),
+ ?LOG_DEBUG("cookie auth secret is not set",[]),
Req;
SecretStr ->
Secret = ?l2b(SecretStr),
@@ -207,7 +207,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) ->
end.
cookie_auth_header(#httpd{user_ctx=#user_ctx{name=null}}, _Headers) -> [];
-cookie_auth_header(#httpd{user_ctx=#user_ctx{name=User}, auth={Secret, true}}, Headers) ->
+cookie_auth_header(#httpd{user_ctx=#user_ctx{name=User}, auth={Secret, true}}=Req, Headers) ->
% Note: we only set the AuthSession cookie if:
% * a valid AuthSession cookie has been received
% * we are outside a 10% timeout window
@@ -220,18 +220,18 @@ cookie_auth_header(#httpd{user_ctx=#user_ctx{name=User}, auth={Secret, true}}, H
AuthSession = couch_util:get_value("AuthSession", Cookies),
if AuthSession == undefined ->
TimeStamp = make_cookie_time(),
- [cookie_auth_cookie(?b2l(User), Secret, TimeStamp)];
+ [cookie_auth_cookie(Req, ?b2l(User), Secret, TimeStamp)];
true ->
[]
end;
cookie_auth_header(_Req, _Headers) -> [].
-cookie_auth_cookie(User, Secret, TimeStamp) ->
+cookie_auth_cookie(Req, User, Secret, TimeStamp) ->
SessionData = User ++ ":" ++ erlang:integer_to_list(TimeStamp, 16),
Hash = crypto:sha_mac(Secret, SessionData),
mochiweb_cookies:cookie("AuthSession",
couch_util:encodeBase64Url(SessionData ++ ":" ++ ?b2l(Hash)),
- [{path, "/"}, {http_only, true}]). % TODO add {secure, true} when SSL is detected
+ [{path, "/"}] ++ cookie_scheme(Req)).
hash_password(Password, Salt) ->
?l2b(couch_util:to_hex(crypto:sha(<<Password/binary, Salt/binary>>))).
@@ -247,13 +247,17 @@ ensure_cookie_auth_secret() ->
% session handlers
% Login handler with user db
-% TODO this should also allow a JSON POST
handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
ReqBody = MochiReq:recv_body(),
Form = case MochiReq:get_primary_header_value("content-type") of
% content type should be json
"application/x-www-form-urlencoded" ++ _ ->
mochiweb_util:parse_qs(ReqBody);
+ "application/json" ++ _ ->
+ {Pairs} = ?JSON_DECODE(ReqBody),
+ lists:map(fun({Key, Value}) ->
+ {?b2l(Key), ?b2l(Value)}
+ end, Pairs);
_ ->
[]
end,
@@ -272,7 +276,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
% setup the session cookie
Secret = ?l2b(ensure_cookie_auth_secret()),
CurrentTime = make_cookie_time(),
- Cookie = cookie_auth_cookie(?b2l(UserName), <<Secret/binary, UserSalt/binary>>, CurrentTime),
+ Cookie = cookie_auth_cookie(Req, ?b2l(UserName), <<Secret/binary, UserSalt/binary>>, CurrentTime),
% TODO document the "next" feature in Futon
{Code, Headers} = case couch_httpd:qs_value(Req, "next", nil) of
nil ->
@@ -288,7 +292,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
]});
_Else ->
% clear the session
- Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}, {http_only, true}]),
+ Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}] ++ cookie_scheme(Req)),
send_json(Req, 401, [Cookie], {[{error, <<"unauthorized">>},{reason, <<"Name or password is incorrect.">>}]})
end;
% get user info
@@ -318,7 +322,7 @@ handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req) ->
end;
% logout by deleting the session
handle_session_req(#httpd{method='DELETE'}=Req) ->
- Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}, {http_only, true}]),
+ Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}] ++ cookie_scheme(Req)),
{Code, Headers} = case couch_httpd:qs_value(Req, "next", nil) of
nil ->
{200, [Cookie]};
@@ -347,3 +351,10 @@ to_int(Value) when is_integer(Value) ->
make_cookie_time() ->
{NowMS, NowS, _} = erlang:now(),
NowMS * 1000000 + NowS.
+
+cookie_scheme(#httpd{mochi_req=MochiReq}) ->
+ [{http_only, true}] ++
+ case MochiReq:get(scheme) of
+ http -> [];
+ https -> [{secure, true}]
+ end.
View
287 apps/couch/src/couch_httpd_db.erl
@@ -20,7 +20,7 @@
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
- start_json_response/2,start_json_response/3,
+ send_response/4,start_json_response/2,start_json_response/3,
send_chunk/2,last_chunk/1,end_json_response/1,
start_chunked_response/3, absolute_uri/2, send/2,
start_response_length/4]).
@@ -55,7 +55,15 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
do_db_req(Req, Handler)
end.
+handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+ couch_httpd:validate_ctype(Req, "application/json"),
+ handle_changes_req1(Req, Db);
handle_changes_req(#httpd{method='GET'}=Req, Db) ->
+ handle_changes_req1(Req, Db);
+handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+ send_method_not_allowed(Req, "GET,HEAD,POST").
+
+handle_changes_req1(Req, Db) ->
MakeCallback = fun(Resp) ->
fun({change, Change, _}, "continuous") ->
send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
@@ -106,13 +114,16 @@ handle_changes_req(#httpd{method='GET'}=Req, Db) ->
FeedChangesFun(MakeCallback(Resp))
end
end,
- couch_stats_collector:track_process_count(
+ couch_stats_collector:increment(
{httpd, clients_requesting_changes}
),
- WrapperFun(ChangesFun);
-
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->