Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit cedd243

Browse files
authored
Merge pull request #529 from cloudant/validation-feature
Add validation for doc ID and attachment names
2 parents 107c28d + 730ef2d commit cedd243

7 files changed

Lines changed: 765 additions & 9 deletions

File tree

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Unreleased
22
- [DEPRECATED] This library is now deprecated and will be EOL on Dec 31 2021.
33
- [FIXED] Type of `sinceSeq` can be also a `String` besides an `Integer`.
4+
- [IMPROVED] - Document IDs and attachment names are now rejected if they could cause an unexpected
5+
Cloudant request. We have seen that some applications pass unsantized document IDs to SDK functions
6+
(e.g. direct from user requests). In response to this we have updated many functions to reject
7+
obviously invalid paths. However, for complete safety applications must still validate that
8+
document IDs and attachment names match expected patterns.
49

510
# 2.19.2 (2021-04-07)
611
- [NEW] Add migration guide to the newly supported cloudant-java-sdk (coordinates: com.ibm.cloud:cloudant).

cloudant-client/src/main/java/com/cloudant/client/api/Database.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2016, 2019 IBM Corp. All rights reserved.
2+
* Copyright © 2016, 2021 IBM Corp. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
55
* except in compliance with the License. You may obtain a copy of the License at
@@ -47,6 +47,7 @@
4747
import com.cloudant.client.org.lightcouch.DocumentConflictException;
4848
import com.cloudant.client.org.lightcouch.NoDocumentException;
4949
import com.cloudant.client.org.lightcouch.Response;
50+
import com.cloudant.client.org.lightcouch.internal.CouchDbUtil;
5051
import com.cloudant.http.Http;
5152
import com.cloudant.http.HttpConnection;
5253
import com.google.gson.Gson;
@@ -605,8 +606,8 @@ public void deleteIndex(String indexName, String designDocId, String type) {
605606
assertNotEmpty(indexName, "indexName");
606607
assertNotEmpty(designDocId, "designDocId");
607608
assertNotNull(type, "type");
608-
if (!designDocId.startsWith("_design")) {
609-
designDocId = "_design/" + designDocId;
609+
if (!designDocId.startsWith(CouchDbUtil.DESIGN_PREFIX)) {
610+
designDocId = CouchDbUtil.DESIGN_PREFIX + designDocId;
610611
}
611612
URI uri = new DatabaseURIHelper(db.getDBUri()).path("_index").path(designDocId)
612613
.path(type).path(indexName).build();

cloudant-client/src/main/java/com/cloudant/client/api/DesignDocumentManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015 IBM Corp. All rights reserved.
2+
* Copyright (c) 2015, 2021 IBM Corp. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
55
* except in compliance with the License. You may obtain a copy of the License at
@@ -76,7 +76,7 @@
7676
*/
7777
public class DesignDocumentManager {
7878

79-
private static final String DESIGN_PREFIX = "_design/";
79+
private static final String DESIGN_PREFIX = CouchDbUtil.DESIGN_PREFIX;
8080

8181
private final CloudantClient client;
8282
private final Database db;
@@ -220,7 +220,7 @@ public Response remove(DesignDocument designDocument) {
220220
*/
221221
public List<DesignDocument> list() throws IOException {
222222
return db.getAllDocsRequestBuilder()
223-
.startKey("_design/")
223+
.startKey(DESIGN_PREFIX)
224224
.endKey("_design0")
225225
.inclusiveEnd(false)
226226
.includeDocs(true)

cloudant-client/src/main/java/com/cloudant/client/org/lightcouch/CouchDatabaseBase.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Copyright (C) 2011 lightcouch.org
3-
* Copyright (c) 2015 IBM Corp. All rights reserved.
3+
* Copyright © 2015, 2021 IBM Corp. All rights reserved.
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
66
* except in compliance with the License. You may obtain a copy of the License at
@@ -15,8 +15,10 @@
1515

1616
package com.cloudant.client.org.lightcouch;
1717

18+
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertDocumentTypeId;
1819
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNotEmpty;
1920
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNull;
21+
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertValidAttachmentName;
2022
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.close;
2123
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.generateUUID;
2224
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getAsString;
@@ -81,6 +83,7 @@ public abstract class CouchDatabaseBase {
8183
public <T> T find(Class<T> classType, String id) {
8284
assertNotEmpty(classType, "Class");
8385
assertNotEmpty(id, "id");
86+
assertDocumentTypeId(id);
8487
final URI uri = new DatabaseURIHelper(dbUri).documentUri(id);
8588
return couchDbClient.get(uri, classType);
8689
}
@@ -98,6 +101,7 @@ public <T> T find(Class<T> classType, String id) {
98101
public <T> T find(Class<T> classType, String id, Params params) {
99102
assertNotEmpty(classType, "Class");
100103
assertNotEmpty(id, "id");
104+
assertDocumentTypeId(id);
101105
final URI uri = new DatabaseURIHelper(dbUri).documentUri(id, params);
102106
return couchDbClient.get(uri, classType);
103107
}
@@ -116,6 +120,7 @@ public <T> T find(Class<T> classType, String id, String rev) {
116120
assertNotEmpty(classType, "Class");
117121
assertNotEmpty(id, "id");
118122
assertNotEmpty(id, "rev");
123+
assertDocumentTypeId(id);
119124
final URI uri = new DatabaseURIHelper(dbUri).documentUri(id, "rev", rev);
120125
return couchDbClient.get(uri, classType);
121126
}
@@ -145,6 +150,7 @@ public <T> T findAny(Class<T> classType, String uri) {
145150
*/
146151
public InputStream find(String id) {
147152
assertNotEmpty(id, "id");
153+
assertDocumentTypeId(id);
148154
return couchDbClient.get(new DatabaseURIHelper(dbUri).documentUri(id));
149155
}
150156

@@ -160,6 +166,7 @@ public InputStream find(String id) {
160166
public InputStream find(String id, String rev) {
161167
assertNotEmpty(id, "id");
162168
assertNotEmpty(rev, "rev");
169+
assertDocumentTypeId(id);
163170
final URI uri = new DatabaseURIHelper(dbUri).documentUri(id, "rev", rev);
164171
return couchDbClient.get(uri);
165172
}
@@ -172,6 +179,7 @@ public InputStream find(String id, String rev) {
172179
*/
173180
public boolean contains(String id) {
174181
assertNotEmpty(id, "id");
182+
assertDocumentTypeId(id);
175183
InputStream response = null;
176184
try {
177185
response = couchDbClient.head(new DatabaseURIHelper(dbUri).documentUri(id));
@@ -255,6 +263,7 @@ public Response remove(Object object) {
255263
public Response remove(String id, String rev) {
256264
assertNotEmpty(id, "id");
257265
assertNotEmpty(rev, "rev");
266+
assertDocumentTypeId(id);
258267
final URI uri = new DatabaseURIHelper(dbUri).documentUri(id, rev);
259268
return couchDbClient.delete(uri);
260269
}
@@ -308,6 +317,8 @@ public List<Response> bulk(List<?> objects, boolean allOrNothing) {
308317
* @return the attachment in the form of an {@code InputStream}.
309318
*/
310319
public InputStream getAttachment(String docId, String attachmentName, String revId) {
320+
assertDocumentTypeId(docId);
321+
assertValidAttachmentName(attachmentName);
311322
final URI uri = new DatabaseURIHelper(dbUri).attachmentUri(docId, revId, attachmentName);
312323
return getAttachment(uri);
313324
}
@@ -362,6 +373,7 @@ public Response saveAttachment(InputStream in, String name, String contentType,
362373
String docRev) {
363374
assertNotEmpty(in, "in");
364375
assertNotEmpty(name, "name");
376+
assertValidAttachmentName(name);
365377
assertNotEmpty(contentType, "ContentType");
366378
if (docId == null) {
367379
docId = generateUUID();
@@ -375,6 +387,7 @@ public Response saveAttachment(InputStream in, String name, String contentType,
375387
assertNotEmpty(docRev, "docRev");
376388
}
377389
}
390+
assertDocumentTypeId(docId);
378391
final URI uri = new DatabaseURIHelper(dbUri).attachmentUri(docId, docRev, name);
379392
return couchDbClient.put(uri, in, contentType);
380393
}
@@ -410,8 +423,10 @@ public Response removeAttachment(Object object, String attachmentName) {
410423
*/
411424
public Response removeAttachment(String id, String rev, String attachmentName) {
412425
assertNotEmpty(id, "id");
426+
assertDocumentTypeId(id);
413427
assertNotEmpty(rev, "rev");
414428
assertNotEmpty(attachmentName, "attachmentName");
429+
assertValidAttachmentName(attachmentName);
415430
final URI uri = new DatabaseURIHelper(dbUri).attachmentUri(id, rev, attachmentName);
416431
return couchDbClient.delete(uri);
417432
}

cloudant-client/src/main/java/com/cloudant/client/org/lightcouch/CouchDbClient.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Copyright (C) 2011 lightcouch.org
3-
* Copyright © 2015, 2019 IBM Corp. All rights reserved.
3+
* Copyright © 2015, 2021 IBM Corp. All rights reserved.
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
66
* except in compliance with the License. You may obtain a copy of the License at
@@ -14,6 +14,7 @@
1414
*/
1515
package com.cloudant.client.org.lightcouch;
1616

17+
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertDocumentTypeId;
1718
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNotEmpty;
1819
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNull;
1920
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.close;
@@ -480,6 +481,7 @@ public Response put(URI uri, Object object, boolean newEntity, int writeQuorum)
480481
assertNotEmpty(id, "id");
481482
assertNotEmpty(rev, "rev");
482483
}
484+
assertDocumentTypeId(id);
483485
URI httpUri = null;
484486
if (writeQuorum > -1) {
485487
httpUri = new DatabaseURIHelper(uri).documentUri(id, "w", writeQuorum);

cloudant-client/src/main/java/com/cloudant/client/org/lightcouch/internal/CouchDbUtil.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Copyright (C) 2011 lightcouch.org
3-
* Copyright (c) 2015 IBM Corp. All rights reserved.
3+
* Copyright © 2015, 2021 IBM Corp. All rights reserved.
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
66
* except in compliance with the License. You may obtain a copy of the License at
@@ -49,6 +49,10 @@
4949
*/
5050
final public class CouchDbUtil {
5151

52+
public static final String DESIGN_PREFIX = "_design/";
53+
public static final String LOCAL_PREFIX = "_local/";
54+
55+
5256
private CouchDbUtil() {
5357
// Utility class
5458
}
@@ -73,6 +77,36 @@ public static void assertNull(Object object, String prefix) throws IllegalArgume
7377
}
7478
}
7579

80+
/*
81+
* Throws an IllegalArgument exception if the ID is an _ prefixed name that isn't
82+
* either _design or _local.
83+
*
84+
* @param id
85+
* @throws IllegalArgumentException
86+
*/
87+
public static void assertDocumentTypeId(String id) throws IllegalArgumentException {
88+
boolean invalid = false;
89+
if (id.startsWith("_")) {
90+
if (id.startsWith(DESIGN_PREFIX) && !DESIGN_PREFIX.equals(id)) {
91+
invalid = false;
92+
} else if (id.startsWith(LOCAL_PREFIX) && !LOCAL_PREFIX.equals(id)) {
93+
invalid = false;
94+
} else {
95+
invalid = true;
96+
}
97+
98+
}
99+
if (invalid) {
100+
throw new IllegalArgumentException(format("%s is not a valid document ID.", id));
101+
}
102+
}
103+
104+
public static void assertValidAttachmentName(String attachmentName) throws IllegalArgumentException {
105+
if (attachmentName.startsWith("_")) {
106+
throw new IllegalArgumentException(format("%s is not a valid attachment name.", attachmentName));
107+
}
108+
}
109+
76110
public static String generateUUID() {
77111
return UUID.randomUUID().toString().replace("-", "");
78112
}

0 commit comments

Comments
 (0)