You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by jc...@apache.org on 2009/03/05 07:14:39 UTC

svn commit: r750332 [2/2] - in /couchdb/branches/rep_security: ./ bin/ etc/couchdb/ etc/default/ etc/init/ share/ share/server/ share/www/ share/www/script/ share/www/script/test/ share/www/style/ src/couchdb/ src/ibrowse/ src/mochiweb/ test/

Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl Thu Mar  5 06:14:36 2009
@@ -102,6 +102,7 @@
     send_method_not_allowed(Req, "POST");
 
 db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
+    couch_stats_collector:increment({httpd, bulk_requests}),
     {JsonProps} = couch_httpd:json_body(Req),
     DocsArray = proplists:get_value(<<"docs">>, JsonProps),
     case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
@@ -202,46 +203,48 @@
     } = QueryArgs = couch_httpd_view:parse_view_query(Req),
 
     {ok, Info} = couch_db:get_db_info(Db),
-    TotalRowCount = proplists:get_value(doc_count, Info),
-
-    FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-        TotalRowCount, #view_fold_helper_funs{
-            reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
-        }),
-    StartKey2 = case StartKey of
-        nil -> 0;
-        <<>> -> 100000000000;
-        {} -> 100000000000;
-        StartKey when is_integer(StartKey) -> StartKey
-    end,
-    {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
-        fun(DocInfo, Offset, Acc) ->
-            #doc_info{
-                id=Id,
-                rev=Rev,
-                update_seq=UpdateSeq,
-                deleted=Deleted,
-                conflict_revs=ConflictRevs,
-                deleted_conflict_revs=DelConflictRevs
-            } = DocInfo,
-            Json = {
-                [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++
-                case ConflictRevs of
-                    []  ->  [];
-                    _   ->  [{<<"conflicts">>, couch_doc:rev_to_strs(ConflictRevs)}]
-                end ++
-                case DelConflictRevs of
-                    []  ->  [];
-                    _   ->  [{<<"deleted_conflicts">>, couch_doc:rev_to_strs(DelConflictRevs)}]
-                end ++
-                case Deleted of
-                    true -> [{<<"deleted">>, true}];
-                    false -> []
-                end
-            },
-            FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
-        end, {Limit, SkipCount, undefined, []}),
-    couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+    CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        TotalRowCount = proplists:get_value(doc_count, Info),
+        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+            TotalRowCount, #view_fold_helper_funs{
+                reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
+            }),
+        StartKey2 = case StartKey of
+            nil -> 0;
+            <<>> -> 100000000000;
+            {} -> 100000000000;
+            StartKey when is_integer(StartKey) -> StartKey
+        end,
+        {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
+            fun(DocInfo, Offset, Acc) ->
+                #doc_info{
+                    id=Id,
+                    rev=Rev,
+                    update_seq=UpdateSeq,
+                    deleted=Deleted,
+                    conflict_revs=ConflictRevs,
+                    deleted_conflict_revs=DelConflictRevs
+                } = DocInfo,
+                Json = {
+                    [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++
+                    case ConflictRevs of
+                        []  ->  [];
+                        _   ->  [{<<"conflicts">>, couch_doc:rev_to_strs(ConflictRevs)}]
+                    end ++
+                    case DelConflictRevs of
+                        []  ->  [];
+                        _   ->  [{<<"deleted_conflicts">>, couch_doc:rev_to_strs(DelConflictRevs)}]
+                    end ++
+                    case Deleted of
+                        true -> [{<<"deleted">>, true}];
+                        false -> []
+                    end
+                },
+                FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
+            end, {Limit, SkipCount, undefined, []}),
+        couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+    end);
 
 db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) ->
     send_method_not_allowed(Req, "GET,HEAD");
@@ -276,9 +279,8 @@
     PathFront = "/" ++ couch_httpd:quote(binary_to_list(DbName)) ++ "/",
     RawSplit = regexp:split(MochiReq:get(raw_path),"_design%2F"),
     {ok, [PathFront|PathTail]} = RawSplit,
-    RedirectTo = couch_httpd:absolute_uri(Req, PathFront ++ "_design/" ++ 
-        mochiweb_util:join(PathTail, "%2F")),
-    couch_httpd:send_response(Req, 301, [{"Location", RedirectTo}], <<>>);
+    couch_httpd:send_redirect(Req, PathFront ++ "_design/" ++ 
+        mochiweb_util:join(PathTail, "_design%2F"));
 
 db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name]}=Req, Db) ->
     db_doc_req(Req, Db, <<"_design/",Name/binary>>);
@@ -303,77 +305,81 @@
         direction = Dir
     } = QueryArgs = couch_httpd_view:parse_view_query(Req, Keys),    
     {ok, Info} = couch_db:get_db_info(Db),
-    TotalRowCount = proplists:get_value(doc_count, Info),
-    StartId = if is_binary(StartKey) -> StartKey;
-    true -> StartDocId
-    end,
-    FoldAccInit = {Limit, SkipCount, undefined, []},
+    CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
     
-    PassedEndFun = 
-    case Dir of
-    fwd ->
-        fun(ViewKey, _ViewId) ->
-            couch_db_updater:less_docid(EndKey, ViewKey)
-        end;
-    rev->
-        fun(ViewKey, _ViewId) ->
-            couch_db_updater:less_docid(ViewKey, EndKey)
-        end
-    end,
-    
-    case Keys of
-    nil ->
-        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-            TotalRowCount, #view_fold_helper_funs{
-                reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
-                passed_end = PassedEndFun
-            }),
-        AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
-            case couch_doc:to_doc_info(FullDocInfo) of
-            #doc_info{deleted=false, rev=Rev} ->
-                FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
-            #doc_info{deleted=true} ->
-                {ok, Acc}
-            end
+        TotalRowCount = proplists:get_value(doc_count, Info),
+        StartId = if is_binary(StartKey) -> StartKey;
+        true -> StartDocId
         end,
-        {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir, 
-            AdapterFun, FoldAccInit),
-        couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
-    _ ->
-        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-            TotalRowCount, #view_fold_helper_funs{
-                reduce_count = fun(Offset) -> Offset end
-            }),
-        KeyFoldFun = case Dir of
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+    
+        PassedEndFun = 
+        case Dir of
         fwd ->
-            fun lists:foldl/3;
-        rev ->
-            fun lists:foldr/3
+            fun(ViewKey, _ViewId) ->
+                couch_db_updater:less_docid(EndKey, ViewKey)
+            end;
+        rev->
+            fun(ViewKey, _ViewId) ->
+                couch_db_updater:less_docid(ViewKey, EndKey)
+            end
         end,
-        {ok, FoldResult} = KeyFoldFun(
-            fun(Key, {ok, FoldAcc}) ->
-                DocInfo = (catch couch_db:get_doc_info(Db, Key)),
-                Doc = case DocInfo of
-                {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
-                    {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
-                {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
-                    {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
-                not_found ->
-                    {{Key, error}, not_found};
-                _ ->
-                    ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
-                    throw({error, invalid_doc_info})
-                end,
-                Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
-                case Acc of
-                {stop, Acc2} ->
-                    {ok, Acc2};
-                _ ->
-                    Acc
+    
+        case Keys of
+        nil ->
+            FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+                TotalRowCount, #view_fold_helper_funs{
+                    reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
+                    passed_end = PassedEndFun
+                }),
+            AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
+                case couch_doc:to_doc_info(FullDocInfo) of
+                #doc_info{deleted=false, rev=Rev} ->
+                    FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
+                #doc_info{deleted=true} ->
+                    {ok, Acc}
                 end
-            end, {ok, FoldAccInit}, Keys),
-        couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})        
-    end.
+            end,
+            {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir, 
+                AdapterFun, FoldAccInit),
+            couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+        _ ->
+            FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+                TotalRowCount, #view_fold_helper_funs{
+                    reduce_count = fun(Offset) -> Offset end
+                }),
+            KeyFoldFun = case Dir of
+            fwd ->
+                fun lists:foldl/3;
+            rev ->
+                fun lists:foldr/3
+            end,
+            {ok, FoldResult} = KeyFoldFun(
+                fun(Key, {ok, FoldAcc}) ->
+                    DocInfo = (catch couch_db:get_doc_info(Db, Key)),
+                    Doc = case DocInfo of
+                    {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
+                        {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
+                    {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
+                        {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
+                    not_found ->
+                        {{Key, error}, not_found};
+                    _ ->
+                        ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
+                        throw({error, invalid_doc_info})
+                    end,
+                    Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
+                    case Acc of
+                    {stop, Acc2} ->
+                        {ok, Acc2};
+                    _ ->
+                        Acc
+                    end
+                end, {ok, FoldAccInit}, Keys),
+            couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})        
+        end
+    end).
 
 
 
@@ -400,7 +406,7 @@
             [] -> [{"Etag", DiskEtag}]; % output etag only when we have no meta
             _ -> []
             end,
-            send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))            
+            send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))
         end);
     _ ->
         {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
@@ -595,18 +601,31 @@
 
 db_attachment_req(#httpd{method=Method}=Req, Db, DocId, FileNameParts)
         when (Method == 'PUT') or (Method == 'DELETE') ->
-    FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1, FileNameParts),"/")),
+    FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1, 
+        FileNameParts),"/")),
     NewAttachment = case Method of
         'DELETE' ->
             [];
         _ ->
