/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* !#
*/
package net.ontopia.net.data;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import net.ontopia.net.Base64Decoder;
import net.ontopia.net.Base64Encoder;
import net.ontopia.utils.OntopiaRuntimeException;
/**
* INTERNAL: A class for parsing and representing data URLs.
*/
public class DataURL {
private byte[] contents;
private int length;
private String mediaType;
private boolean base64;
public DataURL(URL url) throws IOException {
this(url.toExternalForm());
}
public DataURL(String rawurl) throws IOException {
// cut off "data:"
rawurl = rawurl.substring(5);
// parse the URL parameters
int semiPos = rawurl.indexOf(';');
int commaPos = rawurl.indexOf(',');
if (commaPos == -1)
throw new IOException("data URL syntax error: comma required");
mediaType = "text/plain";
base64 = false;
// data:" [ mediatype ] [ ";base64" ] "," data
if (commaPos > 0) {
// parse media type
if (semiPos > 0)
mediaType = rawurl.substring(0, semiPos);
else if (semiPos == -1)
mediaType = rawurl.substring(0, commaPos);
// FIXME: should really parse out mediatype param values here
// while (semiPos != -1 && semiPos < commaPos) {
// int next = rawurl.indexOf(';', semiPos + 1);
// }
// check content encoding
base64 = rawurl.substring(commaPos - 7, commaPos).equals(";base64");
}
if (base64)
decodeBase64(rawurl, commaPos + 1);
else
decodeURL(rawurl, commaPos + 1);
}
public int getContentLength() {
return length;
}
public String getMediaType() {
return mediaType;
}
public String getContentEncoding() {
if (base64)
return "base64";
else
return null;
}
public String getContents() {
try {
return new String(contents, 0, length, "iso-8859-1");
} catch (UnsupportedEncodingException e) {
throw new OntopiaRuntimeException(e); // shouldn't be possible
}
}
public String getEncodedContents() {
if (base64) {
try {
return Base64Encoder.encode(new String(contents, 0, length));
} catch (IOException e) {
throw new OntopiaRuntimeException(e);
}
} else {
return getContents();
}
}
public ByteArrayInputStream getContentsAsStream() throws IOException {
return new ByteArrayInputStream(contents, 0, length);
}
// --- Internal methods
// decode a URL from base64 encoding to the raw bytes encoded by the
// URL string, as described in section 6.8 of RFC 2045
private void decodeBase64(String rawurl, int start) throws IOException {
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
Base64Decoder.decode(rawurl.substring(start, rawurl.length()), ostream);
contents = ostream.toByteArray();
length = contents.length;
}
private void decodeURL(String rawurl, int start) throws IOException {
char[] rawsource = rawurl.toCharArray();
byte[] decoded = new byte[rawurl.length()];
int written = 0;
for (int ix = start; ix < rawsource.length; ix++) {
if (rawsource[ix] == '%') {
if (ix + 2 >= rawsource.length) break; // FIXME
decoded[written++] = (byte) ((digitVal(rawsource[ix + 1]) * 16) +
digitVal(rawsource[ix + 2]));
ix += 2;
} else if (rawsource[ix] == '+')
decoded[written++] = ' ';
else
decoded[written++] = (byte) rawsource[ix];
}
// set up internal members
contents = decoded;
length = written;
}
private byte digitVal(char hexDigit) throws IOException {
if (hexDigit >= '0' && hexDigit <= '9')
return (byte) (hexDigit - '0');
else if (hexDigit >= 'A' && hexDigit <= 'F')
return (byte) (hexDigit - '7'); // 'A' - 10 = '7'
else if (hexDigit >= 'a' && hexDigit <= 'f')
return (byte) (hexDigit - 'W'); // 'a' - 10 = 'W'
else
throw new IOException("data URL syntax error: Invalid hex digit");
}
public String toExternalForm() {
StringBuilder sb = new StringBuilder();
sb.append("data:");
if (mediaType != null && !mediaType.equals("text/plain"))
sb.append(mediaType);
if (base64) sb.append(";base64");
sb.append(",");
sb.append(URLEncoder.encode(getEncodedContents()));
return sb.toString();
}
public String toString() {
return toExternalForm();
}
}