/*
*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.j2me.content;
import javax.microedition.io.Connector;
import javax.microedition.io.Connection;
import javax.microedition.io.ContentConnection;
import javax.microedition.io.HttpConnection;
import java.io.IOException;
/**
* The helper class extending GCF Connector functionality with demands
* of the JSR 211 specification:
* <ul>
* <li> the connection may deliver the content from a cache.
* <li> the connection should use user credentials.
* </ul>
*/
class ContentReader {
private String url;
private String username;
private String password;
ContentReader(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
/**
* Creates and opens a Connection to the content addressed by
* the <code>url</code>. This method is
* similar to <code>Connector.open(url, READ, timeouts)</code>
* but may deliver the content from a cache.
* Regardless of whether or not the content is cached, the
* application must have permission to access
* the content via the <code>url</code>.
*
* @param timeouts a flag to indicate that the caller
* wants timeout exceptions
* @return a Connection object
*
* @exception ConnectionNotFoundException is thrown if:
* <ul>
* <li>the target URL can not be found, or</li>
* <li>the requested protocol type is not supported</li>
* </ul>
* @exception NullPointerException if the URL is null
* @exception IllegalArgumentException if an <code>url</code> parameter is invalid.
* @exception java.io.IOException if some other kind of I/O error occurs
* @exception SecurityException is thrown if access to the
* protocol handler is prohibited
*/
Connection open(boolean timeouts) throws IOException, SecurityException {
return openPrim(timeouts);
}
/**
* Finds the type of the content in this Invocation.
* <p>
* The calling thread blocks while the type is being determined.
* If a network access is needed there may be an associated delay.
*
* @return the content type.
* May be <code>null</code> if the type can not be determined.
*
* @exception IOException if access to the content fails
* @exception IllegalArgumentException if the content is accessed via
* the URL and the URL is invalid
* @exception SecurityException is thrown if access to the content
* is required and is not permitted
*/
String findType() throws IOException, SecurityException {
String type = null;
Connection conn = openPrim(true);
if (conn instanceof ContentConnection) {
if( conn instanceof HttpConnection ){
HttpConnection hc = (HttpConnection)conn;
hc.setRequestMethod(HttpConnection.HEAD);
// actual connection performed, some delay...
if (hc.getResponseCode() != HttpConnection.HTTP_OK)
return null;
}
type = ((ContentConnection)conn).getType();
conn.close();
if (type != null) {
// Check for and remove any parameters (rfc2616)
int ndx = type.indexOf(';');
if (ndx >= 0) {
type = type.substring(0, ndx);
}
type = type.trim();
if (type.length() == 0) {
type = null;
}
}
}
return type;
}
/**
* The method currently supports only HTTP protocol and basic authentication.
*
* @param timeouts a flag to indicate that the caller
* wants timeout exceptions.
* @param headsOnly open connection for content type discover.
*
* @return a Connection object
*
* @exception IOException if access to the content fails
* @exception IllegalArgumentException if the content is accessed via
* the URL and the URL is invalid
* @exception SecurityException is thrown if access to the content
* is required and is not permitted
*/
private Connection openPrim(boolean timeouts)
throws IOException, SecurityException {
Connection conn = Connector.open(url, Connector.READ, timeouts);
if (conn instanceof HttpConnection &&
(username != null || password != null)) {
HttpConnection httpc = (HttpConnection)conn;
httpc.setRequestMethod(HttpConnection.HEAD);
// actual connection performed, some delay...
int rc = httpc.getResponseCode();
// try to set authorization
if (rc == HttpConnection.HTTP_UNAUTHORIZED ||
rc == HttpConnection.HTTP_PROXY_AUTH) {
String authType = httpc.getHeaderField("WWW-Authenticate");
if (authType == null || !authType.trim().equalsIgnoreCase("basic")) {
throw new IOException("not supported authorization");
}
conn.close();
// reopen connection with authorization property set
conn = Connector.open(url, Connector.READ, timeouts);
httpc = (HttpConnection)conn;
httpc.setRequestProperty(
rc == HttpConnection.HTTP_UNAUTHORIZED?
"Authorization": "Proxy-Authorization",
formatAuthCredentials(username, password));
return conn;
}
conn.close();
conn = Connector.open(url, Connector.READ, timeouts);
}
return conn;
}
/**
* Formats the username and password for HTTP basic authentication
* according RFC 2617.
*
* @param username for HTTP authentication
* @param password for HTTP authentication
*
* @return properly formated basic authentication credential
*/
private static String formatAuthCredentials(String username,
String password) {
byte[] data = new byte[username.length() + password.length() + 1];
int j = 0;
for (int i = 0; i < username.length(); i++, j++) {
data[j] = (byte)username.charAt(i);
}
data[j] = (byte)':';
j++;
for (int i = 0; i < password.length(); i++, j++) {
data[j] = (byte)password.charAt(i);
}
return "Basic " + encode(data, 0, data.length);
}
/**
* Converts a byte array into a Base64 encoded string.
* @param data bytes to encode
* @param offset which byte to start at
* @param length how many bytes to encode; padding will be added if needed
* @return base64 encoding of data; 4 chars for every 3 bytes
*/
private static String encode(byte[] data, int offset, int length) {
int i;
int encodedLen;
char[] encoded;
// 4 chars for 3 bytes, run input up to a multiple of 3
encodedLen = (length + 2) / 3 * 4;
encoded = new char [encodedLen];
for (i = 0, encodedLen = 0; encodedLen < encoded.length;
i += 3, encodedLen += 4) {
encodeQuantum(data, offset + i, length - i, encoded, encodedLen);
}
return new String(encoded);
}
/**
* Encodes 1, 2, or 3 bytes of data as 4 Base64 chars.
*
* @param in buffer of bytes to encode
* @param inOffset where the first byte to encode is
* @param len how many bytes to encode
* @param out buffer to put the output in
* @param outOffset where in the output buffer to put the chars
*/
private static void encodeQuantum(byte in[], int inOffset, int len,
char out[], int outOffset) {
byte a = 0, b = 0, c = 0;
a = in[inOffset];
out[outOffset] = ALPHABET[(a >>> 2) & 0x3F];
if (len > 2) {
b = in[inOffset + 1];
c = in[inOffset + 2];
out[outOffset + 1] = ALPHABET[((a << 4) & 0x30) +
((b >>> 4) & 0xf)];
out[outOffset + 2] = ALPHABET[((b << 2) & 0x3c) +
((c >>> 6) & 0x3)];
out[outOffset + 3] = ALPHABET[c & 0x3F];
} else if (len > 1) {
b = in[inOffset + 1];
out[outOffset + 1] = ALPHABET[((a << 4) & 0x30) +
((b >>> 4) & 0xf)];
out[outOffset + 2] = ALPHABET[((b << 2) & 0x3c) +
((c >>> 6) & 0x3)];
out[outOffset + 3] = '=';
} else {
out[outOffset + 1] = ALPHABET[((a << 4) & 0x30) +
((b >>> 4) & 0xf)];
out[outOffset + 2] = '=';
out[outOffset + 3] = '=';
}
}
/**
* This character array provides the alphabet map from RFC1521.
*/
private final static char ALPHABET[] = {
// 0 1 2 3 4 5 6 7
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5
'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6
'4', '5', '6', '7', '8', '9', '+', '/' // 7
};
}