Example #1
0
public class AclParser {
  private static AclsAuthorizationMessages log =
      MessagesFactory.get(AclsAuthorizationMessages.class);

  public String resourceRole;
  public ArrayList<String> users;
  public ArrayList<String> groups;
  public boolean anyUser = true;
  public boolean anyGroup = true;
  public IpAddressValidator ipv;

  public AclParser() {}

  public void parseAcls(String resourceRole, String acls) throws InvalidACLException {
    if (acls != null) {
      String[] parts = acls.split(";");
      if (parts.length != 3) {
        log.invalidAclsFoundForResource(resourceRole);
        throw new InvalidACLException(
            "Invalid ACLs specified for requested resource: " + resourceRole);
      } else {
        log.aclsFoundForResource(resourceRole);
      }
      parseUserAcls(parts);

      parseGroupAcls(parts);

      parseIpAddressAcls(parts);
    } else {
      log.noAclsFoundForResource(resourceRole);
      users = new ArrayList<String>();
      groups = new ArrayList<String>();
      ipv = new IpAddressValidator(null);
    }
  }

  private void parseUserAcls(String[] parts) {
    users = new ArrayList<String>();
    Collections.addAll(users, parts[0].split(","));
    if (!users.contains("*")) {
      anyUser = false;
    }
  }

  private void parseGroupAcls(String[] parts) {
    groups = new ArrayList<String>();
    Collections.addAll(groups, parts[1].split(","));
    if (!groups.contains("*")) {
      anyGroup = false;
    }
  }

  private void parseIpAddressAcls(String[] parts) {
    ipv = new IpAddressValidator(parts[2]);
  }
}
Example #2
0
public class UrlConnectionDispatch extends AbstractGatewayFilter {

  private static final GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
  private static final GatewayResources RES = ResourcesFactory.get(GatewayResources.class);
  private static Auditor auditor =
      AuditServiceFactory.getAuditService()
          .getAuditor(
              AuditConstants.DEFAULT_AUDITOR_NAME,
              AuditConstants.KNOX_SERVICE_NAME,
              AuditConstants.KNOX_COMPONENT_NAME);

  @Override
  protected void doFilter(
      HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    String method = request.getMethod().toUpperCase();
    if (method.equals("GET")) {
      try {
        doGet(getDispatchUrl(request), request, response);
      } catch (URISyntaxException e) {
        throw new ServletException(e);
      }
    } else {
      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }
  }

  protected static URI getDispatchUrl(HttpServletRequest request) {
    StringBuffer str = request.getRequestURL();
    String query = request.getQueryString();
    if (query != null) {
      str.append('?');
      str.append(query);
    }
    URI url = URI.create(str.toString());
    return url;
  }

  public void doGet(URI url, HttpServletRequest request, HttpServletResponse response)
      throws IOException, URISyntaxException {
    String sourcePathInfo = request.getPathInfo();
    String sourcePattern = getConfig().getInitParameter("pattern");
    String targetPattern = getConfig().getInitParameter("target");

    // TODO: Some of the compilation should be done at servlet init for performance reasons.
    Template sourceTemplate = Parser.parseTemplate(sourcePattern);
    Template targetTemplate = Parser.parseTemplate(targetPattern);

    Resolver resolver = new DispatchParamResolver(getConfig(), request);
    URI sourceUri = new URI(sourcePathInfo);
    URI targetUri = Rewriter.rewrite(sourceUri, sourceTemplate, targetTemplate, resolver, null);

    //    //TODO: This should be more at filter init.
    //    Pattern sourceRegex = UrlRewriter.compileUrlRegex( sourcePattern );
    //    Matcher matcher = sourceRegex.matcher( sourcePathInfo );
    //    String targetUrl = MessageFormat.format( targetPattern, Regex.toGroupArray( matcher ) );
    //    System.out.println( "Source URI: " + expect.getRequestURI() );
    //    System.out.println( "Source URL: " + expect.getRequestURL() );
    //    System.out.println( "Source Query: " + expect.getQueryString() );
    //    System.out.println( "Source pathInfo: " + sourcePathInfo );
    //    System.out.println( "Source pattern: " + sourcePattern );
    //    System.out.println( "Target pattern: " + targetPattern );
    //    System.out.println( "Resolved target: " + targetUrl );

    StringBuilder paramStr = new StringBuilder();
    Enumeration paramNames = request.getParameterNames();
    if (paramNames.hasMoreElements()) {
      paramStr.append("?");
    }
    while (paramNames.hasMoreElements()) {
      String paramName = (String) paramNames.nextElement();
      String paramValue = request.getParameter(paramName);
      paramStr.append(paramName);
      paramStr.append("=");
      paramStr.append(URLEncoder.encode(paramValue, "UTF-8"));
      if (paramNames.hasMoreElements()) {
        paramStr.append("&");
      }
    }
    String urlStr = targetUri.toString() + paramStr.toString();
    try {
      URL clientUrl = new URL(urlStr);
      // System.out.println( "Resolved query: " + clientUrl );
      AuthenticatedURL.Token token = new AuthenticatedURL.Token();
      KerberosAuthenticator authenticator = new KerberosAuthenticator();
      auditor.audit(Action.DISPATCH, urlStr, ResourceType.URI, ActionOutcome.UNAVAILABLE);
      HttpURLConnection conn = new AuthenticatedURL(authenticator).openConnection(clientUrl, token);
      // System.out.println( "STATUS=" + conn.getResponseCode() );
      InputStream input = conn.getInputStream();
      if (input != null) {
        OutputStream output = response.getOutputStream();
        try {
          IOUtils.copy(input, output);
        } finally {
          output.flush();
          input.close();
        }
      }
      auditor.audit(Action.DISPATCH, urlStr, ResourceType.URI, ActionOutcome.SUCCESS);
    } catch (AuthenticationException e) {
      response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
      LOG.failedToEstablishConnectionToUrl(urlStr, e);
      auditor.audit(
          Action.DISPATCH,
          urlStr,
          ResourceType.URI,
          ActionOutcome.FAILURE,
          RES.responseStatus(HttpServletResponse.SC_UNAUTHORIZED));
    } catch (FileNotFoundException e) {
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
      LOG.failedToEstablishConnectionToUrl(urlStr, e);
      auditor.audit(
          Action.DISPATCH,
          urlStr,
          ResourceType.URI,
          ActionOutcome.FAILURE,
          RES.responseStatus(HttpServletResponse.SC_NOT_FOUND));
    }
  }
}
Example #3
0
public class JettySSLService implements SSLService {
  private static final String EPHEMERAL_DH_KEY_SIZE_PROPERTY = "jdk.tls.ephemeralDHKeySize";
  private static final String GATEWAY_TRUSTSTORE_PASSWORD = "******";
  private static final String GATEWAY_CREDENTIAL_STORE_NAME = "__gateway";
  private static GatewayMessages log = MessagesFactory.get(GatewayMessages.class);

