HeaderUtil.java

1
package com.jsql.util;
2
3
import com.jsql.model.InjectionModel;
4
import com.jsql.view.subscriber.Seal;
5
import com.jsql.model.exception.JSqlException;
6
import org.apache.commons.lang3.StringUtils;
7
import org.apache.logging.log4j.LogManager;
8
import org.apache.logging.log4j.Logger;
9
10
import java.io.IOException;
11
import java.net.HttpCookie;
12
import java.net.URLEncoder;
13
import java.net.http.HttpRequest.Builder;
14
import java.net.http.HttpResponse;
15
import java.net.http.HttpResponse.BodyHandlers;
16
import java.nio.charset.StandardCharsets;
17
import java.text.DecimalFormat;
18
import java.util.AbstractMap.SimpleEntry;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.regex.Pattern;
22
import java.util.stream.Stream;
23
24
public class HeaderUtil {
25
    
26
    private static final Logger LOGGER = LogManager.getRootLogger();
27
    
28
    public static final String CONTENT_TYPE_REQUEST = "Content-Type";
29
    public static final String WWW_AUTHENTICATE_RESPONSE = "www-authenticate";
30
    private static final String REGEX_HTTP_STATUS = "4\\d\\d";
31
    private static final String FOUND_STATUS_HTTP = "Found status HTTP";
32
33
    private final InjectionModel injectionModel;
34
    
35
    public HeaderUtil(InjectionModel injectionModel) {
36
        this.injectionModel = injectionModel;
37
    }
38
39
    /**
40
     * Parse the header component and decode any character of the form %xy
41
     * except for cookie
42
     * @param httpRequest where decoded value will be set
43
     * @param header string to decode
44
     */
45
    public static void sanitizeHeaders(Builder httpRequest, SimpleEntry<String, String> header) throws JSqlException {
46
        String keyHeader = header.getKey().trim();
47
        String valueHeader = header.getValue().trim();
48
49 2 1. sanitizeHeaders : negated conditional → NO_COVERAGE
2. sanitizeHeaders : negated conditional → NO_COVERAGE
        if ("cookie".equalsIgnoreCase(keyHeader) && Pattern.compile(".+=.*").matcher(valueHeader).find()) {
50
            // Encode cookies to double quotes: Cookie: key="<value>"
51
            List<String> cookies = Stream.of(valueHeader.split(";"))
52 2 1. lambda$sanitizeHeaders$0 : replaced boolean return with false for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$0 → NO_COVERAGE
2. lambda$sanitizeHeaders$0 : replaced boolean return with true for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$0 → NO_COVERAGE
                .filter(value -> value.contains("="))
53 1 1. lambda$sanitizeHeaders$1 : replaced return value with null for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$1 → NO_COVERAGE
                .map(cookie -> cookie.split("=", 2))
54 1 1. lambda$sanitizeHeaders$2 : replaced return value with "" for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$2 → NO_COVERAGE
                .map(arrayEntry -> arrayEntry[0].trim() + "=" + (
55 1 1. lambda$sanitizeHeaders$2 : negated conditional → NO_COVERAGE
                    arrayEntry[1] == null
56
                    ? StringUtils.EMPTY
57
                    // Url encode: new cookie RFC restricts chars to non ()<>@,;:\"/[]?={} => server must url decode the request
58
                    : URLEncoder.encode(arrayEntry[1].trim().replace("+", "%2B"), StandardCharsets.UTF_8)
59
                ))
60
                .toList();
61
            valueHeader = String.join("; ", cookies);
62
        }
63
64
        try {
65
            httpRequest.setHeader(
66
                keyHeader,
67
                valueHeader.replaceAll("[^\\p{ASCII}]", StringUtils.EMPTY)
68
            );
69
        } catch (IllegalArgumentException e) {
70
            throw new JSqlException(e);
71
        }
72
    }
73
74
    /**
75
     * Verify the headers received after a request, detect authentication response and
76
     * send the headers to the view.
77
     * @param httpRequestBuilder calls URL
78
     * @return httpResponse with response headers
79
     * @throws IOException when an error occurs during connection
80
     */
81
    public HttpResponse<String> checkResponseHeader(Builder httpRequestBuilder, String body) throws IOException, InterruptedException {
82
        var httpRequest = httpRequestBuilder.build();
83
        HttpResponse<String> httpResponse = this.injectionModel.getMediatorUtils().connectionUtil().getHttpClient().build().send(
84
            httpRequest,
85
            BodyHandlers.ofString()
86
        );
87
        String pageSource = httpResponse.body();
88
        
89
        List<HttpCookie> cookies = this.injectionModel.getMediatorUtils().connectionUtil().getCookieManager().getCookieStore().getCookies();
90 1 1. checkResponseHeader : negated conditional → NO_COVERAGE
        if (!cookies.isEmpty()) {
91
            LOGGER.info("Cookies set by host: {}", cookies);
92
        }
93
94
        var responseCode = Integer.toString(httpResponse.statusCode());
95
        Map<String, String> mapResponseHeaders = ConnectionUtil.getHeadersMap(httpResponse);
96 1 1. checkResponseHeader : removed call to com/jsql/util/HeaderUtil::checkResponse → NO_COVERAGE
        this.checkResponse(responseCode, mapResponseHeaders);
97 1 1. checkResponseHeader : removed call to com/jsql/util/HeaderUtil::checkStatus → NO_COVERAGE
        this.checkStatus(httpResponse);
98
        
99 1 1. checkResponseHeader : removed call to com/jsql/util/FormUtil::parseForms → NO_COVERAGE
        this.injectionModel.getMediatorUtils().formUtil().parseForms(httpResponse.statusCode(), pageSource);
100 1 1. checkResponseHeader : removed call to com/jsql/util/CsrfUtil::parseForCsrfToken → NO_COVERAGE
        this.injectionModel.getMediatorUtils().csrfUtil().parseForCsrfToken(pageSource, mapResponseHeaders);
101 1 1. checkResponseHeader : removed call to com/jsql/util/DigestUtil::parseWwwAuthenticate → NO_COVERAGE
        this.injectionModel.getMediatorUtils().digestUtil().parseWwwAuthenticate(mapResponseHeaders);
102
103
        int sizeHeaders = mapResponseHeaders.keySet()
104
            .stream()
105 2 1. lambda$checkResponseHeader$3 : replaced Integer return value with 0 for com/jsql/util/HeaderUtil::lambda$checkResponseHeader$3 → NO_COVERAGE
2. lambda$checkResponseHeader$3 : Replaced integer addition with subtraction → NO_COVERAGE
            .map(key -> mapResponseHeaders.get(key).length() + key.length())
106
            .mapToInt(Integer::intValue)
107
            .sum();
108 2 1. checkResponseHeader : Replaced float division with multiplication → NO_COVERAGE
2. checkResponseHeader : Replaced integer addition with subtraction → NO_COVERAGE
        float size = (float) (pageSource.length() + sizeHeaders) / 1024;
109
        var decimalFormat = new DecimalFormat("0.000");
110
111
        // Inform the view about the log info
112 1 1. checkResponseHeader : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
        this.injectionModel.sendToViews(new Seal.MessageHeader(
113
            httpRequest.uri().toURL().toString(),
114
            body,
115
            ConnectionUtil.getHeadersMap(httpRequest.headers()),
116
            mapResponseHeaders,
117
            pageSource,
118
            decimalFormat.format(size),
119
            "#none",
120
            "test#conn",
121
            null
122
        ));
123
124 1 1. checkResponseHeader : replaced return value with null for com/jsql/util/HeaderUtil::checkResponseHeader → NO_COVERAGE
        return httpResponse;
125
    }
126
127
    private void checkStatus(HttpResponse<String> response) {
128 2 1. checkStatus : negated conditional → NO_COVERAGE
2. checkStatus : changed conditional boundary → NO_COVERAGE
        if (response.statusCode() >= 400) {
129 1 1. checkStatus : negated conditional → NO_COVERAGE
            if (this.injectionModel.getMediatorUtils().preferencesUtil().isNotTestingConnection()) {
130
                LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Connection test disabled, skipping error {}...", response.statusCode());
131
            } else {
132
                LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Try with option 'Disable connection test' to skip HTTP error {}", response.statusCode());
133
            }
134
        }
135
    }
136
137
    private void checkResponse(String responseCode, Map<String, String> mapResponse) {
138 1 1. checkResponse : negated conditional → NO_COVERAGE
        if (this.isBasicAuth(responseCode, mapResponse)) {
139
            LOGGER.log(
140
                LogLevelUtil.CONSOLE_ERROR,
141
                "Basic Authentication detected: "
142
                + "set authentication in preferences, "
143
                + "or add header 'Authorization: Basic b3N..3Jk', with b3N..3Jk as "
144
                + "'osUserName:osPassword' encoded in Base64 (use the Coder in jSQL to encode the string)."
145
            );
146 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (this.isNtlm(responseCode, mapResponse)) {
147
            LOGGER.log(
148
                LogLevelUtil.CONSOLE_ERROR,
149
                "NTLM Authentication detected: "
150
                + "set authentication in preferences, "
151
                + "or add username, password and domain information to the URL, e.g. http://domain\\user:password@127.0.0.1/[..]"
152
            );
153 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (this.isDigest(responseCode, mapResponse)) {
154
            LOGGER.log(
155
                LogLevelUtil.CONSOLE_ERROR,
156
                "Digest Authentication detected: set authentication in preferences."
157
            );
158 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (this.isNegotiate(responseCode, mapResponse)) {
159
            LOGGER.log(
160
                LogLevelUtil.CONSOLE_ERROR,
161
                "Negotiate Authentication detected: "
162
                + "add username, password and domain information to the URL, e.g. http://domain\\user:password@127.0.0.1/[..]"
163
            );
164 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (Pattern.matches("1\\d\\d", responseCode)) {
165
            LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} {} Informational", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
166 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (Pattern.matches("2\\d\\d", responseCode)) {
167
            LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "{} {} Success", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
168 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (Pattern.matches("3\\d\\d", responseCode)) {
169
            
170
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Redirection", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
171
            
172 1 1. checkResponse : negated conditional → NO_COVERAGE
            if (!this.injectionModel.getMediatorUtils().preferencesUtil().isFollowingRedirection()) {
173
                LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "If injection fails retry with option 'Follow HTTP redirection' activated");
174
            } else {
175
                LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Redirecting to the next page...");
176
            }
177 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)) {
178
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Client Error", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
179 1 1. checkResponse : negated conditional → NO_COVERAGE
        } else if (Pattern.matches("5\\d\\d", responseCode)) {
180
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Server Error", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
181
        } else {
182
            LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} {} Unknown", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
183
        }
184
    }
185
    
186
    private boolean isNegotiate(String responseCode, Map<String, String> mapResponse) {
187 2 1. isNegotiate : negated conditional → NO_COVERAGE
2. isNegotiate : replaced boolean return with true for com/jsql/util/HeaderUtil::isNegotiate → NO_COVERAGE
        return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
188 1 1. isNegotiate : negated conditional → NO_COVERAGE
            && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
189 1 1. isNegotiate : negated conditional → NO_COVERAGE
            && "Negotiate".equals(mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE));
190
    }
191
192
    private boolean isDigest(String responseCode, Map<String, String> mapResponse) {
193 2 1. isDigest : negated conditional → NO_COVERAGE
2. isDigest : replaced boolean return with true for com/jsql/util/HeaderUtil::isDigest → NO_COVERAGE
        return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
194 1 1. isDigest : negated conditional → NO_COVERAGE
            && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
195 1 1. isDigest : negated conditional → NO_COVERAGE
            && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE) != null
196 1 1. isDigest : negated conditional → NO_COVERAGE
            && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE).startsWith("Digest ");
197
    }