+            % see couch_db:doc_flush_binaries for usage of this structure
             [{FileName, {
-                list_to_binary(couch_httpd:header_value(Req,"Content-Type")),
+                case couch_httpd:header_value(Req,"Content-Type") of
+                undefined ->
+                    % We could throw an error here or guess by the FileName.
+                    % Currently, just giving it a default.
+                    <<"application/octet-stream">>;
+                CType ->
+                    list_to_binary(CType)
+                end,
                 case couch_httpd:header_value(Req,"Content-Length") of
                 undefined -> 
-                    throw({bad_request, "Attachment uploads must be fixed length"});
+                    {fun(MaxChunkSize, ChunkFun, InitState) -> 
+                        couch_httpd:recv_chunked(Req, MaxChunkSize, 
+                            ChunkFun, InitState) 
+                    end, undefined};
                 Length -> 
-                    {fun() -> couch_httpd:recv(Req, 0) end, list_to_integer(Length)}
+                    {fun() -> couch_httpd:recv(Req, 0) end,
+                        list_to_integer(Length)}
                 end
             }}]
     end,
@@ -677,7 +696,6 @@
         undefined -> undefined;
         Value -> couch_doc:parse_rev(string:strip(Value, both, $"))
     end,
-
     case {ExplicitRev, Etag} of
     {undefined, undefined} -> missing_rev;
     {_, undefined} -> ExplicitRev;

Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl Thu Mar  5 06:14:36 2009
@@ -106,6 +106,8 @@
                 Args;
             {<<"code">>, Value} ->
                 Args#extern_resp_args{code=Value};
+            {<<"stop">>, true} ->
+                Args#extern_resp_args{stop=true};
             {<<"json">>, Value} ->
                 Args#extern_resp_args{
                     data=?JSON_ENCODE(Value),

Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl Thu Mar  5 06:14:36 2009
@@ -14,7 +14,7 @@
 
 -export([handle_welcome_req/2,handle_favicon_req/2,handle_utils_dir_req/2,
     handle_all_dbs_req/1,handle_replicate_req/1,handle_restart_req/1,
-    handle_uuids_req/1,handle_config_req/1,handle_stats_req/1,
+    handle_uuids_req/1,handle_config_req/1,
     handle_task_status_req/1]).
     
 -export([increment_update_seq_req/2]).
@@ -50,8 +50,7 @@
         couch_httpd:serve_file(Req, RelativePath, DocumentRoot);
     {_ActionKey, "", _RelativePath} ->
         % GET /_utils
-        Headers = [{"Location", couch_httpd:absolute_uri(Req, "/_utils/")}],
-        couch_httpd:send_response(Req, 301, Headers, <<>>)
+        couch_httpd:send_redirect(Req, "/_utils/")
     end;
 handle_utils_dir_req(Req, _) ->
     send_method_not_allowed(Req, "GET,HEAD").
@@ -64,13 +63,6 @@
     send_method_not_allowed(Req, "GET,HEAD").
 
 
-handle_stats_req(#httpd{method='GET'}=Req) ->
-    ok = couch_httpd:verify_is_server_admin(Req),
-    send_json(Req, {couch_server:get_stats() ++ couch_file_stats:get_stats()});
-handle_stats_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-
 handle_task_status_req(#httpd{method='GET'}=Req) ->
     ok = couch_httpd:verify_is_server_admin(Req),
     % convert the list of prop lists to a list of json objects
@@ -111,14 +103,18 @@
     send_method_not_allowed(Req, "POST").
 
 
-handle_uuids_req(#httpd{method='POST'}=Req) ->
+handle_uuids_req(#httpd{method='GET'}=Req) ->
     Count = list_to_integer(couch_httpd:qs_value(Req, "count", "1")),
+    CacheBustingHeaders = [{"Date", httpd_util:rfc1123_date()},
+                           {"Cache-Control", "no-cache"},
+                           {"Expires", "Fri, 01 Jan 1990 00:00:00 GMT"},  % Past date, ON PURPOSE!
+                           {"Pragma", "no-cache"}],
     % generate the uuids
     UUIDs = [ couch_util:new_uuid() || _ <- lists:seq(1,Count)],
     % send a JSON response
-    send_json(Req, {[{<<"uuids">>, UUIDs}]});
+    send_json(Req, 200, CacheBustingHeaders, {[{<<"uuids">>, UUIDs}]});
 handle_uuids_req(Req) ->
-    send_method_not_allowed(Req, "POST").
+    send_method_not_allowed(Req, "GET").
 
 
 % Config request handler

Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl Thu Mar  5 06:14:36 2009
@@ -19,19 +19,23 @@
 
 -import(couch_httpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
-    start_json_response/2,send_chunk/2,end_json_response/1,
+    start_json_response/2,send_chunk/2,
     start_chunked_response/3, send_error/4]).
     
 handle_doc_show_req(#httpd{
         method='GET',
-        path_parts=[_, _, DesignName, ShowName, Docid]
+        path_parts=[_, _, DesignName, ShowName, DocId]
     }=Req, Db) ->
     DesignId = <<"_design/", DesignName/binary>>,
     #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
     Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
     ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
-    Doc = couch_httpd_db:couch_doc_open(Db, Docid, nil, []),
-    send_doc_show_response(Lang, ShowSrc, Doc, Req, Db);
+    Doc = try couch_httpd_db:couch_doc_open(Db, DocId, nil, []) of
+        FoundDoc -> FoundDoc
+    catch
+        _ -> nil
+    end,
+    send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db);
 
 handle_doc_show_req(#httpd{
         method='GET',
@@ -41,7 +45,7 @@
     #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
     Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
     ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
-    send_doc_show_response(Lang, ShowSrc, nil, Req, Db);
+    send_doc_show_response(Lang, ShowSrc, nil, nil, Req, Db);
 
 handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
     send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
@@ -56,6 +60,9 @@
     ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]),
     send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db);
 
+handle_view_list_req(#httpd{method='GET'}=Req, _Db) ->
+    send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
+
 handle_view_list_req(Req, _Db) ->
     send_method_not_allowed(Req, "GET,HEAD").
 
@@ -71,30 +78,29 @@
     throw({not_found, json_mismatch}).
 
 send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
-    % TODO add etags when we get view etags
     #view_query_args{
         stale = Stale,
         reduce = Reduce
-    } = QueryArgs = couch_httpd_view:parse_view_query(Req),
+    } = QueryArgs = couch_httpd_view:parse_view_query(Req, nil, nil, true),
     case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
-    {ok, View} ->    
-        output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+    {ok, View, Group} ->    
+        output_map_list(Req, Lang, ListSrc, View, Group, Db, QueryArgs);
     {not_found, _Reason} ->
         case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
-        {ok, ReduceView} ->
+        {ok, ReduceView, Group} ->
             case Reduce of
             false ->
                 MapView = couch_view:extract_map_view(ReduceView),
-                output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+                output_map_list(Req, Lang, ListSrc, MapView, Group, Db, QueryArgs);
             _ ->
-                throw({not_implemented, reduce_view_lists})
+                output_reduce_list(Req, Lang, ListSrc, ReduceView, Group, Db, QueryArgs)
             end;
         {not_found, Reason} ->
             throw({not_found, Reason})
         end
     end.
-    
-output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+
+output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs) ->
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -104,71 +110,158 @@
     } = QueryArgs,
     {ok, RowCount} = couch_view:get_row_count(View),
     Start = {StartKey, StartDocId},