  private MasterService ms;
  private KeystoreService ks;
  private AliasService as;
  private List<String> sslExcludeProtocols = null;
  private boolean clientAuthNeeded;
  private boolean trustAllCerts;
  private String truststorePath;
  private String keystoreType;
  private String trustStoreType;

  public void setMasterService(MasterService ms) {
    this.ms = ms;
  }

  public void setAliasService(AliasService as) {
    this.as = as;
  }

  public void setKeystoreService(KeystoreService ks) {
    this.ks = ks;
  }

  @Override
  public void init(GatewayConfig config, Map<String, String> options)
      throws ServiceLifecycleException {
    // set any JSSE or security related system properties
    System.setProperty(EPHEMERAL_DH_KEY_SIZE_PROPERTY, config.getEphemeralDHKeySize());
    try {
      if (!ks.isCredentialStoreForClusterAvailable(GATEWAY_CREDENTIAL_STORE_NAME)) {
        log.creatingCredentialStoreForGateway();
        ks.createCredentialStoreForCluster(GATEWAY_CREDENTIAL_STORE_NAME);
        // LET'S NOT GENERATE A DIFFERENT KEY PASSPHRASE BY DEFAULT ANYMORE
        // IF A DEPLOYMENT WANTS TO CHANGE THE KEY PASSPHRASE TO MAKE IT MORE SECURE THEN
        // THEY CAN ADD THE ALIAS EXPLICITLY WITH THE CLI
        // as.generateAliasForCluster(GATEWAY_CREDENTIAL_STORE_NAME, GATEWAY_IDENTITY_PASSPHRASE);
      } else {
        log.credentialStoreForGatewayFoundNotCreating();
      }
    } catch (KeystoreServiceException e) {
      throw new ServiceLifecycleException(
          "Keystore was not loaded properly - the provided (or persisted) master secret may not match the password for the keystore.",
          e);
    }

    try {
      if (!ks.isKeystoreForGatewayAvailable()) {
        log.creatingKeyStoreForGateway();
        ks.createKeystoreForGateway();
        char[] passphrase = null;
        try {
          passphrase = as.getGatewayIdentityPassphrase();
        } catch (AliasServiceException e) {
          throw new ServiceLifecycleException(
              "Error accessing credential store for the gateway.", e);
        }
        if (passphrase == null) {
          passphrase = ms.getMasterSecret();
        }
        ks.addSelfSignedCertForGateway("gateway-identity", passphrase);
      } else {
        log.keyStoreForGatewayFoundNotCreating();
      }
      logAndValidateCertificate();
    } catch (KeystoreServiceException e) {
      throw new ServiceLifecycleException(
          "Keystore was not loaded properly - the provided (or persisted) master secret may not match the password for the keystore.",
          e);
    }

    keystoreType = config.getKeystoreType();
    sslExcludeProtocols = config.getExcludedSSLProtocols();
    clientAuthNeeded = config.isClientAuthNeeded();
    truststorePath = config.getTruststorePath();
    trustAllCerts = config.getTrustAllCerts();
    trustStoreType = config.getTruststoreType();
  }

