<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>share/www/script/jsbn.js</filename>
    </added>
    <added>
      <filename>share/www/script/jsbn2.js</filename>
    </added>
    <added>
      <filename>share/www/script/prng4.js</filename>
    </added>
    <added>
      <filename>share/www/script/rng.js</filename>
    </added>
    <added>
      <filename>share/www/script/sha1.js</filename>
    </added>
    <added>
      <filename>share/www/script/srp.js</filename>
    </added>
    <added>
      <filename>share/www/script/test/cookie_auth.js</filename>
    </added>
    <added>
      <filename>src/couchdb/couch_httpd_auth.erl</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -11,9 +11,13 @@ Build and System Integration:
  * Changed `couchdb` script configuration options.
  * Added default.d and local.d configuration directories to load sequence.
  * Updated ownership and permission advice in `README` for better security.
- * The SysV/BSD daemon script now creates the PID directory on each invokation
+ * The SysV/BSD daemon script now creates the PID directory on each invocation
    because PREFIX/var/run might be a temporary filesystem.
 
+HTTP Interface:
+
+ * Added optional cookie-based authentication handler.
+
 Version 0.9.0
 -------------
 </diff>
      <filename>CHANGES</filename>
    </modified>
    <modified>
      <diff>@@ -16,7 +16,7 @@ batch_save_interval = 1000 ; milliseconds after which to save batches
 [httpd]
 port = 5984
 bind_address = 127.0.0.1
-authentication_handler = {couch_httpd, default_authentication_handler}
+authentication_handler = {couch_httpd_auth, default_authentication_handler}
 default_handler = {couch_httpd_db, handle_request}
 WWW-Authenticate = Basic realm=&quot;administrator&quot;
 
@@ -66,6 +66,8 @@ _sleep = {couch_httpd_misc_handlers, handle_sleep_req}
 [httpd_db_handlers]
 _compact = {couch_httpd_db, handle_compact_req}
 _design = {couch_httpd_db, handle_design_req}
+_login = {couch_httpd_auth, handle_login_req}
+_logout = {couch_httpd_auth, handle_logout_req}
 _temp_view = {couch_httpd_view, handle_temp_view_req}
 _changes = {couch_httpd_db, handle_changes_req}
 </diff>
      <filename>etc/couchdb/default.ini.tpl.in</filename>
    </modified>
    <modified>
      <diff>@@ -92,6 +92,12 @@ nobase_dist_localdata_DATA = \
     www/script/jquery.resizer.js \
     www/script/jquery.suggest.js \
     www/script/json2.js \
+    www/script/srp.js \
+    www/script/jsbn.js \
+    www/script/jsbn2.js \
+    www/script/rng.js \
+    www/script/prng4.js \
+    www/script/sha1.js \
     www/script/test/basics.js \
     www/script/test/delayed_commits.js \
     www/script/test/all_docs.js \
@@ -136,6 +142,7 @@ nobase_dist_localdata_DATA = \
     www/script/test/purge.js \
     www/script/test/config.js \
     www/script/test/security_validation.js \
+    www/script/test/cookie_auth.js \
     www/script/test/stats.js \
     www/script/test/changes.js \
     www/style/layout.css</diff>
      <filename>share/Makefile.am</filename>
    </modified>
    <modified>
      <diff>@@ -22,6 +22,12 @@ specific language governing permissions and limitations under the License.
     &lt;script src=&quot;script/jquery.js?1.3.1&quot;&gt;&lt;/script&gt;
     &lt;script src=&quot;script/jquery.cookies.js?0.9.0&quot;&gt;&lt;/script&gt;
     &lt;script src=&quot;script/jquery.couch.js?0.9.0&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;script/jsbn.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;script/jsbn2.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;script/prng4.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;script/rng.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;script/sha1.js?2.1a&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;script/srp.js&quot;&gt;&lt;/script&gt;
     &lt;script src=&quot;script/couch.js?0.9.0&quot;&gt;&lt;/script&gt;
     &lt;script src=&quot;script/futon.js?0.9.0&quot;&gt;&lt;/script&gt;
     &lt;script src=&quot;script/couch_test_runner.js&quot;&gt;&lt;/script&gt;</diff>
      <filename>share/www/couch_tests.html</filename>
    </modified>
    <modified>
      <diff>@@ -241,7 +241,25 @@ function CouchDB(name, httpHeaders) {
     CouchDB.maybeThrowError(this.last_req);
     return JSON.parse(this.last_req.responseText);
   }
