You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2013/01/08 22:18:32 UTC

git commit: Send attachment headers in multipart responses

Updated Branches:
  refs/heads/master 4b2041839 -> a2b3cc722


Send attachment headers in multipart responses

Closes COUCHDB-1368

Patch by:
 - Jan Lehnardt
 - Robert Newson


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/a2b3cc72
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/a2b3cc72
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/a2b3cc72

Branch: refs/heads/master
Commit: a2b3cc72229b86805ad11a27e93e74a78d6bdfa6
Parents: 4b20418
Author: Jan Lehnardt <ja...@apache.org>
Authored: Wed Nov 14 14:59:58 2012 +0100
Committer: Robert Newson <rn...@apache.org>
Committed: Tue Jan 8 21:17:35 2013 +0000

----------------------------------------------------------------------
 share/www/script/test/attachments_multipart.js |   47 +++++++++-----
 src/couchdb/couch_doc.erl                      |   64 +++++++++++++++++--
 2 files changed, 87 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/a2b3cc72/share/www/script/test/attachments_multipart.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/attachments_multipart.js b/share/www/script/test/attachments_multipart.js
index 37dd461..6f924a7 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);
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/a2b3cc72/src/couchdb/couch_doc.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl
index 1742cff..0c7b2d9 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 ->