  private void logAndValidateCertificate() throws ServiceLifecycleException {
    // let's log the hostname (CN) and cert expiry from the gateway's public cert to aid in SSL
    // debugging
    Certificate cert;
    try {
      cert = as.getCertificateForGateway("gateway-identity");
    } catch (AliasServiceException e) {
      throw new ServiceLifecycleException(
          "Cannot Retreive Gateway SSL Certificate. Server will not start.", e);
    }
    if (cert != null) {
      if (cert instanceof X509Certificate) {
        X500Principal x500Principal = ((X509Certificate) cert).getSubjectX500Principal();
        X500PrincipalParser parser = new X500PrincipalParser(x500Principal);
        log.certificateHostNameForGateway(parser.getCN());
        Date notBefore = ((X509Certificate) cert).getNotBefore();
        Date notAfter = ((X509Certificate) cert).getNotAfter();
        log.certificateValidityPeriod(notBefore, notAfter);

        // let's not even start if the current date is not within the validity period for the SSL
        // cert
        try {
          ((X509Certificate) cert).checkValidity();
        } catch (CertificateExpiredException e) {
          throw new ServiceLifecycleException(
              "Gateway SSL Certificate is Expired. Server will not start.", e);
        } catch (CertificateNotYetValidException e) {
          throw new ServiceLifecycleException(
              "Gateway SSL Certificate is not yet valid. Server will not start.", e);
        }
      } else {
        throw new ServiceLifecycleException(
            "Public certificate for the gateway cannot be found with the alias gateway-identity. Plase check the identity certificate alias.");
      }
    } else {
      throw new ServiceLifecycleException(
          "Public certificate for the gateway is not of the expected type of X509Certificate. Something is wrong with the gateway keystore.");
    }
  }

  public Object buildSSlConnector(String keystoreFileName) {
    SslContextFactory sslContextFactory = new SslContextFactory(true);
    sslContextFactory.setCertAlias("gateway-identity");
    sslContextFactory.setKeyStoreType(keystoreType);
    sslContextFactory.setKeyStorePath(keystoreFileName);
    char[] master = ms.getMasterSecret();
    sslContextFactory.setKeyStorePassword(new String(master));
    char[] keypass = null;
    try {
      keypass = as.getGatewayIdentityPassphrase();
    } catch (AliasServiceException e) {
      // nop - default passphrase will be used
    }
    if (keypass == null) {
      // there has been no alias created for the key - let's assume it is the same as the keystore
      // password
      keypass = master;
    }
    sslContextFactory.setKeyManagerPassword(new String(keypass));

    String truststorePassword = null;
    if (clientAuthNeeded) {
      if (truststorePath != null) {
        sslContextFactory.setTrustStore(truststorePath);
        char[] truststorePwd = null;
        try {
          truststorePwd = as.getPasswordFromAliasForGateway(GATEWAY_TRUSTSTORE_PASSWORD);
        } catch (AliasServiceException e) {
          // nop - master secret will be used
        }
        if (truststorePwd != null) {
          truststorePassword = new String(truststorePwd);
        } else {
          truststorePassword = new String(master);
        }
        sslContextFactory.setTrustStorePassword(truststorePassword);
        sslContextFactory.setTrustStoreType(trustStoreType);
      } else {
        // when clientAuthIsNeeded but no truststore provided
        // default to the server's keystore and details
        sslContextFactory.setTrustStore(keystoreFileName);
        sslContextFactory.setTrustStorePassword(new String(master));
        sslContextFactory.setTrustStoreType(keystoreType);
      }
    }
    sslContextFactory.setNeedClientAuth(clientAuthNeeded);
    sslContextFactory.setTrustAll(trustAllCerts);
    if (sslExcludeProtocols != null) {
      sslContextFactory.setExcludeProtocols((String[]) sslExcludeProtocols.toArray());
    }
    SslConnector sslConnector = new SslSelectChannelConnector(sslContextFactory);

    return sslConnector;
  }

  @Override
  public void start() throws ServiceLifecycleException {
    // TODO Auto-generated method stub

  }

  @Override
  public void stop() throws ServiceLifecycleException {
    // TODO Auto-generated method stub

  }
}
public class IdentityAsserterHttpServletRequestWrapper extends HttpServletRequestWrapper {

  private static SpiGatewayMessages log = MessagesFactory.get(SpiGatewayMessages.class);

  private static final String PRINCIPAL_PARAM = "user.name";
  private static final String DOAS_PRINCIPAL_PARAM = "doAs";

  String username = null;

  public IdentityAsserterHttpServletRequestWrapper(HttpServletRequest request, String principal) {
    super(request);
    username = principal;
  }

  @Override
  public String getParameter(String name) {
    if (name.equals(PRINCIPAL_PARAM)) {
      return username;
    }
    return super.getParameter(name);
  }

