diff --git a/HOCON.md b/HOCON.md index ccb0e351..dfe46799 100644 --- a/HOCON.md +++ b/HOCON.md @@ -1111,6 +1111,10 @@ The details: "2" then the resulting array would have indices "0" and "1", i.e. missing indices in the object are eliminated. +## MIME Type + +Use "application/hocon" for Content-Type. + ## API Recommendations Implementations of HOCON ideally follow certain conventions and diff --git a/config/src/main/java/com/typesafe/config/impl/Parseable.java b/config/src/main/java/com/typesafe/config/impl/Parseable.java index 87356017..35c75001 100644 --- a/config/src/main/java/com/typesafe/config/impl/Parseable.java +++ b/config/src/main/java/com/typesafe/config/impl/Parseable.java @@ -18,6 +18,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; @@ -104,6 +105,10 @@ public abstract class Parseable implements ConfigParseable { return null; } + ConfigSyntax contentType() { + return null; + } + ConfigParseable relativeTo(String filename) { // fall back to classpath; we treat the "filename" as absolute // (don't add a package name in front), @@ -186,8 +191,23 @@ public abstract class Parseable implements ConfigParseable { protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) throws IOException { Reader reader = reader(); + + // after reader() we will have loaded the Content-Type. + ConfigSyntax contentType = contentType(); + + ConfigParseOptions optionsWithContentType; + if (contentType != null) { + if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) + trace("Overriding syntax " + finalOptions.getSyntax() + + " with Content-Type which specified " + contentType); + + optionsWithContentType = finalOptions.setSyntax(contentType); + } else { + optionsWithContentType = finalOptions; + } + try { - return rawParseValue(reader, origin, finalOptions); + return rawParseValue(reader, origin, optionsWithContentType); } finally { reader.close(); } @@ -240,12 +260,16 @@ public abstract class Parseable implements ConfigParseable { } private static Reader readerFromStream(InputStream input) { + return readerFromStream(input, "UTF-8"); + } + + private static Reader readerFromStream(InputStream input, String encoding) { try { // well, this is messed up. If we aren't going to close // the passed-in InputStream then we have no way to // close these readers. So maybe we should not have an // InputStream version, only a Reader version. - Reader reader = new InputStreamReader(input, "UTF-8"); + Reader reader = new InputStreamReader(input, encoding); return new BufferedReader(reader); } catch (UnsupportedEncodingException e) { throw new ConfigException.BugOrBroken("Java runtime does not support UTF-8", e); @@ -390,6 +414,7 @@ public abstract class Parseable implements ConfigParseable { private final static class ParseableURL extends Parseable { final private URL input; + private String contentType = null; ParseableURL(URL input, ConfigParseOptions options) { this.input = input; @@ -400,7 +425,22 @@ public abstract class Parseable implements ConfigParseable { protected Reader reader() throws IOException { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from a URL: " + input.toExternalForm()); - InputStream stream = input.openStream(); + URLConnection connection = input.openConnection(); + connection.connect(); + + // save content type for later + contentType = connection.getContentType(); + if (contentType != null) { + if (ConfigImpl.traceLoadsEnabled()) + trace("URL sets Content-Type: '" + contentType + "'"); + contentType = contentType.trim(); + int semi = contentType.indexOf(';'); + if (semi >= 0) + contentType = contentType.substring(0, semi); + } + + InputStream stream = connection.getInputStream(); + return readerFromStream(stream); } @@ -409,6 +449,25 @@ public abstract class Parseable implements ConfigParseable { return syntaxFromExtension(input.getPath()); } + @Override + ConfigSyntax contentType() { + if (contentType != null) { + if (contentType.equals("application/json")) + return ConfigSyntax.JSON; + else if (contentType.equals("text/x-java-properties")) + return ConfigSyntax.PROPERTIES; + else if (contentType.equals("application/hocon")) + return ConfigSyntax.CONF; + else { + if (ConfigImpl.traceLoadsEnabled()) + trace("'" + contentType + "' isn't a known content type"); + return null; + } + } else { + return null; + } + } + @Override ConfigParseable relativeTo(String filename) { URL url = relativeTo(input, filename);