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
35package com.sun.slp;
36
37import java.util.*;
38
39/**
40 * Handles attribute string encoding and decoding for SLPv1.
41 *
42 * @author James Kempf
43 */
44
45class 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