-  
+
+  this.login = function(username, password) {
+    var _db = this;
+    function req(body, callback) {
+      _db.last_req = _db.request(&quot;POST&quot;, _db.uri + &quot;_login&quot;, {
+        body: body
+      });
+      return callback(JSON.parse(_db.last_req.responseText));
+    }
+    var data = {};
+    authSRP(req, username, password, function(_data) { data = _data; });
+    return data;
+  }
+
+  this.logout = function(username, password) {
+    this.last_req = this.request(&quot;POST&quot;, this.uri + &quot;_logout&quot;, {});
+    return JSON.parse(this.last_req.responseText);
+  }
+
   // Convert a options object to an url query string.
   // ex: {key:'value',key2:'value2'} becomes '?key=&quot;value&quot;&amp;key2=&quot;value2&quot;'
   function encodeOptions(options) {
@@ -395,4 +413,4 @@ CouchDB.params = function(options) {
     returnArray.push(key + &quot;=&quot; + value);
   }
   return returnArray.join(&quot;&amp;&quot;);
-}
\ No newline at end of file
+}</diff>
      <filename>share/www/script/couch.js</filename>
    </modified>
    <modified>
      <diff>@@ -73,6 +73,7 @@ loadTest(&quot;purge.js&quot;);
 loadTest(&quot;config.js&quot;);
 loadTest(&quot;form_submit.js&quot;);
 loadTest(&quot;security_validation.js&quot;);
+loadTest(&quot;cookie_auth.js&quot;);
 loadTest(&quot;stats.js&quot;);
 loadTest(&quot;rev_stemming.js&quot;);
 </diff>
      <filename>share/www/script/couch_tests.js</filename>
    </modified>
    <modified>
      <diff>@@ -82,6 +82,41 @@
       });
     },
 
+    // TODO make login/logout and db.login/db.logout DRY
+    login: function(options) {
+      options = options || {};
+      $.ajax({
+        type: &quot;POST&quot;, url: this.uri + &quot;_login&quot;, dataType: &quot;json&quot;,
+        data: {username: options.username, password: options.password},
+        complete: function(req) {
+          var resp = $.httpData(req, &quot;json&quot;);
+          if (req.status == 200) {
+            if (options.success) options.success(resp);
+          } else if (options.error) {
+            options.error(req.status, resp.error, resp.reason);
+          } else {
+            alert(&quot;An error occurred logging in: &quot; + resp.reason);
+          }
+        }
+      });
+    },
+    logout: function(options) {
+      options = options || {};
+      $.ajax({
+        type: &quot;POST&quot;, url: this.uri + &quot;_logout&quot;, dataType: &quot;json&quot;,
+        complete: function(req) {
+          var resp = $.httpData(req, &quot;json&quot;);
+          if (req.status == 200) {
+            if (options.success) options.success(resp);
+          } else if (options.error) {
+            options.error(req.status, resp.error, resp.reason);
+          } else {
+            alert(&quot;An error occurred logging out: &quot; + resp.reason);
+          }
+        }
+      });
+    },
+
     db: function(name) {
       return {
         name: name,
@@ -332,6 +367,39 @@
               }
             }
           });