  @SuppressWarnings("rawtypes")
  @Override
  public Map getParameterMap() {
    return getParams();
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  @Override
  public Enumeration getParameterNames() {
    Map<String, String[]> params = getParams();
    if (params == null) {
      params = new HashMap<String, String[]>();
    }
    Enumeration<String> e = Collections.enumeration((Collection<String>) params.keySet());

    return e;
  }

  @Override
  public String[] getParameterValues(String name) {
    Map<String, String[]> params = getParams();
    if (params == null) {
      params = new HashMap<String, String[]>();
    }

    return params.get(name);
  }

  private Map<String, String[]> getParams(String qString) {
    Map<String, String[]> params = null;
    if (getMethod().equals("GET")) {
      if (qString != null && qString.length() > 0) {
        params = HttpUtils.parseQueryString(qString);
      } else {
        params = new HashMap<String, String[]>();
      }
    } else {
      if (qString == null || qString.length() == 0) {
        return null;
      } else {
        params = HttpUtils.parseQueryString(qString);
      }
    }
    return params;
  }

  private Map<String, String[]> getParams() {
    return getParams(super.getQueryString());
  }

  @Override
  public String getQueryString() {
    String q = null;
    Map<String, String[]> params = getParams();

    if (params == null) {
      params = new HashMap<String, String[]>();
    }

    ArrayList<String> al = new ArrayList<String>();
    al.add(username);
    String[] a = {""};

    if ("true".equals(System.getProperty(GatewayConfig.HADOOP_KERBEROS_SECURED))) {
      params.put(DOAS_PRINCIPAL_PARAM, al.toArray(a));
      params.remove(PRINCIPAL_PARAM);
    } else {
      params.put(PRINCIPAL_PARAM, al.toArray(a));
    }

    String encoding = getCharacterEncoding();
    if (encoding == null) {
      encoding = Charset.defaultCharset().name();
    }
    q = urlEncode(params, encoding);
    return q;
  }

  @Override
  public int getContentLength() {
    int len;
    String contentType = getContentType();
    // If the content type is a form we might rewrite the body so default it to -1.
    if (contentType != null && contentType.startsWith("application/x-www-form-urlencoded")) {
      len = -1;
    } else {
      len = super.getContentLength();
    }
    return len;
  }

  @Override
  public ServletInputStream getInputStream() throws java.io.IOException {
    String contentType = getContentType();
    if (contentType != null && contentType.startsWith("application/x-www-form-urlencoded")) {
      String encoding = getCharacterEncoding();
      if (encoding == null) {
        encoding = Charset.defaultCharset().name();
      }
      String body = IOUtils.toString(super.getInputStream(), encoding);
      Map<String, String[]> params = getParams(body);
      if (params == null) {
        params = new HashMap<String, String[]>();
      }
      body = urlEncode(params, encoding);
      // ASCII is OK here because the urlEncode about should have already escaped
      return new ServletInputStreamWrapper(new ByteArrayInputStream(body.getBytes("US-ASCII")));
    } else {
      return super.getInputStream();
    }
  }

  static String urlEncode(String string, String encoding) {
    try {
      return URLEncoder.encode(string, encoding);
    } catch (UnsupportedEncodingException e) {
      throw new UnsupportedOperationException(e);
    }
  }

  public static String urlEncode(Map<String, String[]> map, String encoding) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, String[]> entry : map.entrySet()) {
      String name = entry.getKey();
      if (name != null && name.length() > 0) {
        String[] values = entry.getValue();
        if (values == null || values.length == 0) {
          sb.append(entry.getKey());
        } else {
          for (int i = 0; i < values.length; i++) {
            String value = values[i];
            if (value != null) {
              if (sb.length() > 0) {
                sb.append("&");
              }
              try {
                sb.append(urlEncode(name, encoding));
                sb.append("=");
                sb.append(urlEncode(value, encoding));
              } catch (IllegalArgumentException e) {
                log.skippingUnencodableParameter(name, value, encoding, e);
              }
            }
          }
        }
      }
    }
    return sb.toString();
  }

  private class ServletInputStreamWrapper extends ServletInputStream {

    private InputStream stream;

    private ServletInputStreamWrapper(InputStream stream) {
      this.stream = stream;
    }

    @Override
    public int read() throws IOException {
      return stream.read();
    }
  }
}
Example #5
0
public class JWTFederationFilter implements Filter {

