Esempio n. 1
0
 /**
  * Checks the specified template and adds a variable.
  *
  * @param tmp template string
  * @param type allowed type
  * @param declared variable declaration flags
  * @return resulting variable
  * @throws QueryException query exception
  */
 private QNm checkVariable(final String tmp, final Type type, final boolean[] declared)
     throws QueryException {
   final Var[] args = function.args;
   final Matcher m = TEMPLATE.matcher(tmp);
   if (!m.find()) error(INV_TEMPLATE, tmp);
   final byte[] vn = token(m.group(1));
   if (!XMLToken.isQName(vn)) error(INV_VARNAME, vn);
   final QNm qnm = new QNm(vn, context);
   int r = -1;
   while (++r < args.length && !args[r].name.eq(qnm)) ;
   if (r == args.length) error(UNKNOWN_VAR, vn);
   if (declared[r]) error(VAR_ASSIGNED, vn);
   final SeqType st = args[r].declaredType();
   if (args[r].checksType() && !st.type.instanceOf(type)) error(INV_VARTYPE, vn, type);
   declared[r] = true;
   return qnm;
 }
Esempio n. 2
0
 /**
  * Replace occurrences of "%ab" with the character represented by the hex value. Strings of
  * escaped characters are treated as UTF-8 byte sequences and decoded appropriately.
  */
 private static String decode(String s) {
   int length = s.length();
   StringBuilder str = new StringBuilder(length);
   Matcher matcher = PATTERN.matcher(s);
   int offset = 0;
   byte[] bb = null;
   while (matcher.find(offset)) {
     int count = matcher.groupCount();
     for (int i = 0; i < count; i++) {
       String match = matcher.group(0);
       int num = match.length() / 3;
       if (bb == null || bb.length < num) {
         bb = new byte[num];
       }
       for (int j = 0; j < num; j++) {
         int head = j * 3 + 1;
         int tail = head + 2;
         bb[j] = (byte) Integer.parseInt(match.substring(head, tail), 16);
       }
       try {
         String text = new String(bb, "UTF-8");
         str.append(s.substring(offset, matcher.start()));
         str.append(text);
       } catch (UnsupportedEncodingException e) {
         // NOTE: This should *never* be thrown because all
         //       JVMs are required to support UTF-8. I mean,
         //       the strings in the .class file are all in
         //       a modified UTF-8, for pete's sake! :)
       }
     }
     offset = matcher.end();
   }
   if (offset < length) {
     str.append(s.substring(offset));
   }
   return str.toString();
 }