+        },
+        login: function(options) {
+          options = options || {};
+          $.ajax({
+            type: &quot;POST&quot;, url: this.uri + &quot;_login&quot;, dataType: &quot;json&quot;,
+            data: {username: options.username, password: options.password},
+            complete: function(req) {
+              var resp = $.httpData(req, &quot;json&quot;);
+              if (req.status == 200) {
+                if (options.success) options.success(resp);
+              } else if (options.error) {
+                options.error(req.status, resp.error, resp.reason);
+              } else {
+                alert(&quot;An error occurred logging in: &quot; + resp.reason);
+              }
+            }
+          });
+        },
+        logout: function(options) {
+          options = options || {};
+          $.ajax({
+            type: &quot;POST&quot;, url: this.uri + &quot;_logout&quot;, dataType: &quot;json&quot;,
+            complete: function(req) {
+              var resp = $.httpData(req, &quot;json&quot;);
+              if (req.status == 200) {
+                if (options.success) options.success(resp);
+              } else if (options.error) {
+                options.error(req.status, resp.error, resp.reason);
+              } else {
+                alert(&quot;An error occurred logging out: &quot; + resp.reason);
+              }
+            }
+          });
         }
       };
     },</diff>
      <filename>share/www/script/jquery.couch.js</filename>
    </modified>
    <modified>
      <diff>@@ -41,13 +41,13 @@ couchTests.security_validation = function(debug) {
   run_on_modified_server(
     [{section: &quot;httpd&quot;,
       key: &quot;authentication_handler&quot;,
-      value: &quot;{couch_httpd, special_test_authentication_handler}&quot;},
+      value: &quot;{couch_httpd_auth, special_test_authentication_handler}&quot;},
      {section:&quot;httpd&quot;,
       key: &quot;WWW-Authenticate&quot;,
       value:  &quot;X-Couch-Test-Auth&quot;}],
   
     function () {
-      // try saving document usin the wrong credentials
+      // try saving document using the wrong credentials
       var wrongPasswordDb = new CouchDB(&quot;test_suite_db&quot;,
         {&quot;WWW-Authenticate&quot;: &quot;X-Couch-Test-Auth Damien Katz:foo&quot;}
       );</diff>
      <filename>share/www/script/test/security_validation.js</filename>
    </modified>
    <modified>
      <diff>@@ -318,7 +318,7 @@ couchTests.show_documents = function(debug) {
   var doc2 = {_id:&quot;foo&quot;, a:2};
   db.save(doc1);
 
-  //create the conflict with a all_or_nothing bulk docs request
+  // create the conflict with an all_or_nothing bulk docs request
   var docs = [doc2];
   db.bulkSave(docs, {all_or_nothing:true});
 </diff>
      <filename>share/www/script/test/show_documents.js</filename>
    </modified>
    <modified>
      <diff>@@ -55,6 +55,7 @@ source_files = \
     couch_file.erl \
     couch_httpd.erl \
     couch_httpd_db.erl \
+    couch_httpd_auth.erl \
     couch_httpd_external.erl \
     couch_httpd_show.erl \
     couch_httpd_view.erl \
@@ -99,6 +100,7 @@ compiled_files = \
     couch_file.beam \
     couch_httpd.beam \
     couch_httpd_db.beam \
+    couch_httpd_auth.beam \
     couch_httpd_external.beam \
     couch_httpd_show.beam \
     couch_httpd_view.beam \</diff>
      <filename>src/couchdb/Makefile.am</filename>
    </modified>
    <modified>
      <diff>@@ -67,7 +67,8 @@
     db_url_handlers,
     user_ctx,
     req_body = undefined,
-    design_url_handlers
+    design_url_handlers,
+    auth
     }).
     
 </diff>
      <filename>src/couchdb/couch_db.hrl</filename>
    </modified>
    <modified>
      <diff>@@ -23,8 +23,8 @@
 -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]).
--export([default_authentication_handler/1,special_test_authentication_handler/1]).
--export([null_authentication_handler/1]).
+
+-import(couch_httpd_auth, [cookie_auth_header/2]).
 
 start_link() -&gt;
     % read config and register for configuration changes