+    Headers = MReq:get(headers),
+    Hlist = mochiweb_headers:to_list(Headers),
+    Accept = proplists:get_value('Accept', Hlist),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        % get the os process here
+        % pass it into the view fold with closures
+        {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+        StartListRespFun = fun(Req2, _Etag, TotalViewCount, Offset) ->
+            ExternalResp = couch_query_servers:render_list_head(QueryServer, 
+                Req2, Db, TotalViewCount, Offset),
+            JsonResp = apply_etag(ExternalResp, CurrentEtag),
+            #extern_resp_args{
+                code = Code,
+                data = BeginBody,
+                ctype = CType,
+                headers = ExtHeaders
+            } = couch_httpd_external:parse_external_response(JsonResp),
+            JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+            {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+            {ok, Resp, binary_to_list(BeginBody)}
+        end,
+    
+        SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value}, 
+            RowFront, _IncludeDocs) ->
+            JsonResp = couch_query_servers:render_list_row(QueryServer, 
+                Req, Db2, {{Key, DocId}, Value}),
+            #extern_resp_args{
+                stop = StopIter,
+                data = RowBody
+            } = couch_httpd_external:parse_external_response(JsonResp),
+            case StopIter of
+            true -> stop;
+            _ ->
+                RowFront2 = case RowFront of
+                nil -> [];
+                _ -> RowFront
+                end,
+                Chunk = RowFront2 ++ binary_to_list(RowBody),
+                case Chunk of
+                    [] -> {ok, Resp};
+                    _ -> send_chunk(Resp, Chunk)
+                end
+            end
+        end,
+    
+        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
+            #view_fold_helper_funs{
+                reduce_count = fun couch_view:reduce_to_count/1,
+                start_response = StartListRespFun,
+                send_row = SendListRowFun
+            }),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+        finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+    end).
+
+output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs) ->
+    #view_query_args{
+        limit = Limit,
+        direction = Dir,
+        skip = SkipCount,
+        start_key = StartKey,
+        start_docid = StartDocId,
+        end_key = EndKey,
+        end_docid = EndDocId,
+        group_level = GroupLevel
+    } = QueryArgs,
     % get the os process here
     % pass it into the view fold with closures
     {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
-
-    StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
-        JsonResp = couch_query_servers:render_list_head(QueryServer, 
-            Req2, Db, TotalViewCount, Offset),
-        #extern_resp_args{
-            code = Code,
-            data = BeginBody,
-            ctype = CType,
-            headers = Headers
-        } = couch_httpd_external:parse_external_response(JsonResp),
-        JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
-        {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
-        {ok, Resp, binary_to_list(BeginBody)}
-    end,
+    Headers = MReq:get(headers),
+    Hlist = mochiweb_headers:to_list(Headers),
+    Accept = proplists:get_value('Accept', Hlist),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        StartListRespFun = fun(Req2, _Etag, _, _) ->
+            JsonResp = couch_query_servers:render_reduce_head(QueryServer, 
+                Req2, Db),
+            JsonResp2 = apply_etag(JsonResp, CurrentEtag),
+            #extern_resp_args{
+                code = Code,
+                data = BeginBody,
+                ctype = CType,
+                headers = ExtHeaders
+            } = couch_httpd_external:parse_external_response(JsonResp2),
+            JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+            {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+            {ok, Resp, binary_to_list(BeginBody)}
+        end,
     
-    SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value}, 
-        RowFront, _IncludeDocs) ->
-        JsonResp = couch_query_servers:render_list_row(QueryServer, 
-            Req, Db2, {{Key, DocId}, Value}),
-        #extern_resp_args{
-            data = RowBody
-        } = couch_httpd_external:parse_external_response(JsonResp),
-        RowFront2 = case RowFront of
-        nil -> [];
-        _ -> RowFront
+        SendListRowFun = fun(Resp, {Key, Value}, RowFront) ->
+            JsonResp = couch_query_servers:render_reduce_row(QueryServer, 
+                Req, Db, {Key, Value}),
+            #extern_resp_args{
+                stop = StopIter,
+                data = RowBody
+            } = couch_httpd_external:parse_external_response(JsonResp),
+            RowFront2 = case RowFront of
+            nil -> [];
+            _ -> RowFront
+            end,
+            case StopIter of
+            true -> stop;
+            _ ->
+                Chunk = RowFront2 ++ binary_to_list(RowBody),
+                case Chunk of
+                    [] -> {ok, Resp};
+                    _ -> send_chunk(Resp, Chunk)
+                end
+            end
         end,
-        send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
-    end,
     
-    FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db, RowCount,
-        #view_fold_helper_funs{
-            reduce_count = fun couch_view:reduce_to_count/1,
-            start_response = StartListRespFun,
-            send_row = SendListRowFun
-        }),
-    FoldAccInit = {Limit, SkipCount, undefined, []},
-    FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
-    finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+        {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req, 
+            GroupLevel, QueryArgs, CurrentEtag, 
+            #reduce_fold_helper_funs{
+                start_response = StartListRespFun,
+                send_row = SendListRowFun
+            }),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
+            {EndKey, EndDocId}, GroupRowsFun, RespFun,
+            FoldAccInit),
+        finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
+    end).
 
-finish_view_list(Req, Db, QueryServer, TotalRows, 
-    FoldResult, StartListRespFun) ->
+finish_list(Req, Db, QueryServer, Etag, FoldResult, StartListRespFun, TotalRows) ->
     case FoldResult of
-    {ok, {_, _, undefined, _}} ->
-        {ok, Resp, BeginBody} = StartListRespFun(Req, 200, TotalRows, null),
+    {ok, Acc} ->
         JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
         #extern_resp_args{
             data = Tail
         } = couch_httpd_external:parse_external_response(JsonTail),
-        send_chunk(Resp, BeginBody ++ Tail),
-        send_chunk(Resp, []);
-    {ok, {_, _, Resp, _AccRevRows}} ->
-        JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
-        #extern_resp_args{
-            data = Tail
-        } = couch_httpd_external:parse_external_response(JsonTail),
-        send_chunk(Resp, Tail),
+        {Resp, BeginBody} = case Acc of
+        {_, _, undefined, _} ->
+            {ok, Resp2, BeginBody2} = StartListRespFun(Req, Etag, TotalRows, null),
+            {Resp2, BeginBody2};
+        {_, _, Resp2, _} ->
+            {Resp2, ""}
+        end,
+        Chunk = BeginBody ++ binary_to_list(Tail),
+        case Chunk of
+            [] -> ok;
+            _ -> send_chunk(Resp, Chunk)
+        end,
         send_chunk(Resp, []);
     Error ->
         throw(Error)
     end.
 
-send_doc_show_response(Lang, ShowSrc, nil, #httpd{mochi_req=MReq}=Req, Db) ->
+send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db) ->
     % compute etag with no doc
     Headers = MReq:get(headers),
     Hlist = mochiweb_headers:to_list(Headers),
@@ -176,12 +269,12 @@
     CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
         ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, 
-            nil, Req, Db),
+            DocId, nil, Req, Db),
         JsonResp = apply_etag(ExternalResp, CurrentEtag),
         couch_httpd_external:send_external_response(Req, JsonResp)
     end);
 
-send_doc_show_response(Lang, ShowSrc, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
+send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
     % calculate the etag
     Headers = MReq:get(headers),
     Hlist = mochiweb_headers:to_list(Headers),
@@ -190,7 +283,7 @@
     % We know our etag now    
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
         ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, 
-            Doc, Req, Db),
+            DocId, Doc, Req, Db),
         JsonResp = apply_etag(ExternalResp, CurrentEtag),
         couch_httpd_external:send_external_response(Req, JsonResp)
     end).

Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl Thu Mar  5 06:14:36 2009
@@ -15,12 +15,12 @@
 
 -export([handle_view_req/2,handle_temp_view_req/2]).
 
--export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5,
-    finish_view_fold/3, view_row_obj/3]).
+-export([parse_view_query/1,parse_view_query/2,parse_view_query/4,make_view_fold_fun/6,
+    finish_view_fold/3, view_row_obj/3, view_group_etag/1, view_group_etag/2, make_reduce_fold_funs/5]).
 
 -import(couch_httpd,
-    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
-    start_json_response/2,send_chunk/2,end_json_response/1]).
+    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2,
+    start_json_response/2, start_json_response/3, end_json_response/1]).
 
 design_doc_view(Req, Db, Id, ViewName, Keys) ->
     #view_query_args{
@@ -28,24 +28,26 @@
         reduce = Reduce
     } = QueryArgs = parse_view_query(Req, Keys),
     DesignId = <<"_design/", Id/binary>>,
-    case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
-    {ok, View} ->    
-        output_map_view(Req, View, Db, QueryArgs, Keys);
+    Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
+    {ok, View, Group} ->
+        output_map_view(Req, View, Group, Db, QueryArgs, Keys);
     {not_found, Reason} ->
         case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
-        {ok, ReduceView} ->
+        {ok, ReduceView, Group} ->
             parse_view_query(Req, Keys, true), % just for validation
             case Reduce of
             false ->
                 MapView = couch_view:extract_map_view(ReduceView),
-                output_map_view(Req, MapView, Db, QueryArgs, Keys);
+                output_map_view(Req, MapView, Group, Db, QueryArgs, Keys);
             _ ->
-                output_reduce_view(Req, ReduceView, QueryArgs, Keys)
+                output_reduce_view(Req, ReduceView, Group, QueryArgs, Keys)
             end;
         _ ->
             throw({not_found, Reason})
         end