  private static final String BEARER = "Bearer ";
  private static JWTMessages log = MessagesFactory.get(JWTMessages.class);
  private JWTokenAuthority authority = null;

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    GatewayServices services =
        (GatewayServices)
            filterConfig
                .getServletContext()
                .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
    authority = (JWTokenAuthority) services.getService(GatewayServices.TOKEN_SERVICE);
  }

  public void destroy() {}

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    String header = ((HttpServletRequest) request).getHeader("Authorization");
    if (header != null && header.startsWith(BEARER)) {
      // what follows the bearer designator should be the JWT token being used to request or as an
      // access token
      String wireToken = header.substring(BEARER.length());
      JWTToken token;
      try {
        token = JWTToken.parseToken(wireToken);
      } catch (ParseException e) {
        throw new ServletException(
            "ParseException encountered while processing the JWT token: ", e);
      }
      boolean verified = false;
      try {
        verified = authority.verifyToken(token);
      } catch (TokenServiceException e) {
        log.unableToVerifyToken(e);
      }
      if (verified) {
        // TODO: validate expiration
        // confirm that audience matches intended target - which for this filter must be HSSO
        if (token.getAudience().equals("HSSO")) {
          // TODO: verify that the user requesting access to the service/resource is authorized for
          // it - need scopes?
          Subject subject = createSubjectFromToken(token);
          continueWithEstablishedSecurityContext(
              subject, (HttpServletRequest) request, (HttpServletResponse) response, chain);
        } else {
          ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
          return; // break filter chain
        }
      } else {
        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
        return; // break filter chain
      }
    } else {
      // no token provided in header
      // TODO: may have to check cookie and url as well before sending error
      ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
      return; // break filter chain
    }
  }

  private void continueWithEstablishedSecurityContext(
      Subject subject,
      final HttpServletRequest request,
      final HttpServletResponse response,
      final FilterChain chain)
      throws IOException, ServletException {
    try {
      Subject.doAs(
          subject,
          new PrivilegedExceptionAction<Object>() {
            @Override
            public Object run() throws Exception {
              chain.doFilter(request, response);
              return null;
            }
          });
    } catch (PrivilegedActionException e) {
      Throwable t = e.getCause();
      if (t instanceof IOException) {
        throw (IOException) t;
      } else if (t instanceof ServletException) {
        throw (ServletException) t;
      } else {
        throw new ServletException(t);
      }
    }
  }

  private Subject createSubjectFromToken(JWTToken token) {
    final String principal = token.getPrincipal();

    HashSet emptySet = new HashSet();
    Set<Principal> principals = new HashSet<Principal>();
    Principal p =
        new Principal() {
          @Override
          public String getName() {
            return principal;
          }
        };
    principals.add(p);

    //        The newly constructed Sets check whether this Subject has been set read-only
    //        before permitting subsequent modifications. The newly created Sets also prevent
    //        illegal modifications by ensuring that callers have sufficient permissions.
    //
    //        To modify the Principals Set, the caller must have AuthPermission("modifyPrincipals").
    //        To modify the public credential Set, the caller must have
    // AuthPermission("modifyPublicCredentials").
    //        To modify the private credential Set, the caller must have
    // AuthPermission("modifyPrivateCredentials").
    javax.security.auth.Subject subject =
        new javax.security.auth.Subject(true, principals, emptySet, emptySet);
    return subject;
  }
}
Example #6
0
public abstract class HtmlFilterReaderBase extends Reader implements UrlRewriteFilterReader {

  private static List<String> JSTYPES =
      Arrays.asList(
          new String[] {
            "application/javascript",
            "text/javascript",
            "*/javascript",
            "application/x-javascript",
            "text/x-javascript",
            "*/x-javascript"
          });
  private static final String SCRIPTTAG = "script";
  private static final UrlRewriteFilterPathDescriptor.Compiler<Pattern> REGEX_COMPILER =
      new RegexCompiler();

  private static final UrlRewriteMessages LOG = MessagesFactory.get(UrlRewriteMessages.class);

  private Document document;
  private Stack<Level> stack;
  private Reader reader;
  private StreamedSource parser;
  private Iterator<Segment> iterator;
  private int lastSegEnd;
  private int offset;
  private StringWriter writer;
  private StringBuffer buffer;
  private UrlRewriteFilterContentDescriptor config = null;

  protected HtmlFilterReaderBase(Reader reader) throws IOException, ParserConfigurationException {
    this.reader = reader;
    document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    stack = new Stack<Level>();
    parser = new StreamedSource(reader);
    iterator = parser.iterator();
    writer = new StringWriter();
    buffer = writer.getBuffer();
    offset = 0;
  }

  protected HtmlFilterReaderBase(Reader reader, UrlRewriteFilterContentDescriptor config)
      throws IOException, ParserConfigurationException {
    this(reader);
    this.config = config;
  }

  protected abstract String filterAttribute(
      QName elementName, QName attributeName, String attributeValue, String ruleName);

  protected abstract String filterText(QName elementName, String text, String ruleName);

  @Override
  public int read(char[] destBuffer, int destOffset, int destCount) throws IOException {
    int count = 0;
    int available = buffer.length() - offset;

    if (available == 0) {
      if (iterator.hasNext()) {
        iterator.next();
        processCurrentSegment();
        available = buffer.length() - offset;
      } else {
        count = -1;
      }
    }

    if (available > 0) {
      count = Math.min(destCount, available);
      buffer.getChars(offset, offset + count, destBuffer, destOffset);
      offset += count;
      if (offset == buffer.length()) {
        offset = 0;
        buffer.setLength(0);
      }
    }

    return count;
  }

