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