xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/web/Laundromat.java (revision 5d9f3aa0ca3da3a714233f987fa732f62c0965f6)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2020, Chris Fraire <cfraire@me.com>.
22  */
23 package org.opengrok.indexer.web;
24 
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.stream.Collectors;
28 
29 /**
30  * Represents a container for sanitizing methods for avoiding classifications as
31  * taint bugs.
32  */
33 public class Laundromat {
34 
35     private static final String ESC_N_R_T_F = "[\\n\\r\\t\\f]";
36     private static final String ESG_N_R_T_F__1_n = ESC_N_R_T_F + "+";
37 
38     /**
39      * Sanitize {@code value} where it will be used in subsequent OpenGrok
40      * (non-logging) processing.
41      * @return {@code null} if null or else {@code value} with "pattern-breaking
42      * characters" (tabs, CR, LF, FF) replaced as underscores (one for one)
43      */
launderInput(String value)44     public static String launderInput(String value) {
45         return replaceAll(value, ESC_N_R_T_F, "_");
46     }
47 
48     /**
49      * Sanitize {@code value} where it will be used in a Lucene query.
50      * @return {@code null} if null or else {@code value} with "pattern-breaking
51      * characters" (tabs, CR, LF, FF) replaced as spaces. Contiguous matches are
52      * replaced with one space.
53      */
launderQuery(String value)54     public static String launderQuery(String value) {
55         return replaceAll(value, ESG_N_R_T_F__1_n, " ");
56     }
57 
58     /**
59      * Sanitize {@code value} where it will be used in a log message only.
60      * @return {@code null} if null or else {@code value} with "pattern-breaking
61      * characters" tabs, CR, LF, and FF replaced as {@code "<TAB>"},
62      * {@code "<CR>"}, {@code "<LF>"}, and {@code "<FF>"} resp.
63      */
launderLog(String value)64     public static String launderLog(String value) {
65         if (value == null) {
66             return null;
67         }
68         return value.replaceAll("\\n", "<LF>").
69                 replaceAll("\\r", "<CR>").
70                 replaceAll("\\t", "<TAB>").
71                 replaceAll("\\f", "<FF>");
72     }
73 
74     /**
75      * Sanitize {@code map} where it will be used in a log message only.
76      * @return {@code null} if null or else {@code map} with keys and values
77      * sanitized with {@link #launderLog(String)}. If the sanitizing causes key
78      * collisions, the colliding keys' values are combined.
79      */
launderLog(Map<String, String[]> map)80     public static Map<String, String[]> launderLog(Map<String, String[]> map) {
81         if (map == null) {
82             return null;
83         }
84 
85         HashMap<String, String[]> safes = new HashMap<>();
86         for (Map.Entry<String, String[]> entry : map.entrySet().stream().sorted(
87                 Map.Entry.comparingByKey()).collect(Collectors.toList())) {
88             String key = launderLog(entry.getKey());
89             String[] safeValues = safes.get(key);
90             String[] fullySafe = mergeLogArrays(entry.getValue(), safeValues);
91             safes.put(key, fullySafe);
92         }
93         return safes;
94     }
95 
mergeLogArrays(String[] values, String[] safeValues)96     private static String[] mergeLogArrays(String[] values, String[] safeValues) {
97         if (values == null && safeValues == null) {
98             return null;
99         }
100 
101         int n = (values != null ? values.length : 0) +
102                 (safeValues != null ? safeValues.length : 0);
103         String[] result = new String[n];
104 
105         int i = 0;
106         if (values != null) {
107             for (; i < values.length; ++i) {
108                 result[i] = launderLog(values[i]);
109             }
110         }
111         if (safeValues != null) {
112             System.arraycopy(safeValues, 0, result, i, safeValues.length);
113         }
114         return result;
115     }
116 
replaceAll(String value, String regex, String replacement)117     private static String replaceAll(String value, String regex, String replacement) {
118         if (value == null) {
119             return null;
120         }
121         return value.replaceAll(regex, replacement);
122     }
123 
124     /* private to enforce static */
Laundromat()125     private Laundromat() {
126     }
127 }
128