-    end.
+    end,
+    couch_stats_collector:increment({httpd, view_reads}),
+    Result.
 
 handle_view_req(#httpd{method='GET',path_parts=[_,_, Id, ViewName]}=Req, Db) ->
     design_doc_view(Req, Db, Id, ViewName, nil);
@@ -60,7 +62,7 @@
 
 handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
     QueryArgs = parse_view_query(Req),
-
+    couch_stats_collector:increment({httpd, temporary_view_reads}),
     case couch_httpd:primary_header_value(Req, "content-type") of
         undefined -> ok;
         "application/json" -> ok;
@@ -73,19 +75,19 @@
     Keys = proplists:get_value(<<"keys">>, Props, nil),
     case proplists:get_value(<<"reduce">>, Props, null) of
     null ->
-        {ok, View} = couch_view:get_temp_map_view(Db, Language, 
+        {ok, View, Group} = couch_view:get_temp_map_view(Db, Language, 
             DesignOptions, MapSrc),
-        output_map_view(Req, View, Db, QueryArgs, Keys);
+        output_map_view(Req, View, Group, Db, QueryArgs, Keys);
     RedSrc ->
-        {ok, View} = couch_view:get_temp_reduce_view(Db, Language, 
+        {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language, 
             DesignOptions, MapSrc, RedSrc),
-        output_reduce_view(Req, View, QueryArgs, Keys)
+        output_reduce_view(Req, View, Group, QueryArgs, Keys)
     end;
 
 handle_temp_view_req(Req, _Db) ->
     send_method_not_allowed(Req, "POST").
 
-output_map_view(Req, View, Db, QueryArgs, nil) ->
+output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -93,38 +95,44 @@
         start_key = StartKey,
         start_docid = StartDocId
     } = QueryArgs,
-    {ok, RowCount} = couch_view:get_row_count(View),
-    Start = {StartKey, StartDocId},
-    FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
-    FoldAccInit = {Limit, SkipCount, undefined, []},
-    FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
-    finish_view_fold(Req, RowCount, FoldResult);
+    CurrentEtag = view_group_etag(Group),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
+        {ok, RowCount} = couch_view:get_row_count(View),
+        Start = {StartKey, StartDocId},
+        FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+        finish_view_fold(Req, RowCount, FoldResult)
+    end);
     
-output_map_view(Req, View, Db, QueryArgs, Keys) ->
+output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
     #view_query_args{
         limit = Limit,
         direction = Dir,
         skip = SkipCount,
         start_docid = StartDocId
     } = QueryArgs,
-    {ok, RowCount} = couch_view:get_row_count(View),
-    FoldAccInit = {Limit, SkipCount, undefined, []},
-    FoldResult = lists:foldl(
-        fun(Key, {ok, FoldAcc}) ->
-            Start = {Key, StartDocId},
-            FoldlFun = make_view_fold_fun(Req,
-                QueryArgs#view_query_args{
-                    start_key = Key,
-                    end_key = Key
-                }, Db, RowCount, 
-                #view_fold_helper_funs{
-                    reduce_count = fun couch_view:reduce_to_count/1
-                }),
-            couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
-        end, {ok, FoldAccInit}, Keys),
-    finish_view_fold(Req, RowCount, FoldResult).
+    CurrentEtag = view_group_etag(Group, Keys),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->     
+        {ok, RowCount} = couch_view:get_row_count(View),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = lists:foldl(
+            fun(Key, {ok, FoldAcc}) ->
+                Start = {Key, StartDocId},
+                FoldlFun = make_view_fold_fun(Req,
+                    QueryArgs#view_query_args{
+                        start_key = Key,
+                        end_key = Key
+                    }, CurrentEtag, Db, RowCount, 
+                    #view_fold_helper_funs{
+                        reduce_count = fun couch_view:reduce_to_count/1
+                    }),
+                couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
+            end, {ok, FoldAccInit}, Keys),
+        finish_view_fold(Req, RowCount, FoldResult)
+    end).
 
-output_reduce_view(Req, View, QueryArgs, nil) ->
+output_reduce_view(Req, View, Group, QueryArgs, nil) ->
     #view_query_args{
         start_key = StartKey,
         end_key = EndKey,
@@ -135,15 +143,16 @@
         end_docid = EndDocId,
         group_level = GroupLevel
     } = QueryArgs,
-    {ok, Resp} = start_json_response(Req, 200),
-    {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
-    send_chunk(Resp, "{\"rows\":["),
-    {ok, _} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId}, 
-        {EndKey, EndDocId}, GroupRowsFun, RespFun, {"", Skip, Limit}),
-    send_chunk(Resp, "]}"),
-    end_json_response(Resp);
+    CurrentEtag = view_group_etag(Group),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
+        FoldAccInit = {Limit, Skip, undefined, []},
+        {ok, {_, _, Resp, _}} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId}, 
+            {EndKey, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
+        finish_reduce_fold(Req, Resp)
+    end);
     
-output_reduce_view(Req, View, QueryArgs, Keys) ->
+output_reduce_view(Req, View, Group, QueryArgs, Keys) ->
     #view_query_args{
         limit = Limit,
         skip = Skip,
@@ -152,21 +161,27 @@
         end_docid = EndDocId,
         group_level = GroupLevel
     } = QueryArgs,
-    {ok, Resp} = start_json_response(Req, 200),
-    {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
-    send_chunk(Resp, "{\"rows\":["),
-    lists:foldl(
-        fun(Key, AccSeparator) ->
-            {ok, {NewAcc, _, _}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId}, 
-                {Key, EndDocId}, GroupRowsFun, RespFun, 
-                {AccSeparator, Skip, Limit}),
-            NewAcc % Switch to comma
-        end,
-    "", Keys), % Start with no comma
-    send_chunk(Resp, "]}"),
-    end_json_response(Resp).
+    CurrentEtag = view_group_etag(Group),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
+        {Resp, _} = lists:foldl(
+            fun(Key, {Resp, AccSeparator}) ->
+                FoldAccInit = {Limit, Skip, Resp, AccSeparator},
+                {_, {_, _, Resp2, NewAcc}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId}, 
+                    {Key, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
+                % Switch to comma
+                {Resp2, NewAcc}
+            end,
+        {undefined, []}, Keys), % Start with no comma
+        finish_reduce_fold(Req, Resp)
+    end).
     
-make_reduce_fold_funs(Resp, GroupLevel) ->
+make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
+    #reduce_fold_helper_funs{
+        start_response = StartRespFun,
+        send_row = SendRowFun
+    } = apply_default_helper_funs(HelperFuns),
+
     GroupRowsFun =
         fun({_Key1,_}, {_Key2,_}) when GroupLevel == 0 ->
             true;
@@ -176,31 +191,47 @@
         ({Key1,_}, {Key2,_}) ->
             Key1 == Key2
         end,
-    RespFun = fun(_Key, _Red, {AccSeparator,AccSkip,AccLimit}) when AccSkip > 0 ->
-        {ok, {AccSeparator,AccSkip-1,AccLimit}};
-    (_Key, _Red, {AccSeparator,0,AccLimit}) when AccLimit == 0 ->
-        {stop, {AccSeparator,0,AccLimit}};
-    (_Key, Red, {AccSeparator,0,AccLimit}) when GroupLevel == 0 ->
-        Json = ?JSON_ENCODE({[{key, null}, {value, Red}]}),
-        send_chunk(Resp, AccSeparator ++ Json),
-        {ok, {",",0,AccLimit-1}};
-    (Key, Red, {AccSeparator,0,AccLimit})
+    RespFun = fun(_Key, _Red, {AccLimit, AccSkip, Resp, AccSeparator}) when AccSkip > 0 ->
+        {ok, {AccLimit, AccSkip - 1, Resp, AccSeparator}};
+    (_Key, _Red, {0, 0, Resp, AccSeparator}) ->
+        {stop, {0, 0, Resp, AccSeparator}};
+    (_Key, Red, {AccLimit, 0, Resp, AccSeparator}) when GroupLevel == 0 ->
+        {ok, Resp2, RowSep} = case Resp of
+        undefined -> StartRespFun(Req, Etag, null, null);
+        _ -> {ok, Resp, nil}
+        end,
+        RowResult = case SendRowFun(Resp2, {null, Red}, RowSep) of
+        stop -> stop;
+        _ -> ok
+        end,
+        {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}};
+    (Key, Red, {AccLimit, 0, Resp, AccSeparator})
             when is_integer(GroupLevel) 
             andalso is_list(Key) ->
-        Json = ?JSON_ENCODE(
-            {[{key, lists:sublist(Key, GroupLevel)},{value, Red}]}),
-        send_chunk(Resp, AccSeparator ++ Json),
-        {ok, {",",0,AccLimit-1}};
-    (Key, Red, {AccSeparator,0,AccLimit}) ->
-        Json = ?JSON_ENCODE({[{key, Key}, {value, Red}]}),
-        send_chunk(Resp, AccSeparator ++ Json),
-        {ok, {",",0,AccLimit-1}}
+        {ok, Resp2, RowSep} = case Resp of
+        undefined -> StartRespFun(Req, Etag, null, null);
+        _ -> {ok, Resp, nil}
+        end,
+        RowResult = case SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowSep) of
+        stop -> stop;
+        _ -> ok
+        end,
+        {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}};
+    (Key, Red, {AccLimit, 0, Resp, AccSeparator}) ->
+        {ok, Resp2, RowSep} = case Resp of
+        undefined -> StartRespFun(Req, Etag, null, null);
+        _ -> {ok, Resp, nil}
+        end,
+        RowResult = case SendRowFun(Resp2, {Key, Red}, RowSep) of
+        stop -> stop;
+        _ -> ok
+        end,
+        {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}}
     end,
     {ok, GroupRowsFun, RespFun}.
     
 
 