@@ -171,7 +171,7 @@ handle_request(MochiReq, DefaultFun,
 
     {ok, Resp} =
     try
-        HandlerFun(HttpReq#httpd{user_ctx=AuthenticationFun(HttpReq)})
+        HandlerFun(AuthenticationFun(HttpReq))
     catch
         throw:Error -&gt;
             % ?LOG_DEBUG(&quot;Minor error in HTTP request: ~p&quot;,[Error]),
@@ -197,48 +197,6 @@ handle_request(MochiReq, DefaultFun,
 increment_method_stats(Method) -&gt;
     couch_stats_collector:increment({httpd_request_methods, Method}).
 
-special_test_authentication_handler(Req) -&gt;
-    case header_value(Req, &quot;WWW-Authenticate&quot;) of
-    &quot;X-Couch-Test-Auth &quot; ++ NamePass -&gt;
-        % NamePass is a colon separated string: &quot;joe schmoe:a password&quot;.
-        {ok, [Name, Pass]} = regexp:split(NamePass, &quot;:&quot;),
-        case {Name, Pass} of
-        {&quot;Jan Lehnardt&quot;, &quot;apple&quot;} -&gt; ok;
-        {&quot;Christopher Lenz&quot;, &quot;dog food&quot;} -&gt; ok;
-        {&quot;Noah Slater&quot;, &quot;biggiesmalls endian&quot;} -&gt; ok;
-        {&quot;Chris Anderson&quot;, &quot;mp3&quot;} -&gt; ok;
-        {&quot;Damien Katz&quot;, &quot;pecan pie&quot;} -&gt; ok;
-        {_, _} -&gt;
-            throw({unauthorized, &lt;&lt;&quot;Name or password is incorrect.&quot;&gt;&gt;})
-        end,
-        #user_ctx{name=?l2b(Name)};
-    _ -&gt;
-        % No X-Couch-Test-Auth credentials sent, give admin access so the
-        % previous authentication can be restored after the test
-        #user_ctx{roles=[&lt;&lt;&quot;_admin&quot;&gt;&gt;]}
-    end.
-
-default_authentication_handler(Req) -&gt;
-    case basic_username_pw(Req) of
-    {User, Pass} -&gt;
-        case couch_server:is_admin(User, Pass) of
-        true -&gt;
-            #user_ctx{name=?l2b(User), roles=[&lt;&lt;&quot;_admin&quot;&gt;&gt;]};
-        false -&gt;
-            throw({unauthorized, &lt;&lt;&quot;Name or password is incorrect.&quot;&gt;&gt;})
-        end;
-    nil -&gt;
-        case couch_server:has_admins() of
-        true -&gt;
-            #user_ctx{};
-        false -&gt;
-            % if no admins, then everyone is admin! Yay, admin party!
-            #user_ctx{roles=[&lt;&lt;&quot;_admin&quot;&gt;&gt;]}
-        end
-    end.
-
-null_authentication_handler(_Req) -&gt;
-    #user_ctx{roles=[&lt;&lt;&quot;_admin&quot;&gt;&gt;]}.
 
 % Utilities
 
@@ -257,8 +215,9 @@ header_value(#httpd{mochi_req=MochiReq}, Key, Default) -&gt;
 primary_header_value(#httpd{mochi_req=MochiReq}, Key) -&gt;
     MochiReq:get_primary_header_value(Key).
     
-serve_file(#httpd{mochi_req=MochiReq}, RelativePath, DocumentRoot) -&gt;
-    {ok, MochiReq:serve_file(RelativePath, DocumentRoot, server_header())}.
+serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot) -&gt;
+    {ok, MochiReq:serve_file(RelativePath, DocumentRoot,
+        server_header() ++ cookie_auth_header(Req, []))}.
 
 qs_value(Req, Key) -&gt;
     qs_value(Req, Key, undefined).
@@ -349,37 +308,21 @@ verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) -&gt;
 
 
 
-basic_username_pw(Req) -&gt;
-    case header_value(Req, &quot;Authorization&quot;) of
-    &quot;Basic &quot; ++ Base64Value -&gt;
-        case string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),&quot;:&quot;) of
-        [User, Pass] -&gt;
-            {User, Pass};
-        [User] -&gt;
-            {User, &quot;&quot;};
-        _ -&gt;
-            nil
-        end;
-    _ -&gt;
-        nil
-    end.
-
-
-start_chunked_response(#httpd{mochi_req=MochiReq}, Code, Headers) -&gt;
+start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) -&gt;
     couch_stats_collector:increment({httpd_status_codes, Code}),
-    {ok, MochiReq:respond({Code, Headers ++ server_header(), chunked})}.
+    {ok, MochiReq:respond({Code, Headers ++ server_header() ++ cookie_auth_header(Req, Headers), chunked})}.
 
 send_chunk(Resp, Data) -&gt;
     Resp:write_chunk(Data),
     {ok, Resp}.
 
-send_response(#httpd{mochi_req=MochiReq}, Code, Headers, Body) -&gt;
+send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) -&gt;
     couch_stats_collector:increment({httpd_status_codes, Code}),
     if Code &gt;= 400 -&gt;
         ?LOG_DEBUG(&quot;httpd ~p error response:~n ~s&quot;, [Code, Body]);
     true -&gt; ok
     end,
-    {ok, MochiReq:respond({Code, Headers ++ server_header(), Body})}.
+    {ok, MochiReq:respond({Code, Headers ++ server_header() ++ cookie_auth_header(Req, Headers), Body})}.
 
 send_method_not_allowed(Req, Methods) -&gt;
     send_response(Req, 405, [{&quot;Allow&quot;, Methods}], &lt;&lt;&gt;&gt;).
@@ -500,12 +443,17 @@ error_info(Error) -&gt;
 send_error(_Req, {already_sent, Resp, _Error}) -&gt;
     {ok, Resp};
     
-send_error(Req, Error) -&gt;
+send_error(#httpd{mochi_req=MochiReq}=Req, Error) -&gt;
     {Code, ErrorStr, ReasonStr} = error_info(Error),
     if Code == 401 -&gt;     
-        case couch_config:get(&quot;httpd&quot;, &quot;WWW-Authenticate&quot;, nil) of
-        nil -&gt;
-            Headers = [];
+        case MochiReq:get_header_value(&quot;X-CouchDB-WWW-Authenticate&quot;) of
+        undefined -&gt;
+            case couch_config:get(&quot;httpd&quot;, &quot;WWW-Authenticate&quot;, nil) of
+            nil -&gt;
+                Headers = [];
+            Type -&gt;
+                Headers = [{&quot;WWW-Authenticate&quot;, Type}]
+            end;
         Type -&gt;
             Headers = [{&quot;WWW-Authenticate&quot;, Type}]
         end;</diff>
      <filename>src/couchdb/couch_httpd.erl</filename>
    </modified>
    <modified>
      <diff>@@ -57,7 +57,8 @@ process_external_req(HttpReq, Db, Name) -&gt;
 json_req_obj(#httpd{mochi_req=Req, 
                method=Verb,
                path_parts=Path,
-               req_body=ReqBody
+               req_body=ReqBody,
+               user_ctx=#user_ctx{name=UserName, roles=UserRoles}
             }, Db) -&gt;
     Body = case ReqBody of
         undefined -&gt; Req:recv_body();
@@ -69,6 +70,10 @@ json_req_obj(#httpd{mochi_req=Req,
         _ -&gt;
             []
     end,
+    UserCtx = case UserName of
+        null -&gt; {[{&lt;&lt;&quot;roles&quot;&gt;&gt;, UserRoles}]};
+        _Else -&gt; {[{&lt;&lt;&quot;name&quot;&gt;&gt;, UserName}, {&lt;&lt;&quot;roles&quot;&gt;&gt;, UserRoles}]}
+    end,
     Headers = Req:get(headers),
     Hlist = mochiweb_headers:to_list(Headers),
     {ok, Info} = couch_db:get_db_info(Db),
@@ -80,7 +85,8 @@ json_req_obj(#httpd{mochi_req=Req,
         {&lt;&lt;&quot;headers&quot;&gt;&gt;, to_json_terms(Hlist)},
         {&lt;&lt;&quot;body&quot;&gt;&gt;, Body},
         {&lt;&lt;&quot;form&quot;&gt;&gt;, to_json_terms(ParsedForm)},
-        {&lt;&lt;&quot;cookie&quot;&gt;&gt;, to_json_terms(Req:parse_cookie())}]}.
+        {&lt;&lt;&quot;cookie&quot;&gt;&gt;, to_json_terms(Req:parse_cookie())},
+        {&lt;&lt;&quot;userCtx&quot;&gt;&gt;, UserCtx}]}.
 
 to_json_terms(Data) -&gt;
     to_json_terms(Data, []).</diff>
      <filename>src/couchdb/couch_httpd_external.erl</filename>
    </modified>
    <modified>
      <diff>@@ -38,7 +38,6 @@ handle_doc_show_req(#httpd{
     send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db);
 
 handle_doc_show_req(#httpd{
-        method='GET',
         path_parts=[_DbName, _Design, DesignName, _Show, ShowName]
     }=Req, Db) -&gt;
     DesignId = &lt;&lt;&quot;_design/&quot;, DesignName/binary&gt;&gt;,