Esempio n. 3
0
    public Writer getErrorReport(
        Writer to, final HttpServletRequest request, CharTransformer escape) throws IOException {
      final Writer logMsg = new StringWriter();
      final Writer tee = new org.mmbase.util.ChainedWriter(to, logMsg);
      Writer msg = tee;

      LinkedList<Throwable> stack = getStack();
      String ticket = new Date().toString();

      Map<String, String> props;
      try {
        props = org.mmbase.util.ApplicationContextReader.getProperties("mmbase_errorpage");
      } catch (javax.naming.NamingException ne) {
        props = Collections.emptyMap();
        log.info(ne);
      }

      if (request != null) {
        {
          msg.append("Headers\n----------\n");
          // request properties
          for (Object name : Collections.list(request.getHeaderNames())) {
            msg.append(
                escape.transform(
                    name + ": " + escape.transform(request.getHeader((String) name)) + "\n"));
          }
        }
        {
          msg.append("\nAttributes\n----------\n");
          Pattern p = requestIgnore;
          if (p == null && props.get("request_ignore") != null) {
            p = Pattern.compile(props.get("request_ignore"));
          }
          for (Object name : Collections.list(request.getAttributeNames())) {
            if (p == null || !p.matcher((String) name).matches()) {
              msg.append(
                  escape.transform(name + ": " + request.getAttribute((String) name) + "\n"));
            }
          }
        }
        if (Boolean.TRUE.equals(showSession)
            || (showSession == null && !"false".equals(props.get("show_session")))) {
          HttpSession ses = request.getSession(false);
          if (ses != null) {
            msg.append("\nSession\n----------\n");
            Pattern p = sessionIgnore;
            if (p == null && props.get("session_ignore") != null) {
              p = Pattern.compile(props.get("session_ignore"));
            }
            for (Object name : Collections.list(ses.getAttributeNames())) {
              if (p == null || !p.matcher((String) name).matches()) {
                msg.append(escape.transform(name + ": " + ses.getAttribute((String) name) + "\n"));
              }
            }
          }
        }
      }
      msg.append("\n");
      msg.append("Misc. properties\n----------\n");

      if (request != null) {
        msg.append("method: ").append(escape.transform(request.getMethod())).append("\n");
        msg.append("querystring: ").append(escape.transform(request.getQueryString())).append("\n");
        msg.append("requesturl: ")
            .append(escape.transform(request.getRequestURL().toString()))
            .append("\n");
      }
      if (Boolean.TRUE.equals(showMMBaseVersion)
          || (showMMBaseVersion == null && !"false".equals(props.get("show_mmbase_version")))) {
        msg.append("mmbase version: ").append(org.mmbase.Version.get()).append("\n");
      }
      msg.append("status: ").append("").append(String.valueOf(status)).append("\n\n");

      if (request != null) {
        msg.append("Parameters\n----------\n");
        // request parameters
        Enumeration en = request.getParameterNames();
        while (en.hasMoreElements()) {
          String name = (String) en.nextElement();
          msg.append(name)
              .append(": ")
              .append(escape.transform(request.getParameter(name)))
              .append("\n");
        }
      }
      msg.append("\nException ")
          .append(ticket)
          .append("\n----------\n\n")
          .append(
              exception != null
                  ? (escape.transform(exception.getClass().getName()))
                  : "NO EXCEPTION")
          .append(": ");

      int wroteCauses = 0;
      while (!stack.isEmpty()) {

        Throwable t = stack.removeFirst();
        // add stack stacktraces
        if (t != null) {
          if (stack.isEmpty()) { // write last message always
            msg = tee;
          }
          String message = t.getMessage();
          if (msg != tee) {
            to.append("\n=== skipped(see log)  : ")
                .append(escape.transform(t.getClass().getName()))
                .append(": ")
                .append(message)
                .append("\n");
          }

          msg.append("\n\n").append(escape.transform(t.getClass().getName() + ": " + message));
          StackTraceElement[] stackTrace = t.getStackTrace();
          for (StackTraceElement e : stackTrace) {
            msg.append("\n        at ").append(escape.transform(e.toString()));
          }
          if (!stack.isEmpty()) {
            msg.append("\n-------caused:\n");
          }
          wroteCauses++;
          if (wroteCauses >= MAX_CAUSES) {
            msg = logMsg;
          }
        }
      }
      // write errors to  log
      if (status == 500) {
        try {
          if (props.get("to") != null && props.get("to").length() > 0) {
            javax.naming.Context initCtx = new javax.naming.InitialContext();
            javax.naming.Context envCtx = (javax.naming.Context) initCtx.lookup("java:comp/env");
            Object mailSession = envCtx.lookup("mail/Session");
            Class sessionClass = Class.forName("javax.mail.Session");
            Class recipientTypeClass = Class.forName("javax.mail.Message$RecipientType");
            Class messageClass = Class.forName("javax.mail.internet.MimeMessage");
            Object mail = messageClass.getConstructor(sessionClass).newInstance(mailSession);
            messageClass
                .getMethod("addRecipients", recipientTypeClass, String.class)
                .invoke(mail, recipientTypeClass.getDeclaredField("TO").get(null), props.get("to"));
            messageClass.getMethod("setSubject", String.class).invoke(mail, ticket);
            mail.getClass().getMethod("setText", String.class).invoke(mail, logMsg.toString());
            Class.forName("javax.mail.Transport")
                .getMethod("send", Class.forName("javax.mail.Message"))
                .invoke(null, mail);
            tee.append("\nmailed to (").append(String.valueOf(props)).append(")");
          }

        } catch (Exception nnfe) {
          tee.append("\nnot mailed (").append(String.valueOf(nnfe)).append(")");
          if (log.isDebugEnabled()) {
            log.debug(nnfe.getMessage(), nnfe);
          }
        }
        log.error("TICKET " + ticket + ":\n" + logMsg);
      }
      return to;
    }
Esempio n. 4
0
 public void setSessionIgnore(String i) {
   sessionIgnore = i == null ? null : Pattern.compile(i);
 }
