summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/script/couch.js10
-rw-r--r--share/www/script/couch_tests.js55
-rw-r--r--src/couchdb/couch_httpd.erl108
3 files changed, 170 insertions, 3 deletions
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index 861fd070..38c9cf34 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -80,6 +80,16 @@ function CouchDB(name) {
return result;
}
+ // Deletes an attachment from a document
+ this.deleteDocAttachment = function(doc, attachment_name) {
+ var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
+ var result = JSON.parse(req.responseText);
+ if (req.status != 200)
+ throw result;
+ doc._rev = result.rev; //record rev in input document
+ return result;
+ }
+
this.bulkSave = function(docs, options) {
var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
body: JSON.stringify({"docs": docs})
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 213c5cfc..a0e6b2ff 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -556,8 +556,59 @@ var tests = {
T(db.save(binAttDoc).ok);
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt");
- T(xhr.responseText == "This is a base64 encoded text")
- T(xhr.getResponseHeader("Content-Type") == "text/plain")
+ T(xhr.responseText == "This is a base64 encoded text");
+ T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+ // test RESTful doc API
+
+ // test without rev, should fail
+ var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc/foo.txt");
+ T(xhr.status == 412);
+
+ db.deleteDocAttachment({_id:"bin_doc"}, "foo.txt");
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt");
+ T(xhr.status == 404);
+
+ var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/foo.txt", {
+ body: "This is not base64 encoded text",
+ headers:{"Content-Type":"text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 201);
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt");
+ T(xhr.responseText == "This is not base64 encoded text");
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ // test binary data
+ var xhr = CouchDB.request("GET", "/favicon.ico");
+ var bin_data = xhr.responseText;
+ // bin_data = 123;
+ var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/favicon.ico", {
+ headers:{"Content-Type":"image/vnd.microsoft.icon"},
+ body:bin_data
+ });
+ T(xhr.status == 201);
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/favicon.ico");
+ T(xhr.responseText == bin_data);
+ T(xhr.getResponseHeader("Content-Type") == "image/vnd.microsoft.icon");
+
+ var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/favicon.ico", {
+ headers:{"Content-Type":"image/vnd.microsoft.icon"},
+ body:bin_data
+ });
+ T(xhr.status == 412);
+
+ var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc/favicon.ico?rev="+rev, {
+ headers:{"Content-Type":"image/vnd.microsoft.icon"},
+ body:bin_data
+ });
+ T(xhr.status == 201);
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc/foo.txt?rev="+rev);
+ T(xhr.status == 200);
},
content_negotiation: function(debug) {
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index 6ae51411..5615ac3e 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -633,8 +633,114 @@ handle_attachment_request(Req, 'GET', _DbName, Db, DocId, FileName) ->
throw(Error)
end;
+handle_attachment_request(Req, 'DELETE', _DbName, Db, DocId, FileName) ->
+ QueryRev = proplists:get_value("rev", Req:parse_qs()),
+ Etag = case Req:get_header_value("If-Match") of
+ undefined ->
+ undefined;
+ Tag ->
+ string:strip(Tag, both, $")
+ end,
+ case {QueryRev, Etag} of
+ {undefined, undefined} ->
+ throw({missing_rev, "Document rev/etag must be specified to delete"});
+ {_, undefined} ->
+ QueryRev;
+ {undefined, _} ->
+ Etag;
+ _ when QueryRev == Etag ->
+ Etag;
+ _ ->
+ throw({bad_request, "Document rev and etag have different values"})
+ end,
+
+ case couch_db:open_doc(Db, DocId, []) of
+ {ok, Doc} ->
+ #doc{attachments=Attachments,revs=Revs} = Doc,
+ case proplists:get_value(FileName, Attachments) of
+ undefined ->
+ throw({not_found, missing});
+ {_Type, _Bin} ->
+
+ NewAttachmentList = proplists:delete(FileName, Attachments),
+
+ {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{
+ id=DocId,
+ revs=Revs,
+ attachments=NewAttachmentList
+ }, []),
+
+ send_json(Req, 200, {obj, [
+ {ok, true},
+ {id, DocId},
+ {rev, NewRev}
+ ]})
+ end;
+ Error ->
+ throw(Error)
+ end;
+
+handle_attachment_request(Req, 'PUT', _DbName, Db, DocId, FileName) ->
+ case couch_db:open_doc(Db, DocId, []) of
+ {ok, Doc} ->
+ #doc{attachments=Attachments,revs=OldRevs} = Doc,
+
+ NewAttachments =
+ case proplists:get_value(FileName, Attachments) of
+ undefined -> % new attachment, just append to list
+ Revs = OldRevs,
+ lists:append(Attachments, [{FileName, {
+ Req:get_header_value("Content-Type"),
+ Req:recv_body()
+ }}]);
+
+ {_Type, _Bin} -> % update of an existing attachment, delete and re-add
+ QueryRev = proplists:get_value("rev", Req:parse_qs()),
+ Etag = case Req:get_header_value("If-Match") of
+ undefined ->
+ undefined;
+ Tag ->
+ string:strip(Tag, both, $")
+ end,
+ Revs = case {QueryRev, Etag} of
+ {undefined, undefined} ->
+ throw({missing_rev, "Document rev/etag must be specified to delete"});
+ {_, undefined} ->
+ [QueryRev];
+ {undefined, _} ->
+ [Etag];
+ _ when QueryRev == Etag ->
+ [Etag];
+ _ ->
+ throw({bad_request, "Document rev and etag have different values"})
+ end,
+
+ lists:append(
+ proplists:delete(FileName, Attachments),
+ [{FileName, {
+ Req:get_header_value("Content-Type"),
+ Req:recv_body()
+ }
+ }])
+ end,
+
+ {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{
+ id=DocId,
+ revs=Revs,
+ attachments=NewAttachments
+ }, []),
+
+ send_json(Req, 201, {obj, [
+ {ok, true},
+ {id, DocId},
+ {rev, NewRev}
+ ]});
+ Error ->
+ throw(Error)
+ end;
+
handle_attachment_request(_Req, _Method, _DbName, _Db, _DocId, _FileName) ->
- throw({method_not_allowed, "GET,HEAD"}).
+ throw({method_not_allowed, "GET,HEAD,DELETE,PUT"}).
% View request handling internals