Skip to content

Commit

Permalink
- Ensured header 'cty' raw value reflects a compact form but getConte…
Browse files Browse the repository at this point in the history
…ntType() return value reflects a normalized value per per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10
  • Loading branch information
lhazlewood committed Aug 9, 2023
1 parent 7ed0b77 commit 0ac5ecc
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 12 deletions.
5 changes: 4 additions & 1 deletion impl/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.jsonwebtoken.impl;

import io.jsonwebtoken.Header;
import io.jsonwebtoken.impl.lang.CompactMediaTypeIdConverter;
import io.jsonwebtoken.impl.lang.Field;
import io.jsonwebtoken.impl.lang.Fields;
import io.jsonwebtoken.lang.Registry;
Expand All @@ -26,7 +27,9 @@
public class DefaultHeader extends FieldMap implements Header {

static final Field<String> TYPE = Fields.string(Header.TYPE, "Type");
static final Field<String> CONTENT_TYPE = Fields.string(Header.CONTENT_TYPE, "Content Type");
static final Field<String> CONTENT_TYPE = Fields.builder(String.class)
.setId(Header.CONTENT_TYPE).setName("Content Type")
.setConverter(CompactMediaTypeIdConverter.INSTANCE).build();
static final Field<String> ALGORITHM = Fields.string(Header.ALGORITHM, "Algorithm");
static final Field<String> COMPRESSION_ALGORITHM = Fields.string(Header.COMPRESSION_ALGORITHM, "Compression Algorithm");
@SuppressWarnings("DeprecatedIsStillUsed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CompactMediaTypeIdConverter;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.Functions;
import io.jsonwebtoken.impl.lang.Services;
Expand Down Expand Up @@ -325,7 +324,7 @@ public JwtBuilder content(byte[] content) {
public JwtBuilder content(byte[] content, String cty) {
Assert.notEmpty(content, "content byte array cannot be null or empty.");
Assert.hasText(cty, "Content Type String cannot be null or empty.");
cty = CompactMediaTypeIdConverter.INSTANCE.applyFrom(cty);
//cty = (String)CompactMediaTypeIdConverter.INSTANCE.applyTo(cty);
this.headerBuilder.contentType(cty);
return content(content);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public final class CompactMediaTypeIdConverter implements Converter<String, Obje

public static final Converter<String, Object> INSTANCE = new CompactMediaTypeIdConverter();

private static final String APP_MEDIA_TYPE_PREFIX = "application/";
private static final char FORWARD_SLASH = '/';

private static final String APP_MEDIA_TYPE_PREFIX = "application" + FORWARD_SLASH;

static String compactIfPossible(String cty) {
Assert.hasText(cty, "Value cannot be null or empty.");
Expand All @@ -31,7 +33,7 @@ static String compactIfPossible(String cty) {
// we can only use the compact form if no other '/' exists in the string
for (int i = cty.length() - 1; i >= APP_MEDIA_TYPE_PREFIX.length(); i--) {
char c = cty.charAt(i);
if (c == '/') {
if (c == FORWARD_SLASH) {
return cty; // found another '/', can't compact, so just return unmodified
}
}
Expand All @@ -51,6 +53,18 @@ public String applyFrom(Object o) {
Assert.notNull(o, "Value cannot be null.");
Assert.isInstanceOf(String.class, o, "Value must be a string.");
String s = (String) o;
return compactIfPossible(s);
//s = compactIfPossible(s);

// https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10:
//
// A recipient using the media type value MUST treat it as if
// "application/" were prepended to any "cty" value not containing a
// '/'.
//
if (s.indexOf(FORWARD_SLASH) < 0) {
s = APP_MEDIA_TYPE_PREFIX + s;
}

return s;
}
}
5 changes: 4 additions & 1 deletion impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ class JwtsTest {
String cty = "application/$subtype"
String compact = Jwts.builder().content(s.getBytes(StandardCharsets.UTF_8), cty).compact()
def jwt = Jwts.parser().enableUnsecured().build().parseContentJwt(compact)
assertEquals subtype, jwt.header.getContentType() // assert that the compact form was used
// assert raw value is compact form:
assertEquals subtype, jwt.header.get('cty')
// assert getter reflects normalized form per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10:
assertEquals cty, jwt.header.getContentType()
assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ class DefaultHeaderTest {
@Test
void testContentType() {
header = h([cty: 'bar'])
assertEquals 'bar', header.getContentType()
assertEquals 'bar', header.get('cty')
// Per per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10, the raw header should have a
// compact form, but application developers shouldn't have to check for that all the time, so our getter has
// the normalized form:
assertEquals 'bar', header.get('cty') // raw compact form
assertEquals 'application/bar', header.getContentType() // getter normalized form
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,10 @@ class DefaultJwtHeaderBuilderTest {
@Test
void testDeprecatedSetters() { // TODO: remove before 1.0
assertEquals 'foo', builder.setType('foo').build().getType()
assertEquals 'foo', builder.setContentType('foo').build().getContentType()

assertEquals 'foo', builder.setContentType('foo').build().get('cty') // compact form
assertEquals 'application/foo', builder.build().getContentType() // normalized form

assertEquals 'foo', builder.setCompressionAlgorithm('foo').build().getCompressionAlgorithm()
assertEquals 'foo', builder.setKeyId('foo').build().getKeyId()
assertEquals 'foo', builder.setAlgorithm('foo').build().getAlgorithm()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ class CompactMediaTypeIdConverterTest {
void testNonApplicationMediaType() {
String cty = 'foo'
assertEquals cty, converter.applyTo(cty)
assertEquals cty, converter.applyFrom(cty)
// must auto-prepend 'application/' if no slash in cty value
// per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10:
assertEquals "application/$cty" as String, converter.applyFrom(cty)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,10 @@ class RFC7520Section5Test {
assertEquals alg.getId(), parsed.header.getAlgorithm()
assertEquals FIGURE_99, b64Url(parsed.header.getPbes2Salt())
assertEquals p2c, parsed.header.getPbes2Count()
assertEquals cty, parsed.header.getContentType()

assertEquals cty, parsed.header.get('cty') // compact form
assertEquals "application/$cty" as String, parsed.header.getContentType() // normalized form

assertEquals enc.getId(), parsed.header.getEncryptionAlgorithm()
assertEquals FIGURE_95, utf8(parsed.payload)
}
Expand Down

0 comments on commit 0ac5ecc

Please sign in to comment.