Esempio n. 5
0
 public void setRequestIgnore(String i) {
   requestIgnore = i == null ? null : Pattern.compile(i);
 }
Esempio n. 6
0
/**
 * This class represents a single RESTXQ function.
 *
 * @author BaseX Team 2005-12, BSD License
 * @author Christian Gruen
 */
final class RestXqFunction implements Comparable<RestXqFunction> {
  /** Pattern for a single template. */
  private static final Pattern TEMPLATE = Pattern.compile("\\s*\\{\\s*\\$(.+?)\\s*\\}\\s*");

  /** Supported methods. */
  EnumSet<HTTPMethod> methods = EnumSet.allOf(HTTPMethod.class);
  /** Serialization parameters. */
  final SerializerProp output = new SerializerProp();
  /** Associated function. */
  final StaticUserFunc function;
  /** Associated module. */
  final RestXqModule module;
  /** Path. */
  RestXqPath path;

  /** Query parameters. */
  final ArrayList<RestXqParam> queryParams = new ArrayList<RestXqParam>();
  /** Form parameters. */
  final ArrayList<RestXqParam> formParams = new ArrayList<RestXqParam>();
  /** Header parameters. */
  final ArrayList<RestXqParam> headerParams = new ArrayList<RestXqParam>();
  /** Cookie parameters. */
  final ArrayList<RestXqParam> cookieParams = new ArrayList<RestXqParam>();

  /** Query context. */
  private final QueryContext context;
  /** Consumed media types. */
  private final StringList consumes = new StringList();
  /** Returned media types. */
  private final StringList produces = new StringList();
  /** Post/Put variable. */
  private QNm requestBody;

  /**
   * Constructor.
   *
   * @param uf associated user function
   * @param qc query context
   * @param m associated module
   */
  RestXqFunction(final StaticUserFunc uf, final QueryContext qc, final RestXqModule m) {
    function = uf;
    context = qc;
    module = m;
  }

  /**
   * Processes the HTTP request. Parses new modules and discards obsolete ones.
   *
   * @param http HTTP context
   * @throws Exception exception
   */
  void process(final HTTPContext http) throws Exception {
    try {
      module.process(http, this);
    } catch (final QueryException ex) {
      if (ex.file() == null) ex.info(function.info);
      throw ex;
    }
  }

  /**
   * Checks a function for RESTFful annotations.
   *
   * @return {@code true} if module contains relevant annotations
   * @throws QueryException query exception
   */
  boolean analyze() throws QueryException {
    // parse all annotations
    final EnumSet<HTTPMethod> mth = EnumSet.noneOf(HTTPMethod.class);
    final boolean[] declared = new boolean[function.args.length];
    boolean found = false;
    final int as = function.ann.size();
    for (int a = 0; a < as; a++) {
      final QNm name = function.ann.names[a];
      final Value value = function.ann.values[a];
      final byte[] local = name.local();
      final byte[] uri = name.uri();
      final boolean rexq = eq(uri, QueryText.RESTXQURI);
      if (rexq) {
        if (eq(PATH, local)) {
          // annotation "path"
          if (path != null) error(ANN_TWICE, "%", name.string());
          path = new RestXqPath(toString(value, name));
          for (final String s : path) {
            if (s.trim().startsWith("{")) checkVariable(s, AtomType.AAT, declared);
          }
        } else if (eq(CONSUMES, local)) {
          // annotation "consumes"
          strings(value, name, consumes);
        } else if (eq(PRODUCES, local)) {
          // annotation "produces"
          strings(value, name, produces);
        } else if (eq(QUERY_PARAM, local)) {
          // annotation "query-param"
          queryParams.add(param(value, name, declared));
        } else if (eq(FORM_PARAM, local)) {
          // annotation "form-param"
          formParams.add(param(value, name, declared));
        } else if (eq(HEADER_PARAM, local)) {
          // annotation "header-param"
          headerParams.add(param(value, name, declared));
        } else if (eq(COOKIE_PARAM, local)) {
          // annotation "cookie-param"
          cookieParams.add(param(value, name, declared));
        } else {
          // method annotations
          final HTTPMethod m = HTTPMethod.get(string(local));
          if (m == null) error(ANN_UNKNOWN, "%", name.string());
          if (!value.isEmpty()) {
            // remember post/put variable
            if (requestBody != null) error(ANN_TWICE, "%", name.string());
            if (m != POST && m != PUT) error(METHOD_VALUE, m);
            requestBody = checkVariable(toString(value, name), declared);
          }
          if (mth.contains(m)) error(ANN_TWICE, "%", name.string());
          mth.add(m);
        }
      } else if (eq(uri, QueryText.OUTPUTURI)) {
        // serialization parameters
        final String key = string(local);
        final String val = toString(value, name);
        if (output.get(key) == null) error(UNKNOWN_SER, key);
        output.set(key, val);
      }
      found |= rexq;
    }
    if (!mth.isEmpty()) methods = mth;

    if (found) {
      if (path == null) error(ANN_MISSING, PATH);
      for (int i = 0; i < declared.length; i++)
        if (!declared[i]) error(VAR_UNDEFINED, function.args[i].name.string());
    }
    return found;
  }

