1 16 package org.outerj.daisy.frontend; 17 18 import org.apache.cocoon.transformation.AbstractTransformer; 19 import org.apache.cocoon.environment.SourceResolver; 20 import org.apache.cocoon.environment.Request; 21 import org.apache.cocoon.environment.ObjectModelHelper; 22 import org.apache.cocoon.ProcessingException; 23 import org.apache.cocoon.util.HashMap; 24 import org.apache.cocoon.matching.helpers.WildcardHelper; 25 import org.apache.cocoon.xml.IncludeXMLConsumer; 26 import org.apache.cocoon.xml.SaxBuffer; 27 import org.apache.cocoon.components.source.SourceUtil; 28 import org.apache.avalon.framework.parameters.Parameters; 29 import org.apache.avalon.framework.service.Serviceable; 30 import org.apache.avalon.framework.service.ServiceManager; 31 import org.apache.avalon.framework.service.ServiceException; 32 import org.apache.avalon.framework.context.Contextualizable; 33 import org.apache.avalon.framework.context.Context; 34 import org.apache.avalon.framework.context.ContextException; 35 import org.apache.excalibur.source.Source; 36 import org.apache.excalibur.xml.sax.SAXParser; 37 import org.apache.excalibur.store.Store; 38 import org.apache.commons.lang.exception.ExceptionUtils; 39 import org.xml.sax.SAXException ; 40 import org.xml.sax.Attributes ; 41 import org.xml.sax.helpers.AttributesImpl ; 42 import org.xml.sax.helpers.DefaultHandler ; 43 import org.outerj.daisy.frontend.util.CacheHelper; 44 import org.outerj.daisy.frontend.util.WikiDataDirHelper; 45 46 import java.util.Map ; 47 import java.util.List ; 48 import java.util.ArrayList ; 49 import java.util.regex.Pattern ; 50 import java.io.IOException ; 51 import java.io.File ; 52 import java.net.URI ; 53 import java.net.InetAddress ; 54 import java.net.UnknownHostException ; 55 import java.net.URISyntaxException ; 56 57 61 public class ExternalIncludeTransformer extends AbstractTransformer implements Serviceable, Contextualizable { 62 private static final String NAMESPACE = "http://outerx.org/daisy/1.0#externalinclude"; 63 private Context context; 64 private ServiceManager serviceManager; 65 private SourceResolver sourceResolver; 66 private Request request; 67 private PermissionRule[] rules; 68 69 public void setup(SourceResolver sourceResolver, Map objectModel, String s, Parameters parameters) throws ProcessingException, SAXException , IOException { 70 this.sourceResolver = sourceResolver; 71 this.request = ObjectModelHelper.getRequest(objectModel); 72 this.rules = null; 73 } 74 75 public void contextualize(Context context) throws ContextException { 76 this.context = context; 77 } 78 79 public void service(ServiceManager serviceManager) throws ServiceException { 80 this.serviceManager = serviceManager; 81 } 82 83 public void recycle() { 84 this.rules = null; 85 this.request = null; 86 this.sourceResolver = null; 87 } 88 89 public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { 90 if (namespaceURI.equals(NAMESPACE) && localName.equals("include")) { 91 String src = attributes.getValue("src"); 92 src = src.trim(); 93 if (src == null || src.equals("")) { 94 throw new SAXException ("Missing src attribute on " + qName + " element."); 95 } 96 97 if (src.startsWith("cocoon:/") && !src.startsWith("cocoon://")) { 98 String daisyCocoonPath = WikiHelper.getDaisyCocoonPath(request); 102 src = "cocoon:/" + daisyCocoonPath + src.substring("cocoon:".length()); 103 } 104 105 Source source = null; 106 try { 107 boolean canRead; 108 if (src.startsWith("cocoon:")) { 109 canRead = canRead("cocoon", src); 112 } else { 113 source = sourceResolver.resolveURI(src); 114 canRead = canRead(source.getScheme(), source.getURI()); 115 } 116 117 if (!canRead) { 118 throw new Exception ("Inclusion of this URL is not allowed."); 119 } 120 121 if (source == null) 122 source = sourceResolver.resolveURI(src); 123 SaxBuffer buffer = new SaxBuffer(); 124 SourceUtil.toSAX(source, buffer); 125 buffer.toSAX(new IncludeXMLConsumer(xmlConsumer)); 126 } catch (Throwable e) { 127 getLogger().error("Error processing include of " + src, e); 128 AttributesImpl attrs = new AttributesImpl (); 129 attrs.addAttribute("", "class", "class", "CDATA", "daisy-error"); 130 contentHandler.startElement("", "p", "p", attrs); 131 StringBuffer message = new StringBuffer ("Error processing inclusion of "); 132 message.append(src).append(" : ").append(e.getMessage()); 133 Throwable cause = e; 134 while ((cause = ExceptionUtils.getCause(cause)) != null) { 135 message.append(", cause: ").append(cause.getMessage()); 136 } 137 contentHandler.characters(message.toString().toCharArray(), 0, message.length()); 138 contentHandler.endElement("", "p", "p"); 139 } 140 } else { 141 super.startElement(namespaceURI, localName, qName, attributes); 142 } 143 } 144 145 public void endElement(String namespaceURI, String localName, String qName) throws SAXException { 146 if (namespaceURI.equals(NAMESPACE) && localName.equals("include")) { 147 } else { 149 super.endElement(namespaceURI, localName, qName); 150 } 151 } 152 153 private boolean canRead(String scheme, String uri) throws Exception { 154 PermissionRule[] rules = getPermissionRules(); 155 PermissionCheckContext context = new PermissionCheckContext(scheme, uri); 156 AccessOutcome outcome = new AccessOutcome(); 157 for (int i = 0; i < rules.length; i++) { 158 try { 159 rules[i].evaluate(context, outcome); 160 } catch (Throwable e) { 161 throw new Exception ("Error evaluating external include permission rules.", e); 162 } 163 } 164 return outcome.getPermission() == Permission.GRANT; 165 } 166 167 static class PermissionCheckContext { 168 private String scheme; 169 private String uriString; 170 private String ip; 171 private URI uri; 172 173 public PermissionCheckContext(String scheme, String uriString) { 174 this.scheme = scheme; 175 this.uriString = uriString; 176 } 177 178 public String getScheme() { 179 return scheme; 180 } 181 182 public String getUriString() { 183 return uriString; 184 } 185 186 public String getIp() throws UnknownHostException , URISyntaxException { 187 if (ip == null) { 188 URI uri = getUri(); 189 String host = uri.getHost(); 190 InetAddress address = InetAddress.getByName(host); 191 ip = address.getHostAddress(); 192 } 193 return ip; 194 } 195 196 public URI getUri() throws URISyntaxException { 197 if (uri == null) { 198 uri = new URI (uriString); 199 } 200 return uri; 201 } 202 } 203 204 static class Permission { 205 String name; 206 207 private Permission(String name) { 208 this.name = name; 209 } 210 211 public String toString() { 212 return name; 213 } 214 215 static final Permission GRANT = new Permission("grant"); 216 static final Permission DENY = new Permission("deny"); 217 } 218 219 static class AccessOutcome { 220 Permission permission = Permission.DENY; 221 222 public Permission getPermission() { 223 return permission; 224 } 225 226 public void setPermission(Permission permission) { 227 this.permission = permission; 228 } 229 } 230 231 static class PermissionRule { 232 private final String scheme; 233 private final ValueType valueType; 234 private final Matcher matcher; 235 private final Permission permission; 236 private final int portFrom; 237 private final int portTo; 238 239 public PermissionRule(String scheme, ValueType valueType, Matcher matcher, Permission permission, int portFrom, int portTo) { 240 this.scheme = scheme; 241 this.valueType = valueType; 242 this.matcher = matcher; 243 this.permission = permission; 244 this.portFrom = portFrom; 245 this.portTo = portTo; 246 if ((portFrom != -1 || portTo != -1) && !scheme.equals("http")) 247 throw new IllegalArgumentException ("portFrom/portTo can only be used with the http scheme."); 248 } 249 250 public void evaluate(PermissionCheckContext context, AccessOutcome outcome) throws Exception { 251 if (!scheme.equalsIgnoreCase(context.getScheme())) 252 return; 253 254 if (valueType != null) { 255 if (matcher.matches(valueType.getValue(context), context.getUri())) { 256 if (portFrom != -1 || portTo != -1) { 257 int port = context.getUri().getPort(); 258 if (port == -1) 259 port = 80; 260 if ((portFrom != -1 && port < portFrom) || (portTo != -1 && port > portTo)) { 261 return; 262 } 263 } 264 outcome.setPermission(permission); 265 } 266 } else { 267 outcome.setPermission(permission); 268 } 269 } 270 } 271 272 static interface ValueType { 273 String getValue(PermissionCheckContext context) throws Exception ; 274 } 275 276 static interface Matcher { 277 boolean matches(String value, URI uri) throws Exception ; 278 } 279 280 static class StringMatcher implements Matcher { 281 private final String matchValue; 282 283 public StringMatcher(String matchValue) { 284 this.matchValue = matchValue; 285 } 286 287 public boolean matches(String value, URI uri) { 288 return matchValue.equals(value); 289 } 290 } 291 292 static class StringIgnoreCaseMatcher implements Matcher { 293 private final String matchValue; 294 295 public StringIgnoreCaseMatcher(String matchValue) { 296 this.matchValue = matchValue; 297 } 298 299 public boolean matches(String value, URI uri) { 300 return matchValue.equalsIgnoreCase(value); 301 } 302 } 303 304 static class WildcardMatcher implements Matcher { 305 private final int[] expression; 306 307 public WildcardMatcher(String matchValue) { 308 this.expression = WildcardHelper.compilePattern(matchValue); 309 } 310 311 public boolean matches(String value, URI uri) { 312 return WildcardHelper.match(new HashMap(), value, expression); 313 } 314 } 315 316 static class WildcardIgnoreCaseMatcher implements Matcher { 317 private final int[] expression; 318 319 public WildcardIgnoreCaseMatcher(String matchValue) { 320 this.expression = WildcardHelper.compilePattern(matchValue.toLowerCase()); 321 } 322 323 public boolean matches(String value, URI uri) { 324 return WildcardHelper.match(new HashMap(), value.toLowerCase(), expression); 325 } 326 } 327 328 static class RegExpMatcher implements Matcher { 329 private final Pattern pattern; 330 331 public RegExpMatcher(String matchValue) { 332 this.pattern = Pattern.compile(matchValue); 333 } 334 335 public boolean matches(String value, URI uri) { 336 java.util.regex.Matcher matcher = pattern.matcher(value); 337 return matcher.matches(); 338 } 339 } 340 341 static class RegExpIgnoreCaseMatcher implements Matcher { 342 private final Pattern pattern; 343 344 public RegExpIgnoreCaseMatcher(String matchValue) { 345 this.pattern = Pattern.compile(matchValue, Pattern.CASE_INSENSITIVE); 346 } 347 348 public boolean matches(String value, URI uri) { 349 java.util.regex.Matcher matcher = pattern.matcher(value); 350 return matcher.matches(); 351 } 352 } 353 354 static class SubdirMatcher implements Matcher { 355 private final String matchValue; 356 357 public SubdirMatcher(String matchValue) throws IOException { 358 this.matchValue = new File (matchValue).getCanonicalPath(); 359 } 360 361 public boolean matches(String value, URI uri) throws Exception { 362 File file = new File (uri); 363 return file.getCanonicalPath().startsWith(matchValue); 364 } 365 } 366 367 static class UriValueType implements ValueType { 368 public String getValue(PermissionCheckContext context) { 369 return context.getUriString(); 370 } 371 } 372 private static final UriValueType URI_VALUE_TYPE = new UriValueType(); 373 374 static class HostValueType implements ValueType { 375 public String getValue(PermissionCheckContext context) throws Exception { 376 return context.getUri().getHost(); 377 } 378 } 379 private static final HostValueType HOST_VALUE_TYPE = new HostValueType(); 380 381 static class IpAddressValueType implements ValueType { 382 public String getValue(PermissionCheckContext context) throws Exception { 383 return context.getIp(); 384 } 385 } 386 private static final IpAddressValueType IP_ADDRESS_VALUE_TYPE = new IpAddressValueType(); 387 388 private PermissionRule[] getPermissionRules() throws Exception { 389 loadRules(); 390 return this.rules; 391 } 392 393 private void loadRules() throws Exception { 394 if (this.rules != null) 395 return; 396 397 String src = WikiDataDirHelper.getWikiDataDir(context) + "/external-include-rules.xml"; 398 Source source = null; 399 SAXParser parser = null; 400 Store store = null; 401 try { 402 source = sourceResolver.resolveURI(src); 403 store = (Store)serviceManager.lookup(Store.TRANSIENT_STORE); 404 this.rules = (PermissionRule[]) CacheHelper.getFromCache(store, source, this.getClass().getName()); 405 if (this.rules == null) { 406 if (!source.exists()) 407 throw new Exception ("No external-include-rules.xml file found."); 408 409 parser = (SAXParser)serviceManager.lookup(SAXParser.ROLE); 410 PermissionRuleBuilder builder = new PermissionRuleBuilder(); 411 parser.parse(SourceUtil.getInputSource(source), builder); 412 this.rules = builder.getRules(); 413 CacheHelper.setInCache(store, rules, source, this.getClass().getName()); 414 } 415 } finally { 416 if (source != null) 417 sourceResolver.release(source); 418 if (parser != null) 419 serviceManager.release(parser); 420 if (store != null) 421 serviceManager.release(store); 422 } 423 } 424 425 static class PermissionRuleBuilder extends DefaultHandler { 426 private List rules = new ArrayList (); 427 428 public PermissionRule[] getRules() { 429 return (PermissionRule[])rules.toArray(new PermissionRule[rules.size()]); 430 } 431 432 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 433 if (uri.equals("") && localName.equals("rule")) { 434 try { 435 String scheme = attributes.getValue("scheme"); 436 if (scheme == null || scheme.trim().length() == 0) 437 throw new SAXException ("Missing or empty scheme attribute."); 438 439 String permission = attributes.getValue("permission"); 440 if (permission == null || permission.trim().length() == 0) 441 throw new SAXException ("Missing or empty permission attribute."); 442 Permission perm; 443 if (permission.equalsIgnoreCase("grant")) 444 perm = Permission.GRANT; 445 else if (permission.equalsIgnoreCase("deny")) 446 perm = Permission.DENY; 447 else 448 throw new SAXException ("Invalid value for permission attribute: " + permission); 449 450 PermissionRule rule; 451 String value = attributes.getValue("value"); 452 if (value == null || value.trim().length() == 0) { 453 rule = new PermissionRule(scheme, null, null, perm, -1, -1); 454 } else { 455 ValueType valueType; 456 if (value.equalsIgnoreCase("uri")) 457 valueType = URI_VALUE_TYPE; 458 else if (value.equalsIgnoreCase("host")) 459 valueType = HOST_VALUE_TYPE; 460 else if (value.equalsIgnoreCase("ip")) 461 valueType = IP_ADDRESS_VALUE_TYPE; 462 else 463 throw new SAXException ("Invalid value for 'value' attribute: " + value); 464 465 String matchValue = attributes.getValue("matchValue"); 466 if (matchValue == null || matchValue.trim().length() == 0) 467 throw new SAXException ("Missing or empty matchValue attribute."); 468 469 String matchType = attributes.getValue("matchType"); 470 if (matchType == null || matchType.trim().length() == 0) 471 throw new SAXException ("Missing or empty matchType attribute."); 472 Matcher matcher; 473 if (matchType.equalsIgnoreCase("string")) 474 matcher = new StringMatcher(matchValue); 475 else if (matchType.equalsIgnoreCase("stringIgnoreCase")) 476 matcher = new StringIgnoreCaseMatcher(matchValue); 477 else if (matchType.equalsIgnoreCase("wildcard")) 478 matcher = new WildcardMatcher(matchValue); 479 else if (matchType.equalsIgnoreCase("wildcardIgnoreCase")) 480 matcher = new WildcardIgnoreCaseMatcher(matchValue); 481 else if (matchType.equalsIgnoreCase("regexp")) 482 matcher = new RegExpMatcher(matchValue); 483 else if (matchType.equalsIgnoreCase("regexpIgnoreCase")) 484 matcher = new RegExpIgnoreCaseMatcher(matchValue); 485 else if (matchType.equalsIgnoreCase("subdir")) 486 matcher = new SubdirMatcher(matchValue); 487 else 488 throw new SAXException ("Invalid value for matchType attribute: " + matchType); 489 490 String portFromString = attributes.getValue("portFrom"); 491 int portFrom = -1; 492 if (portFromString != null) 493 portFrom = Integer.parseInt(portFromString); 494 495 String portToString = attributes.getValue("portTo"); 496 int portTo = -1; 497 if (portToString != null) 498 portTo = Integer.parseInt(portToString); 499 500 501 rule = new PermissionRule(scheme, valueType, matcher, perm, portFrom, portTo); 502 } 503 rules.add(rule); 504 } catch (Throwable e) { 505 if (e instanceof Exception ) 506 throw new SAXException ("Error processing a rule.", (Exception )e); 507 else 508 throw new SAXException ("Error processing a rule: " + e.getMessage()); 509 } 510 } 511 } 512 } 513 } 514 | Popular Tags |