  private void processCurrentSegment() {
    Segment segment = parser.getCurrentSegment();
    // If this tag is inside the previous tag (e.g. a server tag) then
    // ignore it as it was already output along with the previous tag.
    if (segment.getEnd() <= lastSegEnd) {
      return;
    }
    lastSegEnd = segment.getEnd();
    if (segment instanceof Tag) {
      if (segment instanceof StartTag) {
        processStartTag((StartTag) segment);
      } else if (segment instanceof EndTag) {
        processEndTag((EndTag) segment);
      } else {
        writer.write(segment.toString());
      }
    } else {
      processText(segment);
    }
  }

  private void processEndTag(EndTag tag) {
    while (!stack.isEmpty()) {
      Level popped = stack.pop();
      if (popped.getTag().getName().equalsIgnoreCase(tag.getName())) {
        break;
      }
    }
    writer.write(tag.toString());
  }

  private void processStartTag(StartTag tag) {
    if ("<".equals(tag.getTagType().getStartDelimiter())) {
      Element e = document.createElement(tag.getNameSegment().toString());
      stack.push(new Level(tag));
      writer.write("<");
      writer.write(tag.getNameSegment().toString());
      Attributes attributes = tag.getAttributes();
      if (!attributes.isEmpty()) {
        for (Attribute attribute : attributes) {
          processAttribute(attribute);
        }
      }
      if (tag.toString().trim().endsWith("/>") || tag.isEmptyElementTag()) {
        stack.pop();
        writer.write("/>");
      } else {
        writer.write(">");
      }
    } else {
      writer.write(tag.toString());
    }
  }

  private void processAttribute(Attribute attribute) {
    writer.write(" ");
    writer.write(attribute.getName());
    if (attribute.hasValue()) {
      String inputValue = attribute.getValue();
      String outputValue = inputValue;
      try {
        Level tag = stack.peek();
        outputValue =
            filterAttribute(tag.getQName(), tag.getQName(attribute.getName()), inputValue, null);
        if (outputValue == null) {
          outputValue = inputValue;
        }
      } catch (Exception e) {
        LOG.failedToFilterAttribute(attribute.getName(), e);
      }
      writer.write("=");
      writer.write(attribute.getQuoteChar());
      writer.write(outputValue);
      writer.write(attribute.getQuoteChar());
    }
  }

  private void processText(Segment segment) {
    String inputValue = segment.toString();
    String outputValue = inputValue;
    try {
      if (stack.isEmpty()) {
        // This can happen for whitespace outside of the root element.
        // outputValue = filterText( null, inputValue );
      } else {
        String tagType = stack.peek().getTag().getAttributeValue("type");
        String tagName = stack.peek().getTag().getName();
        if (SCRIPTTAG.equals(tagName)
            && JSTYPES.contains(tagType)
            && config != null
            && !config.getSelectors().isEmpty()) {
          // embedded javascript content
          outputValue = UrlRewriteUtil.filterJavaScript(inputValue, config, this, REGEX_COMPILER);
        } else {
          outputValue = filterText(stack.peek().getQName(), inputValue, null);
        }
      }
      if (outputValue == null) {
        outputValue = inputValue;
      }
    } catch (Exception e) {
      LOG.failedToFilterValue(inputValue, null, e);
    }
    writer.write(outputValue);
  }

  @Override
  public void close() throws IOException {
    parser.close();
    reader.close();
    writer.close();
    stack.clear();
  }

  private String getNamespace(String prefix) {
    String namespace = null;
    for (Level level : stack) {
      namespace = level.getNamespace(prefix);
      if (namespace != null) {
        break;
      }
    }
    return namespace;
  }

  private static class Level {
    private StartTag tag;
    private QName name;
    private Map<String, String> namespaces;

    private Level(StartTag tag) {
      this.tag = tag;
      this.name = null;
      this.namespaces = null;
    }

    private StartTag getTag() {
      return tag;
    }

    private QName getQName() {
      if (name == null) {
        name = getQName(tag.getName());
      }
      return name;
    }

    private String getNamespace(String prefix) {
      return getNamespaces().get(prefix);
    }

    private QName getQName(String name) {
      String prefix;
      String local;
      int colon = (name == null ? -1 : name.indexOf(':'));
      if (colon < 0) {
        prefix = "";
        local = name;
      } else {
        prefix = name.substring(0, colon);
        local = (colon + 1 < name.length() ? name.substring(colon + 1) : "");
      }
      String namespace = (prefix == null) ? null : getNamespace(prefix);
      return new QName(namespace, local, prefix);
    }

    private Map<String, String> getNamespaces() {
      if (namespaces == null) {
        namespaces = new HashMap<String, String>();
        parseNamespaces();
      }
      return namespaces;
    }