  /**
   * Checks if an HTTP request matches this function and its constraints.
   *
   * @param http http context
   * @return result of check
   */
  boolean matches(final HTTPContext http) {
    // check method, path, consumed and produced media type
    return methods.contains(http.method) && pathMatches(http) && consumes(http) && produces(http);
  }

  /**
   * Binds the annotated variables.
   *
   * @param http http context
   * @param arg argument array
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  void bind(final HTTPContext http, final Expr[] arg) throws QueryException, IOException {
    // bind variables from segments
    for (int s = 0; s < path.size; s++) {
      final Matcher m = TEMPLATE.matcher(path.segment[s]);
      if (!m.find()) continue;
      final QNm qnm = new QNm(token(m.group(1)), context);
      bind(qnm, arg, new Atm(http.segment(s)));
    }

    // cache request body
    final String ct = http.contentType();
    IOContent body = null;

    if (requestBody != null) {
      body = cache(http, null);
      try {
        // bind request body in the correct format
        body.name(http.method + IO.XMLSUFFIX);
        bind(requestBody, arg, Parser.item(body, context.context.prop, ct));
      } catch (final IOException ex) {
        error(INPUT_CONV, ex);
      }
    }

    // bind query parameters
    final Map<String, String[]> params = http.params();
    for (final RestXqParam rxp : queryParams) bind(rxp, arg, params.get(rxp.key));

    // bind form parameters
    if (!formParams.isEmpty()) {
      if (MimeTypes.APP_FORM.equals(ct)) {
        // convert parameters encoded in a form
        body = cache(http, body);
        addParams(body.toString(), params);
      }
      for (final RestXqParam rxp : formParams) bind(rxp, arg, params.get(rxp.key));
    }

    // bind header parameters
    for (final RestXqParam rxp : headerParams) {
      final StringList sl = new StringList();
      final Enumeration<?> en = http.req.getHeaders(rxp.key);
      while (en.hasMoreElements()) {
        for (final String s : en.nextElement().toString().split(", *")) sl.add(s);
      }
      bind(rxp, arg, sl.toArray());
    }

    // bind cookie parameters
    final Cookie[] ck = http.req.getCookies();
    for (final RestXqParam rxp : cookieParams) {
      String v = null;
      if (ck != null) {
        for (final Cookie c : ck) {
          if (rxp.key.equals(c.getName())) v = c.getValue();
        }
      }
      if (v == null) bind(rxp, arg);
      else bind(rxp, arg, v);
    }
  }

  /**
   * Creates an exception with the specified message.
   *
   * @param msg message
   * @param ext error extension
   * @return exception
   * @throws QueryException query exception
   */
  QueryException error(final String msg, final Object... ext) throws QueryException {
    throw new QueryException(function.info, Err.BASX_RESTXQ, Util.info(msg, ext));
  }

  @Override
  public int compareTo(final RestXqFunction rxf) {
    return path.compareTo(rxf.path);
  }

  // PRIVATE METHODS ====================================================================

  /**
   * Checks the specified template and adds a variable.
   *
   * @param tmp template string
   * @param declared variable declaration flags
   * @return resulting variable
   * @throws QueryException query exception
   */
  private QNm checkVariable(final String tmp, final boolean[] declared) throws QueryException {
    return checkVariable(tmp, AtomType.ITEM, declared);
  }

