summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/couchdb/couch_db.erl11
-rw-r--r--src/couchdb/couch_httpd_db.erl15
-rwxr-xr-xtest/etap/140-attachment-comp.t124
3 files changed, 147 insertions, 3 deletions
diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl
index f4a9e352..85a83c08 100644
--- a/src/couchdb/couch_db.erl
+++ b/src/couchdb/couch_db.erl
@@ -850,7 +850,16 @@ with_stream(Fd, #att{md5=InMd5,type=Type,encoding=Enc}=Att, Fun) ->
{Len, IdentityLen, gzip}
end;
gzip ->
- {Att#att.att_len, Att#att.disk_len, Enc}
+ case {Att#att.att_len, Att#att.disk_len} of
+ {AL, DL} when AL =:= undefined orelse DL =:= undefined ->
+ % Compressed attachment uploaded through the standalone API.
+ {Len, Len, gzip};
+ {AL, DL} ->
+ % This case is used for efficient push-replication, where a
+ % compressed attachment is located in the body of multipart
+ % content-type request.
+ {AL, DL, gzip}
+ end
end,
Att#att{
data={Fd,StreamInfo},
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 1e11e0d3..d7f479bc 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -945,7 +945,20 @@ db_attachment_req(#httpd{method=Method,mochi_req=MochiReq}=Req, Db, DocId, FileN
Length ->
list_to_integer(Length)
end,
- md5 = get_md5_header(Req)
+ md5 = get_md5_header(Req),
+ encoding = case string:to_lower(string:strip(
+ couch_httpd:header_value(Req,"Content-Encoding","identity")
+ )) of
+ "identity" ->
+ identity;
+ "gzip" ->
+ gzip;
+ _ ->
+ throw({
+ bad_ctype,
+ "Only gzip and identity content-encodings are supported"
+ })
+ end
}]
end,
diff --git a/test/etap/140-attachment-comp.t b/test/etap/140-attachment-comp.t
index b6c09d53..d244b5b0 100755
--- a/test/etap/140-attachment-comp.t
+++ b/test/etap/140-attachment-comp.t
@@ -22,7 +22,7 @@ test_db_name() ->
main(_) ->
test_util:init_code_path(),
- etap:plan(65),
+ etap:plan(78),
case (catch test()) of
ok ->
etap:end_tests();
@@ -54,6 +54,27 @@ test() ->
tests_for_2nd_text_att(),
tests_for_2nd_png_att(),
+ create_already_compressed_att(db_url() ++ "/doc_comp_att", "readme.txt"),
+ test_already_compressed_att(db_url() ++ "/doc_comp_att", "readme.txt"),
+
+ test_create_already_compressed_att_with_invalid_content_encoding(
+ db_url() ++ "/doc_att_deflate",
+ "readme.txt",
+ zlib:compress(test_text_data()),
+ "deflate"
+ ),
+
+ test_create_already_compressed_att_with_invalid_content_encoding(
+ db_url() ++ "/doc_att_compress",
+ "readme.txt",
+ % Note: As of OTP R13B04, it seems there's no LZW compression
+ % (i.e. UNIX compress utility implementation) lib in OTP.
+ % However there's a simple working Erlang implementation at:
+ % http://scienceblogs.com/goodmath/2008/01/simple_lempelziv_compression_i.php
+ test_text_data(),
+ "compress"
+ ),
+
timer:sleep(3000), % to avoid mochiweb socket closed exceptions
couch_server:delete(test_db_name(), []),
couch_server_sup:stop(),
@@ -121,6 +142,20 @@ create_2nd_png_att() ->
etap:is(Code, 201, "Created png attachment using the non-standalone api"),
ok.
+create_already_compressed_att(DocUri, AttName) ->
+ {ok, {{_, Code, _}, _Headers, _Body}} = http:request(
+ put,
+ {DocUri ++ "/" ++ AttName, [{"Content-Encoding", "gzip"}],
+ "text/plain", zlib:gzip(test_text_data())},
+ [],
+ [{sync, true}]),
+ etap:is(
+ Code,
+ 201,
+ "Created already compressed attachment using the standalone api"
+ ),
+ ok.
+
tests_for_1st_text_att() ->
test_get_1st_text_att_with_accept_encoding_gzip(),
test_get_1st_text_att_without_accept_encoding_header(),
@@ -576,6 +611,93 @@ test_2nd_png_att_stub() ->
),
ok.
+test_already_compressed_att(DocUri, AttName) ->
+ test_get_already_compressed_att_with_accept_gzip(DocUri, AttName),
+ test_get_already_compressed_att_without_accept(DocUri, AttName),
+ test_get_already_compressed_att_stub(DocUri, AttName).
+
+test_get_already_compressed_att_with_accept_gzip(DocUri, AttName) ->
+ {ok, {{_, Code, _}, Headers, Body}} = http:request(
+ get,
+ {DocUri ++ "/" ++ AttName, [{"Accept-Encoding", "gzip"}]},
+ [],
+ [{sync, true}]),
+ etap:is(Code, 200, "HTTP response code is 200"),
+ Gziped = lists:member({"content-encoding", "gzip"}, Headers),
+ etap:is(Gziped, true, "received body is gziped"),
+ etap:is(
+ iolist_to_binary(Body),
+ iolist_to_binary(zlib:gzip(test_text_data())),
+ "received data for the already compressed attachment is ok"
+ ),
+ ok.
+
+test_get_already_compressed_att_without_accept(DocUri, AttName) ->
+ {ok, {{_, Code, _}, Headers, Body}} = http:request(
+ get,
+ {DocUri ++ "/" ++ AttName, []},
+ [],
+ [{sync, true}]),
+ etap:is(Code, 200, "HTTP response code is 200"),
+ Gziped = lists:member({"content-encoding", "gzip"}, Headers),
+ etap:is(Gziped, false, "received body is not gziped"),
+ etap:is(
+ iolist_to_binary(Body),
+ iolist_to_binary(test_text_data()),
+ "received data for the already compressed attachment is ok"
+ ),
+ ok.
+
+test_get_already_compressed_att_stub(DocUri, AttName) ->
+ {ok, {{_, Code, _}, _Headers, Body}} = http:request(
+ get,
+ {DocUri ++ "?att_encoding_info=true", []},
+ [],
+ [{sync, true}]),
+ etap:is(Code, 200, "HTTP response code is 200"),
+ Json = couch_util:json_decode(Body),
+ {AttJson} = couch_util:get_nested_json_value(
+ Json,
+ [<<"_attachments">>, iolist_to_binary(AttName)]
+ ),
+ AttLength = proplists:get_value(<<"length">>, AttJson),
+ etap:is(
+ AttLength,
+ iolist_size((zlib:gzip(test_text_data()))),
+ "Already compressed attachment stub length matches the "
+ "compressed length"
+ ),
+ Encoding = proplists:get_value(<<"encoding">>, AttJson),
+ etap:is(
+ Encoding,
+ <<"gzip">>,
+ "Already compressed attachment stub has the encoding field set to gzip"
+ ),
+ EncLength = proplists:get_value(<<"encoded_length">>, AttJson),
+ etap:is(
+ EncLength,
+ AttLength,
+ "Already compressed attachment stub encoded_length matches the "
+ "length field value"
+ ),
+ ok.
+
+test_create_already_compressed_att_with_invalid_content_encoding(
+ DocUri, AttName, AttData, Encoding) ->
+ {ok, {{_, Code, _}, _Headers, _Body}} = http:request(
+ put,
+ {DocUri ++ "/" ++ AttName, [{"Content-Encoding", Encoding}],
+ "text/plain", AttData},
+ [],
+ [{sync, true}]),
+ etap:is(
+ Code,
+ 415,
+ "Couldn't create an already compressed attachment using the "
+ "unsupported encoding '" ++ Encoding ++ "'"
+ ),
+ ok.
+
test_png_data() ->
{ok, Data} = file:read_file(
test_util:source_file("share/www/image/logo.png")