@@ -51,7 +50,7 @@ handle_doc_show_req(#httpd{method='GET'}=Req, _Db) -&gt;
     send_error(Req, 404, &lt;&lt;&quot;show_error&quot;&gt;&gt;, &lt;&lt;&quot;Invalid path.&quot;&gt;&gt;);
 
 handle_doc_show_req(Req, _Db) -&gt;
-    send_method_not_allowed(Req, &quot;GET,HEAD&quot;).
+    send_method_not_allowed(Req, &quot;GET,POST,HEAD&quot;).
 
 handle_view_list_req(#httpd{method='GET',
         path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) -&gt;
@@ -159,7 +158,7 @@ make_map_send_row_fun(QueryServer, Req) -&gt;
         end
     end.
 
-output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) -&gt;
+output_map_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) -&gt;
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -172,7 +171,7 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
     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}),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept, UserCtx}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -&gt;
         % get the os process here
         % pass it into the view fold with closures
@@ -192,7 +191,7 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
         finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
     end);
 
-output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) -&gt;
+output_map_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) -&gt;
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -203,7 +202,7 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
     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}),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept, UserCtx}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -&gt;
         % get the os process here
         % pass it into the view fold with closures
@@ -271,7 +270,7 @@ make_reduce_send_row_fun(QueryServer, Req, Db) -&gt;
         end
     end.
 