198
199
    private boolean isNtlm(String responseCode, Map<String, String> mapResponse) {
200 2 1. isNtlm : negated conditional → NO_COVERAGE
2. isNtlm : replaced boolean return with true for com/jsql/util/HeaderUtil::isNtlm → NO_COVERAGE
        return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
201 1 1. isNtlm : negated conditional → NO_COVERAGE
            && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
202 1 1. isNtlm : negated conditional → NO_COVERAGE
            && "NTLM".equals(mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE));
203
    }
204
205
    private boolean isBasicAuth(String responseCode, Map<String, String> mapResponse) {
206 2 1. isBasicAuth : replaced boolean return with true for com/jsql/util/HeaderUtil::isBasicAuth → NO_COVERAGE
2. isBasicAuth : negated conditional → NO_COVERAGE
        return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
207 1 1. isBasicAuth : negated conditional → NO_COVERAGE
            && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
208 1 1. isBasicAuth : negated conditional → NO_COVERAGE
            && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE) != null
209 1 1. isBasicAuth : negated conditional → NO_COVERAGE
            && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE).startsWith("Basic ");
210
    }
211
}

Mutations

49

1.1
Location : sanitizeHeaders
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : sanitizeHeaders
Killed by : none
negated conditional → NO_COVERAGE