  /**
   * Checks the specified template and adds a variable.
   *
   * @param tmp template string
   * @param type allowed type
   * @param declared variable declaration flags
   * @return resulting variable
   * @throws QueryException query exception
   */
  private QNm checkVariable(final String tmp, final Type type, final boolean[] declared)
      throws QueryException {
    final Var[] args = function.args;
    final Matcher m = TEMPLATE.matcher(tmp);
    if (!m.find()) error(INV_TEMPLATE, tmp);
    final byte[] vn = token(m.group(1));
    if (!XMLToken.isQName(vn)) error(INV_VARNAME, vn);
    final QNm qnm = new QNm(vn, context);
    int r = -1;
    while (++r < args.length && !args[r].name.eq(qnm)) ;
    if (r == args.length) error(UNKNOWN_VAR, vn);
    if (declared[r]) error(VAR_ASSIGNED, vn);
    final SeqType st = args[r].declaredType();
    if (args[r].checksType() && !st.type.instanceOf(type)) error(INV_VARTYPE, vn, type);
    declared[r] = true;
    return qnm;
  }

  /**
   * Checks if the path matches the HTTP request.
   *
   * @param http http context
   * @return result of check
   */
  private boolean pathMatches(final HTTPContext http) {
    return path.matches(http);
  }

  /**
   * Checks if the consumed content type matches.
   *
   * @param http http context
   * @return result of check
   */
  private boolean consumes(final HTTPContext http) {
    // return true if no type is given
    if (consumes.isEmpty()) return true;
    // return true if no content type is specified by the user
    final String ct = http.contentType();
    if (ct == null) return true;
    // check if any combination matches
    for (final String c : consumes) {
      if (MimeTypes.matches(c, ct)) return true;
    }
    return false;
  }

  /**
   * Checks if the produced content type matches.
   *
   * @param http http context
   * @return result of check
   */
  private boolean produces(final HTTPContext http) {
    // return true if no type is given
    if (produces.isEmpty()) return true;
    // check if any combination matches
    for (final String pr : http.produces()) {
      for (final String p : produces) {
        if (MimeTypes.matches(p, pr)) return true;
      }
    }
    return false;
  }

  /**
   * Binds the specified parameter to a variable.
   *
   * @param rxp parameter
   * @param args argument array
   * @param values values to be bound; the parameter's default value is assigned if the argument is
   *     {@code null} or empty
   * @throws QueryException query exception
   */
  private void bind(final RestXqParam rxp, final Expr[] args, final String... values)
      throws QueryException {
    final Value val;
    if (values == null || values.length == 0) {
      val = rxp.value;
    } else {
      final ValueBuilder vb = new ValueBuilder();
      for (final String s : values) vb.add(new Atm(s));
      val = vb.value();
    }
    bind(rxp.name, args, val);
  }

  /**
   * Binds the specified value to a variable.
   *
   * @param name variable name
   * @param args argument array
   * @param value value to be bound
   * @throws QueryException query exception
   */
  private void bind(final QNm name, final Expr[] args, final Value value) throws QueryException {
    // skip nulled values
    if (value == null) return;

    for (int i = 0; i < function.args.length; i++) {
      final Var var = function.args[i];
      if (!var.name.eq(name)) continue;
      // casts and binds the value
      args[i] = var.checkType(value, context, null);
      break;
    }
  }

  /**
   * Returns the specified value as an atomic string.
   *
   * @param value value
   * @param name name
   * @return string
   * @throws QueryException HTTP exception
   */
  private String toString(final Value value, final QNm name) throws QueryException {
    if (!(value instanceof Str)) error(ANN_STRING, "%", name.string(), value);
    return ((Str) value).toJava();
  }

  /**
   * Adds items to the specified list.
   *
   * @param value value
   * @param name name
   * @param list list to add values to
   * @throws QueryException HTTP exception
   */
  private void strings(final Value value, final QNm name, final StringList list)
      throws QueryException {

    final long vs = value.size();
    for (int v = 0; v < vs; v++) list.add(toString(value.itemAt(v), name));
  }