-output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) -&gt;
+output_reduce_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) -&gt;
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -288,7 +287,7 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
     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}),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept, UserCtx}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -&gt;
         StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
         SendListRowFun = make_reduce_send_row_fun(QueryServer, Req, Db),
@@ -306,7 +305,7 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
         finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
     end);
 
-output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) -&gt;
+output_reduce_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) -&gt;
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -321,7 +320,7 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
     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, Keys}),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept, UserCtx, Keys}),
 
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -&gt;
         StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
@@ -366,12 +365,12 @@ render_head_for_empty_list(StartListRespFun, Req, Etag, null) -&gt;
 render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows) -&gt;
     StartListRespFun(Req, Etag, TotalRows, null, []).
     
-send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db) -&gt;
+send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Db) -&gt;
     % compute etag with no doc
     Headers = MReq:get(headers),
     Hlist = mochiweb_headers:to_list(Headers),
     Accept = proplists:get_value('Accept', Hlist),
-    CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept}),
+    CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept, UserCtx}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -&gt; 
         ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, 
             DocId, nil, Req, Db),
@@ -379,12 +378,12 @@ send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db
         couch_httpd_external:send_external_response(Req, JsonResp)
     end);
 
-send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq}=Req, Db) -&gt;
+send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Db) -&gt;
     % calculate the etag
     Headers = MReq:get(headers),
     Hlist = mochiweb_headers:to_list(Headers),
     Accept = proplists:get_value('Accept', Hlist),
