001/** 002 * Java Web Archive Toolkit - Software to read and validate ARC, WARC 003 * and GZip files. (http://jwat.org/) 004 * Copyright 2011-2012 Netarkivet.dk (http://netarkivet.dk/) 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.jwat.common; 019 020import java.net.URISyntaxException; 021 022/** 023 * Implementation of an URI profile. A profile can be used to customize which 024 * characters and features are acceptable when a certain profile is used. 025 * 026 * An array of integers is used to defined which categories the first 027 * 8-bit characters belong to. 028 * 029 * @author nicl 030 */ 031public class UriProfile { 032 033 /** Bit to categorize a char as an alpha. */ 034 public static final int B_ALPHAS = 1 << 0; 035 /** Bit to categorize a char as a digit. */ 036 public static final int B_DIGITS = 1 << 1; 037 /** Bit to categorize a char as first in scheme. */ 038 public static final int B_SCHEME_FIRST = 1 << 2; 039 /** Bit to categorize a char as following in scheme. */ 040 public static final int B_SCHEME_FOLLOW = 1 << 3; 041 /** Bit to categorize a char as UNRESERVED in RFC3986. */ 042 public static final int B_UNRESERVED = 1 << 4; 043 /** Bit to categorize a char as GEN-DELIMS in RFC3986. */ 044 public static final int B_GEN_DELIMS = 1 << 5; 045 /** Bit to categorize a char as SUB-DELIMS in RFC3986. */ 046 public static final int B_SUB_DELIMS = 1 << 6; 047 /** Bit to categorize a char as RESERVED in RFC3986. */ 048 public static final int B_RESERVED = 1 << 7; 049 /** Bit to categorize a char as PCHAR in RFC3986. */ 050 public static final int B_PCHAR = 1 << 8; 051 /** Bit to categorize a char as USERINFO in RFC3986. */ 052 public static final int B_USERINFO = 1 << 9; 053 /** Bit to categorize a char as REGNAME in RFC3986. */ 054 public static final int B_REGNAME = 1 << 10; 055 /** Bit to categorize a char as SEGMENT in RFC3986. */ 056 public static final int B_SEGMENT = 1 << 11; 057 /** Bit to categorize a char as SEGMENT-NZ in RFC3986. */ 058 public static final int B_SEGMENT_NZ = 1 << 12; 059 /** Bit to categorize a char as SEGMENT-NZ-NC in RFC3986. */ 060 public static final int B_SEGMENT_NZ_NC = 1 << 13; 061 /** Bit to categorize a char as PATH in RFC3986. */ 062 public static final int B_PATH = 1 << 14; 063 /** Bit to categorize a char as QUERY in RFC3986. */ 064 public static final int B_QUERY = 1 << 15; 065 /** Bit to categorize a char as FRAGMENT in RFC3986. */ 066 public static final int B_FRAGMENT = 1 << 16; 067 068 /** Array of integers used to categorize all 8bit chars. */ 069 protected final int[] charTypeMap = new int[256]; 070 071 /** Does profile allow relative URIs. */ 072 public boolean bAllowRelativeUris; 073 074 /** Does profile allow 16-bit percent encoding. */ 075 public boolean bAllow16bitPercentEncoding; 076 077 /** Does profile allow invalid percent encoding. */ 078 public boolean bAllowInvalidPercentEncoding; 079 080 /** 081 * Construct an <code>UriProfile</code> initialized with RFC3986 082 * rules. 083 */ 084 public UriProfile() { 085 for (int i=0; i<defaultCharTypeMap.length; ++i) { 086 charTypeMap[i] = defaultCharTypeMap[i]; 087 } 088 bAllowRelativeUris = true; 089 bAllow16bitPercentEncoding = false; 090 bAllowInvalidPercentEncoding = false; 091 } 092 093 /** 094 * Construct an <code>UriProfile</code> initialized from another profile. 095 * @param uriProfile URI profile to base a new profile on 096 */ 097 public UriProfile(UriProfile uriProfile) { 098 for (int i=0; i<charTypeMap.length; ++i) { 099 charTypeMap[i] = uriProfile.charTypeMap[i]; 100 } 101 bAllowRelativeUris = uriProfile.bAllowRelativeUris; 102 bAllow16bitPercentEncoding = uriProfile.bAllow16bitPercentEncoding; 103 bAllowInvalidPercentEncoding = uriProfile.bAllowInvalidPercentEncoding; 104 } 105 106 /** 107 * Helper method to populate the profiles integer array with character 108 * categories. 109 * @param chars characters to categorize or null 110 * @param bw_and character categories to include in new category 111 * @param bw_or new category/categories 112 */ 113 public void charTypeAddAndOr(String chars, int bw_and, int bw_or) { 114 UriProfile.charTypeAddAndOr(charTypeMap, chars, bw_and, bw_or); 115 } 116 117 /** 118 * Given a collection of characters find the index of the first occurrence 119 * of any of the characters in the supplied string starting from a certain 120 * position. 121 * @param bw_and bits identifying one or more character categories 122 * @param str string to search through 123 * @param pos position to start searching from 124 * @return index of the first character in the string present in the 125 * character collection(s) 126 * @throws URISyntaxException if a character with a value larger that 255 127 * is encountered 128 */ 129 public int indexOf(int bw_and, String str, int pos) throws URISyntaxException { 130 int limit = str.length(); 131 char c; 132 while (pos < limit) { 133 c = str.charAt(pos); 134 if (c < 256) { 135 if ((charTypeMap[c] & bw_and) != 0) { 136 return pos; 137 } 138 ++pos; 139 } else { 140 throw new URISyntaxException(str, "Invalid URI character '" + (Character.isISOControl(c)?String.format("0x%02x", (int)c):c) + "'"); 141 } 142 } 143 return -1; 144 } 145 146 /** 147 * Validate an URI component using two character categories. One category 148 * for the first character and another for the following characters. 149 * @param str URI component string 150 * @param bw_and_first bits identifying first character categories 151 * @param bw_and_follow bits identifying following character categories 152 * @throws URISyntaxException if an error occurs parsing component 153 */ 154 public void validate_first_follow(String str, int bw_and_first, int bw_and_follow) throws URISyntaxException { 155 int pos = 0; 156 int limit = str.length(); 157 char c; 158 while (pos < limit) { 159 c = str.charAt(pos); 160 // indexOf has ensured that the scheme does not have character values > 255. 161 if (pos == 0 && ((charTypeMap[c] & UriProfile.B_SCHEME_FIRST) == 0)) { 162 throw new URISyntaxException(str, "Invalid URI scheme component"); 163 } else if ((charTypeMap[c] & UriProfile.B_SCHEME_FOLLOW) == 0) { 164 throw new URISyntaxException(str, "Invalid URI scheme component"); 165 } 166 ++pos; 167 } 168 } 169 170 /** 171 * Validates an URI component according to the supplied character category 172 * bitfield. 173 * @param bw_and bits identifying one or more character categories 174 * @param componentName URI component name 175 * @param str URI component string 176 * @return decoded and validated string 177 * @throws URISyntaxException if an error occurs parsing/validating component 178 */ 179 public String validate_decode(int bw_and, String componentName, String str) throws URISyntaxException { 180 StringBuilder sb = new StringBuilder(); 181 int pos = 0; 182 int ppos; 183 int limit = str.length(); 184 char c; 185 int decode = 0; 186 int tmpC; 187 char decodedC; 188 boolean bValid; 189 while (pos < limit) { 190 c = str.charAt(pos++); 191 if (c < 256) { 192 if ((charTypeMap[c] & bw_and) == 0) { 193 if (c == '%') { 194 ppos = pos - 1; 195 if (pos < limit) { 196 c = str.charAt(pos); 197 if (c == 'u' || c == 'U') { 198 if (!bAllow16bitPercentEncoding) { 199 if (!bAllowInvalidPercentEncoding) { 200 throw new URISyntaxException(str, "Invalid URI " + componentName + " component - 16-bit percent encoding not allowed"); 201 } else { 202 bValid = false; 203 } 204 } else { 205 ++pos; 206 decode = 4; 207 bValid = true; 208 } 209 } else { 210 decode = 2; 211 bValid = true; 212 } 213 decodedC = 0; 214 while (bValid && decode > 0) { 215 if (pos < limit) { 216 c = str.charAt(pos++); 217 decodedC <<= 4; 218 if (c < 256) { 219 tmpC = asciiHexTab[c]; 220 if (tmpC != -1) { 221 decodedC |= tmpC; 222 --decode; 223 } else { 224 bValid = false; 225 } 226 } else { 227 bValid = false; 228 } 229 } else { 230 bValid = false; 231 } 232 } 233 if (!bValid && !bAllowInvalidPercentEncoding) { 234 throw new URISyntaxException(str, "Invalid URI " + componentName + " component - invalid percent encoding"); 235 } 236 sb.append((char) decodedC); 237 } else { 238 if (!bAllowInvalidPercentEncoding) { 239 throw new URISyntaxException(str, "Invalid URI " + componentName + " component - incomplete percent encoding"); 240 } else { 241 bValid = false; 242 } 243 } 244 if (!bValid) { 245 while (ppos < pos) { 246 sb.append(str.charAt(ppos++)); 247 } 248 } 249 } else { 250 throw new URISyntaxException(str, "Invalid URI " + componentName + " component - invalid character '" + (Character.isISOControl(c)?String.format("0x%02x", (int)c):c) + "'"); 251 } 252 } else { 253 sb.append(c); 254 } 255 } else { 256 throw new URISyntaxException(str, "Invalid URI " + componentName + " component - invalid character '" + (Character.isISOControl(c)?String.format("0x%02x", (int)c):c) + "'"); 257 } 258 } 259 return sb.toString(); 260 } 261 262 /** Hex char to integer conversion table. */ 263 public static int[] asciiHexTab = new int[256]; 264 265 /** Integer to hex char conversion table. */ 266 public static char[] hexTab = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 267 268 /* 269 * Initialize ASCII hex table. 270 */ 271 static { 272 String hex = "0123456789abcdef"; 273 for (int i=0; i<asciiHexTab.length; ++i) { 274 asciiHexTab[i] = hex.indexOf(i); 275 } 276 hex = hex.toUpperCase(); 277 for (int i=0; i<hex.length(); ++i) { 278 asciiHexTab[hex.charAt(i)] = i; 279 } 280 } 281 282 /** 283 * Helper method to populate an integer array with character categories. 284 * @param charTypeMap integer array with character categories 285 * @param chars characters to categorize or null 286 * @param bw_and character categories to include in new category 287 * @param bw_or new category/categories 288 */ 289 public static void charTypeAddAndOr(int[] charTypeMap, String chars, int bw_and, int bw_or) { 290 if (chars != null) { 291 for (int i=0; i<chars.length(); ++i) { 292 charTypeMap[chars.charAt(i)] |= bw_or; 293 } 294 } 295 if (bw_and != 0) { 296 for (int i=0; i<charTypeMap.length; ++i) { 297 if ((charTypeMap[i] & bw_and) != 0) { 298 charTypeMap[i] |= bw_or; 299 } 300 } 301 } 302 } 303 304 /** Default array of integers used to categorize all 8bit chars. */ 305 protected static int[] defaultCharTypeMap = new int[256]; 306 307 /* 308 * Initialize default char type map. 309 */ 310 static { 311 // Alphas. 312 String alphas = "abcdefghijklmnopqrstuvwxyz"; 313 charTypeAddAndOr(defaultCharTypeMap, alphas, 0, B_ALPHAS); 314 charTypeAddAndOr(defaultCharTypeMap, alphas.toUpperCase(), 0, B_ALPHAS); 315 316 // Digits. 317 String digits = "1234567890"; 318 charTypeAddAndOr(defaultCharTypeMap, digits, 0, B_DIGITS); 319 320 // scheme first/follow. 321 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) 322 String scheme = "+-."; 323 charTypeAddAndOr(defaultCharTypeMap, null, B_ALPHAS, B_SCHEME_FIRST | B_SCHEME_FOLLOW); 324 charTypeAddAndOr(defaultCharTypeMap, scheme, B_DIGITS, B_SCHEME_FOLLOW); 325 326 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 327 String unreserved = "-._~"; 328 charTypeAddAndOr(defaultCharTypeMap, unreserved, B_ALPHAS | B_DIGITS, B_UNRESERVED); 329 330 // gen-delims (reserved) 331 String genDelims = ":/?#[]@"; 332 charTypeAddAndOr(defaultCharTypeMap, genDelims, 0, B_GEN_DELIMS | B_RESERVED); 333 334 // sub-delims (reserved) 335 String subDelims = "!$&'()*+,;="; 336 charTypeAddAndOr(defaultCharTypeMap, subDelims, 0, B_SUB_DELIMS | B_RESERVED); 337 338 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 339 String pchar = ":@"; 340 charTypeAddAndOr(defaultCharTypeMap, pchar, B_UNRESERVED | B_SUB_DELIMS, B_PCHAR); 341 342 // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) 343 String userinfo = ":"; 344 charTypeAddAndOr(defaultCharTypeMap, userinfo, B_UNRESERVED | B_SUB_DELIMS, B_USERINFO); 345 346 // reg-name = *( unreserved / pct-encoded / sub-delims ) 347 charTypeAddAndOr(defaultCharTypeMap, null, B_UNRESERVED | B_SUB_DELIMS, B_REGNAME); 348 349 // segment = *pchar 350 // segment-nz = 1*pchar 351 charTypeAddAndOr(defaultCharTypeMap, null, B_PCHAR, B_SEGMENT); 352 charTypeAddAndOr(defaultCharTypeMap, null, B_PCHAR, B_SEGMENT_NZ); 353 354 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) 355 // non-zero-length segment without any colon ":" 356 String segment_nz_nc = "@"; 357 charTypeAddAndOr(defaultCharTypeMap, segment_nz_nc, B_UNRESERVED | B_SUB_DELIMS, B_SEGMENT_NZ_NC); 358 359 // path 360 String path = "/"; 361 charTypeAddAndOr(defaultCharTypeMap, path, B_PCHAR, B_PATH); 362 363 // query = *( pchar / "/" / "?" ) 364 String query = "/?"; 365 charTypeAddAndOr(defaultCharTypeMap, query, B_PCHAR, B_QUERY); 366 367 // fragment = *( pchar / "/" / "?" ) 368 String fragment = "/?"; 369 charTypeAddAndOr(defaultCharTypeMap, fragment, B_PCHAR, B_FRAGMENT); 370 } 371 372 /** RFC3986 compliant URI profile. */ 373 public static final UriProfile RFC3986; 374 375 /** Modified RFC3986 URI profile which disallows relative URIs 376 * and allows 16-bit percent encoding. */ 377 public static final UriProfile RFC3986_ABS_16BIT; 378 379 /** Relaxed RFC3986 URI profile which disallows relative URIs, 380 * allows 16-bit percent encoding, allow invalid percent encoding 381 * and allows most 8-bit characters. */ 382 public static final UriProfile RFC3986_ABS_16BIT_LAX; 383 384 /* 385 * Initialize default profile. 386 */ 387 static { 388 RFC3986 = new UriProfile(); 389 390 RFC3986_ABS_16BIT = new UriProfile(); 391 RFC3986_ABS_16BIT.bAllowRelativeUris = false; 392 RFC3986_ABS_16BIT.bAllow16bitPercentEncoding = true; 393 RFC3986_ABS_16BIT.bAllowInvalidPercentEncoding = false; 394 395 StringBuilder sb = new StringBuilder("[]"); 396 for (int i=33; i<127; ++i) { 397 if ((defaultCharTypeMap[i] & (B_FRAGMENT | B_RESERVED)) == 0 && i != '%') { 398 sb.append((char) i); 399 } 400 } 401 for (int i=161; i<255; ++i) { 402 sb.append((char) i); 403 } 404 // The following characters are accepted in the relaxed mode. And NOT in the official RFC. 405 // []"<>\^`{|}¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ 406 RFC3986_ABS_16BIT_LAX = new UriProfile(); 407 RFC3986_ABS_16BIT_LAX.bAllowRelativeUris = false; 408 RFC3986_ABS_16BIT_LAX.bAllow16bitPercentEncoding = true; 409 RFC3986_ABS_16BIT_LAX.bAllowInvalidPercentEncoding = true; 410 RFC3986_ABS_16BIT_LAX.charTypeAddAndOr(sb.toString(), 0, B_PATH | B_QUERY | B_FRAGMENT); 411 RFC3986_ABS_16BIT_LAX.charTypeAddAndOr("#", 0, B_FRAGMENT); 412 } 413 414}