52

1.1
Location : lambda$sanitizeHeaders$0
Killed by : none
replaced boolean return with false for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$0 → NO_COVERAGE

2.2
Location : lambda$sanitizeHeaders$0
Killed by : none
replaced boolean return with true for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$0 → NO_COVERAGE

53

1.1
Location : lambda$sanitizeHeaders$1
Killed by : none
replaced return value with null for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$1 → NO_COVERAGE

54

1.1
Location : lambda$sanitizeHeaders$2
Killed by : none
replaced return value with "" for com/jsql/util/HeaderUtil::lambda$sanitizeHeaders$2 → NO_COVERAGE

55

1.1
Location : lambda$sanitizeHeaders$2
Killed by : none
negated conditional → NO_COVERAGE

90

1.1
Location : checkResponseHeader
Killed by : none
negated conditional → NO_COVERAGE

96

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/util/HeaderUtil::checkResponse → NO_COVERAGE

97

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/util/HeaderUtil::checkStatus → NO_COVERAGE

99

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/util/FormUtil::parseForms → NO_COVERAGE

100

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/util/CsrfUtil::parseForCsrfToken → NO_COVERAGE

101

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/util/DigestUtil::parseWwwAuthenticate → NO_COVERAGE

105

1.1
Location : lambda$checkResponseHeader$3
Killed by : none
replaced Integer return value with 0 for com/jsql/util/HeaderUtil::lambda$checkResponseHeader$3 → NO_COVERAGE