  /**
   * Returns a parameter.
   *
   * @param value value
   * @param name name
   * @param declared variable declaration flags
   * @return parameter
   * @throws QueryException HTTP exception
   */
  private RestXqParam param(final Value value, final QNm name, final boolean[] declared)
      throws QueryException {
    // [CG] RESTXQ: allow identical field names?
    final long vs = value.size();
    if (vs < 2) error(ANN_PARAMS, "%", name.string(), 2);
    // name of parameter
    final String key = toString(value.itemAt(0), name);
    // variable template
    final QNm qnm = checkVariable(toString(value.itemAt(1), name), declared);
    // default value
    final ValueBuilder vb = new ValueBuilder();
    for (int v = 2; v < vs; v++) vb.add(value.itemAt(v));
    return new RestXqParam(qnm, key, vb.value());
  }

  // PRIVATE STATIC METHODS =============================================================

  /**
   * Caches the request body, if not done yet.
   *
   * @param http http context
   * @param cache cache existing cache reference
   * @return cache
   * @throws IOException I/O exception
   */
  private static IOContent cache(final HTTPContext http, final IOContent cache) throws IOException {

    if (cache != null) return cache;
    final BufferInput bi = new BufferInput(http.req.getInputStream());
    final IOContent io = new IOContent(bi.content());
    io.name(http.method + IO.XMLSUFFIX);
    return io;
  }

