On Thu, Jun 24, 2010 at 1:21 AM, <jch...@apache.org> wrote: > Author: jchris > Date: Thu Jun 24 05:21:30 2010 > New Revision: 957422 > > URL: http://svn.apache.org/viewvc?rev=957422&view=rev > Log: > use JSON content type in replicator, require it in the _bulk_docs and other > POST apis > > Modified: > couchdb/trunk/share/www/script/couch.js > couchdb/trunk/share/www/script/test/basics.js > couchdb/trunk/share/www/script/test/batch_save.js > couchdb/trunk/share/www/script/test/stats.js > couchdb/trunk/src/couchdb/couch_httpd.erl > couchdb/trunk/src/couchdb/couch_httpd_auth.erl > couchdb/trunk/src/couchdb/couch_httpd_db.erl > couchdb/trunk/src/couchdb/couch_httpd_show.erl > couchdb/trunk/src/couchdb/couch_rep.erl > couchdb/trunk/src/couchdb/couch_rep_writer.erl > couchdb/trunk/src/couchdb/couch_util.erl > > Modified: couchdb/trunk/share/www/script/couch.js > URL: > http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch.js?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/share/www/script/couch.js [utf-8] (original) > +++ couchdb/trunk/share/www/script/couch.js [utf-8] Thu Jun 24 05:21:30 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/trunk/share/www/script/test/basics.js > URL: > http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/basics.js?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/share/www/script/test/basics.js (original) > +++ couchdb/trunk/share/www/script/test/basics.js Thu Jun 24 05:21:30 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/trunk/share/www/script/test/batch_save.js > URL: > http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/batch_save.js?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/share/www/script/test/batch_save.js (original) > +++ couchdb/trunk/share/www/script/test/batch_save.js Thu Jun 24 05:21:30 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/trunk/share/www/script/test/stats.js > URL: > http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/stats.js?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/share/www/script/test/stats.js (original) > +++ couchdb/trunk/share/www/script/test/stats.js Thu Jun 24 05:21:30 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/trunk/src/couchdb/couch_httpd.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_httpd.erl (original) > +++ couchdb/trunk/src/couchdb/couch_httpd.erl Thu Jun 24 05:21:30 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/trunk/src/couchdb/couch_httpd_auth.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_auth.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_httpd_auth.erl (original) > +++ couchdb/trunk/src/couchdb/couch_httpd_auth.erl Thu Jun 24 05:21:30 2010 > @@ -251,6 +251,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/trunk/src/couchdb/couch_httpd_db.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original) > +++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Thu Jun 24 05:21:30 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}]}); > > @@ -195,6 +196,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 > <<"">> -> > @@ -262,6 +264,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 = couch_util:get_value(<<"docs">>, JsonProps), > case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of > @@ -323,6 +326,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], > > @@ -367,7 +371,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 = > @@ -586,14 +589,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/trunk/src/couchdb/couch_httpd_show.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original) > +++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Thu Jun 24 05:21:30 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). > > @@ -376,21 +376,6 @@ render_head_for_empty_list(StartListResp > render_head_for_empty_list(StartListRespFun, Req, Etag, CurrentSeq, > TotalRows) -> > StartListRespFun(Req, Etag, TotalRows, null, [], CurrentSeq). > > - > -% 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 > @@ -404,8 +389,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/trunk/src/couchdb/couch_rep.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_rep.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_rep.erl (original) > +++ couchdb/trunk/src/couchdb/couch_rep.erl Thu Jun 24 05:21:30 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 = couch_util: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 couch_util:get_value(<<"ok">>, ResultProps) of > > Modified: couchdb/trunk/src/couchdb/couch_rep_writer.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_rep_writer.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_rep_writer.erl (original) > +++ couchdb/trunk/src/couchdb/couch_rep_writer.erl Thu Jun 24 05:21:30 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, couch_util:get_value(<<"error">>, FailProps)}); > > Modified: couchdb/trunk/src/couchdb/couch_util.erl > URL: > http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_util.erl?rev=957422&r1=957421&r2=957422&view=diff > ============================================================================== > --- couchdb/trunk/src/couchdb/couch_util.erl (original) > +++ couchdb/trunk/src/couchdb/couch_util.erl Thu Jun 24 05:21:30 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]). > @@ -143,6 +144,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}, > > >
Why the change to requiring the JSON header? I thought we'd always left that lax for the clients that are stuck in 1980. Also, are we ok in requiring exactly application/json and not the non-RFC-compliant things like text/json et al? Paul