    private void parseNamespaces() {
      Attributes attributes = tag.getAttributes();
      if (attributes != null) {
        for (Attribute attribute : tag.getAttributes()) {
          String name = attribute.getName();
          if (name.toLowerCase().startsWith("xmlns")) {
            int colon = name.indexOf(":", 5);
            String prefix;
            if (colon == 0) {
              prefix = "";
            } else {
              prefix = name.substring(colon);
            }
            namespaces.put(prefix, attribute.getValue());
          }
        }
      }
    }
  }
}
public class UrlRewriteServletContextListener implements ServletContextListener {

  public static final String PROCESSOR_ATTRIBUTE_NAME = UrlRewriteProcessor.class.getName();
  public static final String DESCRIPTOR_LOCATION_INIT_PARAM_NAME = "rewriteDescriptorLocation";
  public static final String DESCRIPTOR_DEFAULT_FILE_NAME = "rewrite.xml";
  public static final String DESCRIPTOR_DEFAULT_LOCATION =
      "/WEB-INF/" + DESCRIPTOR_DEFAULT_FILE_NAME;
  private static final UrlRewriteMessages LOG = MessagesFactory.get(UrlRewriteMessages.class);

  @Override
  public void contextInitialized(ServletContextEvent event) {
    UrlRewriteRulesDescriptor descriptor = null;
    try {
      URL url = locateDescriptor(event.getServletContext());
      descriptor = loadDescriptor(url);
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
    ServletContext context = event.getServletContext();
    UrlRewriteEnvironment environment = new UrlRewriteServletEnvironment(context);
    UrlRewriteProcessor processor = new UrlRewriteProcessor();
    processor.initialize(environment, descriptor);
    event.getServletContext().setAttribute(PROCESSOR_ATTRIBUTE_NAME, processor);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    UrlRewriteProcessor processor =
        (UrlRewriteProcessor) event.getServletContext().getAttribute(PROCESSOR_ATTRIBUTE_NAME);
    event.getServletContext().removeAttribute(PROCESSOR_ATTRIBUTE_NAME);
    if (processor != null) {
      processor.destroy();
    }
  }

  public static UrlRewriter getUrlRewriter(ServletContext context) {
    return ((UrlRewriteProcessor) context.getAttribute(PROCESSOR_ATTRIBUTE_NAME));
  }

  private static URL locateDescriptor(ServletContext context) throws IOException {
    String param = context.getInitParameter(DESCRIPTOR_LOCATION_INIT_PARAM_NAME);
    if (param == null) {
      param = DESCRIPTOR_DEFAULT_LOCATION;
    }
    URL url;
    try {
      url = context.getResource(param);
    } catch (MalformedURLException e) {
      // Ignore it and try using the value directly as a URL.
      url = null;
    }
    if (url == null) {
      url = new URL(param);
    }
    if (url == null) {
      throw new FileNotFoundException(param);
    }
    return url;
  }

  private static UrlRewriteRulesDescriptor loadDescriptor(URL url) throws IOException {
    InputStream stream = url.openStream();
    Reader reader = new InputStreamReader(stream, "UTF-8");
    UrlRewriteRulesDescriptor descriptor = UrlRewriteRulesDescriptorFactory.load("xml", reader);
    try {
      reader.close();
    } catch (IOException closeException) {
      LOG.failedToLoadRewriteRulesDescriptor(closeException);
    }
    return descriptor;
  }
}
public class UrlRewriteRequest extends GatewayRequestWrapper implements Resolver {

  private static final UrlRewriteMessages LOG = MessagesFactory.get(UrlRewriteMessages.class);

  private UrlRewriter rewriter;
  private String urlRuleName;
  private String bodyFilterName;
  private String headersFilterName;
  private UrlRewriteFilterContentDescriptor headersFilterConfig;
  private String cookiesFilterName;
  private UrlRewriteFilterContentDescriptor cookiesFilterConfig;

  /**
   * Constructs a request object wrapping the given request.
   *
   * @throws IllegalArgumentException if the request is null
   */
  public UrlRewriteRequest(FilterConfig config, HttpServletRequest request) throws IOException {
    super(request);
    this.rewriter = UrlRewriteServletContextListener.getUrlRewriter(config.getServletContext());
    this.urlRuleName = config.getInitParameter(UrlRewriteServletFilter.REQUEST_URL_RULE_PARAM);
    this.bodyFilterName =
        config.getInitParameter(UrlRewriteServletFilter.REQUEST_BODY_FILTER_PARAM);
    this.headersFilterName =
        config.getInitParameter(UrlRewriteServletFilter.REQUEST_HEADERS_FILTER_PARAM);
    this.headersFilterConfig =
        getRewriteFilterConfig(headersFilterName, UrlRewriteServletFilter.HEADERS_MIME_TYPE);
    this.cookiesFilterName =
        config.getInitParameter(UrlRewriteServletFilter.REQUEST_COOKIES_FILTER_PARAM);
    this.cookiesFilterConfig =
        getRewriteFilterConfig(cookiesFilterName, UrlRewriteServletFilter.COOKIES_MIME_TYPE);
  }

