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