diff options
-rw-r--r-- | share/www/script/test/attachments_multipart.js | 47 | ||||
-rw-r--r-- | src/couchdb/couch_doc.erl | 64 |
2 files changed, 87 insertions, 24 deletions
diff --git a/share/www/script/test/attachments_multipart.js b/share/www/script/test/attachments_multipart.js index 37dd461cf..6f924a7fb 100644 --- a/share/www/script/test/attachments_multipart.js +++ b/share/www/script/test/attachments_multipart.js @@ -18,7 +18,7 @@ couchTests.attachments_multipart= function(debug) { // mime multipart - xhr = CouchDB.request("PUT", "/test_suite_db/multipart", { + var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", { headers: {"Content-Type": "multipart/related;boundary=\"abc123\""}, body: "--abc123\r\n" + @@ -175,23 +175,36 @@ couchTests.attachments_multipart= function(debug) { T(xhr.status == 200); // parse out the multipart - var sections = parseMultipart(xhr); - + TEquals("790", xhr.getResponseHeader("Content-Length"), + "Content-Length should be correct"); T(sections.length == 3); - - // 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"); - + // The first section is the json doc. Check it's content-type. + // Each part carries their own meta data. + TEquals("application/json", sections[0].headers['Content-Type'], + "Content-Type should be application/json for section[0]"); + TEquals("application/test", sections[1].headers['Content-Type'], + "Content-Type should be application/test for section[1]"); + TEquals("application/test", sections[2].headers['Content-Type'], + "Content-Type should be application/test for section[2]"); + + TEquals("21", sections[1].headers['Content-Length'], + "Content-Length should be 21 section[1]"); + TEquals("18", sections[2].headers['Content-Length'], + "Content-Length should be 18 section[2]"); + + TEquals('attachment; filename="foo.txt"', sections[1].headers['Content-Disposition'], + "Content-Disposition should be foo.txt section[1]"); + TEquals('attachment; filename="bar.txt"', sections[2].headers['Content-Disposition'], + "Content-Disposition should be bar.txt section[2]"); + 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"); + TEquals("this is 18 chars l", sections[2].body, "should be 18 chars long"); // now get attachments incrementally (only the attachments changes since // a certain rev). @@ -210,7 +223,7 @@ couchTests.attachments_multipart= function(debug) { T(doc._attachments['foo.txt'].stub == true); T(doc._attachments['bar.txt'].follows == true); - T(sections[1].body == "this is 18 chars l"); + TEquals("this is 18 chars l", sections[1].body, "should be 18 chars long 2"); // try the atts_since parameter together with the open_revs parameter xhr = CouchDB.request( @@ -230,7 +243,7 @@ couchTests.attachments_multipart= function(debug) { var innerSections = parseMultipart(sections[0]); // 2 inner sections: a document body section plus an attachment data section T(innerSections.length === 2); - T(innerSections[0].headers['content-type'] === 'application/json'); + T(innerSections[0].headers['Content-Type'] === 'application/json'); doc = JSON.parse(innerSections[0].body); @@ -256,8 +269,7 @@ couchTests.attachments_multipart= function(debug) { 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"); - + TEquals("this is 18 chars l", sections[2].body, "should be 18 chars long 3"); // try it with a rev that doesn't exist, and one that does xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\",\"" + firstrev + "\"]", @@ -274,8 +286,7 @@ couchTests.attachments_multipart= function(debug) { T(doc._attachments['foo.txt'].stub == true); T(doc._attachments['bar.txt'].follows == true); - T(sections[1].body == "this is 18 chars l"); - + TEquals("this is 18 chars l", sections[1].body, "should be 18 chars long 4"); // check that with the document multipart/mixed API it's possible to receive // attachments in compressed form (if they're stored in compressed form) @@ -346,7 +357,7 @@ couchTests.attachments_multipart= function(debug) { var innerSections = parseMultipart(sections[0]); // 3 inner sections: a document body section plus 2 attachment data sections TEquals(3, innerSections.length); - TEquals('application/json', innerSections[0].headers['content-type']); + TEquals('application/json', innerSections[0].headers['Content-Type']); doc = JSON.parse(innerSections[0].body); @@ -387,7 +398,7 @@ couchTests.attachments_multipart= function(debug) { innerSections = parseMultipart(sections[0]); // 2 inner sections: a document body section plus 1 attachment data section TEquals(2, innerSections.length); - TEquals('application/json', innerSections[0].headers['content-type']); + TEquals('application/json', innerSections[0].headers['Content-Type']); doc = JSON.parse(innerSections[0].body); diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index 1742cffe1..0c7b2d9cb 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -445,7 +445,15 @@ fold_streamed_data(RcvFun, LenLeft, Fun, Acc) when LenLeft > 0-> fold_streamed_data(RcvFun, LenLeft - size(Bin), Fun, ResultAcc). len_doc_to_multi_part_stream(Boundary, JsonBytes, Atts, SendEncodedAtts) -> - AttsSize = lists:foldl(fun(#att{data=Data} = Att, AccAttsSize) -> + AttsSize = lists:foldl(fun(Att, AccAttsSize) -> + #att{ + data=Data, + name=Name, + att_len=AttLen, + disk_len=DiskLen, + type=Type, + encoding=Encoding + } = Att, case Data of stub -> AccAttsSize; @@ -453,13 +461,32 @@ len_doc_to_multi_part_stream(Boundary, JsonBytes, Atts, SendEncodedAtts) -> AccAttsSize + 4 + % "\r\n\r\n" case SendEncodedAtts of - true -> - Att#att.att_len; + false -> + % header + length(integer_to_list(DiskLen)) + + DiskLen; _ -> - Att#att.disk_len + % header + length(integer_to_list(AttLen)) + + AttLen end + 4 + % "\r\n--" - size(Boundary) + size(Boundary) + + + % attachment headers + % (the length of the Content-Length has already been set) + size(Name) + + size(Type) + + length("\r\nContent-Disposition: attachment; filename=\"\"") + + length("\r\nContent-Type: ") + + length("\r\nContent-Length: ") + + case Encoding of + identity -> + 0; + _ -> + length(atom_to_list(Encoding)) + + length("\r\nContent-Encoding: ") + end end end, 0, Atts), if AttsSize == 0 -> @@ -482,7 +509,7 @@ doc_to_multi_part_stream(Boundary, JsonBytes, Atts, WriteFun, case lists:any(fun(#att{data=Data})-> Data /= stub end, Atts) of true -> WriteFun([<<"--", Boundary/binary, - "\r\ncontent-type: application/json\r\n\r\n">>, + "\r\nContent-Type: application/json\r\n\r\n">>, JsonBytes, <<"\r\n--", Boundary/binary>>]), atts_to_mp(Atts, Boundary, WriteFun, SendEncodedAtts); false -> @@ -496,6 +523,31 @@ atts_to_mp([#att{data=stub} | RestAtts], Boundary, WriteFun, atts_to_mp(RestAtts, Boundary, WriteFun, SendEncodedAtts); atts_to_mp([Att | RestAtts], Boundary, WriteFun, SendEncodedAtts) -> + #att{ + name=Name, + att_len=AttLen, + disk_len=DiskLen, + type=Type, + encoding=Encoding + } = Att, + + % write headers + LengthBin = case SendEncodedAtts of + true -> list_to_binary(integer_to_list(DiskLen)); + false -> list_to_binary(integer_to_list(AttLen)) + end, + WriteFun(<<"\r\nContent-Disposition: attachment; filename=\"", Name/binary, "\"">>), + WriteFun(<<"\r\nContent-Type: ", Type/binary>>), + WriteFun(<<"\r\nContent-Length: ", LengthBin/binary>>), + case Encoding of + identity -> + ok; + _ -> + EncodingBin = atom_to_binary(Encoding, latin1), + WriteFun(<<"\r\nContent-Encoding: ", EncodingBin/binary>>) + end, + + % write data WriteFun(<<"\r\n\r\n">>), AttFun = case SendEncodedAtts of false -> |