diff --git a/java-api/src/org/servalproject/servaldna/AbstractId.java b/java-api/src/org/servalproject/servaldna/AbstractId.java index 0700270c..1b40a432 100644 --- a/java-api/src/org/servalproject/servaldna/AbstractId.java +++ b/java-api/src/org/servalproject/servaldna/AbstractId.java @@ -51,7 +51,7 @@ public abstract class AbstractId { return binary; } - public abstract String getMimeType(); + public abstract ContentType getMimeType(); public AbstractId(String hex) throws InvalidHexException { if (hex==null) diff --git a/java-api/src/org/servalproject/servaldna/AbstractJsonList.java b/java-api/src/org/servalproject/servaldna/AbstractJsonList.java index 4a51aa77..2f38ab0b 100644 --- a/java-api/src/org/servalproject/servaldna/AbstractJsonList.java +++ b/java-api/src/org/servalproject/servaldna/AbstractJsonList.java @@ -74,10 +74,15 @@ public abstract class AbstractJsonList { httpConnection = httpConnector.newServalDHttpConnection(request.verb, request.url); httpConnection.connect(); - if ("application/json".equals(httpConnection.getContentType())){ - json = new JSONTokeniser( - (httpConnection.getResponseCode() >= 300) ? - httpConnection.getErrorStream() : httpConnection.getInputStream()); + try { + ContentType contentType = new ContentType(httpConnection.getContentType()); + if (ContentType.applicationJson.matches(contentType)){ + json = new JSONTokeniser( + (httpConnection.getResponseCode() >= 300) ? + httpConnection.getErrorStream() : httpConnection.getInputStream()); + } + } catch (ContentType.ContentTypeException e) { + throw new ServalDInterfaceException("malformed HTTP Content-Type: " + httpConnection.getContentType(), e); } if (httpConnection.getResponseCode()!=200){ diff --git a/java-api/src/org/servalproject/servaldna/BundleId.java b/java-api/src/org/servalproject/servaldna/BundleId.java index 11315351..d03941a5 100644 --- a/java-api/src/org/servalproject/servaldna/BundleId.java +++ b/java-api/src/org/servalproject/servaldna/BundleId.java @@ -37,8 +37,9 @@ public class BundleId extends SigningKey { super(binary); } + private static ContentType mimeType = ContentType.fromConstant("rhizome/bid; format=hex"); @Override - public String getMimeType() { - return "rhizome/bid"; + public ContentType getMimeType() { + return mimeType; } } diff --git a/java-api/src/org/servalproject/servaldna/BundleKey.java b/java-api/src/org/servalproject/servaldna/BundleKey.java index ac3951a9..b7dcd08c 100644 --- a/java-api/src/org/servalproject/servaldna/BundleKey.java +++ b/java-api/src/org/servalproject/servaldna/BundleKey.java @@ -30,9 +30,10 @@ public class BundleKey extends AbstractId { return 32; } + private static ContentType mimeType = ContentType.fromConstant("rhizome/bundlekey; format=hex"); @Override - public String getMimeType() { - return "rhizome/bundlekey"; + public ContentType getMimeType() { + return mimeType; } public BundleKey(String hex) throws InvalidHexException { diff --git a/java-api/src/org/servalproject/servaldna/BundleSecret.java b/java-api/src/org/servalproject/servaldna/BundleSecret.java index 7e6e5d22..116ffd8c 100644 --- a/java-api/src/org/servalproject/servaldna/BundleSecret.java +++ b/java-api/src/org/servalproject/servaldna/BundleSecret.java @@ -42,8 +42,9 @@ public class BundleSecret extends AbstractId { super(binary); } + private static ContentType mimeType = ContentType.fromConstant("rhizome/bundlesecret; format=hex"); @Override - public String getMimeType() { - return "rhizome/bundlesecret"; + public ContentType getMimeType() { + return mimeType; } } diff --git a/java-api/src/org/servalproject/servaldna/ContentType.java b/java-api/src/org/servalproject/servaldna/ContentType.java new file mode 100644 index 00000000..0cf88acd --- /dev/null +++ b/java-api/src/org/servalproject/servaldna/ContentType.java @@ -0,0 +1,122 @@ +package org.servalproject.servaldna; + +import java.util.HashMap; +import java.util.Map; + +/** + * Android doesn't include javax.activation.MimeType, so we have to implement it ourselves + */ + +public class ContentType { + public final String type; + public final String subType; + public final Map parameters; + + // a few common content types to match against + public static ContentType textPlain = fromConstant("text/plain; charset=utf-8"); + public static ContentType applicationJson = fromConstant("application/json"); + public static ContentType applicationOctetStream = fromConstant("application/octet-stream"); + + public class ContentTypeException extends Exception{ + ContentTypeException(String message) { + super(message); + } + } + + public ContentType(String contentType) throws ContentTypeException { + int delim = contentType.indexOf(';'); + if (delim < 0) + delim = contentType.length(); + int slash = contentType.indexOf('/'); + if (slash<0 || slash >delim) + throw new ContentTypeException("Failed to parse "+contentType); + type = contentType.substring(0,slash).trim().toLowerCase(); + subType = contentType.substring(slash+1,delim).trim().toLowerCase(); + parameters = new HashMap<>(); + while(delim < contentType.length()){ + int eq = contentType.indexOf('=',delim); + String name = contentType.substring(delim+1,eq).trim().toLowerCase(); + String value; + if (contentType.charAt(eq+1)=='"'){ + delim = eq+1; + StringBuilder sb = new StringBuilder(); + while(true){ + if (delim >= contentType.length()) + throw new ContentTypeException("Failed to parse "+contentType); + char c=contentType.charAt(delim++); + if (c == '"') + break; + if (c == '\\') { + if (delim >= contentType.length()) + throw new ContentTypeException("Failed to parse "+contentType); + c = contentType.charAt(delim++); + } + sb.append(c); + } + value = sb.toString(); + while(delim < contentType.length()){ + char c=contentType.charAt(delim++); + if (c==';') + break; + if (c!=' ') + throw new ContentTypeException("Failed to parse "+contentType); + } + }else{ + delim = contentType.indexOf(';',eq); + if (delim <0) + delim = contentType.length(); + value = contentType.substring(eq+1,delim).trim(); + } + parameters.put(name, value); + } + } + + public boolean matches(ContentType other) { + if (other==null) + return false; + if (type.equals(other.type) && + (subType.equals(other.subType) || subType.equals("*"))){ + for(Map.Entry e: parameters.entrySet()){ + if (!e.getValue().equals(other.parameters.get(e.getKey()))) + return false; + } + return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb + .append(type) + .append('/') + .append(subType); + if (!parameters.isEmpty()){ + for(Map.Entry e: parameters.entrySet()){ + sb + .append(';') + .append(e.getKey()) + .append('='); + String value = e.getValue(); + if (value.indexOf(';')>0 || value.indexOf('"')>0){ + sb + .append('"') + .append(value.replace("\"","\\\"")) + .append('"'); + }else{ + sb.append(value); + } + } + } + return sb.toString(); + } + + public static ContentType fromConstant(String contentType){ + try { + return new ContentType(contentType); + } catch (ContentTypeException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/java-api/src/org/servalproject/servaldna/FileHash.java b/java-api/src/org/servalproject/servaldna/FileHash.java index 83471986..6dd9ecca 100644 --- a/java-api/src/org/servalproject/servaldna/FileHash.java +++ b/java-api/src/org/servalproject/servaldna/FileHash.java @@ -32,9 +32,10 @@ public class FileHash extends AbstractId { return BINARY_SIZE; } + private static ContentType mimeType = ContentType.fromConstant("rhizome/filehash; format=hex"); @Override - public String getMimeType() { - return "rhizome/filehash"; + public ContentType getMimeType() { + return mimeType; } public FileHash(String hex) throws InvalidHexException { diff --git a/java-api/src/org/servalproject/servaldna/PostHelper.java b/java-api/src/org/servalproject/servaldna/PostHelper.java index 795c259e..91578673 100644 --- a/java-api/src/org/servalproject/servaldna/PostHelper.java +++ b/java-api/src/org/servalproject/servaldna/PostHelper.java @@ -77,7 +77,7 @@ public class PostHelper { sb.append('"'); } - protected void writeHeading(String name, String filename, String type, String encoding) + protected void writeHeading(String name, String filename, ContentType type, String encoding) { StringBuilder sb = new StringBuilder(); sb.append("\r\n--").append(boundary).append("\r\n"); @@ -88,7 +88,7 @@ public class PostHelper { quoteString(sb, filename); } sb.append("\r\n"); - sb.append("Content-Type: ").append(type).append("\r\n"); + sb.append("Content-Type: ").append(type.toString()).append("\r\n"); if (encoding!=null) sb.append("Content-Transfer-Encoding: ").append(encoding).append("\r\n"); sb.append("\r\n"); @@ -96,17 +96,17 @@ public class PostHelper { } public void writeField(String name, String value){ - writeHeading(name, null, "text/plain; charset=utf-8", null); + writeHeading(name, null, ContentType.textPlain, null); writer.print(value); } public void writeField(String name, AbstractId value){ - writeHeading(name, null, value.getMimeType() + "; format=hex", null); + writeHeading(name, null, value.getMimeType(), null); writer.print(value.toHex()); } public OutputStream beginFileField(String name, String filename){ - writeHeading(name, filename, "application/octet-stream", "binary"); + writeHeading(name, filename, ContentType.applicationOctetStream, "binary"); writer.flush(); return output; } @@ -119,13 +119,13 @@ public class PostHelper { output.write(buffer, 0, n); } - public void writeField(String name, String type, byte value[]) throws IOException { + public void writeField(String name, ContentType type, byte value[]) throws IOException { writeHeading(name, null, type, "binary"); writer.flush(); output.write(value); } - public void writeField(String name, String type, byte value[], int offset, int length) throws IOException { + public void writeField(String name, ContentType type, byte value[], int offset, int length) throws IOException { writeHeading(name, null, type, "binary"); writer.flush(); output.write(value, offset, length); diff --git a/java-api/src/org/servalproject/servaldna/SigningKey.java b/java-api/src/org/servalproject/servaldna/SigningKey.java index 468c0140..297468ae 100644 --- a/java-api/src/org/servalproject/servaldna/SigningKey.java +++ b/java-api/src/org/servalproject/servaldna/SigningKey.java @@ -42,9 +42,10 @@ public class SigningKey extends AbstractId { return BINARY_SIZE; } + private static ContentType mimeType = ContentType.fromConstant("serval/identity; format=hex"); @Override - public String getMimeType() { - return "serval/identity"; + public ContentType getMimeType() { + return mimeType; } @Override diff --git a/java-api/src/org/servalproject/servaldna/SubscriberId.java b/java-api/src/org/servalproject/servaldna/SubscriberId.java index d99d1088..87790b18 100644 --- a/java-api/src/org/servalproject/servaldna/SubscriberId.java +++ b/java-api/src/org/servalproject/servaldna/SubscriberId.java @@ -31,9 +31,10 @@ public class SubscriberId extends AbstractId { return BINARY_SIZE; } + private static ContentType mimeType = ContentType.fromConstant("serval/sid; format=hex"); @Override - public String getMimeType() { - return "serval/sid"; + public ContentType getMimeType() { + return mimeType; } public SubscriberId(String hex) throws InvalidHexException { diff --git a/java-api/src/org/servalproject/servaldna/keyring/KeyringCommon.java b/java-api/src/org/servalproject/servaldna/keyring/KeyringCommon.java index 124d85ee..fa57493f 100644 --- a/java-api/src/org/servalproject/servaldna/keyring/KeyringCommon.java +++ b/java-api/src/org/servalproject/servaldna/keyring/KeyringCommon.java @@ -23,6 +23,7 @@ package org.servalproject.servaldna.keyring; import org.servalproject.json.JSONInputException; import org.servalproject.json.JSONTokeniser; +import org.servalproject.servaldna.ContentType; import org.servalproject.servaldna.ServalDHttpConnectionFactory; import org.servalproject.servaldna.ServalDInterfaceException; import org.servalproject.servaldna.ServalDNotImplementedException; @@ -43,6 +44,7 @@ public class KeyringCommon { public static class Status { + ContentType contentType; InputStream input_stream; JSONTokeniser json; public int http_status_code; @@ -61,14 +63,20 @@ public class KeyringCommon Status status = new Status(); status.http_status_code = conn.getResponseCode(); status.http_status_message = conn.getResponseMessage(); + try { + status.contentType = new ContentType(conn.getContentType()); + } catch (ContentType.ContentTypeException e) { + throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType(),e); + } + for (int code: expected_response_codes) { if (status.http_status_code == code) { status.input_stream = conn.getInputStream(); return status; } } - if (!conn.getContentType().equals("application/json")) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + if (!ContentType.applicationJson.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType); if (status.http_status_code >= 300) { status.json = new JSONTokeniser(conn.getErrorStream()); decodeRestfulStatus(status); @@ -97,8 +105,6 @@ public class KeyringCommon protected static Status receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException { Status status = receiveResponse(conn, expected_response_codes); - if (!conn.getContentType().equals("application/json")) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); status.json = new JSONTokeniser(status.input_stream); return status; } diff --git a/java-api/src/org/servalproject/servaldna/meshms/MeshMSCommon.java b/java-api/src/org/servalproject/servaldna/meshms/MeshMSCommon.java index cdbce1ae..9adb46a0 100644 --- a/java-api/src/org/servalproject/servaldna/meshms/MeshMSCommon.java +++ b/java-api/src/org/servalproject/servaldna/meshms/MeshMSCommon.java @@ -23,6 +23,7 @@ package org.servalproject.servaldna.meshms; import org.servalproject.json.JSONInputException; import org.servalproject.json.JSONTokeniser; +import org.servalproject.servaldna.ContentType; import org.servalproject.servaldna.PostHelper; import org.servalproject.servaldna.ServalDFailureException; import org.servalproject.servaldna.ServalDHttpConnectionFactory; @@ -44,18 +45,28 @@ public class MeshMSCommon protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, MeshMSException { - if (!"application/json".equals(conn.getContentType())) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + JSONTokeniser json = null; + try { + ContentType contentType = new ContentType(conn.getContentType()); + if (ContentType.applicationJson.matches(contentType)){ + if (conn.getResponseCode()>=300) + json = new JSONTokeniser(conn.getErrorStream()); + else + json = new JSONTokeniser(conn.getInputStream()); + } + } catch (ContentType.ContentTypeException e) { + throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType()); + } for (int code: expected_response_codes) { if (conn.getResponseCode() == code) { - JSONTokeniser json = new JSONTokeniser(conn.getInputStream()); + if (json == null) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); return json; } } switch (conn.getResponseCode()) { case HttpURLConnection.HTTP_NOT_FOUND: case 419: // Authentication Timeout, for missing secret - JSONTokeniser json = new JSONTokeniser(conn.getErrorStream()); Status status = decodeRestfulStatus(json); throwRestfulResponseExceptions(status, conn.getURL()); throw new ServalDInterfaceException("unexpected MeshMS status = " + status.meshms_status_code + ", \"" + status.meshms_status_message + "\""); diff --git a/java-api/src/org/servalproject/servaldna/rhizome/RhizomeCommon.java b/java-api/src/org/servalproject/servaldna/rhizome/RhizomeCommon.java index b9bd11de..4da9371e 100644 --- a/java-api/src/org/servalproject/servaldna/rhizome/RhizomeCommon.java +++ b/java-api/src/org/servalproject/servaldna/rhizome/RhizomeCommon.java @@ -26,6 +26,7 @@ import org.servalproject.json.JSONTokeniser; import org.servalproject.servaldna.BundleId; import org.servalproject.servaldna.BundleKey; import org.servalproject.servaldna.BundleSecret; +import org.servalproject.servaldna.ContentType; import org.servalproject.servaldna.FileHash; import org.servalproject.servaldna.PostHelper; import org.servalproject.servaldna.ServalDFailureException; @@ -49,25 +50,12 @@ import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; import java.util.Enumeration; -import javax.activation.MimeType; -import javax.activation.MimeTypeParseException; public class RhizomeCommon { - public static final MimeType MIME_TYPE_RHIZOME_MANIFEST; - - static { - try { - MIME_TYPE_RHIZOME_MANIFEST = new MimeType("rhizome/manifest; format=text+binarysig"); - } - catch (MimeTypeParseException ex) { - throw new IllegalStateException(ex); - } - } - private static class Status { URL url; - String contentType; + ContentType contentType; InputStream input_stream; public int http_status_code; public String http_status_message; @@ -77,17 +65,6 @@ public class RhizomeCommon String payload_status_message; } - protected static boolean mimeTypeMatches(MimeType required, MimeType candidate) { - if (!candidate.match(required)) - return false; - for (Enumeration e = required.getParameters().getNames(); e.hasMoreElements(); ) { - String name = (String) e.nextElement(); - if (!required.getParameter(name).equals(candidate.getParameter(name))) - return false; - } - return true; - } - protected static Status receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException { int[] expected_response_codes = { expected_response_code }; @@ -98,7 +75,11 @@ public class RhizomeCommon { Status status = new Status(); status.url = conn.getURL(); - status.contentType = conn.getContentType(); + try { + status.contentType = new ContentType(conn.getContentType()); + } catch (ContentType.ContentTypeException e) { + throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType(), e); + } status.http_status_code = conn.getResponseCode(); status.http_status_message = conn.getResponseMessage(); for (int code: expected_response_codes) { @@ -107,8 +88,8 @@ public class RhizomeCommon return status; } } - if (!status.contentType.equals("application/json")) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + if (!ContentType.applicationJson.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType); if (status.http_status_code >= 300) { JSONTokeniser json = new JSONTokeniser(conn.getErrorStream()); @@ -149,8 +130,8 @@ public class RhizomeCommon protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException { Status status = receiveResponse(conn, expected_response_codes); - if (!conn.getContentType().equals("application/json")) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + if (!ContentType.applicationJson.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType); if (status.input_stream == null) throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message); return new JSONTokeniser(status.input_stream); @@ -255,26 +236,18 @@ public class RhizomeCommon case NEW: return null; case SAME: - try { - MimeType content_type = new MimeType(conn.getContentType()); - if (mimeTypeMatches(MIME_TYPE_RHIZOME_MANIFEST, content_type)) { - RhizomeManifest manifest = RhizomeManifest.fromTextFormat(status.input_stream); - BundleExtra extra = bundleExtraFromHeaders(conn); - return new RhizomeManifestBundle(manifest, extra.rowId, extra.insertTime, extra.author, extra.secret); - } - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + content_type); - } - catch (MimeTypeParseException ex) { - throw new ServalDInterfaceException("invalid HTTP Content-Type: " + conn.getContentType()); - } + if (!RhizomeManifest.MIME_TYPE.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType); + RhizomeManifest manifest = RhizomeManifest.fromTextFormat(status.input_stream); + BundleExtra extra = bundleExtraFromHeaders(conn); + return new RhizomeManifestBundle(manifest, extra.rowId, extra.insertTime, extra.author, extra.secret); case ERROR: throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL()); } } catch (RhizomeManifestParseException e) { throw new ServalDInterfaceException("malformed manifest from daemon", e); - } - finally { + } finally { if (status.input_stream != null) status.input_stream.close(); } @@ -310,8 +283,8 @@ public class RhizomeCommon } // FALL THROUGH case STORED: { - if (status.input_stream != null && !conn.getContentType().equals("application/octet-stream")) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + if (status.input_stream != null && !ContentType.applicationOctetStream.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType); RhizomeManifest manifest = manifestFromHeaders(conn); BundleExtra extra = bundleExtraFromHeaders(conn); RhizomePayloadRawBundle ret = new RhizomePayloadRawBundle(manifest, status.input_stream, extra.rowId, extra.insertTime, extra.author, extra.secret); @@ -386,8 +359,8 @@ public class RhizomeCommon } // FALL THROUGH case STORED: { - if (status.input_stream != null && !conn.getContentType().equals("application/octet-stream")) - throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + if (status.input_stream != null && !ContentType.applicationOctetStream.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType); RhizomeManifest manifest = manifestFromHeaders(conn); BundleExtra extra = bundleExtraFromHeaders(conn); RhizomePayloadBundle ret = new RhizomePayloadBundle(manifest, status.input_stream, extra.rowId, extra.insertTime, extra.author, extra.secret); @@ -484,9 +457,8 @@ public class RhizomeCommon decodeHeaderBundleStatus(status, conn); checkBundleStatus(status); - MimeType content_type = new MimeType(status.contentType); - if (!mimeTypeMatches(MIME_TYPE_RHIZOME_MANIFEST, content_type)) - throw new ServalDInterfaceException("unexpected HTTP Content-Type " + content_type + " from " + status.url + ", expecting " + MIME_TYPE_RHIZOME_MANIFEST); + if (!RhizomeManifest.MIME_TYPE.matches(status.contentType)) + throw new ServalDInterfaceException("unexpected HTTP Content-Type " + status.contentType + " from " + status.url); RhizomeManifest returned_manifest = RhizomeManifest.fromTextFormat(status.input_stream); BundleExtra extra = bundleExtraFromHeaders(conn); @@ -495,9 +467,6 @@ public class RhizomeCommon catch (RhizomeManifestParseException e) { throw new ServalDInterfaceException("malformed manifest from daemon", e); } - catch (MimeTypeParseException ex) { - throw new ServalDInterfaceException("invalid HTTP Content-Type: " + status.contentType); - } finally { if (status.input_stream != null) status.input_stream.close(); diff --git a/java-api/src/org/servalproject/servaldna/rhizome/RhizomeManifest.java b/java-api/src/org/servalproject/servaldna/rhizome/RhizomeManifest.java index 498be8fe..d085787c 100644 --- a/java-api/src/org/servalproject/servaldna/rhizome/RhizomeManifest.java +++ b/java-api/src/org/servalproject/servaldna/rhizome/RhizomeManifest.java @@ -35,6 +35,7 @@ import java.io.OutputStreamWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import org.servalproject.servaldna.AbstractId; +import org.servalproject.servaldna.ContentType; import org.servalproject.servaldna.SubscriberId; import org.servalproject.servaldna.BundleId; import org.servalproject.servaldna.FileHash; @@ -43,7 +44,7 @@ import org.servalproject.servaldna.BundleKey; public class RhizomeManifest { public final static int TEXT_FORMAT_MAX_SIZE = 8192; - public static final String MIME_TYPE = "rhizome/manifest; format=text+binarysig"; + public static final ContentType MIME_TYPE = ContentType.fromConstant("rhizome/manifest; format=text+binarysig"); // Core fields used for routing and expiry (cannot be null) public final BundleId id;