2.2
Location : lambda$checkResponseHeader$3
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

108

1.1
Location : checkResponseHeader
Killed by : none
Replaced float division with multiplication → NO_COVERAGE

2.2
Location : checkResponseHeader
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

112

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE

124

1.1
Location : checkResponseHeader
Killed by : none
replaced return value with null for com/jsql/util/HeaderUtil::checkResponseHeader → NO_COVERAGE

128

1.1
Location : checkStatus
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : checkStatus
Killed by : none
changed conditional boundary → NO_COVERAGE

129

1.1
Location : checkStatus
Killed by : none
negated conditional → NO_COVERAGE

138

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

146

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

153

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

158

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

164

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

166

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

168

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

172

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

177

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

179

1.1
Location : checkResponse
Killed by : none
negated conditional → NO_COVERAGE

187

1.1
Location : isNegotiate
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : isNegotiate
Killed by : none
replaced boolean return with true for com/jsql/util/HeaderUtil::isNegotiate → NO_COVERAGE

188

1.1
Location : isNegotiate
Killed by : none
negated conditional → NO_COVERAGE

189

1.1
Location : isNegotiate
Killed by : none
negated conditional → NO_COVERAGE

193

1.1
Location : isDigest
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : isDigest
Killed by : none
replaced boolean return with true for com/jsql/util/HeaderUtil::isDigest → NO_COVERAGE

194

1.1
Location : isDigest
Killed by : none
negated conditional → NO_COVERAGE

195

1.1
Location : isDigest
Killed by : none
negated conditional → NO_COVERAGE

196

1.1
Location : isDigest
Killed by : none
negated conditional → NO_COVERAGE

200

1.1
Location : isNtlm
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : isNtlm
Killed by : none
replaced boolean return with true for com/jsql/util/HeaderUtil::isNtlm → NO_COVERAGE

201

1.1
Location : isNtlm
Killed by : none
negated conditional → NO_COVERAGE

202

1.1
Location : isNtlm
Killed by : none
negated conditional → NO_COVERAGE

206

1.1
Location : isBasicAuth
Killed by : none
replaced boolean return with true for com/jsql/util/HeaderUtil::isBasicAuth → NO_COVERAGE

2.2
Location : isBasicAuth
Killed by : none
negated conditional → NO_COVERAGE

207

1.1
Location : isBasicAuth
Killed by : none
negated conditional → NO_COVERAGE

208

1.1
Location : isBasicAuth
Killed by : none
negated conditional → NO_COVERAGE

209

1.1
Location : isBasicAuth
Killed by : none
negated conditional → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.1