1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 1999 by Sun Microsystems, Inc.
23  * All rights reserved.
24  *
25  */
26 
27 //  ServiceURL.java :  The service URL.
28 //  Author:           James Kempf, Erik Guttman
29 //
30 
31 package com.sun.slp;
32 
33 import java.util.*;
34 import java.io.*;
35 import java.net.*;
36 
37 /**
38  * The ServiceURL object models the SLP service URL. Both service: URLs
39  * and regular URLs are handled by this class.
40  *
41  * @author James Kempf, Erik Guttman
42  */
43 
44 public class ServiceURL extends Object implements Serializable   {
45 
46     // Recognized transports.
47 
48     private final static String IPX = "ipx";
49     private final static String AT = "at";
50 
51     /**
52      * Indicates that no port information is required or was returned
53      * for this service URL.
54      */
55 
56     public static final int NO_PORT = 0;
57 
58     /**
59      * No life time parameter is given.
60      */
61 
62     public static final int LIFETIME_NONE    =  0;
63 
64     /**
65      * Default lifetime, 3 hours.
66      */
67 
68     public static final int LIFETIME_DEFAULT = 10800;
69 
70     /**
71      * Maximum lifetime, approximately 18 hours.
72      */
73 
74     public static final int LIFETIME_MAXIMUM = 0xFFFF;
75 
76     /**
77      * Reregister periodically.
78      */
79 
80     public static final int LIFETIME_PERMANENT = -1;
81 
82     // Maximum port size.
83 
84     static final int PORT_MAXIMUM = 0xFFFF;
85 
86 
87     //
88     // data fields
89     //
90 
91     private ServiceType serviceType = null;
92     private ServiceType originalServiceType = null;
93     private String transport = "";
94     private String host = "";
95     private int port = NO_PORT;
96     private String URLPath = "";
97     private int lifetime = LIFETIME_DEFAULT;
98     private boolean isPermanent = false;
99     private boolean noDoubleSlash = false;
100 
101     /**
102      * Construct a service URL object.
103      *
104      * @param URL		The service URL as a string.
105      * @param iLifetime		The service advertisement lifetime.
106      * @exception IllegalArgumentException Thrown if parse
107      *				          errors occur in the
108      *					  parameter.
109      */
110 
ServiceURL(String URL, int iLifetime)111     public ServiceURL(String URL, int iLifetime)
112 	throws IllegalArgumentException {
113 
114 	Assert.nonNullParameter(URL, "URL");
115 
116 	if ((iLifetime > LIFETIME_MAXIMUM) ||
117 	   (iLifetime < LIFETIME_PERMANENT)) {
118 	    throw
119 		new IllegalArgumentException(
120 		SLPConfig.getSLPConfig().formatMessage("lifetime_error",
121 						       new Object[0]));
122 	}
123 
124 	checkURLString(URL);
125 	parseURL(URL);
126 
127 	if (iLifetime == LIFETIME_PERMANENT) {
128 	    isPermanent = true;
129 	    iLifetime = LIFETIME_MAXIMUM;
130 
131 	}
132 
133 	lifetime = iLifetime;
134     }
135 
136     //
137     // ------------------------------------------------------------------
138     // Accessors
139     // ------------------------------------------------------------------
140     //
141 
142     /**
143      * @return The service type name.
144      */
145 
getServiceType()146     public ServiceType getServiceType() {
147 	return serviceType;
148 
149     }
150 
151     /**
152      * Set service type and naming authority if this is not a service: URL.
153      *
154      * @param type The new ServiceType object.
155      * @exception IllegalArgumentException If the service type name or
156      *					 naming authority name is invalid.
157      */
158 
setServiceType(ServiceType type)159     public void setServiceType(ServiceType type) {
160 	if (!serviceType.isServiceURL()) {
161 	    serviceType = type;
162 
163 	}
164 
165     }
166 
167     /**
168      * @return The machine name or IP address.
169      */
170 
getHost()171     public String getHost() {
172 	return host;
173 
174     }
175 
176     /**
177      * @return The port number, if any.
178      */
179 
getPort()180     public int getPort() {
181 	return port;
182 
183     }
184 
185     /**
186      * @return The URL path description, if any.
187      */
188 
getURLPath()189     public String getURLPath() {
190 	return URLPath;
191 
192     }
193 
194     /**
195      * @return The service advertisement lifetime.
196      */
197 
getLifetime()198     public int getLifetime() {
199 	return lifetime;
200 
201     }
202 
203     /**
204      * Formats the service URL into standard URL form.
205      *
206      * @return Formatted string with the service URL.
207      */
208 
toString()209     public String toString() {  // Overrides Object.toString();
210 
211 	return
212 	    originalServiceType.toString() +
213 	    ":/" + transport + (noDoubleSlash == false ? "/":"") +
214 	    host + (port != NO_PORT ? (":" + port) : "") +
215 	    URLPath;
216 
217     }
218 
hashCode()219     public int hashCode() {
220 	return
221 	    serviceType.hashCode() +
222 	    transport.hashCode() +
223 	    host.hashCode() +
224 	    port +
225 	    URLPath.hashCode();
226     }
227 
equals(Object obj)228     public boolean equals(Object obj) {
229 
230 	if (obj == this) {
231 	    return true;
232 
233 	}
234 
235 	if (!(obj instanceof ServiceURL)) {
236 	    return false;
237 	}
238 
239 	ServiceURL surl = (ServiceURL)obj;
240 
241 	return
242 	    serviceType.equals(surl.serviceType) &&
243 	    transport.equals(surl.transport) &&
244 	    host.equals(surl.host) &&
245 	    (port == surl.port) &&
246 	    (noDoubleSlash == surl.noDoubleSlash) &&
247 	    URLPath.equals(surl.URLPath);
248 
249     }
250 
251     // Return permanent status.
252 
getIsPermanent()253     boolean getIsPermanent() {
254 	return isPermanent;
255 
256     }
257 
258     // Check URL characters for correctness.
259 
checkURLString(String s)260     private void checkURLString(String s)
261 	throws IllegalArgumentException {
262 	for (int i = 0; i < s.length(); i++) {
263 	    char c = s.charAt(i);
264 	    // allowed by RFC1738
265 	    if (c == '/' || c == ':' || c == '-' || c == ':' ||
266 		c == '.' || c == '%' || c == '_' || c == '\'' ||
267 		c == '*' || c == '(' || c == ')' || c == '$' ||
268 		c == '!' || c == ',' || c == '+' || c == '\\') {
269 							// defer to Windows
270 		continue;
271 
272 	    }
273 
274 	    // reserved by RFC1738, and thus allowed, pg. 20
275 	    if (c == ';' || c == '@' || c == '?' || c == '&' || c == '=') {
276 		continue;
277 
278 	    }
279 
280 	    if (Character.isLetterOrDigit(c)) {
281 		continue;
282 	    }
283 
284 	    SLPConfig conf = SLPConfig.getSLPConfig();
285 
286 	    throw
287 		new IllegalArgumentException(
288 				conf.formatMessage("url_char_error",
289 						   new Object[] {
290 				    new Character(c)}));
291 	}
292     }
293 
294     // Parse the incoming service URL specification.
295 
parseURL(String sURL)296     private void parseURL(String sURL)
297 	throws IllegalArgumentException {
298 
299 	StringTokenizer st = new StringTokenizer(sURL, "/", true);
300 
301 	try {
302 
303 	    // This loop is a kludgy way to break out of the parse so
304 	    //  we only throw at one location in the code.
305 
306 	    do {
307 		String typeName = st.nextToken();
308 
309 		// First token must be service type name.
310 
311 		if (typeName.equals("/")) {
312 		    break; // error!
313 
314 		}
315 
316 		// Check for colon terminator, not part of service
317 		// type name.
318 
319 		if (!typeName.endsWith(":")) {
320 		    break; // error!
321 
322 		}
323 
324 		// Create service type, remove trailing colon.
325 
326 		serviceType =
327 		    new ServiceType(typeName.substring(0,
328 						       typeName.length() - 1));
329 		originalServiceType = serviceType;
330 
331 		// Separator between service type name and transport.
332 
333 		String slash1 = st.nextToken();
334 
335 		if (!slash1.equals("/")) {
336 		    break; // error!
337 
338 		}
339 
340 		String slash2 = st.nextToken();
341 
342 		String sAddr = "";  // address...
343 
344 		// Check for abstract type or alternate transport.
345 
346 		if (!slash2.equals("/")) {
347 
348 		    // If this is an abstract type, then we could have
349 		    //  something like: service:file-printer:file:/foo/bar.
350 		    //  This is OK. Also, if this is a non-service: URL,
351 		    //  something like file:/foo/bar is OK.
352 
353 		    if (!serviceType.isServiceURL()) {
354 			sAddr = slash2;
355 
356 			noDoubleSlash = true;
357 
358 		    } else {
359 
360 			// We only recognize IPX and Appletalk at this point.
361 
362 			if (!slash2.equalsIgnoreCase(IPX) &&
363 			    !slash2.equalsIgnoreCase(AT)) {
364 
365 			    // Abstract type is OK. We must check here because
366 			    //  something like
367 			    //  service:printing:lpr:/ipx/foo/bar
368 			    //  is allowed.
369 
370 			    if (serviceType.isAbstractType()) {
371 				sAddr = slash2;
372 
373 				noDoubleSlash = true;
374 
375 			    } else {
376 
377 				break;  // error!
378 
379 			    }
380 			} else {
381 
382 			    transport = slash2.toLowerCase();
383 
384 			    // Check for separator between transport and host.
385 
386 			    if (!st.nextToken().equals("/")) {
387 				break; // error!
388 
389 			    }
390 
391 			    sAddr = st.nextToken();
392 			}
393 		    }
394 		} else {
395 
396 		    // Not abstract type, no alternate transport. Get host.
397 
398 		    sAddr = st.nextToken();
399 
400 		}
401 
402 		if (sAddr.equals("/")) {// no host part
403 		    URLPath = "/" + st.nextToken("");
404 		    return; // we're done!
405 
406 		}
407 
408 		host = sAddr;
409 
410 		// Need to check for port number if this is an IP transport.
411 
412 		if (transport.equals("")) {
413 		    StringTokenizer tk = new StringTokenizer(host, ":");
414 
415 		    host = tk.nextToken();
416 
417 		    // Get port if any.
418 
419 		    if (tk.hasMoreTokens()) {
420 			String p = tk.nextToken();
421 
422 			if (tk.hasMoreTokens()) {
423 			    break; // error!
424 
425 			}
426 
427 			try {
428 
429 			    port = Integer.parseInt(p);
430 
431 			} catch (NumberFormatException ex) {
432 			    break; // error!
433 
434 			}
435 
436 			if (port <= 0 || port > PORT_MAXIMUM) {
437 			    break; // error!
438 
439 			}
440 		    }
441 		}
442 
443 		//
444 		// after this point we have to check if there is a token
445 		// remaining before we read it: It is legal to stop at any
446 		// point now.  Before all the tokens were required, so
447 		// missing any was an error.
448 		//
449 		if (st.hasMoreTokens() == false) {
450 					//  minimal url service:t:// a
451 		    return; // we're done!
452 
453 		}
454 
455 		String sSep  = st.nextToken();
456 
457 		if (!sSep.equals("/")) {
458 		    break; // error!
459 
460 		}
461 
462 		// there is a URL path
463 		// URLPath is all remaining tokens
464 		URLPath = sSep;
465 
466 		if (st.hasMoreTokens()) {
467 		    URLPath += st.nextToken("");
468 
469 		}
470 
471 		URLPath = URLPath.trim();
472 
473 		return; // done!
474 
475 	    } while (false); // done with parse.
476 
477 	} catch (NoSuchElementException ex) {
478 	    throw
479 		new IllegalArgumentException(
480 		SLPConfig.getSLPConfig().formatMessage("url_syntax_error",
481 						       new Object[] {sURL}));
482 
483 	}
484 
485 	// The only way to get here is if there was an error in the
486 	//  parse.
487 
488 	throw
489 	    new IllegalArgumentException(
490 		SLPConfig.getSLPConfig().formatMessage("url_syntax_error",
491 						       new Object[] {sURL}));
492 
493     }
494 
495 }
496