diff options
-rw-r--r-- | share/www/script/couch_tests.js | 1 | ||||
-rw-r--r-- | share/www/script/test/attachment_names.js | 87 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 54 |
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. |