diff options
author | Damien F. Katz <damien@apache.org> | 2009-11-14 01:14:59 +0000 |
---|---|---|
committer | Damien F. Katz <damien@apache.org> | 2009-11-14 01:14:59 +0000 |
commit | 4faf660a4dfe5849fae96ea7eb15379069c27405 (patch) | |
tree | 5adf3ed3d9b8b3810d0f0c60eaa9ead2a6e3b303 | |
parent | 1d5b30b916dbfb25e28d3c9a2e9d97da0bac0dff (diff) |
Better testing of multipart/related
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@836087 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | share/Makefile.am | 1 | ||||
-rw-r--r-- | share/www/script/couch_tests.js | 1 | ||||
-rw-r--r-- | share/www/script/test/attachments_multipart.js | 185 | ||||
-rw-r--r-- | src/couchdb/couch_doc.erl | 13 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 12 |
5 files changed, 200 insertions, 12 deletions
diff --git a/share/Makefile.am b/share/Makefile.am index 4adce5db..34711b11 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -97,6 +97,7 @@ nobase_dist_localdata_DATA = \ www/script/sha1.js \ www/script/test/all_docs.js \ www/script/test/attachments.js \ + www/script/test/attachments_multipart.js \ www/script/test/attachment_names.js \ www/script/test/attachment_paths.js \ www/script/test/attachment_views.js \ diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 5c1e2bc3..d991c05d 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -30,6 +30,7 @@ loadTest("basics.js"); // keep sorted loadTest("all_docs.js"); loadTest("attachments.js"); +loadTest("attachments_multipart.js"); loadTest("attachment_names.js"); loadTest("attachment_paths.js"); loadTest("attachment_views.js"); diff --git a/share/www/script/test/attachments_multipart.js b/share/www/script/test/attachments_multipart.js new file mode 100644 index 00000000..dbe2cff6 --- /dev/null +++ b/share/www/script/test/attachments_multipart.js @@ -0,0 +1,185 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.attachments_multipart= function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // mime multipart + + xhr = CouchDB.request("PUT", "/test_suite_db/multipart", { + headers: {"Content-Type": "multipart/related;boundary=\"abc123\""}, + body: + "--abc123\r\n" + + "content-type: application/json\r\n" + + "\r\n" + + JSON.stringify({ + "body":"This is a body.", + "_attachments":{ + "foo.txt": { + "follows":true, + "content_type":"text/plain", + "length":21 + }, + "bar.txt": { + "follows":true, + "content_type":"text/plain", + "length":20 + }, + "baz.txt": { + "follows":true, + "content_type":"text/plain", + "length":19 + } + } + }) + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 21 chars long" + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 20 chars lon" + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 19 chars lo" + + "\r\n--abc123--" + }); + + var result = JSON.parse(xhr.responseText); + + T(result.ok) + + + + TEquals(201, xhr.status, "should send 201 Accepted"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/foo.txt"); + + T(xhr.responseText == "this is 21 chars long"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt"); + + T(xhr.responseText == "this is 20 chars lon"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt"); + + T(xhr.responseText == "this is 19 chars lo"); + + // now edit an attachment + + var doc = db.open("multipart"); + + T(doc._attachments["foo.txt"].stub == true); + T(doc._attachments["bar.txt"].stub == true); + T(doc._attachments["baz.txt"].stub == true); + + //lets change attachment bar + delete doc._attachments["bar.txt"].stub; // remove stub member (or could set to false) + doc._attachments["bar.txt"].length = 18; + doc._attachments["bar.txt"].follows = true; + //lets delete attachment baz: + delete doc._attachments["baz.txt"]; + + var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", { + headers: {"Content-Type": "multipart/related;boundary=\"abc123\""}, + body: + "--abc123\r\n" + + "content-type: application/json\r\n" + + "\r\n" + + JSON.stringify(doc) + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 18 chars l" + + "\r\n--abc123--" + }); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt"); + + T(xhr.responseText == "this is 18 chars l"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt"); + T(xhr.status == 404); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true", + {headers:{"accept": "multipart/related,*/*;"}}); + + var headers = xhr.getAllResponseHeaders(); + + var ctype = xhr.getResponseHeader("Content-Type"); + + var ctypeArgs = ctype.split("; ").slice(1); + var boundary = null; + for(var i=0; i<ctypeArgs.length; i++) { + if (ctypeArgs[i].indexOf("boundary=") == 0) { + boundary = ctypeArgs[i].split("=")[1]; + if (boundary.charAt(0) == '"') { + // stringified boundary, parse as json + // (will maybe not if there are escape quotes) + boundary = JSON.parse(boundary); + } + } + } + + T(boundary != null); + + function parseMime(boundary, mimetext) { + // strip off leading boundary + var leading = "--" + boundary + "\r\n"; + var last = "\r\n--" + boundary + "--"; + + // strip off leading and trailing boundary + var leadingIdx = mimetext.indexOf(leading) + leading.length; + var trailingIdx = mimetext.indexOf(last); + mimetext = mimetext.slice(leadingIdx, trailingIdx); + + // now split the sections + var sections = mimetext.split(new RegExp("\\r\\n--" + boundary)); + + // spilt out the headers for each section + for(var i=0; i < sections.length; i++) { + var section = sections[i]; + var headerEndIdx = section.indexOf("\r\n\r\n"); + var headersraw = section.slice(0, headerEndIdx).split(/\r\n/); + var body = section.slice(headerEndIdx + 4); + var headers = {}; + for(var j=0; j<headersraw.length; j++) { + var tmp = headersraw[j].split(": "); + headers[tmp[0]] = tmp[1]; + } + sections[i] = {"headers":headers, "body":body}; + } + + return sections; + } + + // parse out the multipart + + T(xhr.status == 200); + + var sections = parseMime(boundary, xhr.responseText); + + // The first section is the json doc. Check it's content-type. It contains + // the metadata for all the following attachments + + T(sections[0].headers['content-type'] == "application/json"); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].follows = true); + T(doc._attachments['bar.txt'].follows = true); + + T(sections[1].body == "this is 21 chars long"); + T(sections[2].body == "this is 18 chars l"); + +}; diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index 4cb20d6c..079d9dfa 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -340,14 +340,14 @@ fold_streamed_data(RcvFun, LenLeft, Fun, Acc) when LenLeft > 0-> len_doc_to_multi_part_stream(Boundary,JsonBytes,Atts,AttsSinceRevPos) -> 2 + % "--" size(Boundary) + - 34 + % "\r\ncontent-type: application/json\r\n" + 36 + % "\r\ncontent-type: application/json\r\n\r\n" iolist_size(JsonBytes) + 4 + % "\r\n--" size(Boundary) + + lists:foldl(fun(#att{revpos=RevPos,len=Len}, AccAttsSize) -> if RevPos > AttsSinceRevPos -> AccAttsSize + - 2 + % "\r\n" + 4 + % "\r\n\r\n" Len + 4 + % "\r\n--" size(Boundary); @@ -358,17 +358,18 @@ len_doc_to_multi_part_stream(Boundary,JsonBytes,Atts,AttsSinceRevPos) -> 2. % "--" doc_to_multi_part_stream(Boundary,JsonBytes,Atts,AttsSinceRevPos,WriteFun) -> - WriteFun([<<"--", Boundary, "\r\ncontent-type: application/json\r\n">>, - JsonBytes, <<"\r\n--", Boundary>>]), + WriteFun([<<"--", Boundary/binary, + "\r\ncontent-type: application/json\r\n\r\n">>, + JsonBytes, <<"\r\n--", Boundary/binary>>]), atts_to_mp(Atts, Boundary, WriteFun, AttsSinceRevPos). atts_to_mp([], _Boundary, WriteFun, _AttsSinceRevPos) -> WriteFun(<<"--">>); atts_to_mp([#att{revpos=RevPos} = Att | RestAtts], Boundary, WriteFun, AttsSinceRevPos) when RevPos > AttsSinceRevPos -> - WriteFun(<<"\r\n">>), + WriteFun(<<"\r\n\r\n">>), att_foldl(Att, fun(Data, ok) -> WriteFun(Data) end, ok), - WriteFun(<<"\r\n--", Boundary>>), + WriteFun(<<"\r\n--", Boundary/binary>>), atts_to_mp(RestAtts, Boundary, WriteFun, AttsSinceRevPos); atts_to_mp([_ | RestAtts], Boundary, WriteFun, AttsSinceRevPos) -> atts_to_mp(RestAtts, Boundary, WriteFun, AttsSinceRevPos). diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 7be10d42..4138db7d 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -784,7 +784,7 @@ db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) -> Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)), RespHeaders = [{"Location", Loc}], - case couch_httpd:header_value(Req, "content-type") of + case couch_httpd:header_value(Req, "Content-Type") of ("multipart/related" ++ _Rest) = ContentType-> Doc0 = couch_doc:doc_from_multi_part_stream(ContentType, fun() -> receive_request_data(Req) end), @@ -869,18 +869,18 @@ send_doc_efficiently(Req, #doc{atts=Atts}=Doc, Headers, Options) -> undefined -> []; AcceptHeader -> string:tokens(AcceptHeader, ", ") end, - case lists:member(AcceptedTypes, "multipart/related") of + case lists:member("multipart/related", AcceptedTypes) of false -> send_json(Req, 200, [], couch_doc:to_json_obj(Doc, Options)); true -> Boundary = couch_uuids:random(), - JsonBytes = ?JSON_ENCODE(couch_doc:to_json_obj(Doc, Options)), + JsonBytes = ?JSON_ENCODE(couch_doc:to_json_obj(Doc, [follows|Options])), AttsSinceRevPos = proplists:get_value(atts_after_revpos, Options, 0), Len = couch_doc:len_doc_to_multi_part_stream(Boundary,JsonBytes,Atts, AttsSinceRevPos), - CType = {<<"content-type">>, - <<"multipart/related; boundary=", Boundary/binary>>}, - Resp = start_response_length(Req, 200, [CType | Headers], Len), + CType = {<<"Content-Type">>, + <<"multipart/related; boundary=\"", Boundary/binary, "\"">>}, + {ok, Resp} = start_response_length(Req, 200, [CType|Headers], Len), couch_doc:doc_to_multi_part_stream(Boundary,JsonBytes,Atts, AttsSinceRevPos, fun(Data) -> couch_httpd:send(Resp, Data) end) |