/* * * * 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 }; }