001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.http;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023/**
024 * Utility for masking the values of header names that commonly carry account credentials
025 * (bearer tokens, session cookies, API keys, and the like) before they are written somewhere
026 * observable, such as a log line or an echoed-back response body.
027 *
028 * <p>
029 * Matching is always case-insensitive, mirroring HTTP header-name semantics.
030 *
031 * <p class='bjava'>
032 *    <jc>// Default set: mask only the well-known credential-bearing headers.</jc>
033 *    String <jv>masked</jv> = RedactedHeaders.<jsm>redact</jsm>(<js>"authorization"</js>, <js>"Bearer abc123"</js>);
034 *    <jc>// masked == "[REDACTED]"</jc>
035 *
036 *    String <jv>kept</jv> = RedactedHeaders.<jsm>redact</jsm>(<js>"User-Agent"</js>, <js>"curl/8.0"</js>);
037 *    <jc>// kept == "curl/8.0"</jc>
038 * </p>
039 *
040 * <p>
041 * Callers that need a different or extended header set (for example, an additional
042 * internal-trace header) can pass their own name collection to the two-argument overloads
043 * rather than being limited to {@link #DEFAULT}.
044 *
045 * @since 10.0.0
046 */
047public final class RedactedHeaders {
048
049   /** Sentinel value substituted for a masked header value. */
050   public static final String REDACTED = "[REDACTED]";
051
052   /** Canonical set of header names commonly used to carry credentials, matched case-insensitively. */
053   public static final Set<String> DEFAULT = Set.of(
054      "Authorization", "Cookie", "Set-Cookie", "Proxy-Authorization", "X-API-Key");
055
056   private RedactedHeaders() {}
057
058   /**
059    * Returns {@code true} if the given header name is in {@link #DEFAULT} (case-insensitive).
060    *
061    * @param name The header name to test. Can be {@code null} (returns {@code false}).
062    * @return {@code true} if the name matches one of {@link #DEFAULT}, case-insensitively.
063    */
064   public static boolean isSensitive(String name) {
065      return isSensitive(name, DEFAULT);
066   }
067
068   /**
069    * Returns {@code true} if the given header name matches one of {@code names}, case-insensitively.
070    *
071    * @param name The header name to test. Can be {@code null} (returns {@code false}).
072    * @param names The candidate header names. Can be {@code null} or empty (returns {@code false}).
073    *    {@code null} elements are ignored.
074    * @return {@code true} if {@code name} matches one of {@code names}, case-insensitively.
075    */
076   public static boolean isSensitive(String name, Collection<String> names) {
077      if (name == null || names == null)
078         return false;
079      for (var n : names)
080         if (n != null && eqic(name, n))
081            return true;
082      return false;
083   }
084
085   /**
086    * Masks {@code value} with {@link #REDACTED} if {@code name} is in {@link #DEFAULT}
087    * (case-insensitive); otherwise returns {@code value} unchanged.
088    *
089    * @param name The header name. Can be {@code null} (never matches; {@code value} is returned as-is).
090    * @param value The header value to mask. Can be {@code null}.
091    * @return {@link #REDACTED} if {@code name} is sensitive, else {@code value}.
092    */
093   public static String redact(String name, String value) {
094      return isSensitive(name) ? REDACTED : value;
095   }
096
097   /**
098    * Masks {@code value} with {@link #REDACTED} if {@code name} matches one of {@code names}
099    * (case-insensitive); otherwise returns {@code value} unchanged.
100    *
101    * @param name The header name. Can be {@code null} (never matches; {@code value} is returned as-is).
102    * @param value The header value to mask. Can be {@code null}.
103    * @param names The candidate header names. Can be {@code null} or empty (nothing is masked).
104    * @return {@link #REDACTED} if {@code name} matches one of {@code names}, else {@code value}.
105    */
106   public static String redact(String name, String value, Collection<String> names) {
107      return isSensitive(name, names) ? REDACTED : value;
108   }
109}