summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2009-03-06 00:12:15 +0000
committerJan Lehnardt <jan@apache.org>2009-03-06 00:12:15 +0000
commita05797474397a1598ac2e3436bd8c1e4e2b8678a (patch)
tree8f7fb3b82ce1f8a95f2db7387e22259a558729a0
parent620db891b57c8a429a7f11f23eed302e5479f250 (diff)
Verify attachment names. They must be utf-8 (closes COUCHDB-280) and must not start with an underscore.
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@750695 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--share/www/script/couch_tests.js1
-rw-r--r--share/www/script/test/attachment_names.js87
-rw-r--r--src/couchdb/couch_httpd_db.erl54
3 files changed, 139 insertions, 3 deletions
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 67c3baed..64c366b6 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -41,6 +41,7 @@ loadTest("multiple_rows.js");
loadTest("large_docs.js");
loadTest("utf8.js");
loadTest("attachments.js");
+loadTest("attachment_names.js");
loadTest("attachment_paths.js");
loadTest("attachment_views.js");
loadTest("design_paths.js");
diff --git a/share/www/script/test/attachment_names.js b/share/www/script/test/attachment_names.js
new file mode 100644
index 00000000..802abc08
--- /dev/null
+++ b/share/www/script/test/attachment_names.js
@@ -0,0 +1,87 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attatchment_names = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var binAttDoc = {
+ _id: "bin_doc",
+ _attachments:{
+ "foo\x80txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ }
+
+ // inline attachments
+ try {
+ db.save(binAttDoc);
+ TEquals(1, 2, "Attachment name with non UTF-8 encoding saved. Should never show!");
+ } catch (e) {
+ TEquals("bad_request", e.error, "attachment_name: inline attachments");
+ TEquals("Attachment name is not UTF-8 encoded", e.reason, "attachment_name: inline attachments");
+ }
+
+
+ // standalone docs
+ var bin_data = "JHAPDO*AU£PN ){(3u[d 93DQ9¡€])} ææøo'∂ƒæ≤çæππ•¥∫¶®#†π¶®¥π€ª®˙π8np";
+
+ var xhr = (CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment\x80txt", {
+ headers:{"Content-Type":"text/plain;charset=utf-8"},
+ body:bin_data
+ }));
+
+ var resp = JSON.parse(xhr.responseText);
+ TEquals(400, xhr.status, "attachment_name: standalone API");
+ TEquals("bad_request", resp.error, "attachment_name: standalone API");
+ TEquals("Attachment name is not UTF-8 encoded", resp.reason, "attachment_name: standalone API");
+
+
+ // bulk docs
+ var docs = { docs: [binAttDoc] };
+
+ var xhr = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {
+ body: JSON.stringify(docs)
+ });
+
+ var resp = JSON.parse(xhr.responseText);
+ TEquals(400, xhr.status, "attachment_name: bulk docs");
+ TEquals("bad_request", resp.error, "attachment_name: bulk docs");
+ TEquals("Attachment name is not UTF-8 encoded", resp.reason, "attachment_name: bulk docs");
+
+
+ // leading underscores
+ var binAttDoc = {
+ _id: "bin_doc2",
+ _attachments:{
+ "_foo.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ }
+
+ try {
+ db.save(binAttDoc);
+ TEquals(1, 2, "Attachment name with leading underscore saved. Should never show!");
+ } catch (e) {
+ TEquals("bad_request", e.error, "attachment_name: leading underscore");
+ TEquals("Attachment name can't start with '_'", e.reason, "attachment_name: leading underscore");
+ }
+
+ // todo: form uploads, waiting for cmlenz' test case for form uploads
+
+};
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 524b52b4..0ba8a873 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -116,6 +116,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
Docs = lists:map(
fun({ObjProps} = JsonObj) ->
Doc = couch_doc:from_json_obj(JsonObj),
+ validate_attachment_names(Doc),
Id = case Doc#doc.id of
<<>> -> couch_util:new_uuid();
Id0 -> Id0
@@ -436,7 +437,7 @@ db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
end,
NewAttachments = [
- {list_to_binary(Name), {list_to_binary(ContentType), Content}} ||
+ {validate_attachment_name(Name), {list_to_binary(ContentType), Content}} ||
{Name, {ContentType, _}, Content} <-
proplists:get_all_values("_attachments", Form)
],
@@ -460,6 +461,7 @@ db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) ->
[Rev0|_] -> Rev0;
[] -> undefined
end,
+ validate_attachment_names(Doc),
case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
"true" ->
Options = [full_commit];
@@ -591,8 +593,11 @@ db_attachment_req(#httpd{method='GET'}=Req, Db, DocId, FileNameParts) ->
db_attachment_req(#httpd{method=Method}=Req, Db, DocId, FileNameParts)
when (Method == 'PUT') or (Method == 'DELETE') ->
- FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1,
- FileNameParts),"/")),
+ FileName = validate_attachment_name(
+ mochiweb_util:join(
+ lists:map(fun binary_to_list/1,
+ FileNameParts),"/")),
+
NewAttachment = case Method of
'DELETE' ->
[];
@@ -708,3 +713,46 @@ parse_copy_destination_header(Req) ->
{list_to_binary(DocId), [list_to_binary(Rev)]}
end.
+validate_attachment_names(Doc) ->
+ lists:foreach(fun({Name, _}) ->
+ validate_attachment_name(Name)
+ end, Doc#doc.attachments).
+
+validate_attachment_name(Name) when is_list(Name) ->
+ validate_attachment_name(list_to_binary(Name));
+validate_attachment_name(Name) ->
+ case is_valid_utf8(Name) of
+ true ->
+ case Name of
+ <<"_",_/binary>>=Name -> throw({bad_request, <<"Attachment name can't start with '_'">>});
+ _ -> Name
+ end;
+ false -> throw({bad_request, <<"Attachment name is not UTF-8 encoded">>})
+ end.
+
+%% borrowed from mochijson2:json_bin_is_safe()
+is_valid_utf8(<<>>) ->
+ true;
+is_valid_utf8(<<C, Rest/binary>>) ->
+ case C of
+ $\" ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ false;
+ C when C < 16#7f ->
+ is_valid_utf8(Rest);
+ _ ->
+ false
+ end.