-
 reverse_key_default(nil) -> {};
 reverse_key_default({}) -> nil;
 reverse_key_default(Key) -> Key.
@@ -210,6 +241,8 @@
 parse_view_query(Req, Keys) ->
     parse_view_query(Req, Keys, nil).
 parse_view_query(Req, Keys, IsReduce) ->
+    parse_view_query(Req, Keys, IsReduce, false).
+parse_view_query(Req, Keys, IsReduce, IgnoreExtra) ->
     QueryList = couch_httpd:qs(Req),
     #view_query_args{
         group_level = GroupLevel
@@ -328,10 +361,18 @@
                 Msg1 = "Bad URL query value for 'include_docs' expected \"true\" or \"false\".",
                 throw({query_parse_error, Msg1})
             end;
+        {"format", _} ->
+            % we just ignore format, so that JS can have it
+            Args;
         _ -> % unknown key
-            Msg = lists:flatten(io_lib:format(
-                "Bad URL query key:~s", [Key])),
-            throw({query_parse_error, Msg})
+            case IgnoreExtra of
+            true ->
+                Args;
+            false ->
+                Msg = lists:flatten(io_lib:format(
+                    "Bad URL query key:~s", [Key])),
+                throw({query_parse_error, Msg})
+            end
         end
     end, #view_query_args{}, QueryList),
     case IsReduce of
@@ -371,7 +412,7 @@
         end
     end.
 
-make_view_fold_fun(Req, QueryArgs, Db,
+make_view_fold_fun(Req, QueryArgs, Etag, Db,
     TotalViewCount, HelperFuns) ->
     #view_query_args{
         end_key = EndKey,
@@ -404,15 +445,19 @@
             {ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
         {_, _, _, undefined} ->
             Offset = ReduceCountFun(OffsetReds),
-            {ok, Resp2, BeginBody} = StartRespFun(Req, 200, 
+            {ok, Resp2, BeginBody} = StartRespFun(Req, Etag,
                 TotalViewCount, Offset),
-            SendRowFun(Resp2, Db, 
-                {{Key, DocId}, Value}, BeginBody, IncludeDocs),
-            {ok, {AccLimit - 1, 0, Resp2, AccRevRows}};
+            case SendRowFun(Resp2, Db, 
+                {{Key, DocId}, Value}, BeginBody, IncludeDocs) of
+            stop ->  {stop, {AccLimit - 1, 0, Resp2, AccRevRows}};
+            _ -> {ok, {AccLimit - 1, 0, Resp2, AccRevRows}}
+            end;
         {_, AccLimit, _, Resp} when (AccLimit > 0) ->
-            SendRowFun(Resp, Db, 
-                {{Key, DocId}, Value}, nil, IncludeDocs),
-            {ok, {AccLimit - 1, 0, Resp, AccRevRows}}
+            case SendRowFun(Resp, Db, 
+                {{Key, DocId}, Value}, nil, IncludeDocs) of
+            stop ->  {stop, {AccLimit - 1, 0, Resp, AccRevRows}};
+            _ -> {ok, {AccLimit - 1, 0, Resp, AccRevRows}}
+            end
         end
     end.
 
@@ -442,6 +487,25 @@
         send_row = SendRow2
     }.
 
+apply_default_helper_funs(#reduce_fold_helper_funs{
+    start_response = StartResp,
+    send_row = SendRow
+}=Helpers) ->
+    StartResp2 = case StartResp of
+    undefined -> fun json_reduce_start_resp/4;
+    _ -> StartResp
+    end,
+
+    SendRow2 = case SendRow of
+    undefined -> fun send_json_reduce_row/3;
+    _ -> SendRow
+    end,
+
+    Helpers#reduce_fold_helper_funs{
+        start_response = StartResp2,
+        send_row = SendRow2
+    }.
+
 make_passed_end_fun(Dir, EndKey, EndDocId) ->
     case Dir of
     fwd ->
@@ -454,8 +518,8 @@
         end
     end.
 
-json_view_start_resp(Req, Code, TotalViewCount, Offset) ->
-    {ok, Resp} = couch_httpd:start_json_response(Req, Code),
+json_view_start_resp(Req, Etag, TotalViewCount, Offset) ->
+    {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
     BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
             [TotalViewCount, Offset]),
     {ok, Resp, BeginBody}.
@@ -467,7 +531,28 @@
     _ -> RowFront
     end,
     send_chunk(Resp, RowFront2 ++  ?JSON_ENCODE(JsonObj)).
+
+json_reduce_start_resp(Req, Etag, _, _) ->
+    {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
+    BeginBody = "{\"rows\":[\r\n",
+    {ok, Resp, BeginBody}.
+
+send_json_reduce_row(Resp, {Key, Value}, RowFront) ->
+    RowFront2 = case RowFront of
+    nil -> ",\r\n";
+    _ -> RowFront
+    end,
+    send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})).
+
+view_group_etag(Group) ->
+    view_group_etag(Group, nil).
     
+view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, Extra) ->
+    % This is not as granular as it could be.
+    % If there are updates to the db that do not effect the view index,
+    % they will change the Etag. For more granular Etags we'd need to keep 
+    % track of the last Db seq that caused an index change.
+    couch_httpd:make_etag({Sig, CurrentSeq, Extra}).
 
 view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
     case DocId of
@@ -520,3 +605,14 @@
     Error ->
         throw(Error)
     end.
+
+finish_reduce_fold(Req, Resp) ->
+    case Resp of
+    undefined ->
+        send_json(Req, 200, {[
+            {rows, []}
+        ]});
+    Resp ->
+        send_chunk(Resp, "\r\n]}"),
+        end_json_response(Resp)
+    end.

Modified: couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl Thu Mar  5 06:14:36 2009
@@ -18,7 +18,9 @@
 -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]).
 -export([start_doc_map/2, map_docs/2, stop_doc_map/1]).
 -export([reduce/3, rereduce/3,validate_doc_update/5]).
--export([render_doc_show/5,start_view_list/2,render_list_head/5, render_list_row/4, render_list_tail/3]).
+-export([render_doc_show/6,start_view_list/2,render_list_head/5, 
+        render_list_row/4, render_list_tail/3, render_reduce_head/3, 
+        render_reduce_row/4]).
 % -export([test/0]).
 
 -include("couch_db.hrl").
@@ -122,14 +124,18 @@
     after
         ok = ret_os_process(Lang, Pid)
     end.
+append_docid(DocId, JsonReqIn) ->
+    [{<<"docId">>, DocId} | JsonReqIn].
 
-render_doc_show(Lang, ShowSrc, Doc, Req, Db) ->
+render_doc_show(Lang, ShowSrc, DocId, Doc, Req, Db) ->
     Pid = get_os_process(Lang),
-    JsonDoc = case Doc of
-        nil -> null;
-        _ -> couch_doc:to_json_obj(Doc, [revs])
+    {JsonReqIn} = couch_httpd_external:json_req_obj(Req, Db),
+
+    {JsonReq, JsonDoc} = case {DocId, Doc} of
+        {nil, nil} -> {{JsonReqIn}, null};
+        {DocId, nil} -> {{append_docid(DocId, JsonReqIn)}, null};
+        _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])}
     end,
-    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
     try couch_os_process:prompt(Pid, 
         [<<"show_doc">>, ShowSrc, JsonDoc, JsonReq]) of
     FormResp ->
@@ -160,6 +166,16 @@
     JsonResp.
     
     
+render_reduce_head({_Lang, Pid}, Req, Db) ->
+    Head = {[]},
+    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+    couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
+
+render_reduce_row({_Lang, Pid}, Req, Db, {Key, Value}) ->
+    JsonRow = {[{key, Key}, {value, Value}]},
+    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+    couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow, JsonReq]).
+
 
 init([]) ->
     

Modified: couchdb/branches/rep_security/src/couchdb/couch_rep.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_rep.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_rep.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_rep.erl Thu Mar  5 06:14:36 2009
@@ -203,10 +203,12 @@
     _ ->
         iolist_to_binary(?JSON_ENCODE(JsonBody))
     end,