-    CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, Revs, Accept}),
+    CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, Revs, Accept, UserCtx}),
     % We know our etag now    
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -&gt; 
         ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, </diff>
      <filename>src/couchdb/couch_httpd_show.erl</filename>
    </modified>
    <modified>
      <diff>@@ -16,7 +16,8 @@
 -export([should_flush/0, should_flush/1, to_existing_atom/1, to_binary/1]).
 -export([new_uuid/0, rand32/0, implode/2, collate/2, collate/3]).
 -export([abs_pathname/1,abs_pathname/2, trim/1, ascii_lower/1]).
--export([encodeBase64/1, decodeBase64/1, to_hex/1,parse_term/1,dict_find/3]).
+-export([encodeBase64/1, decodeBase64/1, encodeBase64Url/1, decodeBase64Url/1,
+    to_hex/1,parse_term/1, dict_find/3]).
 -export([file_read_size/1]).
 
 -include(&quot;couch_db.hrl&quot;).
@@ -255,6 +256,23 @@ encodeBase64(&lt;&lt;B:1/binary&gt;&gt;, Acc) -&gt;
 encodeBase64(&lt;&lt;&gt;&gt;, Acc) -&gt;
     Acc.
 
+encodeBase64Url(Bs) when list(Bs) -&gt;
+    encodeBase64Url(list_to_binary(Bs), &lt;&lt;&gt;&gt;);
+encodeBase64Url(Bs) -&gt;
+    encodeBase64Url(Bs, &lt;&lt;&gt;&gt;).
+    
+encodeBase64Url(&lt;&lt;B:3/binary, Bs/binary&gt;&gt;, Acc) -&gt;
+    &lt;&lt;C1:6, C2:6, C3:6, C4:6&gt;&gt; = B,
+    encodeBase64Url(Bs, &lt;&lt;Acc/binary, (encUrl(C1)), (encUrl(C2)), (encUrl(C3)), (encUrl(C4))&gt;&gt;);
+encodeBase64Url(&lt;&lt;B:2/binary&gt;&gt;, Acc) -&gt;
+    &lt;&lt;C1:6, C2:6, C3:6, _:6&gt;&gt; = &lt;&lt;B/binary, 0&gt;&gt;,
+    &lt;&lt;Acc/binary, (encUrl(C1)), (encUrl(C2)), (encUrl(C3))&gt;&gt;;
+encodeBase64Url(&lt;&lt;B:1/binary&gt;&gt;, Acc) -&gt;
+    &lt;&lt;C1:6, C2:6, _:12&gt;&gt; = &lt;&lt;B/binary, 0, 0&gt;&gt;,
+    &lt;&lt;Acc/binary, (encUrl(C1)), (encUrl(C2))&gt;&gt;;
+encodeBase64Url(&lt;&lt;&gt;&gt;, Acc) -&gt;
+    Acc.
+
 %%
 %% decodeBase64(BinaryChars) -&gt; Binary
 %%
@@ -275,6 +293,23 @@ decode1(&lt;&lt;C1, C2, C3, C4, Cs/binary&gt;&gt;, Acc) -&gt;
 decode1(&lt;&lt;&gt;&gt;, Acc) -&gt;
     Acc.
 
+decodeBase64Url(Cs) when is_list(Cs)-&gt;
+    decodeBase64Url(list_to_binary(Cs));
+decodeBase64Url(Cs) -&gt;
+    decode1Url(Cs, &lt;&lt;&gt;&gt;).
+
+decode1Url(&lt;&lt;C1, C2&gt;&gt;, Acc) -&gt;
+    &lt;&lt;B1, _:16&gt;&gt; = &lt;&lt;(decUrl(C1)):6, (decUrl(C2)):6, 0:12&gt;&gt;,
+    &lt;&lt;Acc/binary, B1&gt;&gt;;
+decode1Url(&lt;&lt;C1, C2, C3&gt;&gt;, Acc) -&gt;
+    &lt;&lt;B1, B2, _:8&gt;&gt; = &lt;&lt;(decUrl(C1)):6, (decUrl(C2)):6, (decUrl(C3)):6, (decUrl(0)):6&gt;&gt;,
+    &lt;&lt;Acc/binary, B1, B2&gt;&gt;;
+decode1Url(&lt;&lt;C1, C2, C3, C4, Cs/binary&gt;&gt;, Acc) -&gt;
+    Bin = &lt;&lt;Acc/binary, (decUrl(C1)):6, (decUrl(C2)):6, (decUrl(C3)):6, (decUrl(C4)):6&gt;&gt;,
+    decode1Url(Cs, Bin);
+decode1Url(&lt;&lt;&gt;&gt;, Acc) -&gt;
+    Acc.
+
 %% enc/1 and dec/1
 %%
 %% Mapping: 0-25 -&gt; A-Z, 26-51 -&gt; a-z, 52-61 -&gt; 0-9, 62 -&gt; +, 63 -&gt; /
