Skip to content

Commit

Permalink
CXF-8799: Unxepected URLEncode in MTOM Content-Id
Browse files Browse the repository at this point in the history
(cherry picked from commit ed102a2)
(cherry picked from commit a5ad0b1)
  • Loading branch information
reta authored and dkulp committed Dec 7, 2022
1 parent b31b23c commit e3adb01
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 60 deletions.
Expand Up @@ -242,7 +242,14 @@ private void writeHeaders(String contentType, String attachmentId,
//
String[] address = attachmentId.split("@", 2);
if (address.length == 2) {
writer.write(attachmentId);
// See please AttachmentUtil::createContentID, the domain part is URL encoded
final String decoded = tryDecode(address[1], StandardCharsets.UTF_8);
// If the domain part is encoded, decode it
if (!decoded.equalsIgnoreCase(address[1])) {
writer.write(address[0] + "@" + decoded);
} else {
writer.write(attachmentId);
}
} else {
writer.write(URLEncoder.encode(attachmentId, StandardCharsets.UTF_8.name()));
}
Expand Down Expand Up @@ -378,4 +385,14 @@ public void setXop(boolean xop) {
private static String decode(String s, Charset charset) throws UnsupportedEncodingException {
return URLDecoder.decode(s.replaceAll("([^%])[+]", "$1%2B"), charset.name());
}

// Try to decode the string assuming the decoding may fail, the original string is going to
// be returned in this case.
private static String tryDecode(String s, Charset charset) {
try {
return decode(s, charset);
} catch (IllegalArgumentException ex) {
return s;
}
}
}
63 changes: 20 additions & 43 deletions core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
Expand Up @@ -45,7 +45,6 @@
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.activation.CommandInfo;
import javax.activation.CommandMap;
Expand Down Expand Up @@ -82,15 +81,8 @@ public final class AttachmentUtil {
private static final Random BOUND_RANDOM = new Random();
private static final CommandMap DEFAULT_COMMAND_MAP = CommandMap.getDefaultCommandMap();
private static final MailcapCommandMap COMMAND_MAP = new EnhancedMailcapCommandMap();

/**
* Yet <a href="https://datatracker.ietf.org/doc/html/rfc822#appendix-D">RFC-822 Appendix D (ALPHABETICAL LISTING OF SYNTAX RULES)</a>
* allows more characters in domain-literal,
* this regex is valid to check that the parsed domain is compliant,
* although it is stricter
*/
private static final Pattern ALPHA_NUMERIC_DOMAIN_PATTERN = Pattern.compile("^\\w+(\\.\\w+)*$");



static final class EnhancedMailcapCommandMap extends MailcapCommandMap {
@Override
public synchronized DataContentHandler createDataContentHandler(
Expand Down Expand Up @@ -240,49 +232,24 @@ public static void setStreamedAttachmentProperties(Message message, CachedOutput
}
}

/**
* Creates Content ID from {@link #ATT_UUID} and given namespace
* <p>
* Example:
* <pre>6976d00d-740c-48ed-b63d-8c56707544f7-1@example.com</pre>
* <p>
* <a href="https://datatracker.ietf.org/doc/html/rfc2392#section-2">RFC-2392 Section 2 (The MID and CID URL Schemes)</a>
* specifies Content ID as:
* <pre>
* content-id = url-addr-spec
* url-addr-spec = addr-spec ; URL encoding of RFC 822 addr-spec
* </pre>
* <a href="https://datatracker.ietf.org/doc/html/rfc822#appendix-D">RFC-822 Appendix D (ALPHABETICAL LISTING OF SYNTAX RULES)</a>:
* <pre>
* addr-spec = local-part "@" domain ; global address
* </pre>
*
* @param ns namespace. If null, falls back to "cxf.apache.org"
* @return Content ID
*/
public static String createContentID(String ns) {
public static String createContentID(String ns) throws UnsupportedEncodingException {
// tend to change
String cid = "cxf.apache.org";
if (ns != null && !ns.isEmpty()) {
if (isAlphaNumericDomain(ns)) {
cid = ns;
}
try {
URI uri = new URI(ns);
String host = uri.getHost();
if (host != null && isAlphaNumericDomain(host)) {
if (host != null) {
cid = host;
} else {
cid = ns;
}
} catch (Exception e) {
// Could not parse domain => use fallback value
cid = ns;
}
}
return ATT_UUID + '-' + Integer.toString(COUNTER.incrementAndGet()) + '@'
+ URLEncoder.encode(cid, StandardCharsets.UTF_8);
}

private static boolean isAlphaNumericDomain(String string) {
return ALPHA_NUMERIC_DOMAIN_PATTERN.matcher(string).matches();
+ URLEncoder.encode(cid, StandardCharsets.UTF_8.name());
}

public static String getUniqueBoundaryValue() {
Expand Down Expand Up @@ -522,7 +489,12 @@ public static Attachment createMtomAttachment(boolean isXop, String mimeType, St
source.setContentType(mimeType);
DataHandler handler = new DataHandler(source);

String id = AttachmentUtil.createContentID(elementNS);
String id;
try {
id = AttachmentUtil.createContentID(elementNS);
} catch (UnsupportedEncodingException e) {
throw new Fault(e);
}
AttachmentImpl att = new AttachmentImpl(id, handler);
att.setXOP(isXop);
return att;
Expand Down Expand Up @@ -557,7 +529,12 @@ public static Attachment createMtomAttachmentFromDH(
// ignore, just do the normal attachment thing
}

String id = AttachmentUtil.createContentID(elementNS);
String id;
try {
id = AttachmentUtil.createContentID(elementNS);
} catch (UnsupportedEncodingException e) {
throw new Fault(e);
}
AttachmentImpl att = new AttachmentImpl(id, handler);
if (!StringUtils.isEmpty(handler.getName())) {
//set Content-Disposition attachment header if filename isn't null
Expand Down
Expand Up @@ -183,6 +183,12 @@ public void testMessageMTOMCid() throws Exception {
doTestMessageMTOM("cid:http%3A%2F%2Fcxf.apache.org%2F", "<http://cxf.apache.org/>");
}

@Test
public void testMessageMTOMCidEncoded() throws Exception {
doTestMessageMTOM("cid:cxf@[2001%3A0db8%3A11a3%3A09d7%3A1f34%3A8a2e%3A07a0%3A765d]",
"<cxf@[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]>");
}

@Test
public void testMessageMTOMUrlDecoded() throws Exception {
doTestMessageMTOM("test+me.xml", "<test%2Bme.xml>");
Expand Down
Expand Up @@ -20,30 +20,26 @@

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;

import org.junit.Ignore;
import org.junit.Test;

import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

public class AttachmentUtilTest {

// Yet RFC822 allows more characters in domain-literal,
// this regex is enough to check that the fallback domain is compliant
public static final String CONTENT_ID_WITH_ALPHA_NUMERIC_DOMAIN_PATTERN = ".+@\\w+(\\.\\w+)*";

@Test
public void testContendDispositionFileNameNoQuotes() {
assertEquals("a.txt",
Expand Down Expand Up @@ -141,15 +137,15 @@ public void testCreateContentID() throws Exception {
assertNotEquals(AttachmentUtil.createContentID(null), AttachmentUtil.createContentID(null));
}


@Test
public void testCreateContentIDWithNullDomainNamePassed() {
public void testCreateContentIDWithNullDomainNamePassed() throws UnsupportedEncodingException {
String actual = AttachmentUtil.createContentID(null);

assertThat(actual, matchesPattern(CONTENT_ID_WITH_ALPHA_NUMERIC_DOMAIN_PATTERN));
assertThat(actual, endsWith("@cxf.apache.org"));
}

@Test
public void testCreateContentIDWithDomainNamePassed() {
public void testCreateContentIDWithDomainNamePassed() throws UnsupportedEncodingException {
String domain = "subdomain.example.com";

String actual = AttachmentUtil.createContentID(domain);
Expand All @@ -158,7 +154,7 @@ public void testCreateContentIDWithDomainNamePassed() {
}

@Test
public void testCreateContentIDWithUrlPassed() {
public void testCreateContentIDWithUrlPassed() throws UnsupportedEncodingException {
String domain = "subdomain.example.com";
String url = "https://" + domain + "/a/b/c";

Expand All @@ -168,7 +164,7 @@ public void testCreateContentIDWithUrlPassed() {
}

@Test
public void testCreateContentIDWithIPv4BasedUrlPassed() {
public void testCreateContentIDWithIPv4BasedUrlPassed() throws UnsupportedEncodingException {
String domain = "127.0.0.1";
String url = "https://" + domain + "/a/b/c";

Expand All @@ -178,13 +174,12 @@ public void testCreateContentIDWithIPv4BasedUrlPassed() {
}

@Test
public void testCreateContentIDWithIPv6BasedUrlPassed() {
public void testCreateContentIDWithIPv6BasedUrlPassed() throws UnsupportedEncodingException {
String domain = "[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]";
String url = "http://" + domain + "/a/b/c";

String actual = AttachmentUtil.createContentID(url);

assertThat(actual, matchesPattern(CONTENT_ID_WITH_ALPHA_NUMERIC_DOMAIN_PATTERN));
assertThat(actual, endsWith("@" + URLEncoder.encode(domain, StandardCharsets.UTF_8)));
}

private CachedOutputStream testSetStreamedAttachmentProperties(final String property, final Object value)
Expand Down

0 comments on commit e3adb01

Please sign in to comment.