  /**
   * Adds parameters from the passed on request body.
   *
   * @param body request body
   * @param params map parameters
   */
  private static void addParams(final String body, final Map<String, String[]> params) {
    for (final String nv : body.split("&")) {
      final String[] parts = nv.split("=", 2);
      if (parts.length < 2) continue;
      try {
        params.put(parts[0], new String[] {URLDecoder.decode(parts[1], Token.UTF8)});
      } catch (final Exception ex) {
        Util.notexpected(ex);
      }
    }
  }
}
Esempio n. 7
0
  /**
   * Binds the annotated variables.
   *
   * @param http http context
   * @param arg argument array
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  void bind(final HTTPContext http, final Expr[] arg) throws QueryException, IOException {
    // bind variables from segments
    for (int s = 0; s < path.size; s++) {
      final Matcher m = TEMPLATE.matcher(path.segment[s]);
      if (!m.find()) continue;
      final QNm qnm = new QNm(token(m.group(1)), context);
      bind(qnm, arg, new Atm(http.segment(s)));
    }

    // cache request body
    final String ct = http.contentType();
    IOContent body = null;

    if (requestBody != null) {
      body = cache(http, null);
      try {
        // bind request body in the correct format
        body.name(http.method + IO.XMLSUFFIX);
        bind(requestBody, arg, Parser.item(body, context.context.prop, ct));
      } catch (final IOException ex) {
        error(INPUT_CONV, ex);
      }
    }

    // bind query parameters
    final Map<String, String[]> params = http.params();
    for (final RestXqParam rxp : queryParams) bind(rxp, arg, params.get(rxp.key));

    // bind form parameters
    if (!formParams.isEmpty()) {
      if (MimeTypes.APP_FORM.equals(ct)) {
        // convert parameters encoded in a form
        body = cache(http, body);
        addParams(body.toString(), params);
      }
      for (final RestXqParam rxp : formParams) bind(rxp, arg, params.get(rxp.key));
    }

    // bind header parameters
    for (final RestXqParam rxp : headerParams) {
      final StringList sl = new StringList();
      final Enumeration<?> en = http.req.getHeaders(rxp.key);
      while (en.hasMoreElements()) {
        for (final String s : en.nextElement().toString().split(", *")) sl.add(s);
      }
      bind(rxp, arg, sl.toArray());
    }

    // bind cookie parameters
    final Cookie[] ck = http.req.getCookies();
    for (final RestXqParam rxp : cookieParams) {
      String v = null;
      if (ck != null) {
        for (final Cookie c : ck) {
          if (rxp.key.equals(c.getName())) v = c.getValue();
        }
      }
      if (v == null) bind(rxp, arg);
      else bind(rxp, arg, v);
    }
  }
Esempio n. 8
0
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    // create input/output dir patterns
    String contextPath = httpRequest.getContextPath();
    if (this.inDirPattern == null) {
      // NOTE: Have to do this here because the context path is not
      //       available in init().
      this.inDirPattern =
          Pattern.compile("^" + escape(contextPath) + escape(this.inDirName) + "/(.*)");
      this.outDirPattern =
          Pattern.compile("^" + escape(contextPath) + "/help/[a-z]{2}(?:_[A-Z]{2})?/.*");
      if (ZimbraLog.webclient.isDebugEnabled()) {
        ZimbraLog.webclient.debug("### indir pattern:  " + this.inDirPattern.pattern());
        ZimbraLog.webclient.debug("### outdir pattern: " + this.outDirPattern.pattern());
      }
    }

    // check to see if we need to redirect this request
    String requestUri = httpRequest.getRequestURI();
    if (this.outDirPattern.matcher(requestUri).matches()) {
      // allow it to go through
      chain.doFilter(request, response);
      return;
    }

    // make list of potential locales to check
    Locale preferredLocale = getLocale(httpRequest);
    String language = preferredLocale.getLanguage();
    String country = preferredLocale.getCountry();
    Locale[] locales = {preferredLocale, country != null ? new Locale(language) : null, Locale.US};
    if (ZimbraLog.webclient.isDebugEnabled()) {
      for (Locale locale : locales) {
        ZimbraLog.webclient.debug("locale: " + locale);
      }
    }

    // find out which version of the requested file exists
    Locale actualLocale = preferredLocale;
    Matcher matcher = this.inDirPattern.matcher(requestUri);
    if (!matcher.matches()) {
      httpResponse.sendError(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Help URL doesn't match input pattern.");
      return;
    }

    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### filename: " + matcher.group(1));
    }
    String filename = decode(matcher.group(1)).replace('/', File.separatorChar);
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### filename: " + filename);
    }
    File baseDir = new File(this.context.getRealPath("/"));
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### basedir:  " + baseDir);
    }
    for (Locale locale : locales) {
      if (locale == null) continue;
      File file =
          new File(
              baseDir,
              this.outDirName.replaceAll("\\{locale\\}", locale.toString())
                  + File.separatorChar
                  + filename);
      if (file.exists()) {
        actualLocale = locale;
        break;
      }
    }

    // redirect
    String redirectUrl =
        contextPath
            + this.outDirName.replaceAll("\\{locale\\}", actualLocale.toString())
            + "/"
            + filename;
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("redirecting to: " + redirectUrl);
    }
    httpResponse.sendRedirect(redirectUrl);
  }
Esempio n. 9
0
public class RedirectHelp implements Filter {

  //
  // Constants
  //

  private static final String P_INPUT_DIRNAME = "input.dir";
  private static final String P_OUTPUT_DIRNAME = "output.dir";
  private static final String P_LOCALE_ID = "locid";

  private static final String DEFAULT_INPUT_DIRNAME = "/help";
  private static final String DEFAULT_OUTPUT_DIRNAME = "/help";

  //
  // Data
  //

  private ServletContext context;

  private String inDirName;
  private String outDirName;
  private File outDir;

  private Pattern inDirPattern;
  private Pattern outDirPattern;

  //
  // Filter methods
  //

  public void init(FilterConfig config) throws ServletException {
    this.context = config.getServletContext();

    // get input and output dirs
    this.inDirName = config.getInitParameter(P_INPUT_DIRNAME);
    if (this.inDirName == null) this.inDirName = DEFAULT_INPUT_DIRNAME;
    this.outDirName = config.getInitParameter(P_OUTPUT_DIRNAME);
    if (this.outDirName == null) this.outDirName = DEFAULT_OUTPUT_DIRNAME;
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### indir:  " + this.inDirName);
      ZimbraLog.webclient.debug("### outdir: " + this.outDirName);
    }
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    // create input/output dir patterns
    String contextPath = httpRequest.getContextPath();
    if (this.inDirPattern == null) {
      // NOTE: Have to do this here because the context path is not
      //       available in init().
      this.inDirPattern =
          Pattern.compile("^" + escape(contextPath) + escape(this.inDirName) + "/(.*)");
      this.outDirPattern =
          Pattern.compile("^" + escape(contextPath) + "/help/[a-z]{2}(?:_[A-Z]{2})?/.*");
      if (ZimbraLog.webclient.isDebugEnabled()) {
        ZimbraLog.webclient.debug("### indir pattern:  " + this.inDirPattern.pattern());
        ZimbraLog.webclient.debug("### outdir pattern: " + this.outDirPattern.pattern());
      }
    }

    // check to see if we need to redirect this request
    String requestUri = httpRequest.getRequestURI();
    if (this.outDirPattern.matcher(requestUri).matches()) {
      // allow it to go through
      chain.doFilter(request, response);
      return;
    }

    // make list of potential locales to check
    Locale preferredLocale = getLocale(httpRequest);
    String language = preferredLocale.getLanguage();
    String country = preferredLocale.getCountry();
    Locale[] locales = {preferredLocale, country != null ? new Locale(language) : null, Locale.US};
    if (ZimbraLog.webclient.isDebugEnabled()) {
      for (Locale locale : locales) {
        ZimbraLog.webclient.debug("locale: " + locale);
      }
    }

    // find out which version of the requested file exists
    Locale actualLocale = preferredLocale;
    Matcher matcher = this.inDirPattern.matcher(requestUri);
    if (!matcher.matches()) {
      httpResponse.sendError(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Help URL doesn't match input pattern.");
      return;
    }

    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### filename: " + matcher.group(1));
    }
    String filename = decode(matcher.group(1)).replace('/', File.separatorChar);
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### filename: " + filename);
    }
    File baseDir = new File(this.context.getRealPath("/"));
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("### basedir:  " + baseDir);
    }
    for (Locale locale : locales) {
      if (locale == null) continue;
      File file =
          new File(
              baseDir,
              this.outDirName.replaceAll("\\{locale\\}", locale.toString())
                  + File.separatorChar
                  + filename);
      if (file.exists()) {
        actualLocale = locale;
        break;
      }
    }

    // redirect
    String redirectUrl =
        contextPath
            + this.outDirName.replaceAll("\\{locale\\}", actualLocale.toString())
            + "/"
            + filename;
    if (ZimbraLog.webclient.isDebugEnabled()) {
      ZimbraLog.webclient.debug("redirecting to: " + redirectUrl);
    }
    httpResponse.sendRedirect(redirectUrl);
  }

  public void destroy() {
    this.context = null;
  }

  //
  // Protected methods
  //

  protected Locale getLocale(HttpServletRequest request) {
    String locid = request.getParameter(P_LOCALE_ID);
    if (locid == null || locid.length() == 0) {
      return request.getLocale();
    }
    StringTokenizer tokenizer = new StringTokenizer(locid, "_-");
    String language = String.valueOf(tokenizer.nextToken()).toLowerCase();
    String country = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
    if (country != null) {
      return new Locale(language, country.toUpperCase());
    }
    return new Locale(language);
  }

  //
  // Private functions
  //

  private static String escape(String pattern) {
    return pattern.replaceAll("[?+*\\\\\\{\\[\\(]", "\\$1");
  }

  //
  // Private functions
  //

  private static final Pattern PATTERN = Pattern.compile("((?:%[0-9a-fA-F]{2})+)");

  /**
   * Replace occurrences of "%ab" with the character represented by the hex value. Strings of
   * escaped characters are treated as UTF-8 byte sequences and decoded appropriately.
   */
  private static String decode(String s) {
    int length = s.length();
    StringBuilder str = new StringBuilder(length);
    Matcher matcher = PATTERN.matcher(s);
    int offset = 0;
    byte[] bb = null;
    while (matcher.find(offset)) {
      int count = matcher.groupCount();
      for (int i = 0; i < count; i++) {
        String match = matcher.group(0);
        int num = match.length() / 3;
        if (bb == null || bb.length < num) {
          bb = new byte[num];
        }
        for (int j = 0; j < num; j++) {
          int head = j * 3 + 1;
          int tail = head + 2;
          bb[j] = (byte) Integer.parseInt(match.substring(head, tail), 16);
        }
        try {
          String text = new String(bb, "UTF-8");
          str.append(s.substring(offset, matcher.start()));
          str.append(text);
        } catch (UnsupportedEncodingException e) {
          // NOTE: This should *never* be thrown because all
          //       JVMs are required to support UTF-8. I mean,
          //       the strings in the .class file are all in
          //       a modified UTF-8, for pete's sake! :)
        }
      }
      offset = matcher.end();
    }
    if (offset < length) {
      str.append(s.substring(offset));
    }
    return str.toString();
  }
} // class RedirectHelp