Skip to content

Commit 4c6216a

Browse files
committed
MLE-27077 Added fix for invalid header for empty doc
1 parent 541b63e commit 4c6216a

File tree

3 files changed

+64
-34
lines changed

3 files changed

+64
-34
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import jakarta.mail.BodyPart;
3535
import jakarta.mail.Header;
3636
import jakarta.mail.MessagingException;
37+
import jakarta.mail.internet.ContentDisposition;
3738
import jakarta.mail.internet.MimeMultipart;
39+
import jakarta.mail.internet.ParseException;
3840
import jakarta.mail.util.ByteArrayDataSource;
3941
import jakarta.xml.bind.DatatypeConverter;
4042
import okhttp3.*;
@@ -1808,16 +1810,50 @@ static private long getHeaderLength(String length) {
18081810

18091811
static private String getHeaderUri(BodyPart part) {
18101812
try {
1811-
if (part != null) {
1812-
return part.getFileName();
1813+
if (part == null) {
1814+
return null;
18131815
}
1814-
// if it's not found, just return null
1816+
1817+
try {
1818+
String filename = part.getFileName();
1819+
if (filename != null) {
1820+
return filename;
1821+
}
1822+
} catch (ParseException e) {
1823+
// Jakarta Mail's parser failed due to malformed Content-Disposition header.
1824+
// Check if MarkLogic sent a malformed "format=" parameter at the end, which violates RFC 2183.
1825+
String contentDisposition = getHeader(part, "Content-Disposition");
1826+
if (contentDisposition != null && contentDisposition.matches(".*;\\s*format\\s*=\\s*$")) {
1827+
// Remove the trailing "; format=" to fix the malformed header
1828+
String cleaned = contentDisposition.replaceFirst(";\\s*format\\s*=\\s*$", "").trim();
1829+
logger.debug("Removed trailing 'format=' from malformed Content-Disposition header: {} -> {}", contentDisposition, cleaned);
1830+
return extractFilenameFromContentDisposition(cleaned);
1831+
}
1832+
throw e;
1833+
}
1834+
18151835
return null;
18161836
} catch (MessagingException e) {
18171837
throw new MarkLogicIOException(e);
18181838
}
18191839
}
18201840

1841+
static private String extractFilenameFromContentDisposition(String contentDisposition) {
1842+
if (contentDisposition == null) {
1843+
return null;
1844+
}
1845+
try {
1846+
// Use Jakarta Mail's ContentDisposition parser to extract the filename parameter. This is the class
1847+
// that throws an error when "format=" exists in the value, but that has been removed already.
1848+
ContentDisposition cd = new ContentDisposition(contentDisposition);
1849+
return cd.getParameter("filename");
1850+
} catch (ParseException e) {
1851+
logger.warn("Failed to parse cleaned Content-Disposition header: {}; cause: {}",
1852+
contentDisposition, e.getMessage());
1853+
return null;
1854+
}
1855+
}
1856+
18211857
static private void updateVersion(DocumentDescriptor descriptor, Headers headers) {
18221858
updateVersion(descriptor, extractVersion(headers.get(HEADER_ETAG)));
18231859
}
Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
/*
2-
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
2+
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
33
*/
44
package com.marklogic.client.test.document;
55

66
import com.marklogic.client.DatabaseClient;
7-
import com.marklogic.client.document.*;
7+
import com.marklogic.client.document.DocumentPage;
8+
import com.marklogic.client.document.DocumentRecord;
9+
import com.marklogic.client.document.JSONDocumentManager;
810
import com.marklogic.client.io.BytesHandle;
9-
import com.marklogic.client.io.DocumentMetadataHandle;
1011
import com.marklogic.client.io.StringHandle;
1112
import com.marklogic.client.query.StructuredQueryBuilder;
1213
import com.marklogic.client.query.StructuredQueryDefinition;
14+
import com.marklogic.client.test.AbstractClientTest;
1315
import com.marklogic.client.test.Common;
14-
import org.junit.jupiter.api.Disabled;
1516
import org.junit.jupiter.api.Test;
1617

17-
import static org.junit.jupiter.api.Assertions.assertEquals;
18-
import static org.junit.jupiter.api.Assertions.assertTrue;
18+
import static org.junit.jupiter.api.Assertions.*;
1919

20-
class ReadDocumentPageTest {
20+
class ReadDocumentPageTest extends AbstractClientTest {
2121

22+
/**
23+
* Verifies that the jakarta.mail library, instead of javax.mail, can probably read the URI.
24+
* See MLE-15748, which pertains to issues with javax.mail only allowing US-ASCII characters.
25+
*/
2226
@Test
23-
void test() {
24-
Common.deleteUrisWithPattern("/aaa-page/*");
25-
27+
void uriWithNonUsAsciiCharacters() {
2628
final String uri = "/aaa-page/太田佳伸のXMLファイル.xml";
2729
DocumentRecord documentRecord;
2830
try (DatabaseClient client = Common.newClient()) {
@@ -38,35 +40,27 @@ void test() {
3840
}
3941

4042
@Test
41-
@Disabled("Disabling for now because this seems to be a server bug.")
42-
void testEmptyDocWithNoExtension() {
43-
final String collection = "empty-binary-test";
43+
void emptyTextDocument() {
44+
final String uri = "/sample/empty-file.txt";
4445

4546
try (DatabaseClient client = Common.newClient()) {
46-
writeEmptyDocWithNoFileExtension(client, collection);
47-
4847
JSONDocumentManager documentManager = client.newJSONDocumentManager();
49-
StructuredQueryDefinition query = new StructuredQueryBuilder().collection(collection);
48+
StructuredQueryDefinition query = new StructuredQueryBuilder().document(uri);
5049
DocumentRecord documentRecord;
5150
try (DocumentPage documentPage = documentManager.search(query, 1)) {
5251
assertTrue(documentPage.hasNext(), "Expected a document in the page, but none was found.");
5352
documentRecord = documentPage.next();
5453
}
55-
String uri = documentRecord.getUri();
56-
assertEquals("/test/empty", uri, "The URI of the empty document should match the one written.");
57-
}
58-
}
54+
String actualUri = documentRecord.getUri();
55+
assertEquals(uri, actualUri, "The URI of the empty document should match the one written.");
5956

60-
protected void writeEmptyDocWithNoFileExtension(DatabaseClient client, String... collections) {
61-
DocumentMetadataHandle metadata = new DocumentMetadataHandle()
62-
.withCollections(collections)
63-
.withPermission("rest-reader", DocumentMetadataHandle.Capability.READ, DocumentMetadataHandle.Capability.UPDATE);
64-
// This needs to be a JSON document manager because the empty document is written without a format.
65-
JSONDocumentManager mgr = client.newJSONDocumentManager();
66-
DocumentWriteSet set = mgr.newWriteSet();
67-
BytesHandle emptyBytesHandle = new BytesHandle(new byte[0]);
68-
String uri = "/test/empty";
69-
set.add(uri, metadata, emptyBytesHandle);
70-
mgr.write(set);
57+
IllegalStateException ex = assertThrows(IllegalStateException.class,
58+
() -> documentRecord.getContent(new BytesHandle()));
59+
assertEquals("No bytes to write", ex.getMessage(),
60+
"This assertion is documenting existing behavior, where an empty doc will result in an " +
61+
"exception being thrown when an attempt is made to retrieve its content. " +
62+
"This doesn't seem ideal - returning null seems preferable - but it's the " +
63+
"behavior that has likely always existed.");
64+
}
7165
}
7266
}

test-app/src/main/ml-data/sample/empty-file.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)