-    Options = [
+    Options = case Action of
+        get -> [];
+        _ -> [{transfer_encoding, {chunked, 65535}}]
+    end ++ [
         {content_type, "application/json; charset=utf-8"},
-        {max_pipeline_size, 101},
-        {transfer_encoding, {chunked, 65535}}
+        {max_pipeline_size, 101}
     ],
     case ibrowse:send_req(Url, Headers, Action, Body, Options) of
     {ok, Status, ResponseHeaders, ResponseBody} ->

Modified: couchdb/branches/rep_security/src/couchdb/couch_server.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_server.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_server.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_server.erl Thu Mar  5 06:14:36 2009
@@ -182,7 +182,9 @@
 maybe_close_lru_db(#server{dbs_open=NumOpen}=Server) ->
     % must free up the lru db.
     case try_close_lru(now()) of
-    ok -> {ok, Server#server{dbs_open=NumOpen-1}};
+    ok -> 
+        couch_stats_collector:decrement({couchdb, open_databases}),
+        {ok, Server#server{dbs_open=NumOpen - 1}};
     Error -> Error
     end.
 
@@ -195,15 +197,15 @@
     true ->
         [{_, DbName}] = ets:lookup(couch_dbs_by_lru, LruTime),
         [{_, {MainPid, LruTime}}] = ets:lookup(couch_dbs_by_name, DbName),
-        case couch_db:num_refs(MainPid) of
-        0 ->
+        case couch_db:is_idle(MainPid) of
+        true ->
             exit(MainPid, kill),
             receive {'EXIT', MainPid, _Reason} -> ok end,
             true = ets:delete(couch_dbs_by_lru, LruTime),
             true = ets:delete(couch_dbs_by_name, DbName),
             true = ets:delete(couch_dbs_by_pid, MainPid),
             ok;
-        _NumRefs ->
+        false ->
             % this still has referrers. Go ahead and give it a current lru time
             % and try the next one in the table.
             NewLruTime = now(),
@@ -235,6 +237,7 @@
                     true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
                     true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
                     DbsOpen = Server2#server.dbs_open + 1,
+                    couch_stats_collector:increment({couchdb, open_databases}),
                     {reply, {ok, MainPid},
                             Server2#server{dbs_open=DbsOpen}};
                 Error ->
@@ -270,6 +273,7 @@
                     true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
                     true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
                     DbsOpen = Server2#server.dbs_open + 1,
+                    couch_stats_collector:increment({couchdb, open_databases}),
                     couch_db_update_notifier:notify({created, DbName}),
                     {reply, {ok, MainPid},
                             Server2#server{dbs_open=DbsOpen}};
@@ -299,6 +303,7 @@
             true = ets:delete(couch_dbs_by_name, DbName),
             true = ets:delete(couch_dbs_by_pid, Pid),
             true = ets:delete(couch_dbs_by_lru, LruTime),
+            couch_stats_collector:decrement({couchdb, open_databases}),
             Server#server{dbs_open=Server#server.dbs_open - 1}
         end,
         case file:delete(FullFilepath) of
@@ -328,6 +333,7 @@
     true = ets:delete(couch_dbs_by_pid, Pid),
     true = ets:delete(couch_dbs_by_name, DbName),
     true = ets:delete(couch_dbs_by_lru, LruTime),
-    {noreply, Server#server{dbs_open=DbsOpen-1}};
+    couch_stats_collector:decrement({couchdb, open_databases}),
+    {noreply, Server#server{dbs_open=DbsOpen - 1}};
 handle_info(Info, _Server) ->
     exit({unknown_message, Info}).

Modified: couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl Thu Mar  5 06:14:36 2009
@@ -145,12 +145,6 @@
                 brutal_kill,
                 supervisor,
                 [couch_server]},
-            {couch_file_stats,
-                {couch_file_stats, start_link, []},
-                permanent,
-                brutal_kill,
-                supervisor,
-                [couch_file_stats]},
             {couch_db_update_event,
                 {gen_event, start_link, [{local, couch_db_update}]},
                 permanent,
@@ -158,7 +152,6 @@
                 supervisor,
                 dynamic}]}).
 
-
 start_secondary_services() ->
     DaemonChildSpecs = [
         begin
@@ -173,7 +166,7 @@
         end
         || {Name, SpecStr}
         <- couch_config:get("daemons"), SpecStr /= ""],
-        
+    
     supervisor:start_link({local, couch_secondary_services}, couch_server_sup,
         {{one_for_one, 10, 3600}, DaemonChildSpecs}).
 

Modified: couchdb/branches/rep_security/src/couchdb/couch_stream.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_stream.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_stream.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_stream.erl Thu Mar  5 06:14:36 2009
@@ -136,6 +136,7 @@
     {reply, {Pos, BytesRemaining}, Stream};
 handle_call({set_min_buffer, MinBuffer}, _From, Stream) ->
     {reply, ok, Stream#write_stream{min_alloc = MinBuffer}};
+% set next_alloc if we need more room
 handle_call({ensure_buffer, BufferSizeRequested}, _From, Stream) ->
     #write_stream{bytes_remaining = BytesRemainingInCurrentBuffer} = Stream,
     case BytesRemainingInCurrentBuffer < BufferSizeRequested of

Modified: couchdb/branches/rep_security/src/couchdb/couch_view.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_view.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_view.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_view.erl Thu Mar  5 06:14:36 2009
@@ -61,14 +61,19 @@
     {ok, Count}.
 
 get_temp_reduce_view(Db, Type, DesignOptions, MapSrc, RedSrc) ->
-    {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
-    {ok, {temp_reduce, View}}.
+    {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
+    {ok, {temp_reduce, View}, Group}.
 
 
 get_reduce_view(Db, GroupId, Name, Update) ->
     case get_group(Db, GroupId, Update) of
-    {ok, #group{views=Views,def_lang=Lang}} ->
-        get_reduce_view0(Name, Lang, Views);
+    {ok, #group{views=Views,def_lang=Lang}=Group} ->
+        case get_reduce_view0(Name, Lang, Views) of
+        {ok, View} ->
+            {ok, View, Group};
+        Else ->
+            Else
+        end;
     Error ->
         Error
     end.
@@ -137,13 +142,18 @@
 
 
 get_temp_map_view(Db, Type, DesignOptions, Src) ->
-    {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, Src, []),
-    {ok, View}.
+    {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, Src, []),
+    {ok, View, Group}.
 
 get_map_view(Db, GroupId, Name, Stale) ->
     case get_group(Db, GroupId, Stale) of
-    {ok, #group{views=Views}} ->
-        get_map_view0(Name, Views);
+    {ok, #group{views=Views}=Group} ->
+        case get_map_view0(Name, Views) of
+        {ok, View} ->
+            {ok, View, Group};
+        Else ->
+            Else
+        end;
     Error ->
         Error
     end.
@@ -194,10 +204,10 @@
 
 init([]) ->
     % read configuration settings and register for configuration changes
-    RootDir = couch_config:get("couchdb", "database_dir"),
+    RootDir = couch_config:get("couchdb", "view_index_dir"),
     Self = self(),
     ok = couch_config:register(
-        fun("couchdb", "database_dir")->
+        fun("couchdb", "view_index_dir")->
             exit(Self, config_change)
         end),
         

Modified: couchdb/branches/rep_security/src/ibrowse/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/ibrowse/Makefile.am?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/ibrowse/Makefile.am (original)
+++ couchdb/branches/rep_security/src/ibrowse/Makefile.am Thu Mar  5 06:14:36 2009
@@ -38,7 +38,8 @@
 
 EXTRA_DIST =  \
     $(ibrowse_file_collection) \
-    $(ibrowseebin_static_file)
+    $(ibrowseebin_static_file) \
+    ibrowse.hrl
 
 CLEANFILES = \
     $(ibrowseebin_make_generated_file_list)

Modified: couchdb/branches/rep_security/src/mochiweb/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/Makefile.am?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/Makefile.am (original)
+++ couchdb/branches/rep_security/src/mochiweb/Makefile.am Thu Mar  5 06:14:36 2009
@@ -10,7 +10,7 @@
 ## License for the specific language governing permissions and limitations under
 ## the License.
 
-mochiwebebindir = $(localerlanglibdir)/mochiweb-r82/ebin
+mochiwebebindir = $(localerlanglibdir)/mochiweb-r97/ebin
 
 mochiweb_file_collection = \
     mochifmt.erl \

Modified: couchdb/branches/rep_security/src/mochiweb/mochijson.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochijson.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochijson.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochijson.erl Thu Mar  5 06:14:36 2009
@@ -478,10 +478,6 @@
     true = equiv(E, decode(encode(E))),
     test_one(Rest, 1+N).
 
-e2j_test_vec(unicode) -> 
-    [
-     {"foo" ++ [500] ++ "bar", [$", $f, $o, $o, 500, $b, $a, $r, $"]}
-    ];
 e2j_test_vec(utf8) ->
     [
     {1, "1"},

Modified: couchdb/branches/rep_security/src/mochiweb/mochijson2.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochijson2.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochijson2.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochijson2.erl Thu Mar  5 06:14:36 2009
@@ -70,7 +70,10 @@
 %% @spec decode(iolist()) -> json_term()
 %% @doc Decode the given iolist to Erlang terms.
 decode(S) ->
-    json_decode(S, #decoder{}).
+    try json_decode(S, #decoder{})
+    catch
+        _:_ -> throw({invalid_json, S})
+    end.
 
 test() ->
     test_all().
@@ -129,13 +132,81 @@
     lists:reverse([$\} | Acc1]).
 
 json_encode_string(A, _State) when is_atom(A) ->
-    json_encode_string_unicode(xmerl_ucs:from_utf8(atom_to_list(A)), [?Q]);
+    L = atom_to_list(A),
+    case json_string_is_safe(L) of
+        true ->
+            [?Q, L, ?Q];
+        false ->
+            json_encode_string_unicode(xmerl_ucs:from_utf8(L), [?Q])
+    end;
 json_encode_string(B, _State) when is_binary(B) ->
-    json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q]);
+    case json_bin_is_safe(B) of
+        true ->
+            [?Q, B, ?Q];
+        false ->
+            json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q])
+    end;
 json_encode_string(I, _State) when is_integer(I) ->
-    json_encode_string_unicode(integer_to_list(I), [?Q]);
+    [?Q, integer_to_list(I), ?Q];
 json_encode_string(L, _State) when is_list(L) ->
-    json_encode_string_unicode(L, [?Q]).
+    case json_string_is_safe(L) of
+        true ->
+            [?Q, L, ?Q];
+        false ->
+            json_encode_string_unicode(L, [?Q])
+    end.
+
+json_string_is_safe([]) ->
+    true;
+json_string_is_safe([C | Rest]) ->
+    case C of
+        ?Q ->
+            false;
+        $\\ ->
+            false;
+        $\b ->
+            false;
+        $\f ->
+            false;
+        $\n ->
+            false;
+        $\r ->
+            false;
+        $\t ->
+            false;
+        C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+            false;
+        C when C < 16#7f ->
+            json_string_is_safe(Rest);
+        _ ->
+            false
+    end.
+
+json_bin_is_safe(<<>>) ->
+    true;
+json_bin_is_safe(<<C, Rest/binary>>) ->
+    case C of
+        ?Q ->
+            false;
+        $\\ ->
+            false;
+        $\b ->
+            false;
+        $\f ->
+            false;
+        $\n ->
+            false;
+        $\r ->
+            false;
+        $\t ->
+            false;
+        C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+            false;
+        C when C < 16#7f ->
+            json_bin_is_safe(Rest);
+        _ ->
+            false
+    end.
 
 json_encode_string_unicode([], Acc) ->
     lists:reverse([$\" | Acc]);
@@ -257,8 +328,28 @@
             decode_array(B, S1#decoder{state=any}, Acc)
     end.
 
-tokenize_string(B, S) ->
-    tokenize_string(B, S, []).
+tokenize_string(B, S=#decoder{offset=O}) ->
+    case tokenize_string_fast(B, O) of
+        {escape, O1} ->
+            Length = O1 - O,
+            S1 = ?ADV_COL(S, Length),
+            <<_:O/binary, Head:Length/binary, _/binary>> = B,
+            tokenize_string(B, S1, lists:reverse(binary_to_list(Head)));
+        O1 ->
+            Length = O1 - O,
+            <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B,
+            {{const, String}, ?ADV_COL(S, Length + 1)}
+    end.
+
+tokenize_string_fast(B, O) ->
+    case B of
+        <<_:O/binary, ?Q, _/binary>> ->
+            O;
+        <<_:O/binary, C, _/binary>> when C =/= $\\ ->
+            tokenize_string_fast(B, 1 + O);
+        _ ->
+            {escape, O}
+    end.
 
 tokenize_string(B, S=#decoder{offset=O}, Acc) ->
     case B of

Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb.app
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb.app?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb.app (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb.app Thu Mar  5 06:14:36 2009
@@ -21,7 +21,10 @@
         mochiweb_socket_server,
         mochiweb_sup,
         mochiweb_util,
-        reloader
+        reloader,
+        mochifmt,
+        mochifmt_std,
+        mochifmt_records
 	    ]},
   {registered, []},
   {mod, {mochiweb_app, []}},

Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl Thu Mar  5 06:14:36 2009
@@ -6,7 +6,7 @@
 -module(mochiweb_headers).
 -author('bob@mochimedia.com').
 -export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]).
--export([get_primary_value/2]).
+-export([delete_any/2, get_primary_value/2]).
 -export([default/3, enter_from_list/2, default_from_list/2]).
 -export([to_list/1, make/1]).
 -export([test/0]).
@@ -35,6 +35,8 @@
                         H3),
     "application/x-www-form-urlencoded" = ?MODULE:get_primary_value(
                                              "content-type", H4),
+    H4 = ?MODULE:delete_any("nonexistent-header", H4),
+    H3 = ?MODULE:delete_any("content-type", H4),
     ok.
 
 %% @spec empty() -> headers()
@@ -145,6 +147,12 @@
             gb_trees:update(K1, {K0, V2}, T)
     end.
 
+%% @spec delete_any(key(), headers()) -> headers()
+%% @doc Delete the header corresponding to key if it is present.
+delete_any(K, T) ->
+    K1 = normalize(K),
+    gb_trees:delete_any(K1, T).
+
 %% Internal API
 
 expand({array, L}) ->

Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl Thu Mar  5 06:14:36 2009
@@ -129,7 +129,9 @@
 test() ->
     test_destack(),
     test_tokens(),
+    test_tokens2(),
     test_parse(),
+    test_parse2(),
     test_parse_tokens(),
     test_escape(),
     test_escape_attr(),
@@ -426,6 +428,34 @@
     Expect = parse(D0),
     ok.
 
+test_tokens2() ->
+    D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org</link><description>Bob's Rants</description></channel>">>,
+    Expect = [{start_tag,<<"channel">>,[],false},
+              {start_tag,<<"title">>,[],false},
+              {data,<<"from __future__ import *">>,false},
+              {end_tag,<<"title">>},
+              {start_tag,<<"link">>,[],true},
+              {data,<<"http://bob.pythonmac.org">>,false},
+              {end_tag,<<"link">>},
+              {start_tag,<<"description">>,[],false},
+              {data,<<"Bob's Rants">>,false},
+              {end_tag,<<"description">>},
+              {end_tag,<<"channel">>}],
+    Expect = tokens(D0),
+    ok.
+
+test_parse2() ->
+    D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org<br>foo</link><description>Bob's Rants</description></channel>">>,
+    Expect = {<<"channel">>,[],
+              [{<<"title">>,[],[<<"from __future__ import *">>]},
+               {<<"link">>,[],[
+                               <<"http://bob.pythonmac.org">>,
+                               {<<"br">>,[],[]},
+                               <<"foo">>]},
+               {<<"description">>,[],[<<"Bob's Rants">>]}]},
+    Expect = parse(D0),
+    ok.
+
 test_parse_tokens() ->
     D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]},
           {data,<<"\n">>,true},
@@ -562,8 +592,23 @@
         end,
     case lists:splitwith(F, Stack) of
         {_, []} ->
-            %% No match, no state change
-            Stack;
+            %% If we're parsing something like XML we might find
+            %% a <link>tag</link> that is normally a singleton
+            %% in HTML but isn't here
+            case {is_singleton(TagName), Stack} of
+                {true, [{T0, A0, Acc0} | Post0]} ->
+                    case lists:splitwith(F, Acc0) of
+                        {_, []} ->
+                            %% Actually was a singleton
+                            Stack;
+                        {Pre, [{T1, A1, []} | Post1]} ->
+                            [{T0, A0, [{T1, A1, lists:reverse(Pre)} | Post1]}
+                             | Post0]
+                    end;
+                _ ->
+                    %% No match, no state change
+                    Stack
+            end;
         {_Pre, [_T]} ->
             %% Unfurl the whole stack, we're done
             destack(Stack);

Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl Thu Mar  5 06:14:36 2009
@@ -10,6 +10,7 @@
 
 -define(IDLE_TIMEOUT, 30000).
 
+-define(MAX_HEADERS, 1000).
 -define(DEFAULTS, [{name, ?MODULE},
                    {port, 8888}]).
 
@@ -37,7 +38,7 @@
 
 stop(Name) ->
     mochiweb_socket_server:stop(Name).
-    
+
 start() ->
     start([{ip, "127.0.0.1"},
            {loop, {?MODULE, default_body}}]).
@@ -80,12 +81,12 @@
                                    {body, Req:recv_body()},
                                    Req:dump()]]),
     Req:ok({"text/html", [], frm(Body)});
-default_body(Req, 'POST', _Path) ->    
+default_body(Req, 'POST', _Path) ->
     Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()},
                                    {parse_cookie, Req:parse_cookie()},
                                    {parse_post, Req:parse_post()},
                                    Req:dump()]]),
-    Req:ok({"text/html", [], frm(Body)});           
+    Req:ok({"text/html", [], frm(Body)});
 default_body(Req, _Method, _Path) ->
     Req:respond({501, [], []}).
 
@@ -99,7 +100,7 @@
 request(Socket, Body) ->
     case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
         {ok, {http_request, Method, Path, Version}} ->
-            headers(Socket, {Method, Path, Version}, [], Body);
+            headers(Socket, {Method, Path, Version}, [], Body, 0);
         {error, {http_error, "\r\n"}} ->
             request(Socket, Body);
         {error, {http_error, "\n"}} ->
@@ -109,7 +110,15 @@
             exit(normal)
     end.
 
-headers(Socket, Request, Headers, Body) ->
+headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
+    %% Too many headers sent, bad request.
+    inet:setopts(Socket, [{packet, raw}]),
+    Req = mochiweb:new_request({Socket, Request,
+                                lists:reverse(Headers)}),
+    Req:respond({400, [], []}),
+    gen_tcp:close(Socket),
+    exit(normal);
+headers(Socket, Request, Headers, Body, HeaderCount) ->
     case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
         {ok, http_eoh} ->
             inet:setopts(Socket, [{packet, raw}]),
@@ -125,7 +134,8 @@
                     ?MODULE:loop(Socket, Body)
             end;
         {ok, {http_header, _, Name, _, Value}} ->
-            headers(Socket, Request, [{Name, Value} | Headers], Body);
+            headers(Socket, Request, [{Name, Value} | Headers], Body,
+                    1 + HeaderCount);
         _Other ->
             gen_tcp:close(Socket),
             exit(normal)

Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl Thu Mar  5 06:14:36 2009
@@ -12,7 +12,7 @@
 -define(READ_SIZE, 8192).
 
 -export([get_header_value/1, get_primary_header_value/1, get/1, dump/0]).
--export([send/1, recv/1, recv/2, recv_body/0, recv_body/1]).
+-export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]).
 -export([start_response/1, start_response_length/1, start_raw_response/1]).
 -export([respond/1, ok/1]).
 -export([not_found/0, not_found/1]).
