001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.midp.io.j2me.irdaobex;
027:
028: import java.io.IOException;
029: import java.io.InterruptedIOException;
030: import java.util.Vector;
031: import javax.microedition.io.Connection;
032: import javax.microedition.io.ConnectionNotFoundException;
033: import javax.microedition.io.Connector;
034: import com.sun.cldc.io.ConnectionBaseInterface;
035: import com.sun.kvem.jsr082.obex.ClientSessionImpl;
036: import com.sun.kvem.jsr082.obex.SessionNotifierImpl;
037: import com.sun.midp.security.SecurityToken;
038: import com.sun.midp.midlet.MIDletStateHandler;
039: import com.sun.midp.midlet.MIDletSuite;
040: import com.sun.midp.security.Permissions;
041: import com.sun.midp.security.ImplicitlyTrustedClass;
042: import com.sun.midp.jsr082.SecurityInitializer;
043:
044: /**
045: * Provides a wrapper for the irdaobex protocol implementation
046: * to answer the GCF style.
047: */
048: public class Protocol implements ConnectionBaseInterface {
049:
050: /**
051: * Inner class to request security token from SecurityInitializer.
052: * SecurityInitializer should be able to check this inner class name.
053: */
054: static private class SecurityTrusted implements
055: ImplicitlyTrustedClass {
056: };
057:
058: /** Internal security token that grants access to restricted API. */
059: private static SecurityToken classSecurityToken = SecurityInitializer
060: .requestToken(new SecurityTrusted());
061:
062: /** Shows whether cilent permissions checked successfilly. */
063: private boolean clientPermitted = false;
064:
065: /** Shows whether server permissions checked successfilly. */
066: private boolean serverPermitted = false;
067:
068: /** Keeps the device properties and attributes. */
069: static IrOBEXControl control = null;
070:
071: /** Host name used for the server side. */
072: private final String serverHost = "localhost";
073:
074: /** Host name used for the client side. */
075: private final String clientHost = "discover";
076:
077: /** Default constructor. */
078: public Protocol() {
079: }
080:
081: /**
082: * Returns either ClientSession or SessionNotifier for OBEX connections,
083: * depending whether client or server URL was specified.
084: *
085: * @param name the URL for the connection (without "irdaobex:" prefix)
086: * @param mode only READ_WRITE mode is supported by OBEX
087: * @param timeouts ignored
088: * @return ClientSession for client URL or SessionNotifier for server URL
089: * @exception IllegalArgumentException if the URL specified is invalid
090: * @exception ConnectionNotFoundException if the target cannot be found
091: * @exception IOException if something goes wrong
092: * @exception SecurityException if access is prohibited
093: */
094: public Connection openPrim(String name, int mode, boolean timeouts)
095: throws IOException {
096:
097: // instantiate control class on the first time of method invocation
098: synchronized (Protocol.class) {
099: if (control == null) {
100: control = new IrOBEXControl();
101: }
102: }
103:
104: // save the URL for later use
105: String url = "irdaobex:" + name;
106:
107: if (!name.startsWith("//")) {
108: throw new IllegalArgumentException("Malformed URL: " + url);
109: }
110:
111: // cut off the "//" prefix
112: name = name.substring(2);
113:
114: // OBEX supports READ_WRITE mode only
115: if (mode != Connector.READ_WRITE) {
116: throw new IllegalArgumentException("Unsupported mode: "
117: + mode);
118: }
119:
120: String ias = "OBEX,OBEX:IrXfer"; // Default IAS
121:
122: // ";ias=" indicates the beginning of the IAS list
123: int index = name.toLowerCase().indexOf(";ias=");
124: if (index != -1) {
125: ias = name.substring(index + ";ias=".length());
126: // check IAS validity
127: if (!checkIAS(ias)) {
128: throw new IllegalArgumentException("Invalid IAS: "
129: + ias);
130: }
131: // cut off IAS from the name
132: name = name.substring(0, index);
133: }
134:
135: Vector iasVector = new Vector();
136: ias = ias.concat(",");
137: while (ias.length() > 0) {
138: index = ias.indexOf(',');
139: iasVector.addElement(ias.substring(0, index));
140: ias = ias.substring(index + 1);
141: }
142:
143: String[] iasArray = new String[iasVector.size()];
144: iasVector.copyInto(iasArray);
145: String host = name.toLowerCase();
146: boolean isServer;
147: int hints;
148: if (host.startsWith(serverHost)) {
149: isServer = true;
150: name = name.substring(serverHost.length());
151: hints = 0x0200;
152: } else if (host.startsWith(clientHost)) {
153: isServer = false;
154: name = name.substring(clientHost.length());
155: hints = 0;
156: } else {
157: throw new IllegalArgumentException("Malformed URL: " + url);
158: }
159:
160: if (name.length() > 0 && name.charAt(0) == '.') {
161: // hint bits should follow
162: String hstring = name.substring(1).toUpperCase();
163: if (!checkHints(hstring)) {
164: throw new IllegalArgumentException(
165: "Invalid hint bits: " + hstring);
166: }
167: hints |= Integer.parseInt(hstring, 16);
168: hints &= 0x7f7f7f7f;
169: }
170:
171: if (isServer) {
172: if (!serverPermitted) {
173: checkForPermission(Permissions.OBEX_SERVER, url);
174: serverPermitted = true;
175: }
176: return new SessionNotifierImpl(control
177: .createServerConnection(hints, iasArray));
178:
179: } else {
180: if (!clientPermitted) {
181: checkForPermission(Permissions.OBEX_CLIENT, url);
182: clientPermitted = true;
183: }
184:
185: return new ClientSessionImpl(control
186: .createClientConnection(hints, iasArray));
187: }
188: }
189:
190: /**
191: * Makes sure caller has the com.sun.midp permission set to "allowed".
192: * @param permission requested permission ID
193: * @param name resource name to check permissions against
194: * @exception IOInterruptedException if another thread interrupts the
195: * calling thread while this method is waiting to preempt the
196: * display.
197: */
198: private void checkForPermission(int permission, String name)
199: throws InterruptedIOException {
200: MIDletSuite midletSuite = MIDletStateHandler
201: .getMidletStateHandler().getMIDletSuite();
202:
203: // The class may be used when no suite running
204: if (midletSuite == null) {
205: return;
206: }
207:
208: try {
209: midletSuite.checkForPermission(permission, name);
210: } catch (InterruptedException ie) {
211: throw new InterruptedIOException(
212: "Interrupted while trying to ask the user permission");
213: }
214: }
215:
216: /**
217: * Checks the hint bits. The number of hex digits must be even,
218: * two digits minimum, eight digits maximum.
219: *
220: * @param hints hint bits passed in uppercase
221: * @return true if the parameter is valid, false otherwise
222: */
223: private static boolean checkHints(String hints) {
224: if (hints.length() < 2 || hints.length() > 8
225: || hints.length() % 2 != 0) {
226: return false;
227: }
228:
229: byte[] data = hints.getBytes();
230: for (int i = 0; i < data.length; i++) {
231: if (data[i] >= '0' && data[i] <= '9' || data[i] >= 'A'
232: || data[i] <= 'F') {
233: continue;
234: }
235: return false;
236: }
237: return true;
238: }
239:
240: /**
241: * Checks if the IAS (Information Access Service) string complies
242: * with the grammar.
243: *
244: * @param ias IrDA class names separated by comma
245: * @return true if the list is valid, false otherwise
246: */
247: private static boolean checkIAS(String ias) {
248: // should not be empty, should not start or end with a comma
249: if (ias.length() == 0 || ias.charAt(0) == ','
250: || ias.charAt(ias.length() - 1) == ',') {
251: return false;
252: }
253:
254: // add a comma to the end of the list to facilitate iteration
255: ias = ias.concat(",");
256: while (ias.length() > 0) {
257: int index = ias.indexOf(',');
258: byte[] data = ias.substring(0, index).getBytes();
259: ias = ias.substring(index + 1);
260: int hex = 0;
261: // parse single IrDA class name
262: for (int i = 0; i < data.length; i++) {
263: if (hex > 0) {
264: // hex digit is expected
265: if (data[i] >= '0' && data[i] <= '9'
266: || data[i] >= 'A' && data[i] <= 'F'
267: || data[i] >= 'a' && data[i] <= 'f') {
268: hex--;
269: continue;
270: }
271: return false;
272: }
273: if (data[i] == '%') {
274: // excapedOcted should follow (two hex digits)
275: hex = 2;
276: continue;
277: }
278: if (data[i] >= '0' && data[i] <= '9' || data[i] == ':'
279: || data[i] >= 'A' && data[i] <= 'Z'
280: || data[i] >= 'a' && data[i] <= 'z') {
281: continue;
282: }
283: return false;
284: }
285: if (hex > 0) {
286: return false;
287: }
288: }
289: return true;
290: }
291: }
|