Skip to content

Commit e66ce35

Browse files
committed
Separate checks for attachment attribute, max size and file type, fix file type check NPE, check file type as with the HTML input "accept" attribute
1 parent 56bd5f6 commit e66ce35

File tree

1 file changed

+58
-13
lines changed

1 file changed

+58
-13
lines changed

src/main/java/org/tailormap/api/controller/AttachmentsController.java

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
*/
66
package org.tailormap.api.controller;
77

8+
import com.google.common.base.Splitter;
89
import jakarta.validation.Valid;
910
import java.io.IOException;
1011
import java.io.Serializable;
1112
import java.lang.invoke.MethodHandles;
1213
import java.nio.ByteBuffer;
1314
import java.sql.SQLException;
1415
import java.util.List;
16+
import java.util.Locale;
1517
import java.util.Set;
1618
import java.util.UUID;
19+
import java.util.regex.Pattern;
20+
1721
import org.geotools.api.data.Query;
1822
import org.geotools.api.data.SimpleFeatureSource;
1923
import org.geotools.api.filter.Filter;
@@ -103,22 +107,29 @@ public ResponseEntity<Serializable> addAttachment(
103107
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Layer does not support attachments");
104108
}
105109

106-
AttachmentAttributeType attachmentAttributeType = attachmentAttrSet.stream()
107-
.filter(attr -> (attr.getAttributeName().equals(attachment.getAttributeName())
108-
&& java.util.Arrays.stream(attr.getMimeType().split(","))
109-
.map(String::trim)
110-
.anyMatch(mime -> mime.equals(attachment.getMimeType()))
111-
&& (attr.getMaxAttachmentSize() == null || attr.getMaxAttachmentSize() >= fileData.length)))
110+
AttachmentAttributeType attachmentAttribute = attachmentAttrSet.stream()
111+
.filter(attr -> attr.getAttributeName().equals(attachment.getAttributeName()))
112112
.findFirst()
113113
.orElseThrow(() -> new ResponseStatusException(
114114
HttpStatus.BAD_REQUEST,
115-
"Layer does not support attachments for attribute "
116-
+ attachment.getAttributeName()
117-
+ " with mime type "
118-
+ attachment.getMimeType()
119-
+ " and size "
120-
+ fileData.length));
121-
logger.debug("Using attachment attribute {}", attachmentAttributeType);
115+
"Layer does not support attachments for attribute " + attachment.getAttributeName()));
116+
117+
if (attachmentAttribute.getMaxAttachmentSize() != null
118+
&& attachmentAttribute.getMaxAttachmentSize() < fileData.length) {
119+
throw new ResponseStatusException(
120+
HttpStatus.BAD_REQUEST,
121+
"Attachment size %d exceeds maximum of %d"
122+
.formatted(fileData.length, attachmentAttribute.getMaxAttachmentSize()));
123+
}
124+
125+
if (attachmentAttribute.getMimeType() != null) {
126+
if (!validateMimeTypeAccept(
127+
attachmentAttribute.getMimeType(), attachment.getFileName(), attachment.getMimeType())) {
128+
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "File type or extension not allowed");
129+
}
130+
}
131+
132+
logger.debug("Using attachment attribute {}", attachmentAttribute);
122133

123134
AttachmentMetadata response;
124135
try {
@@ -130,6 +141,40 @@ public ResponseEntity<Serializable> addAttachment(
130141
return new ResponseEntity<>(response, HttpStatus.CREATED);
131142
}
132143

144+
/**
145+
* Validate as <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/file#accept">file
146+
* input "accept" attribute</a>.
147+
*
148+
* @param acceptList comma-separated list of MIME types and file extensions to validate against
149+
* @param fileName name of the file to validate
150+
* @param mimeType MIME type of the file to validate
151+
* @return true if the file's extension or MIME type matches one of the accepted types, false otherwise
152+
*/
153+
private static boolean validateMimeTypeAccept(String acceptList, String fileName, String mimeType) {
154+
Iterable<String> allowedMimeTypes = Splitter.on(Pattern.compile(",\\s*")).split(acceptList);
155+
final Locale locale = Locale.ENGLISH;
156+
for (String allowedType : allowedMimeTypes) {
157+
if (allowedType.startsWith(".")) {
158+
// Check file extension
159+
if (fileName.toLowerCase(locale).endsWith(allowedType.toLowerCase(locale))) {
160+
return true;
161+
}
162+
} else if (allowedType.endsWith("/*")) {
163+
// Check mime type category (e.g. image/*)
164+
String category = allowedType.substring(0, allowedType.length() - 1);
165+
if (mimeType.startsWith(category)) {
166+
return true;
167+
}
168+
} else {
169+
// Check exact mime type match
170+
if (mimeType.equals(allowedType)) {
171+
return true;
172+
}
173+
}
174+
}
175+
return false;
176+
}
177+
133178
/**
134179
* List attachments for a feature.
135180
*

0 commit comments

Comments
 (0)