HeaderUtil.java

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

Mutations

56

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

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

59

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

2.2
Location : lambda$sanitizeHeaders$0
Killed by : none
negated conditional → NO_COVERAGE

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

60

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

61

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

62

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

97

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

103

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

104

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

106

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

107

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

108

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

114

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

118

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

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

132

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE

133

1.1
Location : checkResponseHeader
Killed by : none
removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE

134

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

136

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

140

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

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

141

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

150

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

158

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

165

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

170

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

176

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

178

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

180

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

184

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

189

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

191

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

199

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

200

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

201

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

205

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

206

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

207

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

208

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

212

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

213

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

214

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

218

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

219

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

220

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

221

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

Active mutators

Tests examined


Report generated by PIT 1.19.1