Christian van Spaandonk | 569a4c5 | 2008-08-02 09:56:01 +0000 | [diff] [blame] | 1 | /* |
Richard S. Hall | bbd3feb | 2009-07-24 17:06:37 +0000 | [diff] [blame] | 2 | * Copyright (c) OSGi Alliance (2004, 2008). All Rights Reserved. |
Christian van Spaandonk | 569a4c5 | 2008-08-02 09:56:01 +0000 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | package info.dmtree; |
| 17 | |
| 18 | import java.io.UnsupportedEncodingException; |
Richard S. Hall | bbd3feb | 2009-07-24 17:06:37 +0000 | [diff] [blame] | 19 | import java.lang.reflect.InvocationTargetException; |
| 20 | import java.lang.reflect.Method; |
| 21 | import java.lang.reflect.Modifier; |
Christian van Spaandonk | 569a4c5 | 2008-08-02 09:56:01 +0000 | [diff] [blame] | 22 | import java.security.AccessController; |
| 23 | import java.security.PrivilegedAction; |
| 24 | import java.util.ArrayList; |
| 25 | import java.util.List; |
| 26 | |
| 27 | /** |
| 28 | * This class contains static utility methods to manipulate DMT URIs. |
| 29 | * <p> |
| 30 | * Syntax of valid DMT URIs: |
| 31 | * <ul> |
| 32 | * <li>A slash (<code>'/'</code> \u002F) is the separator of the node names. |
Richard S. Hall | bbd3feb | 2009-07-24 17:06:37 +0000 | [diff] [blame] | 33 | * Slashes used in node name must therefore be escaped using a backslash slash ( |
| 34 | * <code>"\/"</code>). The backslash must be escaped with a double backslash sequence. A |
| 35 | * backslash found must be ignored when it is not followed by a slash or |
| 36 | * backslash. |
| 37 | * <li>The node name can be constructed using full Unicode character set (except |
| 38 | * the Supplementary code, not being supported by CLDC/CDC). However, using the |
| 39 | * full Unicode character set for node names is discouraged because the encoding |
| 40 | * in the underlying storage as well as the encoding needed in communications |
| 41 | * can create significant performance and memory usage overhead. Names that are |
| 42 | * restricted to the URI set <code>[-a-zA-Z0-9_.!~*'()]</code> are most efficient. |
Christian van Spaandonk | 569a4c5 | 2008-08-02 09:56:01 +0000 | [diff] [blame] | 43 | * <li>URIs used in the DMT must be treated and interpreted as case sensitive. |
| 44 | * <li>No End Slash: URI must not end with the delimiter slash (<code>'/'</code> |
Richard S. Hall | bbd3feb | 2009-07-24 17:06:37 +0000 | [diff] [blame] | 45 | * \u002F). This implies that the root node must be denoted as |
Christian van Spaandonk | 569a4c5 | 2008-08-02 09:56:01 +0000 | [diff] [blame] | 46 | * <code>"."</code> and not <code>"./"</code>. |
| 47 | * <li>No parent denotation: URI must not be constructed using the character |
| 48 | * sequence <code>"../"</code> to traverse the tree upwards. |
| 49 | * <li>Single Root: The character sequence <code>"./"</code> must not be used |
| 50 | * anywhere else but in the beginning of a URI. |
| 51 | * </ul> |
Richard S. Hall | bbd3feb | 2009-07-24 17:06:37 +0000 | [diff] [blame] | 52 | * |
| 53 | * @version $Revision: 5673 $ |
Christian van Spaandonk | 569a4c5 | 2008-08-02 09:56:01 +0000 | [diff] [blame] | 54 | */ |
| 55 | public final class Uri { |
| 56 | /* |
| 57 | * NOTE: An implementor may also choose to replace this class in |
| 58 | * their distribution with a class that directly interfaces with the |
| 59 | * info.dmtree implementation. This replacement class MUST NOT alter the |
| 60 | * public/protected signature of this class. |
| 61 | */ |
| 62 | |
| 63 | /* |
| 64 | * This class will load the class named |
| 65 | * by the org.osgi.vendor.dmtree.DigestDelegate property. This class will call |
| 66 | * the public static byte[] digest(byte[]) method on that class. |
| 67 | */ |
| 68 | |
| 69 | private static class ImplHolder implements PrivilegedAction { |
| 70 | // the name of the system property containing the digest delegate class name |
| 71 | private static final String DIGEST_DELEGATE_PROPERTY = |
| 72 | "org.osgi.vendor.dmtree.DigestDelegate"; |
| 73 | |
| 74 | // the Method where message digest requests can be delegated |
| 75 | static final Method digestMethod; |
| 76 | |
| 77 | static { |
| 78 | digestMethod = (Method) AccessController.doPrivileged(new ImplHolder()); |
| 79 | } |
| 80 | |
| 81 | private ImplHolder() { |
| 82 | } |
| 83 | |
| 84 | public Object run() { |
| 85 | String className = System |
| 86 | .getProperty(DIGEST_DELEGATE_PROPERTY); |
| 87 | if (className == null) { |
| 88 | throw new NoClassDefFoundError("Digest " + |
| 89 | "delegate class property '" + |
| 90 | DIGEST_DELEGATE_PROPERTY + |
| 91 | "' must be set to a " + |
| 92 | "class which implements a " + |
| 93 | "public static byte[] digest(byte[]) method."); |
| 94 | } |
| 95 | |
| 96 | Class delegateClass; |
| 97 | try { |
| 98 | delegateClass = Class.forName(className); |
| 99 | } |
| 100 | catch (ClassNotFoundException e) { |
| 101 | throw new NoClassDefFoundError(e.toString()); |
| 102 | } |
| 103 | |
| 104 | Method result; |
| 105 | try { |
| 106 | result = delegateClass.getMethod("digest", |
| 107 | new Class[] {byte[].class}); |
| 108 | } |
| 109 | catch (NoSuchMethodException e) { |
| 110 | throw new NoSuchMethodError(e.toString()); |
| 111 | } |
| 112 | |
| 113 | if (!Modifier.isStatic(result.getModifiers())) { |
| 114 | throw new NoSuchMethodError( |
| 115 | "digest method must be static"); |
| 116 | } |
| 117 | |
| 118 | return result; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | |
| 123 | // the name of the system property containing the URI segment length limit |
| 124 | private static final String SEGMENT_LENGTH_LIMIT_PROPERTY = |
| 125 | "org.osgi.impl.service.dmt.uri.limits.segmentlength"; |
| 126 | |
| 127 | // the smallest valid value for the URI segment length limit |
| 128 | private static final int MINIMAL_SEGMENT_LENGTH_LIMIT = 32; |
| 129 | |
| 130 | // contains the maximum length of node names |
| 131 | private static final int segmentLengthLimit; |
| 132 | |
| 133 | static { |
| 134 | segmentLengthLimit = ((Integer) AccessController |
| 135 | .doPrivileged(new PrivilegedAction() { |
| 136 | public Object run() { |
| 137 | String limitString = System.getProperty(SEGMENT_LENGTH_LIMIT_PROPERTY); |
| 138 | int limit = MINIMAL_SEGMENT_LENGTH_LIMIT; // min. used as default |
| 139 | |
| 140 | try { |
| 141 | int limitInt = Integer.parseInt(limitString); |
| 142 | if(limitInt >= MINIMAL_SEGMENT_LENGTH_LIMIT) |
| 143 | limit = limitInt; |
| 144 | } catch(NumberFormatException e) {} |
| 145 | |
| 146 | return new Integer(limit); |
| 147 | } |
| 148 | })).intValue(); |
| 149 | } |
| 150 | |
| 151 | // base64 encoding table, modified for use in node name mangling |
| 152 | private static final char BASE_64_TABLE[] = { |
| 153 | 'A','B','C','D','E','F','G','H', |
| 154 | 'I','J','K','L','M','N','O','P', |
| 155 | 'Q','R','S','T','U','V','W','X', |
| 156 | 'Y','Z','a','b','c','d','e','f', |
| 157 | 'g','h','i','j','k','l','m','n', |
| 158 | 'o','p','q','r','s','t','u','v', |
| 159 | 'w','x','y','z','0','1','2','3', |
| 160 | '4','5','6','7','8','9','+','_', // !!! this differs from base64 |
| 161 | }; |
| 162 | |
| 163 | |
| 164 | /** |
| 165 | * A private constructor to suppress the default public constructor. |
| 166 | */ |
| 167 | private Uri() {} |
| 168 | |
| 169 | /** |
| 170 | * Returns a node name that is valid for the tree operation methods, based |
| 171 | * on the given node name. This transformation is not idempotent, so it must |
| 172 | * not be called with a parameter that is the result of a previous |
| 173 | * <code>mangle</code> method call. |
| 174 | * <p> |
| 175 | * Node name mangling is needed in the following cases: |
| 176 | * <ul> |
| 177 | * <li>if the name contains '/' or '\' characters |
| 178 | * <li>if the length of the name exceeds the limit defined by the |
| 179 | * implementation |
| 180 | * </ul> |
| 181 | * <p> |
| 182 | * A node name that does not suffer from either of these problems is |
| 183 | * guaranteed to remain unchanged by this method. Therefore the client may |
| 184 | * skip the mangling if the node name is known to be valid (though it is |
| 185 | * always safe to call this method). |
| 186 | * <p> |
| 187 | * The method returns the normalized <code>nodeName</code> as described |
| 188 | * below. Invalid node names are normalized in different ways, depending on |
| 189 | * the cause. If the length of the name does not exceed the limit, but the |
| 190 | * name contains '/' or '\' characters, then these are simply escaped by |
| 191 | * inserting an additional '\' before each occurrence. If the length of the |
| 192 | * name does exceed the limit, the following mechanism is used to normalize |
| 193 | * it: |
| 194 | * <ul> |
| 195 | * <li>the SHA 1 digest of the name is calculated |
| 196 | * <li>the digest is encoded with the base 64 algorithm |
| 197 | * <li>all '/' characters in the encoded digest are replaced with '_' |
| 198 | * <li>trailing '=' signs are removed |
| 199 | * </ul> |
| 200 | * |
| 201 | * @param nodeName the node name to be mangled (if necessary), must not be |
| 202 | * <code>null</code> or empty |
| 203 | * @return the normalized node name that is valid for tree operations |
| 204 | * @throws NullPointerException if <code>nodeName</code> is |
| 205 | * <code>null</code> |
| 206 | * @throws IllegalArgumentException if <code>nodeName</code> is empty |
| 207 | */ |
| 208 | public static String mangle(String nodeName) { |
| 209 | return mangle(nodeName, getMaxSegmentNameLength()); |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Construct a URI from the specified URI segments. The segments must |
| 214 | * already be mangled. |
| 215 | * <p> |
| 216 | * If the specified path is an empty array then an empty URI |
| 217 | * (<code>""</code>) is returned. |
| 218 | * |
| 219 | * @param path a possibly empty array of URI segments, must not be |
| 220 | * <code>null</code> |
| 221 | * @return the URI created from the specified segments |
| 222 | * @throws NullPointerException if the specified path or any of its |
| 223 | * segments are <code>null</code> |
| 224 | * @throws IllegalArgumentException if the specified path contains too many |
| 225 | * or malformed segments or the resulting URI is too long |
| 226 | */ |
| 227 | public static String toUri(String[] path) { |
| 228 | if (0 == path.length) { |
| 229 | return ""; |
| 230 | } |
| 231 | |
| 232 | if (path.length > getMaxUriSegments()) { |
| 233 | throw new IllegalArgumentException( |
| 234 | "Path contains too many segments."); |
| 235 | } |
| 236 | |
| 237 | StringBuffer uri = new StringBuffer(); |
| 238 | int uriLength = 0; |
| 239 | for (int i = 0; i < path.length; ++i) { |
| 240 | // getSegmentLength throws exceptions on malformed segments. |
| 241 | int segmentLength = getSegmentLength(path[i]); |
| 242 | if (segmentLength > getMaxSegmentNameLength()) { |
| 243 | throw new IllegalArgumentException("URI segment too long."); |
| 244 | } |
| 245 | if (i > 0) { |
| 246 | uri.append('/'); |
| 247 | uriLength++; |
| 248 | } |
| 249 | uriLength += segmentLength; |
| 250 | uri.append(path[i]); |
| 251 | } |
| 252 | if (uriLength > getMaxUriLength()) { |
| 253 | throw new IllegalArgumentException("URI too long."); |
| 254 | } |
| 255 | return uri.toString(); |
| 256 | } |
| 257 | |
| 258 | /** |
| 259 | * This method returns the length of a URI segment. The length of the URI |
| 260 | * segment is defined as the number of bytes in the unescaped, UTF-8 encoded |
| 261 | * represenation of the segment. |
| 262 | * <p> |
| 263 | * The method verifies that the URI segment is well-formed. |
| 264 | * |
| 265 | * @param segment the URI segment |
| 266 | * @return URI segment length |
| 267 | * @throws NullPointerException if the specified segment is |
| 268 | * <code>null</code> |
| 269 | * @throws IllegalArgumentException if the specified URI segment is |
| 270 | * malformed |
| 271 | */ |
| 272 | private static int getSegmentLength(String segment) { |
| 273 | if (segment.length() == 0) |
| 274 | throw new IllegalArgumentException("URI segment is empty."); |
| 275 | |
| 276 | StringBuffer newsegment = new StringBuffer(segment); |
| 277 | int i = 0; |
| 278 | while (i < newsegment.length()) { // length can decrease during the loop! |
| 279 | if (newsegment.charAt(i) == '\\') { |
| 280 | if (i == newsegment.length() - 1) // last character cannot be a '\' |
| 281 | throw new IllegalArgumentException( |
| 282 | "URI segment ends with the escape character."); |
| 283 | |
| 284 | newsegment.deleteCharAt(i); // remove the extra '\' |
| 285 | } else if (newsegment.charAt(i) == '/') |
| 286 | throw new IllegalArgumentException( |
| 287 | "URI segment contains an unescaped '/' character."); |
| 288 | |
| 289 | i++; |
| 290 | } |
| 291 | |
| 292 | if (newsegment.toString().equals("..")) |
| 293 | throw new IllegalArgumentException( |
| 294 | "URI segment must not be \"..\"."); |
| 295 | |
| 296 | try { |
| 297 | return newsegment.toString().getBytes("UTF-8").length; |
| 298 | } catch (UnsupportedEncodingException e) { |
| 299 | // This should never happen. All implementations must support |
| 300 | // UTF-8 encoding; |
| 301 | throw new RuntimeException(e.toString()); |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | /** |
| 306 | * Split the specified URI along the path separator '/' charaters and return |
| 307 | * an array of URI segments. Special characters in the returned segments are |
| 308 | * escaped. The returned array may be empty if the specifed URI was empty. |
| 309 | * |
| 310 | * @param uri the URI to be split, must not be <code>null</code> |
| 311 | * @return an array of URI segments created by splitting the specified URI |
| 312 | * @throws NullPointerException if the specified URI is <code>null</code> |
| 313 | * @throws IllegalArgumentException if the specified URI is malformed |
| 314 | */ |
| 315 | public static String[] toPath(String uri) { |
| 316 | if (uri == null) |
| 317 | throw new NullPointerException("'uri' parameter is null."); |
| 318 | |
| 319 | if (!isValidUri(uri)) |
| 320 | throw new IllegalArgumentException("Malformed URI: " + uri); |
| 321 | |
| 322 | if (uri.length() == 0) |
| 323 | return new String[] {}; |
| 324 | |
| 325 | List segments = new ArrayList(); |
| 326 | StringBuffer segment = new StringBuffer(); |
| 327 | |
| 328 | boolean escape = false; |
| 329 | for (int i = 0; i < uri.length(); i++) { |
| 330 | char ch = uri.charAt(i); |
| 331 | |
| 332 | if (escape) { |
| 333 | if(ch == '/' || ch == '\\') |
| 334 | segment.append('\\'); |
| 335 | segment.append(ch); |
| 336 | escape = false; |
| 337 | } else if (ch == '/') { |
| 338 | segments.add(segment.toString()); |
| 339 | segment = new StringBuffer(); |
| 340 | } else if (ch == '\\') { |
| 341 | escape = true; |
| 342 | } else |
| 343 | segment.append(ch); |
| 344 | } |
| 345 | if (segment.length() > 0) { |
| 346 | segments.add(segment.toString()); |
| 347 | } |
| 348 | |
| 349 | return (String[]) segments.toArray(new String[segments.size()]); |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * Returns the maximum allowed number of URI segments. The returned value is |
| 354 | * implementation specific. |
| 355 | * <p> |
| 356 | * The return value of <code>Integer.MAX_VALUE</code> indicates that there |
| 357 | * is no upper limit on the number of URI segments. |
| 358 | * |
| 359 | * @return maximum number of URI segments supported by the implementation |
| 360 | */ |
| 361 | public static int getMaxUriSegments() { |
| 362 | return Integer.MAX_VALUE; |
| 363 | } |
| 364 | |
| 365 | /** |
| 366 | * Returns the maximum allowed length of a URI. The value is implementation |
| 367 | * specific. The length of the URI is defined as the number of bytes in the |
| 368 | * unescaped, UTF-8 encoded represenation of the URI. |
| 369 | * <p> |
| 370 | * The return value of <code>Integer.MAX_VALUE</code> indicates that there |
| 371 | * is no upper limit on the length of URIs. |
| 372 | * |
| 373 | * @return maximum URI length supported by the implementation |
| 374 | */ |
| 375 | public static int getMaxUriLength() { |
| 376 | return Integer.MAX_VALUE; |
| 377 | } |
| 378 | |
| 379 | /** |
| 380 | * Returns the maximum allowed length of a URI segment. The value is |
| 381 | * implementation specific. The length of the URI segment is defined as the |
| 382 | * number of bytes in the unescaped, UTF-8 encoded represenation of the |
| 383 | * segment. |
| 384 | * <p> |
| 385 | * The return value of <code>Integer.MAX_VALUE</code> indicates that there |
| 386 | * is no upper limit on the length of segment names. |
| 387 | * |
| 388 | * @return maximum URI segment length supported by the implementation |
| 389 | */ |
| 390 | public static int getMaxSegmentNameLength() { |
| 391 | return segmentLengthLimit; |
| 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Checks whether the specified URI is an absolute URI. An absolute URI |
| 396 | * contains the complete path to a node in the DMT starting from the DMT |
| 397 | * root ("."). |
| 398 | * |
| 399 | * @param uri the URI to be checked, must not be <code>null</code> and must |
| 400 | * contain a valid URI |
| 401 | * @return whether the specified URI is absolute |
| 402 | * @throws NullPointerException if the specified URI is <code>null</code> |
| 403 | * @throws IllegalArgumentException if the specified URI is malformed |
| 404 | */ |
| 405 | public static boolean isAbsoluteUri(String uri) { |
| 406 | if( null == uri ) { |
| 407 | throw new NullPointerException("'uri' parameter is null."); |
| 408 | } |
| 409 | if( !isValidUri(uri) ) |
| 410 | throw new IllegalArgumentException("Malformed URI: " + uri); |
| 411 | return uri.equals(".") || uri.equals("\\.") || uri.startsWith("./") |
| 412 | || uri.startsWith("\\./"); |
| 413 | } |
| 414 | |
| 415 | /** |
| 416 | * Checks whether the specified URI is valid. A URI is considered valid if |
| 417 | * it meets the following constraints: |
| 418 | * <ul> |
| 419 | * <li>the URI is not <code>null</code>; |
| 420 | * <li>the URI follows the syntax defined for valid DMT URIs; |
| 421 | * <li>the length of the URI is not more than {@link #getMaxUriLength()}; |
| 422 | * <li>the URI doesn't contain more than {@link #getMaxUriSegments()} |
| 423 | * segments; |
| 424 | * <li>the length of each segment of the URI is less than or equal to |
| 425 | * {@link #getMaxSegmentNameLength()}. |
| 426 | * </ul> |
| 427 | * The exact definition of the length of a URI and its segments is |
| 428 | * given in the descriptions of the <code>getMaxUriLength()</code> and |
| 429 | * <code>getMaxSegmentNameLength()</code> methods. |
| 430 | * |
| 431 | * @param uri the URI to be validated |
| 432 | * @return whether the specified URI is valid |
| 433 | */ |
| 434 | public static boolean isValidUri(String uri) { |
| 435 | if (null == uri) |
| 436 | return false; |
| 437 | |
| 438 | int paramLen = uri.length(); |
| 439 | if( paramLen == 0 ) |
| 440 | return true; |
| 441 | if( uri.charAt(0) == '/' || uri.charAt(paramLen-1) == '\\' ) |
| 442 | return false; |
| 443 | |
| 444 | int processedUriLength = 0; |
| 445 | int segmentNumber = 0; |
| 446 | |
| 447 | // append a '/' to indicate the end of the last segment (the URI in the |
| 448 | // parameter must not end with a '/') |
| 449 | uri += '/'; |
| 450 | paramLen++; |
| 451 | |
| 452 | int start = 0; |
| 453 | for(int i = 1; i < paramLen; i++) { // first character is not a '/' |
| 454 | if(uri.charAt(i) == '/' && uri.charAt(i-1) != '\\') { |
| 455 | segmentNumber++; |
| 456 | |
| 457 | String segment = uri.substring(start, i); |
| 458 | if(segmentNumber > 1 && segment.equals(".")) |
| 459 | return false; // the URI contains the "." node name at a |
| 460 | // position other than the beginning of the URI |
| 461 | |
| 462 | int segmentLength; |
| 463 | try { |
| 464 | // also checks that the segment is valid |
| 465 | segmentLength = getSegmentLength(segment); |
| 466 | } catch(IllegalArgumentException e) { |
| 467 | return false; |
| 468 | } |
| 469 | |
| 470 | if(segmentLength > getMaxSegmentNameLength()) |
| 471 | return false; |
| 472 | |
| 473 | // the extra byte is for the separator '/' (will be deducted |
| 474 | // again for the last segment of the URI) |
| 475 | processedUriLength += segmentLength + 1; |
| 476 | start = i+1; |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | processedUriLength--; // remove the '/' added to the end of the URI |
| 481 | |
| 482 | return segmentNumber <= getMaxUriSegments() && |
| 483 | processedUriLength <= getMaxUriLength(); |
| 484 | } |
| 485 | |
| 486 | // Non-public fields and methods |
| 487 | |
| 488 | // package private method for testing purposes |
| 489 | static String mangle(String nodeName, int limit) { |
| 490 | if(nodeName == null) |
| 491 | throw new NullPointerException( |
| 492 | "The 'nodeName' parameter must not be null."); |
| 493 | |
| 494 | if(nodeName.equals("")) |
| 495 | throw new IllegalArgumentException( |
| 496 | "The 'nodeName' parameter must not be empty."); |
| 497 | |
| 498 | if(nodeName.length() > limit) |
| 499 | // create node name hash |
| 500 | return getHash(nodeName); |
| 501 | |
| 502 | // escape any '/' and '\' characters in the node name |
| 503 | StringBuffer nameBuffer = new StringBuffer(nodeName); |
| 504 | for(int i = 0; i < nameBuffer.length(); i++) // 'i' can increase in loop |
| 505 | if(nameBuffer.charAt(i) == '\\' || nameBuffer.charAt(i) == '/') |
| 506 | nameBuffer.insert(i++, '\\'); |
| 507 | |
| 508 | return nameBuffer.toString(); |
| 509 | } |
| 510 | |
| 511 | private static String getHash(String from) { |
| 512 | byte[] byteStream; |
| 513 | try { |
| 514 | byteStream = from.getBytes("UTF-8"); |
| 515 | } |
| 516 | catch (UnsupportedEncodingException e) { |
| 517 | // There's no way UTF-8 encoding is not implemented... |
| 518 | throw new IllegalStateException("there's no UTF-8 encoder here!"); |
| 519 | } |
| 520 | byte[] digest = digestMessage(byteStream); |
| 521 | |
| 522 | // very dumb base64 encoder code. There is no need for multiple lines |
| 523 | // or trailing '='-s.... |
| 524 | // also, we hardcoded the fact that sha-1 digests are 20 bytes long |
| 525 | StringBuffer sb = new StringBuffer(digest.length*2); |
| 526 | for(int i=0;i<6;i++) { |
| 527 | int d0 = digest[i*3]&0xff; |
| 528 | int d1 = digest[i*3+1]&0xff; |
| 529 | int d2 = digest[i*3+2]&0xff; |
| 530 | sb.append(BASE_64_TABLE[d0>>2]); |
| 531 | sb.append(BASE_64_TABLE[(d0<<4|d1>>4)&63]); |
| 532 | sb.append(BASE_64_TABLE[(d1<<2|d2>>6)&63]); |
| 533 | sb.append(BASE_64_TABLE[d2&63]); |
| 534 | } |
| 535 | int d0 = digest[18]&0xff; |
| 536 | int d1 = digest[19]&0xff; |
| 537 | sb.append(BASE_64_TABLE[d0>>2]); |
| 538 | sb.append(BASE_64_TABLE[(d0<<4|d1>>4)&63]); |
| 539 | sb.append(BASE_64_TABLE[(d1<<2)&63]); |
| 540 | |
| 541 | return sb.toString(); |
| 542 | } |
| 543 | |
| 544 | private static byte[] digestMessage(byte[] byteStream) { |
| 545 | try { |
| 546 | try { |
| 547 | return (byte[]) ImplHolder.digestMethod.invoke(null, new Object[] { |
| 548 | byteStream}); |
| 549 | } |
| 550 | catch (InvocationTargetException e) { |
| 551 | throw e.getTargetException(); |
| 552 | } |
| 553 | } |
| 554 | catch (Error e) { |
| 555 | throw e; |
| 556 | } |
| 557 | catch (RuntimeException e) { |
| 558 | throw e; |
| 559 | } |
| 560 | catch (Throwable e) { |
| 561 | throw new RuntimeException(e.toString()); |
| 562 | } |
| 563 | } |
| 564 | } |