xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/ServiceLocationAttributeV1.java (revision 503609a9497e27f206d815a06ce90a747d2ce573)
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 //  ServiceLocationAttributeV1.java: SLPv1 character encoding and decoding
28 //  Author:           James Kempf
29 //  Created On:       Fri Oct  9 19:18:17 1998
30 //  Last Modified By: James Kempf
31 //  Last Modified On: Sat Oct 24 13:17:58 1998
32 //  Update Count:     15
33 //
34 
35 package com.sun.slp;
36 
37 import java.util.*;
38 
39 /**
40  * Handles attribute string encoding and decoding for SLPv1.
41  *
42  * @author James Kempf
43  */
44 
45 class ServiceLocationAttributeV1 extends ServiceLocationAttribute {
46 
47     String charCode = IANACharCode.UTF8;  // how to encode the attribute.
48 
49     // Characters to escape.
50 
51     final private static String UNESCAPABLE_CHARS = ",=!></*()";
52     final private static String ESCAPABLE_CHARS =
53 	UNESCAPABLE_CHARS + "&#;";
54 
55     /**
56      * Handles radix64 string encoding and decoding for SLPv1.
57      *
58      * @author James Kempf
59      */
60 
61     static class Radix64 extends Object {
62 
63 	/**
64 	 * Translates the 6 bit value to the corresponding radix 64
65 	 * representation.
66 	 */
67 	private static char LUT(char cin) {
68 
69 	    int i = (int)(cin & (char)0x00FF);
70 	    char result = ' ';
71 
72 	    if (i < 26) {
73 		result = (char)((char)i + 'A');
74 
75 	    } else if (i < 52) {
76 		result = (char)((char)(i - 26) + 'a');
77 
78 	    } else if (i < 62) {
79 		result = (char)((char)(i - 52) + '0');
80 
81 	    } else if (i == 62) {
82 		result = '+';
83 
84 	    } else if (i == 63) {
85 		result = '/';
86 
87 	    }
88 
89 	    return result;
90 	}
91 
92 	/**
93 	 * Translates a radix 64 representation to the 64 bit value which
94 	 * corresponds to it.
95 	 */
96 	private static char LUT2(char cin, String s)
97 	    throws ServiceLocationException {
98 
99 	    int i = (int)(cin & 0x00ff);
100 	    char c = (char) 0xffff;
101 
102 	    if (((char)i >= 'A') && ((char)i <= 'Z')) {
103 		c = (char)((char)i - 'A');
104 
105 	    }
106 
107 	    if (((char)i >= 'a') && ((char)i <= 'z')) {
108 		c = (char)((char)i - 'a' +(char) 26);
109 
110 	    }
111 
112 	    if (((char)i >= '0') && ((char)i <= '9')) {
113 		c = (char)((char)i - '0' +(char) 52);
114 
115 	    }
116 
117 	    if ((char)i == '+') {
118 		c = (char)62;
119 
120 	    }
121 
122 	    if ((char)i == '/') {
123 		c = (char)63;
124 
125 	    }
126 
127 	    if ((char)i == '=') {
128 		c = (char)0;
129 
130 	    }
131 
132 	    if (c == 0xffff) {
133 		throw
134 		    new ServiceLocationException(
135 				ServiceLocationException.PARSE_ERROR,
136 				"v1_radix64_error",
137 				new Object[] {s});
138 
139 	    }
140 
141 	    return c;
142 	}
143 
144 	// format of the encoding is "(###:encoding)" where ### is the length
145 
146 	// convert a string in the encoding to the buffer format
147 
148 	static Opaque radix64ToOpaque(String s)
149 	    throws ServiceLocationException {
150 
151 	    if (s == null || s.trim().length() == 0) {
152 		return new Opaque(new byte[0]);
153 
154 	    }
155 
156 	    int oplen = 0;
157 	    int scan = 0;
158 
159 	    while (scan < s.length()) {
160 		if (s.charAt(scan) == '(') {
161 		    break;  // scan till begins
162 
163 		}
164 
165 		scan++;
166 	    }
167 
168 	    scan++; // past the '('
169 
170 	    while (scan < s.length()) {
171 		if (Character.isWhitespace(s.charAt(scan)) == false) {
172 		    break;
173 
174 		}
175 		scan++;
176 	    }
177 
178 	    while (scan < s.length()) {
179 
180 		if (Character.isDigit(s.charAt(scan))) {
181 		    oplen *= 10;
182 		    oplen += (s.charAt(scan) - '0');
183 		    scan++;
184 
185 		} else {
186 		    break;
187 
188 		}
189 	    }
190 
191 	    if (scan >= s.length()) {
192 		throw
193 		    new ServiceLocationException(
194 				ServiceLocationException.PARSE_ERROR,
195 				"v1_radix64_error",
196 				new Object[] {s});
197 
198 	    }
199 
200 
201 	    if (s.charAt(scan) != ':') {
202 		throw
203 		    new ServiceLocationException(
204 				ServiceLocationException.PARSE_ERROR,
205 				"v1_radix64_error",
206 				new Object[] {s});
207 
208 	    }
209 
210 	    scan++; // past the ':'
211 
212 	    byte b[] = new byte[oplen];
213 
214 	    int pos = 0;
215 	    int timesthrough = (oplen/3);
216 
217 	    if ((oplen %3) != 0) {
218 		timesthrough++;
219 
220 	    }
221 
222 	    for (int i = 0; i < timesthrough; i++) {
223 
224 		// get 4 bytes to make 3 with, skipping blanks
225 
226 		char v[] = new char[4];
227 
228 		for (int x = 0; x < 4; x++) {
229 
230 		    while ((scan < s.length()) &&
231 			   Character.isWhitespace(s.charAt(scan))) {
232 			scan++; // eat white
233 
234 		    }
235 
236 		    if (scan >= s.length()) {
237 			throw
238 			    new ServiceLocationException(
239 				ServiceLocationException.PARSE_ERROR,
240 				"v1_radix64_error",
241 				new Object[] {s});
242 
243 		    }
244 
245 		    v[x] = LUT2(s.charAt(scan), s);
246 		    scan++;
247 		}
248 
249 		b[pos++] =
250 		    (byte) (((0x3F & v[0]) << 2) + ((0x30 & v[1]) >> 4));
251 		if (pos >= oplen) break;
252 		b[pos++] =
253 		    (byte) (((0x0F & v[1]) << 4) + ((0x3C & v[2]) >> 2));
254 		if (pos >= oplen) break;
255 		b[pos++] = (byte) (((0x03 & v[2]) << 6) + (0x3F & v[3]));
256 
257 	    } // end of conversion loop
258 
259 	    if (scan >= s.length()) {
260 		throw
261 		    new ServiceLocationException(
262 				ServiceLocationException.PARSE_ERROR,
263 				"v1_radix64_error",
264 				new Object[] {s});
265 	    }
266 
267 	    if (s.charAt(scan) != ')') {// check for too many chars.
268 		throw
269 		    new ServiceLocationException(
270 				ServiceLocationException.PARSE_ERROR,
271 				"v1_radix64_error",
272 				new Object[] {s});
273 
274 	    }
275 
276 	    return new Opaque(b);
277 	}
278 
279 	// convert an Opaque to the encoding
280 
281 	static String opaqueToRadix64(Opaque oq) {
282 	    byte[] b = oq.bytes;
283 
284 	    if (b == null) {
285 		return new String("");
286 
287 	    }
288 
289 	    StringBuffer sb = new StringBuffer("("+b.length+":");
290 
291 	    int datalen;
292 	    int fill = b.length%3;
293 
294 	    if (fill == 0) {
295 		datalen = (b.length / 3) * 4;
296 
297 	    } else {
298 		datalen = ((b.length / 3) + 1) * 4;
299 
300 	    }
301 
302 	    int dataoffset = 0;
303 	    int more = (b.length%3);
304 
305 	    if (more != 0) {
306 		more = 1;
307 
308 	    }
309 
310 	    int a[] = new int[4];
311 
312 	    for (int i = 0; i < ((b.length/3)+more-1); i++) {
313 
314 		a[0] =   (int)(0xFC & (char)b[ dataoffset    ]) >> 2;
315 		a[1] =  ((int)(0x03 & (char)b[ dataoffset    ]) << 4) +
316 		    ((int)(0xF0 & (char)b[ dataoffset + 1]) >> 4);
317 		a[2] =  ((int)(0x0F & (char)b[ dataoffset + 1]) << 2) +
318 		    ((int)(0xC0 & (char)b[ dataoffset + 2]) >> 6);
319 		a[3] =   (int)(0x3F & (char)b[ dataoffset + 2]);
320 
321 		for (int j = 0; j < 4; j++) {
322 		    sb.append(LUT((char)a[j]));
323 
324 		}
325 
326 		dataoffset += 3;
327 	    }
328 
329 	    byte f1 = 0, f2 = 0;
330 
331 	    if (fill == 0) {
332 		f1 = b[ dataoffset + 1 ];
333 		f2 = b[ dataoffset + 2 ];
334 
335 	    } else if (fill == 2) {
336 		f1 = b[ dataoffset + 1 ];
337 
338 	    }
339 
340 	    a[0] = (int) (0xFC & (char)b[ dataoffset ]) >> 2;
341 	    a[1] = ((int) (0x03 & (char)b[ dataoffset ]) << 4) +
342 		((int) (0xF0 & (char)f1) >> 4);
343 	    a[2] = ((int) (0x0F & (char)f1) << 2) +
344 		((int) (0xC0 & (char)f2) >> 6);
345 	    a[3] = (int) (0x3F & (char)f2);
346 
347 	    for (int j = 0; j < 4; j++) {
348 		sb.append(LUT((char) a[j]));
349 
350 	    }
351 
352 	    sb.append(")");
353 
354 	    return sb.toString();
355 	}
356     }
357 
358     // Create an SLPv1 attribute from a general attribute.
359 
360     ServiceLocationAttributeV1(ServiceLocationAttribute attr) {
361 	id = attr.id;
362 	values = attr.values;
363 
364     }
365 
366     // Create an SLPv1 attribute from the parenthesized expression, using
367     //  charCode to decode any encodings.
368 
369     ServiceLocationAttributeV1(String exp,
370 			       String charCode,
371 			       boolean allowMultiValuedBooleans)
372 	throws ServiceLocationException {
373 	this.charCode = charCode;
374 
375 	// If start and end paren, then parse out assignment.
376 
377 	if (exp.startsWith("(") && exp.endsWith(")")) {
378 
379 	    StringTokenizer tk =
380 		new StringTokenizer(exp.substring(1, exp.length() - 1),
381 				    "=",
382 				    true);
383 
384 	    try {
385 
386 		// Get the tag.
387 
388 		id =
389 		    unescapeAttributeString(tk.nextToken(), charCode);
390 
391 		if (id.length() <= 0) {
392 		    throw
393 			new ServiceLocationException(
394 				ServiceLocationException.PARSE_ERROR,
395 				"null_id",
396 				new Object[] {exp});
397 		}
398 
399 		tk.nextToken();  // get rid of "="
400 
401 		// Gather the rest.
402 
403 		String rest = tk.nextToken("");
404 
405 		// Parse the comma separated list.
406 
407 		values = SrvLocHeader.parseCommaSeparatedListIn(rest, true);
408 
409 		// Convert to objects.
410 
411 		int i, n = values.size();
412 		Class vecClass = null;
413 
414 		for (i = 0; i < n; i++) {
415 		    String value = (String)values.elementAt(i);
416 
417 		    // Need to determine which type to use.
418 
419 		    Object o = evaluate(value, charCode);
420 
421 		    // Convert Opaque to byte array.
422 
423 		    if (o instanceof Opaque) {
424 			o = ((Opaque)o).bytes;
425 
426 		    }
427 
428 		    values.setElementAt(o, i);
429 
430 		}
431 
432 	    } catch (NoSuchElementException ex) {
433 		throw
434 		    new ServiceLocationException(
435 				ServiceLocationException.PARSE_ERROR,
436 				"assignment_syntax_err",
437 				new Object[] {exp});
438 	    }
439 
440 	    verifyValueTypes(values, allowMultiValuedBooleans);
441 
442 	} else {
443 
444 	    // Check to make sure there's no parens.
445 
446 	    if (exp.indexOf('(') != -1 || exp.indexOf(')') != -1) {
447 		throw
448 		    new ServiceLocationException(
449 				ServiceLocationException.PARSE_ERROR,
450 				"assignment_syntax_err",
451 				new Object[] {exp});
452 	    }
453 
454 	    // Unescape the keyword.
455 
456 	    id = unescapeAttributeString(exp, charCode);
457 
458 	}
459     }
460 
461     // Duplicate of the one in ServiceLocatioAttribute, except we use our
462     //  unescapeAttributeString.
463 
464     static Object evaluate(String value, String charCode)
465 	throws ServiceLocationException {
466 
467 	Object o = null;
468 
469 	// If it can be converted into an integer, then convert it.
470 
471 	try {
472 
473 	    o = Integer.valueOf(value);
474 
475 	} catch (NumberFormatException ex) {
476 
477 	    // Wasn't an integer. Try boolean.
478 
479 	    if (value.equalsIgnoreCase(TRUE) ||
480 		value.equalsIgnoreCase(FALSE)) {
481 		o = Boolean.valueOf(value);
482 
483 	    } else {
484 
485 		// Process the string to remove escapes.
486 
487 		String val = (String)value;
488 
489 		// If it begins with the opaque prefix, treat it as an
490 		//  opaque. Use radix64 parser to convert.
491 
492 		if (val.startsWith("(")) {
493 		    o = Radix64.radix64ToOpaque(val);
494 
495 		} else {
496 		    o = unescapeAttributeString(val, charCode);
497 
498 		}
499 	    }
500 	}
501 
502 	return o;
503 
504     }
505 
506     // Externalize the attribute, using its charCode to encode any reserved
507     //  characters.
508 
509     String externalize()
510 	throws ServiceLocationException {
511 
512 	if (values == null) {	// keyword attribute...
513 	    return escapeAttributeString(id, charCode);
514 	}
515 
516 	Vector v = new Vector();
517 
518 	for (Enumeration e = values.elements(); e.hasMoreElements(); ) {
519 	    Object o = e.nextElement();
520 	    String s = null;
521 
522 	    s = escapeValueInternal(o, charCode);
523 
524 	    v.addElement(s);
525 	}
526 
527 	StringBuffer buf =
528 	    new StringBuffer("(" +
529 			     escapeAttributeString(id, charCode) +
530 			     "=");
531 
532 	buf.append(SrvLocHeader.vectorToCommaSeparatedList(v));
533 
534 	buf.append(")");
535 
536 	return buf.toString();
537     }
538 
539     // Exactly like the one in ServiceLocationAttribute, but use our
540     //  escapeAttributeString.
541 
542     private static String escapeValueInternal(Object val, String charCode) {
543 
544 	String s;
545 
546 	// Escape any characters needing it.
547 
548 	if (val instanceof String) {
549 
550 	    try {
551 
552 		s = escapeAttributeString((String)val, charCode);
553 
554 	    } catch (ServiceLocationException ex) {
555 		throw
556 		    new IllegalArgumentException(ex.getMessage());
557 
558 	    }
559 
560 	} else if (val instanceof Opaque) {
561 
562 	    // Convert to radix 64.
563 
564 	    s = Radix64.opaqueToRadix64((Opaque)val);
565 
566 	} else {
567 	    s = val.toString();
568 
569 	}
570 
571 	return s;
572     }
573 
574     // Escape an attribute string with the char code.
575 
576     static String escapeAttributeString(String string,
577 					String charCode)
578 	throws ServiceLocationException {
579 
580 	StringBuffer buf = new StringBuffer();
581 	int i, n = string.length();
582 	boolean is8bit =
583 	    (charCode.equals(IANACharCode.ASCII) ||
584 	    charCode.equals(IANACharCode.LATIN1));
585 
586 	for (i = 0; i < n; i++) {
587 	    char c = string.charAt(i);
588 
589 	    if (ESCAPABLE_CHARS.indexOf(c) != -1) {
590 
591 		buf.append("&#");
592 		buf.append(IANACharCode.escapeChar(c, charCode));
593 		buf.append(";");
594 
595 	    } else {
596 
597 		// Need to check ASCII and LATIN1 to make sure that
598 		//  the character is not outside their range of
599 		//  representation.
600 
601 		if (is8bit && (short)c > 255) {
602 		    throw
603 			new ServiceLocationException(
604 				ServiceLocationException.PARSE_ERROR,
605 				"v1_8bit_error",
606 				new Object[] {new Character(c)});
607 		}
608 
609 		buf.append(c);
610 
611 	    }
612 	}
613 
614 	return buf.toString();
615     }
616 
617     // Unescape attribute string, using charCode for reserved characters.
618 
619     static String unescapeAttributeString(String string,
620 					  String charCode)
621 	throws ServiceLocationException {
622 
623 	// Process escapes.
624 
625 	int i, n = string.length();
626 	StringBuffer buf = new StringBuffer(n);
627 
628 	for (i = 0; i < n; i++) {
629 	    char c = string.charAt(i);
630 
631 	    // Check for invalids.
632 
633 	    int idx = -1;
634 
635 	    if ((idx = UNESCAPABLE_CHARS.indexOf(c)) != -1) {
636 		throw
637 		    new ServiceLocationException(
638 				ServiceLocationException.PARSE_ERROR,
639 				"v1_escape_error",
640 				new Object[] {string});
641 	    }
642 
643 	    // Check for escapes.
644 
645 	    if (c != '&') {
646 
647 		buf.append(c);
648 
649 	    } else {
650 
651 		// Check to be sure we've got enough characters left. We need
652 		// at least 3.
653 
654 		if ((i + 1) >= n) {
655 		    throw
656 			new ServiceLocationException(
657 				ServiceLocationException.PARSE_ERROR,
658 				"v1_escape_error",
659 				new Object[] {string});
660 		}
661 
662 		c = string.charAt(++i);
663 
664 		if (c != '#') {
665 		    throw
666 			new ServiceLocationException(
667 				ServiceLocationException.PARSE_ERROR,
668 				"v1_escape_error",
669 				new Object[] {string});
670 		}
671 
672 		// Iterate through numbers, collecting.
673 
674 		StringBuffer num = new StringBuffer(n);
675 
676 		for (i++; i < n; i++) {
677 
678 		    c = string.charAt(i);
679 
680 		    if (!Character.isDigit(c)) {
681 			break;
682 		    }
683 
684 		    num.append(c);
685 		}
686 
687 		// If the buffer is empty, then throw exception
688 
689 		if (num.length() <= 0) {
690 		    throw
691 			new ServiceLocationException(
692 				ServiceLocationException.PARSE_ERROR,
693 				"v1_escape_error",
694 				new Object[] {string});
695 		}
696 
697 		// If the last one isn't ";", we've got a problem.
698 
699 		if (c != ';') {
700 		    throw
701 			new ServiceLocationException(
702 				ServiceLocationException.PARSE_ERROR,
703 				"v1_escape_error",
704 				new Object[] {string});
705 		}
706 
707 		// OK, now convert to a character and add to buffer.
708 
709 		try {
710 		    buf.append(IANACharCode.unescapeChar(num.toString(),
711 							 charCode));
712 
713 		} catch (NumberFormatException ex) {
714 
715 		    throw
716 			new ServiceLocationException(
717 				ServiceLocationException.PARSE_ERROR,
718 				"v1_escape_error",
719 				new Object[] {string});
720 		}
721 	    }
722 	}
723 
724 	return buf.toString();
725     }
726 }
727