@@ -171,28 +171,54 @@
 %% @doc Receive the body of the HTTP request (defined by Content-Length).
 %%      Will receive up to MaxBody bytes.
 recv_body(MaxBody) ->
+    % we could use a sane constant for max chunk size
+    Body = stream_body(?MAX_RECV_BODY, fun
+        ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> 
+            iolist_to_binary(lists:reverse(BinAcc));
+        ({Length, Bin}, {LengthAcc, BinAcc}) ->
+            NewLength = Length + LengthAcc,
+            if NewLength > MaxBody ->
+                exit({body_too_large, chunked});
+            true -> 
+                {NewLength, [Bin | BinAcc]}
+            end
+        end, {0, []}, MaxBody),
+    put(?SAVE_BODY, Body),
+    Body.
+
+stream_body(MaxChunkSize, ChunkFun, FunState) ->
+    stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
+    
+stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
+
     case get_header_value("expect") of
         "100-continue" ->
             start_raw_response({100, gb_trees:empty()});
         _Else ->
             ok
     end,
-    Body = case body_length() of
-               undefined ->
-                   undefined;
-               {unknown_transfer_encoding, Unknown} ->
-                   exit({unknown_transfer_encoding, Unknown});
-               chunked ->
-                   read_chunked_body(MaxBody, []);
-               0 ->
-                   <<>>;
-               Length when is_integer(Length), Length =< MaxBody ->
-                   recv(Length);
-               Length ->
-                   exit({body_too_large, Length})
-           end,
-    put(?SAVE_BODY, Body),
-    Body.
+    case body_length() of
+        undefined ->
+            undefined;
+        {unknown_transfer_encoding, Unknown} ->
+            exit({unknown_transfer_encoding, Unknown});
+        chunked ->
+            % In this case the MaxBody is actually used to
+            % determine the maximum allowed size of a single
+            % chunk.
+            stream_chunked_body(MaxChunkSize, ChunkFun, FunState);
+        0 ->
+            <<>>;
+        Length when is_integer(Length) ->
+            case MaxBodyLength of
+            MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
+                exit({body_too_large, content_length});
+            _ ->
+                stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState)
+            end;     
+        Length ->
+            exit({length_not_integer, Length})
+    end.
 
 
 %% @spec start_response({integer(), ioheaders()}) -> response()