@@ -285,6 +320,15 @@ enc(C) -&gt;
 dec(C) -&gt;
     62*?st(C,43) + ?st(C,47) + (C-59)*?st(C,48) - 69*?st(C,65) - 6*?st(C,97).
 
+%% encUrl/1 and decUrl/1
+%%
+%% Mapping: 0-25 -&gt; A-Z, 26-51 -&gt; a-z, 52-61 -&gt; 0-9, 62 -&gt; -, 63 -&gt; _
+%%
+encUrl(C) -&gt;
+    65 + C + 6*?st(C,26) - 75*?st(C,52) -13*?st(C,62) + 49*?st(C,63).
+
+decUrl(C) -&gt;
+    62*?st(C,45) + (C-58)*?st(C,48) - 69*?st(C,65) + 33*?st(C,95) - 39*?st(C,97).
 
 dict_find(Key, Dict, DefaultValue) -&gt;
     case dict:find(Key, Dict) of</diff>
      <filename>src/couchdb/couch_util.erl</filename>
    </modified>
    <modified>
      <diff>@@ -32,7 +32,7 @@ cookie(Key, Value) -&gt;
 %% @spec cookie(Key::string(), Value::string(), Options::[Option]) -&gt; header() 
 %% where Option = {max_age, integer()} | {local_time, {date(), time()}} 
 %%                | {domain, string()} | {path, string()}
-%%                | {secure, true | false}
+%%                | {secure, true | false} | {http_only, true | false}
 %%
 %% @doc Generate a Set-Cookie header field tuple.
 cookie(Key, Value, Options) -&gt;
@@ -83,7 +83,14 @@ cookie(Key, Value, Options) -&gt;
             Path -&gt;
                 [&quot;; Path=&quot;, quote(Path)]
         end,
-    CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart],
+    HttpOnlyPart =
+        case proplists:get_value(http_only, Options) of
+            true -&gt;
+                &quot;; HttpOnly&quot;;
+            _ -&gt;
+                &quot;&quot;
+        end,
+    CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart, HttpOnlyPart],
     {&quot;Set-Cookie&quot;, lists:flatten(CookieParts)}.
 
 </diff>
      <filename>src/mochiweb/mochiweb_cookies.erl</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>1e4460ed33bce939ca96c42dac549f9074c2d91c</id>
    </parent>
  </parents>
  <author>
    <name>Jason Davies</name>
    <login></login>
    <email>jason@jason-daviess-macbook-pro.local</email>
  </author>
  <url>http://github.com/jasondavies/couchdb/commit/6ec463c11750cc45118dba4da9ee51edcc6b3d4a</url>
  <id>6ec463c11750cc45118dba4da9ee51edcc6b3d4a</id>
  <committed-date>2009-05-26T14:03:01-07:00</committed-date>
  <authored-date>2009-05-07T16:50:06-07:00</authored-date>
  <message>Support for cookie-based authentication.

Currently the auth protocol used is SRP, but this will be swapped out
for SCRAM shortly as it is more efficient.</message>
  <tree>b660d2236914b3ca31e54adad620a71c18d0f0da</tree>
  <committer>
    <name>Jason Davies</name>
    <login></login>
    <email>jason@jdd.local</email>
  </committer>
</commit>
