Class MethodSafety

java.lang.Object
org.apache.juneau.rest.server.MethodSafety

public class MethodSafety extends Object
Which HTTP methods are safe, and the startup check that an operation declared Mutating is not bound to one of them.

This is the single definition of the safe-method set for this framework. Anything that needs to know whether a request may change state — notably LoopbackBoundary.isStateChanging(String), which delegates here — reads it from this class, so a security filter and a startup check cannot come to disagree about which methods are writes. A divergence between those two would be silent and would defeat both.

The check

check(List) runs when a resource's operation table is assembled, from RestOperations(RestOperations.Builder) — the one constructor every operation table passes through, so there is no registration to remember and no path around it. It fails the resource when an operation carrying @Mutating resolves to a safe method.

The resolved method is read from RestOpContext.getHttpMethod(), which is what dispatch itself uses. That matters more than it sounds: the method may have been inferred from the Java method name rather than written down (see RestOp.method()), and a check that re-derived it independently could disagree with dispatch about which operation is bound to what. A security-relevant check that disagrees with the router is worse than none.

A wildcard operation (@RestOp(method="*")) matches GET among everything else, so it is a contradiction too and is reported as one.

The same check fails an operation declared read=true that is bound to a method defined as unsafe, to "*" or to "RRPC", or that is also @Mutating. A read can be reached by overriding a safe request, so a misdeclared read would be a write behind a plain link.

Why the rule is a declared contradiction rather than a detected one

See Mutating, which records why inferring "this handler mutates" is not soundly possible and why a check that can wrongly refuse to start an application is worse than no check at all. The short form: this class only ever reports a contradiction the developer wrote down, so it cannot produce a false positive, and it cannot catch a handler that mutates and says nothing.

See Also:
Since:
10.0.0
  • Constructor Details

  • Method Details

    • isSafe

      public static boolean isSafe(String method)
      Whether method is defined as safe, and therefore promises not to change state.
      Parameters:
      method - The request method. Can be null, which is not safe.
      Returns:
      true if the method is one of GET, HEAD, OPTIONS or TRACE.
    • effectiveMethod

      public static String effectiveMethod(String method, String rawQuery, UnaryOperator<String> headers)
      The method a request is judged on by a write-protection check.

      A request is judged on the method it claims, not only on its request line. A method query parameter, or an X-Method, X-HTTP-Method-Override, X-HTTP-Method or X-Method-Override header, naming anything other than a safe method makes the request a write, whether or not the resource it reaches is configured to honour that override. A check that followed dispatch's override configuration instead would open a hole the day a resource's allowed overrides changed.

      The rules fail closed:

      • The request-line method is the floor. An unsafe request line is returned (upper-cased); an override can raise a request's severity but never lower it, so POST ?method=GET stays POST.
      • Any unsafe claim wins, so ?method=GET&method=DELETE is DELETE.
      • An unknown method is unsafe, so ?method=FROB is judged as a write.
      • A claim with a malformed percent-escape is judged on its raw text, which is never a safe method name.
      • A blank claim is ignored.
      Parameters:
      method - The request line's method. Can be null, which is returned as null (unsafe).
      rawQuery - The raw, undecoded query string. Can be null.
      headers - Looks up a request header by name, returning null when absent. Must not be null.
      Returns:
      The upper-cased request-line method when it is not safe, else the first unsafe claim (upper-cased), else the upper-cased request-line method.
    • overrideHeaders

      public static List<String> overrideHeaders()
      The names of the request headers that can claim a request's method, in precedence order.

      The order is part of the contract: dispatch honours the first admitted header in this order.

      Returns:
      An immutable list of header names. Never null.
    • queryClaims

      public static List<String> queryClaims(String rawQuery)
      Every value of the method query parameter, in order.

      The one parser shared by the write-protection filters and by dispatch, so they cannot disagree about what a query string claims. The raw query is split on &, each name and value is percent-decoded, and the name is matched case-insensitively. Values are returned untrimmed. A malformed percent-escape leaves the raw text.

      Parameters:
      rawQuery - The raw, undecoded query string. Can be null.
      Returns:
      The decoded values, in order. Never null; empty when there is no claim.
    • check

      public static void check(List<RestOpContext> ops)
      Fails when any operation declared @Mutating is bound to a safe method, or any operation declared read=true is bound to a method that cannot be a read.
      Parameters:
      ops - The operations of one resource. Must not be null.
      Throws:
      InternalServerError - If a mutating operation is bound to a safe method, or a read declaration is misplaced or contradicts the operation's method or a @Mutating declaration.
    • checkOperation

      public static void checkOperation(String httpMethod, Method javaMethod)
      Fails when javaMethod is declared @Mutating and httpMethod is safe.

      The per-operation half of check(List), taking the two facts it compares rather than a built RestOpContext, so the rule can be exercised directly.

      Parameters:
      httpMethod - The resolved HTTP method, as dispatch sees it. Can be null, which is not safe. "*" matches every method and is therefore treated as including a safe one.
      javaMethod - The Java method implementing the operation. Must not be null.
      Throws:
      InternalServerError - If javaMethod is declared mutating and httpMethod is safe.
    • checkOperation

      public static void checkOperation(String httpMethod, Method javaMethod, boolean read)
      Fails when javaMethod is declared @Mutating and httpMethod is safe, or when the operation is declared read=true and httpMethod cannot be a read.

      A read declaration is refused on the methods defined as unsafe (POST, PUT, PATCH, DELETE, CONNECT), on "*" (which answers DELETE too), on "RRPC" (which dispatches arbitrary interface methods), and together with @Mutating. It is accepted on a custom method, and is redundant on a safe one.

      Parameters:
      httpMethod - The resolved HTTP method, as dispatch sees it. Can be null, which is not safe. "*" matches every method and is therefore treated as including a safe one.
      javaMethod - The Java method implementing the operation. Must not be null.
      read - Whether the operation is declared read=true.
      Throws:
      InternalServerError - If the declarations contradict each other or the method.
      Since:
      10.0.0