You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2010/06/25 16:18:23 UTC

svn commit: r957969 - in /couchdb/branches/0.11.x: share/www/script/ share/www/script/test/ src/couchdb/

Author: jan
Date: Fri Jun 25 14:18:23 2010
New Revision: 957969

URL: http://svn.apache.org/viewvc?rev=957969&view=rev
Log:
Merge r957422 from trunk:

use JSON content type in replicator, require it in the _bulk_docs and other POST apis

Modified:
    couchdb/branches/0.11.x/share/www/script/couch.js
    couchdb/branches/0.11.x/share/www/script/test/basics.js
    couchdb/branches/0.11.x/share/www/script/test/batch_save.js
    couchdb/branches/0.11.x/share/www/script/test/stats.js
    couchdb/branches/0.11.x/src/couchdb/couch_httpd.erl
    couchdb/branches/0.11.x/src/couchdb/couch_httpd_auth.erl
    couchdb/branches/0.11.x/src/couchdb/couch_httpd_db.erl
    couchdb/branches/0.11.x/src/couchdb/couch_httpd_show.erl
    couchdb/branches/0.11.x/src/couchdb/couch_rep.erl
    couchdb/branches/0.11.x/src/couchdb/couch_rep_writer.erl
    couchdb/branches/0.11.x/src/couchdb/couch_util.erl

