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}