  private Template getSourceUrl() {
    Template urlTemplate = null;
    StringBuffer urlString = super.getRequestURL();
    String queryString = super.getQueryString();
    if (queryString != null) {
      urlString.append('?');
      urlString.append(queryString);
    }
    try {
      urlTemplate = Parser.parse(urlString.toString());
    } catch (URISyntaxException e) {
      LOG.failedToParseValueForUrlRewrite(urlString.toString());
      // Shouldn't be possible given that the URL is constructed from parts of an existing URL.
      urlTemplate = null;
    }
    return urlTemplate;
  }

  // Note: Source url was added to the request attributes by the GatewayFilter doFilter method.
  private Template getTargetUrl() {
    boolean rewriteRequestUrl = true;
    Template targetUrl;
    if (rewriteRequestUrl) {
      targetUrl = (Template) getAttribute(AbstractGatewayFilter.TARGET_REQUEST_URL_ATTRIBUTE_NAME);
      if (targetUrl == null) {
        Template sourceUrl = getSourceUrl();
        targetUrl = rewriter.rewrite(this, sourceUrl, UrlRewriter.Direction.IN, urlRuleName);
        setAttribute(AbstractGatewayFilter.TARGET_REQUEST_URL_ATTRIBUTE_NAME, targetUrl);
      }
    } else {
      targetUrl = (Template) getAttribute(AbstractGatewayFilter.SOURCE_REQUEST_URL_ATTRIBUTE_NAME);
    }
    return targetUrl;
  }

  private String[] splitTargetUrl(Template url) {
    String s = url.toString();
    return s.split("\\?");
  }

  @Override
  public StringBuffer getRequestURL() {
    return new StringBuffer(getRequestURI());
  }

  // TODO: I think this method is implemented wrong based on the HttpServletRequest.getRequestURI
  // docs.
  // It should not include the scheme or authority parts.
  @Override
  public String getRequestURI() {
    String[] split = splitTargetUrl(getTargetUrl());
    if (split.length > 0) {
      return split[0];
    } else {
      return "";
    }
  }

  @Override
  public String getQueryString() {
    String[] split = splitTargetUrl(getTargetUrl());
    if (split.length > 1) {
      return split[1];
    } else {
      return null;
    }
  }

  private String rewriteValue(UrlRewriter rewriter, String value, String rule) {
    try {
      Template input = Parser.parse(value);
      Template output = rewriter.rewrite(this, input, UrlRewriter.Direction.IN, rule);
      value = output.toString();
    } catch (URISyntaxException e) {
      LOG.failedToParseValueForUrlRewrite(value);
    }
    return value;
  }

  @Override
  public String getHeader(String name) {
    String value = super.getHeader(name);
    if (value != null) {
      value =
          rewriteValue(
              rewriter,
              super.getHeader(name),
              pickFirstRuleWithEqualsIgnoreCasePathMatch(headersFilterConfig, name));
    }
    return value;
  }

  @SuppressWarnings("unchecked")
  public Enumeration getHeaders(String name) {
    return new EnumerationRewriter(
        rewriter,
        super.getHeaders(name),
        pickFirstRuleWithEqualsIgnoreCasePathMatch(headersFilterConfig, name));
  }

  @Override
  public List<String> resolve(String name) {
    return Collections.emptyList();
  }

  private class EnumerationRewriter implements Enumeration<String> {

    private UrlRewriter rewriter;
    private Enumeration<String> delegate;
    private String rule;

    private EnumerationRewriter(UrlRewriter rewriter, Enumeration<String> delegate, String rule) {
      this.rewriter = rewriter;
      this.delegate = delegate;
      this.rule = rule;
    }

    @Override
    public boolean hasMoreElements() {
      return delegate.hasMoreElements();
    }

    @Override
    public String nextElement() {
      return rewriteValue(rewriter, delegate.nextElement(), rule);
    }
  }

  @Override
  public ServletInputStream getInputStream() throws IOException {
    MimeType mimeType = getMimeType();
    UrlRewriteFilterContentDescriptor filterContentConfig =
        getRewriteFilterConfig(bodyFilterName, mimeType);
    InputStream stream =
        UrlRewriteStreamFilterFactory.create(
            mimeType,
            null,
            super.getInputStream(),
            rewriter,
            this,
            UrlRewriter.Direction.IN,
            filterContentConfig);
    return new UrlRewriteRequestStream(stream);
  }

  @Override
  public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
  }

  @Override
  public int getContentLength() {
    // The rewrite might change the content length so return the default of -1 to indicate the
    // length is unknown.
    return -1;
  }

  private UrlRewriteFilterContentDescriptor getRewriteFilterConfig(
      String filterName, MimeType mimeType) {
    UrlRewriteFilterContentDescriptor filterContentConfig = null;
    UrlRewriteRulesDescriptor rewriteConfig = rewriter.getConfig();
    if (rewriteConfig != null) {
      UrlRewriteFilterDescriptor filterConfig = rewriteConfig.getFilter(filterName);
      if (filterConfig != null) {
        filterContentConfig = filterConfig.getContent(mimeType);
      }
    }
    return filterContentConfig;
  }
}