summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2012-11-14 14:59:58 +0100
committerRobert Newson <rnewson@apache.org>2013-01-08 21:17:35 +0000
commita2b3cc72229b86805ad11a27e93e74a78d6bdfa6 (patch)
tree39e997af71d72b542d0a901d0e4cb83bd82a9de6
parent4b20418391a9a77012ff74c8064f3da910c310fb (diff)
downloadcouchdb-a2b3cc72229b86805ad11a27e93e74a78d6bdfa6.tar.gz
Send attachment headers in multipart responses
Closes COUCHDB-1368 Patch by: - Jan Lehnardt - Robert Newson
-rw-r--r--share/www/script/test/attachments_multipart.js47
-rw-r--r--src/couchdb/couch_doc.erl64
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 ->