Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@
import jakarta.mail.BodyPart;
import jakarta.mail.Header;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.ContentDisposition;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.internet.ParseException;
import jakarta.mail.util.ByteArrayDataSource;
import jakarta.xml.bind.DatatypeConverter;
import okhttp3.*;
Expand Down Expand Up @@ -68,6 +66,20 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.marklogic.client.impl.okhttp.OkHttpUtil.copyDescriptor;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.getHeader;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.getHeaderFormat;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.getHeaderLength;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.getHeaderMimetype;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.getHeaderUri;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.getHeaderVersion;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.updateDescriptor;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.updateFormat;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.updateLength;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.updateMimetype;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.updateServerTimestamp;
import static com.marklogic.client.impl.okhttp.OkHttpUtil.updateVersion;

@SuppressWarnings({"unchecked", "rawtypes"})
public class OkHttpServices implements RESTServices {

Expand Down Expand Up @@ -1641,18 +1653,7 @@ static private boolean isExternalDescriptor(ContentDescriptor desc) {
&& !((DocumentDescriptorImpl) desc).isInternal();
}

static private void updateDescriptor(ContentDescriptor desc,
Headers headers) {
if (desc == null || headers == null) return;

updateFormat(desc, headers);
updateMimetype(desc, headers);
updateLength(desc, headers);
updateServerTimestamp(desc, headers);
}

static private TemporalDescriptor updateTemporalSystemTime(DocumentDescriptor desc,
Headers headers) {
static private TemporalDescriptor updateTemporalSystemTime(DocumentDescriptor desc, Headers headers) {
if (headers == null) return null;

DocumentDescriptorImpl temporalDescriptor;
Expand All @@ -1665,194 +1666,6 @@ static private TemporalDescriptor updateTemporalSystemTime(DocumentDescriptor de
return temporalDescriptor;
}

static private void copyDescriptor(DocumentDescriptor desc,
HandleImplementation handleBase) {
if (handleBase == null) return;

if (desc.getFormat() != null) handleBase.setFormat(desc.getFormat());
if (desc.getMimetype() != null) handleBase.setMimetype(desc.getMimetype());
handleBase.setByteLength(desc.getByteLength());
}

static private void updateFormat(ContentDescriptor descriptor,
Headers headers) {
updateFormat(descriptor, getHeaderFormat(headers));
}

static private void updateFormat(ContentDescriptor descriptor, Format format) {
if (format != null) {
descriptor.setFormat(format);
}
}

static private Format getHeaderFormat(Headers headers) {
String format = headers.get(HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT);
if (format != null && format.length() > 0) {
return Format.valueOf(format.toUpperCase());
}
String contentType = headers.get(HEADER_CONTENT_TYPE);
if (contentType != null && contentType.length() > 0) {
return Format.getFromMimetype(contentType);
}
return null;
}

static private Format getHeaderFormat(BodyPart part) {
String contentDisposition = getHeader(part, HEADER_CONTENT_DISPOSITION);
String formatRegex = ".* format=(text|binary|xml|json).*";
String format = getHeader(part, HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT);
String contentType = getHeader(part, HEADER_CONTENT_TYPE);
if (format != null && format.length() > 0) {
return Format.valueOf(format.toUpperCase());
} else if (contentDisposition != null && contentDisposition.matches(formatRegex)) {
format = contentDisposition.replaceFirst("^.*" + formatRegex + ".*$", "$1");
return Format.valueOf(format.toUpperCase());
} else if (contentType != null && contentType.length() > 0) {
return Format.getFromMimetype(contentType);
}
return null;
}

static private void updateMimetype(ContentDescriptor descriptor,
Headers headers) {
updateMimetype(descriptor, getHeaderMimetype(headers.get(HEADER_CONTENT_TYPE)));
}

static private void updateMimetype(ContentDescriptor descriptor, String mimetype) {
if (mimetype != null) {
descriptor.setMimetype(mimetype);
}
}

static private String getHeader(Map<String, List<String>> headers, String name) {
List<String> values = headers.get(name);
if (values != null && values.size() > 0) {
return values.get(0);
}
return null;
}

static private String getHeader(BodyPart part, String name) {
if (part == null) throw new MarkLogicInternalException("part must not be null");
try {
String[] values = part.getHeader(name);
if (values != null && values.length > 0) {
return values[0];
}
return null;
} catch (MessagingException e) {
throw new MarkLogicIOException(e);
}
}

static private String getHeaderMimetype(String contentType) {
if (contentType != null) {
int offset = contentType.indexOf(";");
String mimetype = (offset == -1) ? contentType : contentType.substring(0, offset);
// TODO: if "; charset=foo" set character set
if (mimetype != null && mimetype.length() > 0) {
return mimetype;
}
}
return null;
}

static private void updateLength(ContentDescriptor descriptor,
Headers headers) {
updateLength(descriptor, getHeaderLength(headers.get(HEADER_CONTENT_LENGTH)));
}

static private void updateLength(ContentDescriptor descriptor, long length) {
descriptor.setByteLength(length);
}

static private void updateServerTimestamp(ContentDescriptor descriptor,
Headers headers) {
updateServerTimestamp(descriptor, getHeaderServerTimestamp(headers));
}

static private long getHeaderServerTimestamp(Headers headers) {
return Utilities.parseLong(headers.get(HEADER_ML_EFFECTIVE_TIMESTAMP));
}

static private void updateServerTimestamp(ContentDescriptor descriptor, long timestamp) {
if (descriptor instanceof HandleImplementation) {
if (descriptor != null && timestamp != -1) {
((HandleImplementation) descriptor).setResponseServerTimestamp(timestamp);
}
}
}

static private long getHeaderLength(String length) {
return Utilities.parseLong(length, ContentDescriptor.UNKNOWN_LENGTH);
}

static private String getHeaderUri(BodyPart part) {
try {
if (part == null) {
return null;
}

try {
String filename = part.getFileName();
if (filename != null) {
return filename;
}
} catch (ParseException e) {
// Jakarta Mail's parser failed due to malformed Content-Disposition header.
// Check if MarkLogic sent a malformed "format=" parameter at the end, which violates RFC 2183.
String contentDisposition = getHeader(part, "Content-Disposition");
if (contentDisposition != null && contentDisposition.matches(".*;\\s*format\\s*=\\s*$")) {
// Remove the trailing "; format=" to fix the malformed header
String cleaned = contentDisposition.replaceFirst(";\\s*format\\s*=\\s*$", "").trim();
logger.debug("Removed trailing 'format=' from malformed Content-Disposition header: {} -> {}", contentDisposition, cleaned);
return extractFilenameFromContentDisposition(cleaned);
}
throw e;
}

return null;
} catch (MessagingException e) {
throw new MarkLogicIOException(e);
}
}

static private String extractFilenameFromContentDisposition(String contentDisposition) {
if (contentDisposition == null) {
return null;
}
try {
// Use Jakarta Mail's ContentDisposition parser to extract the filename parameter. This is the class
// that throws an error when "format=" exists in the value, but that has been removed already.
ContentDisposition cd = new ContentDisposition(contentDisposition);
return cd.getParameter("filename");
} catch (ParseException e) {
logger.warn("Failed to parse cleaned Content-Disposition header: {}; cause: {}",
contentDisposition, e.getMessage());
return null;
}
}

static private void updateVersion(DocumentDescriptor descriptor, Headers headers) {
updateVersion(descriptor, extractVersion(headers.get(HEADER_ETAG)));
}

static private void updateVersion(DocumentDescriptor descriptor, String header) {
updateVersion(descriptor, extractVersion(header));
}

static private void updateVersion(DocumentDescriptor descriptor, long version) {
descriptor.setVersion(version);
}

static private long extractVersion(String header) {
if (header != null && header.length() > 0) {
// trim the double quotes
return Long.parseLong(header.substring(1, header.length() - 1));
}
return DocumentDescriptor.UNKNOWN_VERSION;
}

static private Request.Builder addVersionHeader(DocumentDescriptor desc, Request.Builder requestBldr, String name) {
if (desc != null &&
desc instanceof DocumentDescriptorImpl &&
Expand All @@ -1869,8 +1682,8 @@ static private <R extends AbstractReadHandle> R updateHandle(BodyPart part, R ha
HandleImplementation handleBase = HandleAccessor.as(handle);

updateFormat(handleBase, getHeaderFormat(part));
updateMimetype(handleBase, getHeaderMimetype(OkHttpServices.getHeader(part, HEADER_CONTENT_TYPE)));
updateLength(handleBase, getHeaderLength(OkHttpServices.getHeader(part, HEADER_CONTENT_LENGTH)));
updateMimetype(handleBase, getHeaderMimetype(getHeader(part, HEADER_CONTENT_TYPE)));
updateLength(handleBase, getHeaderLength(getHeader(part, HEADER_CONTENT_LENGTH)));
handleBase.receiveContent(getEntity(part, handleBase.receiveAs()));

return handle;
Expand Down Expand Up @@ -4436,6 +4249,7 @@ static class OkHttpResult {
private Format format;
private String mimetype;
private long length;
private long version = DocumentDescriptor.UNKNOWN_VERSION;

OkHttpResult(RequestLogger reqlog, BodyPart part) {
this.reqlog = reqlog;
Expand Down Expand Up @@ -4490,6 +4304,11 @@ public long getLength() {
return length;
}

public long getVersion() {
extractHeaders();
return version;
}

public String getHeader(String name) {
extractHeaders();
List<String> values = headers.get(name);
Expand All @@ -4512,9 +4331,10 @@ private void extractHeaders() {
headers.put(header.getName(), header.getValue());
}
format = getHeaderFormat(part);
mimetype = getHeaderMimetype(OkHttpServices.getHeader(part, HEADER_CONTENT_TYPE));
length = getHeaderLength(OkHttpServices.getHeader(part, HEADER_CONTENT_LENGTH));
mimetype = getHeaderMimetype(OkHttpUtil.getHeader(part, HEADER_CONTENT_TYPE));
length = getHeaderLength(OkHttpUtil.getHeader(part, HEADER_CONTENT_LENGTH));
uri = getHeaderUri(part);
version = getHeaderVersion(part);
extractedHeaders = true;
} catch (MessagingException e) {
throw new MarkLogicIOException(e);
Expand Down Expand Up @@ -4688,7 +4508,7 @@ public DocumentDescriptor getDescriptor() {
updateFormat(descriptor, getFormat());
updateMimetype(descriptor, getMimetype());
updateLength(descriptor, getLength());
updateVersion(descriptor, content.getHeader(HEADER_ETAG));
updateVersion(descriptor, content.getVersion());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seeing this existing code here makes me think that Copilot's suggestion is a good one about using the ETag as a fallback.

return descriptor;
}

Expand Down
Loading
Loading