Modified: couchdb/branches/0.11.x/share/www/script/couch.js
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/share/www/script/couch.js?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/share/www/script/couch.js [utf-8] (original)
+++ couchdb/branches/0.11.x/share/www/script/couch.js [utf-8] Fri Jun 25 14:18:23 2010
@@ -398,6 +398,8 @@ CouchDB.newXhr = function() {
 
 CouchDB.request = function(method, uri, options) {
   options = options || {};
+  options.headers = options.headers || {};
+  options.headers["Content-Type"] = options.headers["Content-Type"] || "application/json";
   var req = CouchDB.newXhr();
   if(uri.substr(0, "http://".length) != "http://") {
     uri = CouchDB.urlPrefix + uri

Modified: couchdb/branches/0.11.x/share/www/script/test/basics.js
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/share/www/script/test/basics.js?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/share/www/script/test/basics.js (original)
+++ couchdb/branches/0.11.x/share/www/script/test/basics.js Fri Jun 25 14:18:23 2010
@@ -152,7 +152,8 @@ couchTests.basics = function(debug) {
 
   // test that the POST response has a Location header
   var xhr = CouchDB.request("POST", "/test_suite_db", {
-    body: JSON.stringify({"foo":"bar"})
+    body: JSON.stringify({"foo":"bar"}),
+    headers: {"Content-Type": "application/json"}
   });
   var resp = JSON.parse(xhr.responseText);
   T(resp.ok);
@@ -164,6 +165,7 @@ couchTests.basics = function(debug) {
 
   // test that that POST's with an _id aren't overriden with a UUID.
   var xhr = CouchDB.request("POST", "/test_suite_db", {
+    headers: {"Content-Type": "application/json"},
     body: JSON.stringify({"_id": "oppossum", "yar": "matey"})
   });
   var resp = JSON.parse(xhr.responseText);
@@ -202,7 +204,10 @@ couchTests.basics = function(debug) {
     result = JSON.parse(xhr.responseText);
     T(result.error == "doc_validation");
 
-    xhr = CouchDB.request("POST", "/test_suite_db/", {body: data});
+    xhr = CouchDB.request("POST", "/test_suite_db/", {
+      headers: {"Content-Type": "application/json"},
+      body: data
+    });
     T(xhr.status == 500);
     result = JSON.parse(xhr.responseText);
     T(result.error == "doc_validation");

Modified: couchdb/branches/0.11.x/share/www/script/test/batch_save.js
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/share/www/script/test/batch_save.js?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/share/www/script/test/batch_save.js (original)
+++ couchdb/branches/0.11.x/share/www/script/test/batch_save.js Fri Jun 25 14:18:23 2010
@@ -36,7 +36,10 @@ couchTests.batch_save = function(debug) 
 
   // repeat the tests for POST
   for(i=0; i < 100; i++) {
-    var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})});
+    var resp = db.request("POST", db.uri + "?batch=ok", {
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({a:1})
+    });
     T(JSON.parse(resp.responseText).ok);
   }
   

Modified: couchdb/branches/0.11.x/share/www/script/test/stats.js
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/share/www/script/test/stats.js?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/share/www/script/test/stats.js (original)
+++ couchdb/branches/0.11.x/share/www/script/test/stats.js Fri Jun 25 14:18:23 2010
@@ -160,7 +160,10 @@ couchTests.stats = function(debug) {
   
   runTest("couchdb", "database_writes", {
     run: function(db) {
-      CouchDB.request("POST", "/test_suite_db", {body: '{"a": "1"}'})
+      CouchDB.request("POST", "/test_suite_db", {
+        headers: {"Content-Type": "application/json"},
+        body: '{"a": "1"}'
+      })
     },
     test: function(before, after) {
       TEquals(before+1, after, "POST'ing new docs increments doc writes.");

Modified: couchdb/branches/0.11.x/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_httpd.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_httpd.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_httpd.erl Fri Jun 25 14:18:23 2010
@@ -25,7 +25,7 @@
 -export([start_json_response/2, start_json_response/3, end_json_response/1]).
 -export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
 -export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]).
--export([accepted_encodings/1,handle_request_int/5]).
+-export([accepted_encodings/1,handle_request_int/5,validate_referer/1,validate_ctype/2]).
 
 start_link() ->
     % read config and register for configuration changes
@@ -321,6 +321,34 @@ vhost_global(VhostGlobals, MochiReq) ->
     end,
     [true] == [true||V <- VhostGlobals, V == Front].
 
+validate_referer(Req) ->
+    Host = host_for_request(Req),
+    Referer = header_value(Req, "Referer", fail),
+    case Referer of
+    fail ->
+        throw({bad_request, <<"Referer header required.">>});
+    Referer ->
+        {_,RefererHost,_,_,_} = mochiweb_util:urlsplit(Referer),
+        if
+            RefererHost =:= Host -> ok;
+            true -> throw({bad_request, <<"Referer header must match host.">>})
+        end
+    end.
+
+validate_ctype(Req, Ctype) ->
+    case couch_httpd:header_value(Req, "Content-Type") of
+    undefined ->
+        throw({bad_ctype, "Content-Type must be "++Ctype});
+    ReqCtype ->
+        % ?LOG_ERROR("Ctype ~p ReqCtype ~p",[Ctype,ReqCtype]),
+        case re:split(ReqCtype, ";", [{return, list}]) of
+        [Ctype] -> ok;
+        [Ctype, _Rest] -> ok;
+        _Else ->
+            throw({bad_ctype, "Content-Type must be "++Ctype})
+        end
+    end.
+
 % Utilities
 
 partition(Path) ->
@@ -367,9 +395,9 @@ qs(#httpd{mochi_req=MochiReq}) ->
 path(#httpd{mochi_req=MochiReq}) ->
     MochiReq:get(path).
 
-absolute_uri(#httpd{mochi_req=MochiReq}, Path) ->
+host_for_request(#httpd{mochi_req=MochiReq}) ->
     XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"),
-    Host = case MochiReq:get_header_value(XHost) of
+    case MochiReq:get_header_value(XHost) of
         undefined ->
             case MochiReq:get_header_value("Host") of
                 undefined ->    
@@ -379,7 +407,10 @@ absolute_uri(#httpd{mochi_req=MochiReq},
                     Value1
             end;
         Value -> Value
-    end,
+    end.
+
+absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
+    Host = host_for_request(Req),
     XSsl = couch_config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
     Scheme = case MochiReq:get_header_value(XSsl) of
         "on" -> "https";

Modified: couchdb/branches/0.11.x/src/couchdb/couch_httpd_auth.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_httpd_auth.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_httpd_auth.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_httpd_auth.erl Fri Jun 25 14:18:23 2010
@@ -397,6 +397,7 @@ ensure_cookie_auth_secret() ->
 handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
     ReqBody = MochiReq:recv_body(),
     Form = case MochiReq:get_primary_header_value("content-type") of
+        % content type should be json
         "application/x-www-form-urlencoded" ++ _ ->
             mochiweb_util:parse_qs(ReqBody);
         _ ->

Modified: couchdb/branches/0.11.x/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_httpd_db.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_httpd_db.erl Fri Jun 25 14:18:23 2010
@@ -112,6 +112,7 @@ handle_changes_req(#httpd{path_parts=[_,
     send_method_not_allowed(Req, "GET,HEAD").
 
 handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
+    couch_httpd:validate_ctype(Req, "application/json"),
     ok = couch_view_compactor:start_compact(DbName, Id),
     send_json(Req, 202, {[{ok, true}]});
 
@@ -214,6 +215,7 @@ db_req(#httpd{method='GET',path_parts=[_
     send_json(Req, {DbInfo});
 
 db_req(#httpd{method='POST',path_parts=[DbName]}=Req, Db) ->
+    couch_httpd:validate_ctype(Req, "application/json"),
     Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)),
     Doc2 = case Doc#doc.id of
         <<"">> ->
@@ -281,6 +283,7 @@ db_req(#httpd{path_parts=[_,<<"_ensure_f
 
 db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
     couch_stats_collector:increment({httpd, bulk_requests}),
+    couch_httpd:validate_ctype(Req, "application/json"),
     {JsonProps} = couch_httpd:json_body_obj(Req),
     DocsArray = proplists:get_value(<<"docs">>, JsonProps),
     case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of
@@ -342,6 +345,7 @@ db_req(#httpd{path_parts=[_,<<"_bulk_doc
     send_method_not_allowed(Req, "POST");
 
 db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
+    couch_httpd:validate_ctype(Req, "application/json"),
     {IdsRevs} = couch_httpd:json_body_obj(Req),
     IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs],
 
@@ -386,7 +390,6 @@ db_req(#httpd{method='POST',path_parts=[
 db_req(#httpd{path_parts=[_,<<"_missing_revs">>]}=Req, _Db) ->
     send_method_not_allowed(Req, "POST");
 
-
 db_req(#httpd{method='POST',path_parts=[_,<<"_revs_diff">>]}=Req, Db) ->
     {JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
     JsonDocIdRevs2 = 
@@ -605,14 +608,11 @@ db_doc_req(#httpd{method='GET'}=Req, Db,
         end
     end;
 
+
 db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
+    couch_httpd:validate_referer(Req),
     couch_doc:validate_docid(DocId),
-    case couch_httpd:header_value(Req, "Content-Type") of
-    "multipart/form-data" ++  _Rest ->
-        ok;
-    _Else ->
-        throw({bad_ctype, <<"Invalid Content-Type header for form upload">>})
-    end,
+    couch_httpd:validate_ctype(Req, "multipart/form-data"),
     Form = couch_httpd:parse_form(Req),
     case proplists:is_defined("_doc", Form) of
     true ->

Modified: couchdb/branches/0.11.x/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_httpd_show.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_httpd_show.erl Fri Jun 25 14:18:23 2010
@@ -145,7 +145,7 @@ send_doc_update_response(Req, Db, DDoc, 
             {200, JsonResp}
     end,
     
-    JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp1),
+    JsonResp2 = couch_util:json_apply_field({<<"code">>, Code}, JsonResp1),
     % todo set location field
     couch_httpd_external:send_external_response(Req, JsonResp2).
 
@@ -373,21 +373,6 @@ render_head_for_empty_list(StartListResp
 render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows) ->
     StartListRespFun(Req, Etag, TotalRows, null, []).
 
-
-% Maybe this is in the proplists API
-% todo move to couch_util
-json_apply_field(H, {L}) ->
-    json_apply_field(H, L, []).
-json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) ->
-    % drop matching keys
-    json_apply_field({Key, NewValue}, Headers, Acc);
-json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) ->
-    % something else is next, leave it alone.
-    json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]);
-json_apply_field({Key, NewValue}, [], Acc) ->
-    % end of list, add ours
-    {[{Key, NewValue}|Acc]}.
-
 apply_etag({ExternalResponse}, CurrentEtag) ->
     % Here we embark on the delicate task of replacing or creating the
     % headers on the JsonResponse object. We need to control the Etag and
@@ -401,8 +386,8 @@ apply_etag({ExternalResponse}, CurrentEt
     JsonHeaders ->
         {[case Field of
         {<<"headers">>, JsonHeaders} -> % add our headers
-            JsonHeadersEtagged = json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders),
-            JsonHeadersVaried = json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
+            JsonHeadersEtagged = couch_util:json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders),
+            JsonHeadersVaried = couch_util:json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
             {<<"headers">>, JsonHeadersVaried};
         _ -> % skip non-header fields
             Field

Modified: couchdb/branches/0.11.x/src/couchdb/couch_rep.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_rep.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_rep.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_rep.erl Fri Jun 25 14:18:23 2010
@@ -653,10 +653,11 @@ commit_to_both(Source, Target, RequiredS
     end,
     {SourceStartTime, TargetStartTime}.
     
-ensure_full_commit(#http_db{} = Target) ->
+ensure_full_commit(#http_db{headers = Headers} = Target) ->
     Req = Target#http_db{
         resource = "_ensure_full_commit",
-        method = post
+        method = post,
+        headers = [{"content-type", "application/json"} | Headers]
     },
     {ResultProps} = couch_rep_httpc:request(Req),
     true = proplists:get_value(<<"ok">>, ResultProps),
@@ -677,11 +678,12 @@ ensure_full_commit(Target) ->
         InstanceStartTime
     end.
 
-ensure_full_commit(#http_db{} = Source, RequiredSeq) ->
+ensure_full_commit(#http_db{headers = Headers} = Source, RequiredSeq) ->
     Req = Source#http_db{
         resource = "_ensure_full_commit",
         method = post,
-        qs = [{seq, RequiredSeq}]
+        qs = [{seq, RequiredSeq}],
+        headers = [{"content-type", "application/json"} | Headers]
     },
     {ResultProps} = couch_rep_httpc:request(Req),
     case proplists:get_value(<<"ok">>, ResultProps) of

Modified: couchdb/branches/0.11.x/src/couchdb/couch_rep_writer.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_rep_writer.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_rep_writer.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_rep_writer.erl Fri Jun 25 14:18:23 2010
@@ -76,8 +76,9 @@ write_bulk_docs(#http_db{headers = Heade
         resource = "_bulk_docs",
         method = post,
         body = {[{new_edits, false}, {docs, JsonDocs}]},
-        headers = [{"x-couch-full-commit", "false"} | Headers]
+        headers = couch_util:proplist_apply_field({"Content-Type", "application/json"}, [{"X-Couch-Full-Commit", "false"} | Headers])
     },
+    ?LOG_ERROR("headers ~p",[Request#http_db.headers]),
     ErrorsJson = case couch_rep_httpc:request(Request) of
     {FailProps} ->
         exit({target_error, proplists:get_value(<<"error">>, FailProps)});

Modified: couchdb/branches/0.11.x/src/couchdb/couch_util.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/0.11.x/src/couchdb/couch_util.erl?rev=957969&r1=957968&r2=957969&view=diff
==============================================================================
--- couchdb/branches/0.11.x/src/couchdb/couch_util.erl (original)
+++ couchdb/branches/0.11.x/src/couchdb/couch_util.erl Fri Jun 25 14:18:23 2010
@@ -19,6 +19,7 @@
 -export([encodeBase64Url/1, decodeBase64Url/1]).
 -export([to_hex/1, parse_term/1, dict_find/3]).
 -export([file_read_size/1, get_nested_json_value/2, json_user_ctx/1]).
+-export([proplist_apply_field/2, json_apply_field/2]).
 -export([to_binary/1, to_integer/1, to_list/1, url_encode/1]).
 -export([json_encode/1, json_decode/1]).
 -export([verify/2,simple_call/2,shutdown_sync/1]).
@@ -130,6 +131,19 @@ get_nested_json_value(Value, []) ->
 get_nested_json_value(_NotJSONObj, _) ->
     throw({not_found, json_mismatch}).
 
+proplist_apply_field(H, L) ->
+    {R} = json_apply_field(H, {L}),
+    R.
+
+json_apply_field(H, {L}) ->
+    json_apply_field(H, L, []).
+json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) ->
+    json_apply_field({Key, NewValue}, Headers, Acc);
+json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) ->
+    json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]);
+json_apply_field({Key, NewValue}, [], Acc) ->
+    {[{Key, NewValue}|Acc]}.
+
 json_user_ctx(#db{name=DbName, user_ctx=Ctx}) ->
     {[{<<"db">>, DbName},
             {<<"name">>,Ctx#user_ctx.name},