@@ -408,17 +434,33 @@
             Cached
     end.
 
-read_chunked_body(Max, Acc) ->
+%% @spec stream_chunked_body(integer(), fun(), term()) -> term()
+%% @doc The function is called for each chunk.
+%%      Used internally by read_chunked_body.
+stream_chunked_body(MaxChunkSize, Fun, FunState) ->
     case read_chunk_length() of
         0 ->
-            read_chunk(0),
-            iolist_to_binary(lists:reverse(Acc));
-        Length when Length > Max ->
-            exit({body_too_large, chunked});
+            Fun({0, read_chunk(0)}, FunState);
+        Length when Length > MaxChunkSize ->
+            NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState),
+            stream_chunked_body(MaxChunkSize, Fun, NewState);
         Length ->
-            read_chunked_body(Max - Length, [read_chunk(Length) | Acc])
+            NewState = Fun({Length, read_chunk(Length)}, FunState),
+            stream_chunked_body(MaxChunkSize, Fun, NewState)
     end.
 
+stream_unchunked_body(0, _MaxChunkSize, Fun, FunState) ->
+    Fun({0, <<>>}, FunState);
+stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize ->
+    Bin = recv(MaxChunkSize),
+    NewState = Fun({MaxChunkSize, Bin}, FunState),
+    stream_unchunked_body(Length - MaxChunkSize, MaxChunkSize, Fun, NewState);
+stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) ->
+    Bin = recv(Length),
+    NewState = Fun({Length, Bin}, FunState),
+    stream_unchunked_body(0, MaxChunkSize, Fun, NewState).
+
+
 %% @spec read_chunk_length() -> integer()
 %% @doc Read the length of the next HTTP chunk.
 read_chunk_length() ->
@@ -461,6 +503,14 @@
             exit(normal)
     end.
 
+read_sub_chunks(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize ->
+    Bin = recv(MaxChunkSize),
+    NewState = Fun({size(Bin), Bin}, FunState),
+    read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState);
+
+read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) ->
+    Fun({Length, read_chunk(Length)}, FunState).
+    
 %% @spec serve_file(Path, DocRoot) -> Response
 %% @doc Serve a file relative to DocRoot.
 serve_file(Path, DocRoot) ->

Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl Thu Mar  5 06:14:36 2009
@@ -134,12 +134,16 @@
 revjoin([S | Rest], Separator, Acc) ->
     revjoin(Rest, Separator, [S, Separator | Acc]).
 
-%% @spec quote_plus(atom() | integer() | string()) -> string()
+%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string()
 %% @doc URL safe encoding of the given term.
 quote_plus(Atom) when is_atom(Atom) ->
     quote_plus(atom_to_list(Atom));
 quote_plus(Int) when is_integer(Int) ->
     quote_plus(integer_to_list(Int));
+quote_plus(Binary) when is_binary(Binary) ->
+    quote_plus(binary_to_list(Binary));
+quote_plus(Float) when is_float(Float) ->
+    quote_plus(mochinum:digits(Float));
 quote_plus(String) ->
     quote_plus(String, []).
 
@@ -542,6 +546,7 @@
 test_quote_plus() ->
     "foo" = quote_plus(foo),
     "1" = quote_plus(1),
+    "1.1" = quote_plus(1.1),
     "foo" = quote_plus("foo"),
     "foo+bar" = quote_plus("foo bar"),
     "foo%0A" = quote_plus("foo\n"),

Modified: couchdb/branches/rep_security/test/runner.sh
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/test/runner.sh?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/test/runner.sh (original)
+++ couchdb/branches/rep_security/test/runner.sh Thu Mar  5 06:14:36 2009
@@ -4,4 +4,4 @@
 
 erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()"
 
-cat ../share/www/script/couch.js ../share/www/script/couch_test_runner.js ../share/www/script/couch_tests.js test.js  | ../src/couchdb/couchjs -
+cat ../share/www/script/couch.js ../share/www/script/couch_test_runner.js ../share/www/script/couch_tests.js ../share/www/script/test/* test.js  | ../src/couchdb/couchjs -

Modified: couchdb/branches/rep_security/test/test.js
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/test/test.js?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/test/test.js (original)
+++ couchdb/branches/rep_security/test/test.js Thu Mar  5 06:14:36 2009
@@ -194,14 +194,14 @@
 function runAllTestsConsole() {
   var numTests = 0;
   var debug = false;
-  for (var t in tests) {
+  for (var t in couchTests) {
     p(t);
     if (t == "utf8") {
       p("We skip the utf8 test because it fails due to problems in couch_js.c");
       p("Run the in-browser tests to verify utf8.\n");
     } else {
       numTests += 1;
-      var testFun = tests[t];
+      var testFun = couchTests[t];
       runTestConsole(testFun, debug);      
     }
   }