ConnectionUtil.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.InjectionFailureException;
6
import com.jsql.model.exception.JSqlException;
7
import com.jsql.model.injection.method.AbstractMethodInjection;
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.Authenticator;
14
import java.net.CookieManager;
15
import java.net.PasswordAuthentication;
16
import java.net.URI;
17
import java.net.http.HttpClient;
18
import java.net.http.HttpClient.Version;
19
import java.net.http.HttpHeaders;
20
import java.net.http.HttpRequest;
21
import java.net.http.HttpRequest.BodyPublishers;
22
import java.net.http.HttpRequest.Builder;
23
import java.net.http.HttpResponse;
24
import java.net.http.HttpResponse.BodyHandlers;
25
import java.security.SecureRandom;
26
import java.time.Duration;
27
import java.util.*;
28
import java.util.AbstractMap.SimpleEntry;
29
import java.util.Map.Entry;
30
import java.util.stream.Collectors;
31
import java.util.stream.Stream;
32
33
/**
34
 * Utility class in charge of connection to web resources and management
35
 * of source page and request and response headers.
36
 */
37
public class ConnectionUtil {
38
    
39
    private static final Logger LOGGER = LogManager.getRootLogger();
40
    
41
    private String urlByUser;  // URL entered by user
42
    private String urlBase;  // URL entered by user without the query string
43
    private AbstractMethodInjection methodInjection;  // Method of injection: by query string, request or header
44
    private String typeRequest = StringUtil.GET;  // Default HTTP method. It can be changed to a custom method
45
    private final Random randomForUserAgent = new SecureRandom();
46
    private final InjectionModel injectionModel;
47
    private final CookieManager cookieManager = new CookieManager();
48
    
49
    public ConnectionUtil(InjectionModel injectionModel) {
50
        this.injectionModel = injectionModel;
51
    }
52
    
53
    public HttpClient.Builder getHttpClient() {
54
        var httpClientBuilder = HttpClient.newBuilder()
55
            .connectTimeout(Duration.ofSeconds(this.getTimeout()))
56
            .sslContext(this.injectionModel.getMediatorUtils().certificateUtil().getSslContext())
57
            .followRedirects(
58 1 1. getHttpClient : negated conditional → NO_COVERAGE
                this.injectionModel.getMediatorUtils().preferencesUtil().isFollowingRedirection()
59
                ? HttpClient.Redirect.ALWAYS
60
                : HttpClient.Redirect.NEVER
61
            );
62 1 1. getHttpClient : negated conditional → NO_COVERAGE
        if (this.injectionModel.getMediatorUtils().preferencesUtil().isHttp2Disabled()) {
63
            httpClientBuilder.version(Version.HTTP_1_1);
64
        }
65 1 1. getHttpClient : negated conditional → NO_COVERAGE
        if (!this.injectionModel.getMediatorUtils().preferencesUtil().isNotProcessingCookies()) {
66
            httpClientBuilder.cookieHandler(this.cookieManager);
67
        }
68 1 1. getHttpClient : negated conditional → NO_COVERAGE
        if (this.injectionModel.getMediatorUtils().authenticationUtil().isAuthentEnabled()) {
69
            httpClientBuilder.authenticator(new Authenticator() {
70
                @Override
71
                protected PasswordAuthentication getPasswordAuthentication() {
72 1 1. getPasswordAuthentication : replaced return value with null for com/jsql/util/ConnectionUtil$1::getPasswordAuthentication → NO_COVERAGE
                    return new PasswordAuthentication(
73
                        ConnectionUtil.this.injectionModel.getMediatorUtils().authenticationUtil().getUsernameAuthentication(),
74
                        ConnectionUtil.this.injectionModel.getMediatorUtils().authenticationUtil().getPasswordAuthentication().toCharArray()
75
                    );
76
                }
77
            });
78
        }
79 1 1. getHttpClient : replaced return value with null for com/jsql/util/ConnectionUtil::getHttpClient → NO_COVERAGE
        return httpClientBuilder;
80
    }
81
82
    public static <T> Map<String, String> getHeadersMap(HttpResponse<T> httpResponse) {
83
        Map<String, String> sortedMap = ConnectionUtil.getHeadersMap(httpResponse.headers());
84
        String responseCodeHttp = String.valueOf(httpResponse.statusCode());
85
        sortedMap.put(":status", responseCodeHttp);
86 1 1. getHeadersMap : replaced return value with Collections.emptyMap for com/jsql/util/ConnectionUtil::getHeadersMap → NO_COVERAGE
        return sortedMap;
87
    }
88
    
89
    public static Map<String, String> getHeadersMap(HttpHeaders httpHeaders) {
90
        Map<String, String> unsortedMap = httpHeaders.map()
91
            .entrySet()
92
            .stream()
93
            .sorted(Entry.comparingByKey())
94 1 1. lambda$getHeadersMap$0 : replaced return value with null for com/jsql/util/ConnectionUtil::lambda$getHeadersMap$0 → NO_COVERAGE
            .map(entrySet -> new SimpleEntry<>(
95
                entrySet.getKey(),
96
                String.join(", ", entrySet.getValue())
97
            ))
98
            .collect(Collectors.toMap(
99
                SimpleEntry::getKey,
100
                SimpleEntry::getValue
101
            ));
102 1 1. getHeadersMap : replaced return value with Collections.emptyMap for com/jsql/util/ConnectionUtil::getHeadersMap → NO_COVERAGE
        return new TreeMap<>(unsortedMap);
103
    }
104
105
    /**
106
     * Check that the connection to the website is working correctly.
107
     * It uses authentication defined by user, with fixed timeout, and warn
108
     * user in case of authentication detected.
109
     */
110
    public HttpResponse<String> checkConnectionResponse() throws IOException, InterruptedException, JSqlException {
111
        var queryString = this.injectionModel.getMediatorUtils().parameterUtil().getQueryStringFromEntries();
112
        var testUrl = this.getUrlBase().replaceAll("\\?$", StringUtils.EMPTY);
113
114 1 1. checkConnectionResponse : negated conditional → NO_COVERAGE
        if (StringUtils.isNotEmpty(queryString)) {
115
            testUrl += "?"+ queryString;
116
        }
117
118
        String contentTypeRequest = "text/plain";
119
120
        var body = this.injectionModel.getMediatorUtils().parameterUtil().getRawRequest();
121
122 1 1. checkConnectionResponse : negated conditional → NO_COVERAGE
        if (this.injectionModel.getMediatorUtils().parameterUtil().isMultipartRequest()) {
123
            body = body.replaceAll("(?s)\\\\n", "\r\n");
124 1 1. checkConnectionResponse : negated conditional → NO_COVERAGE
        } else if (this.injectionModel.getMediatorUtils().parameterUtil().isRequestSoap()) {
125
            contentTypeRequest = "text/xml";
126 1 1. checkConnectionResponse : negated conditional → NO_COVERAGE
        } else if (!this.injectionModel.getMediatorUtils().parameterUtil().getListRequest().isEmpty()) {
127
            contentTypeRequest = "application/x-www-form-urlencoded";
128
        }
129
130
        // Test the HTTP connection
131
        Builder httpRequest = HttpRequest.newBuilder();
132
        try {
133
            httpRequest.uri(
134
                URI.create(
135
                    testUrl  // Get encoded params without fragment
136
                    .replace(InjectionModel.STAR, StringUtils.EMPTY)  // Ignore injection point during the test
137
                )
138
            );
139
        } catch (IllegalArgumentException e) {
140
            throw new JSqlException(e);
141
        }
142
        httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, contentTypeRequest)
143
            .timeout(Duration.ofSeconds(this.getTimeout()));
144
        
145 1 1. checkConnectionResponse : removed call to com/jsql/util/CsrfUtil::addHeaderToken → NO_COVERAGE
        this.injectionModel.getMediatorUtils().csrfUtil().addHeaderToken(httpRequest);
146 1 1. checkConnectionResponse : removed call to com/jsql/util/DigestUtil::addHeaderToken → NO_COVERAGE
        this.injectionModel.getMediatorUtils().digestUtil().addHeaderToken(httpRequest);
147
148
        httpRequest.method(this.typeRequest, BodyPublishers.ofString(body));
149
150
        // Add headers if exists (Authorization:Basic, etc.)
151
        for (SimpleEntry<String, String> header: this.injectionModel.getMediatorUtils().parameterUtil().getListHeader()) {
152 1 1. checkConnectionResponse : removed call to com/jsql/util/HeaderUtil::sanitizeHeaders → NO_COVERAGE
            HeaderUtil.sanitizeHeaders(httpRequest, header);
153
        }
154
155 1 1. checkConnectionResponse : replaced return value with null for com/jsql/util/ConnectionUtil::checkConnectionResponse → NO_COVERAGE
        return this.injectionModel.getMediatorUtils().headerUtil().checkResponseHeader(httpRequest, body);
156
    }
157
158
    public void testConnection() throws IOException, InterruptedException, JSqlException {
159
        // Check connection is working: define Cookie management, check HTTP status, parse <form> parameters, process CSRF
160
        LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, () -> I18nUtil.valueByKey("LOG_CONNECTION_TEST"));
161
        this.getCookieManager().getCookieStore().removeAll();
162
        HttpResponse<String> httpResponse = this.checkConnectionResponse();
163
164
        if (
165 2 1. testConnection : negated conditional → NO_COVERAGE
2. testConnection : negated conditional → NO_COVERAGE
            (httpResponse.statusCode() == 401 || httpResponse.statusCode() == 403)
166 1 1. testConnection : negated conditional → NO_COVERAGE
            && !this.injectionModel.getMediatorUtils().preferencesUtil().isNotProcessingCookies()
167
            && (
168 1 1. testConnection : negated conditional → NO_COVERAGE
                this.injectionModel.getMediatorUtils().csrfUtil().isCsrf()
169 1 1. testConnection : negated conditional → NO_COVERAGE
                || this.injectionModel.getMediatorUtils().digestUtil().isDigest()
170
            )
171
        ) {
172 1 1. testConnection : negated conditional → NO_COVERAGE
            if (this.injectionModel.getMediatorUtils().preferencesUtil().isProcessingCsrf()) {
173
                LOGGER.log(LogLevelUtil.CONSOLE_INFORM, () -> "Testing CSRF handshake from previous connection...");
174 1 1. testConnection : negated conditional → NO_COVERAGE
            } else if (StringUtils.isNotEmpty(this.injectionModel.getMediatorUtils().digestUtil().getTokenDigest())) {
175
                LOGGER.log(LogLevelUtil.CONSOLE_INFORM, () -> "Testing Digest handshake from previous connection...");
176
            }
177
            httpResponse = this.checkConnectionResponse();
178
        }
179
180 3 1. testConnection : changed conditional boundary → NO_COVERAGE
2. testConnection : negated conditional → NO_COVERAGE
3. testConnection : negated conditional → NO_COVERAGE
        if (httpResponse.statusCode() >= 400 && !this.injectionModel.getMediatorUtils().preferencesUtil().isNotTestingConnection()) {
181
            throw new InjectionFailureException(String.format("Connection failed: problem when calling %s", httpResponse.uri().toURL()));
182
        }
183
    }
184
185
    /**
186
     * Call a URL and return the source page.
187
     * @param url to call
188
     * @return the source page of the URL
189
     */
190
    public String getSourceLineFeed(String url) {
191 1 1. getSourceLineFeed : replaced return value with "" for com/jsql/util/ConnectionUtil::getSourceLineFeed → NO_COVERAGE
        return this.getSource(url, true, false);
192
    }
193
194
    public String getSource(String url) {
195 1 1. getSource : replaced return value with "" for com/jsql/util/ConnectionUtil::getSource → NO_COVERAGE
        return this.getSource(url, false, false);
196
    }
197
198
    public String getSource(String url, boolean isConnectIssueIgnored) {  // reverse init result can be ignored
199 1 1. getSource : replaced return value with "" for com/jsql/util/ConnectionUtil::getSource → NO_COVERAGE
        return this.getSource(url, false, isConnectIssueIgnored);
200
    }
201
202
    public String getSource(String url, boolean lineFeed, boolean isConnectIssueIgnored) {
203
        String pageSource = StringUtils.EMPTY;
204
        Map<String, String> requestHeaders = Map.of();
205
        Map<String, String> responseHeaders = Map.of();
206
207
        try (var httpClient = this.getHttpClient().build()) {
208
            var httpRequest = HttpRequest.newBuilder()
209
                .uri(URI.create(url))
210
                .timeout(Duration.ofSeconds(this.getTimeout()))
211
                .build();
212
213 1 1. getSource : negated conditional → NO_COVERAGE
            if (lineFeed) {
214
                HttpResponse<Stream<String>> response = httpClient.send(httpRequest, BodyHandlers.ofLines());
215
                pageSource = response.body().collect(Collectors.joining("\n"));
216
                responseHeaders = ConnectionUtil.getHeadersMap(response.headers());
217
            } else {
218
                HttpResponse<String> response = httpClient.send(httpRequest, BodyHandlers.ofString());
219
                pageSource = response.body();
220
                responseHeaders = ConnectionUtil.getHeadersMap(response.headers());
221
            }
222
            requestHeaders = ConnectionUtil.getHeadersMap(httpRequest.headers());
223
224
        } catch (IOException e) {
225 1 1. getSource : negated conditional → NO_COVERAGE
            if (!isConnectIssueIgnored) {  // ignoring reverse connection timeout
226
                LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
227
            }
228
        } catch (InterruptedException e) {
229
            LOGGER.log(LogLevelUtil.IGNORE, e, e);
230 1 1. getSource : removed call to java/lang/Thread::interrupt → NO_COVERAGE
            Thread.currentThread().interrupt();
231
        } finally {
232
            // Inform the view about the log infos
233 1 1. getSource : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
            this.injectionModel.sendToViews(new Seal.MessageHeader(
234
                url,
235
                null,
236
                requestHeaders,
237
                responseHeaders,
238
                pageSource,
239
                null,
240
                null,
241
                null,
242
                null
243
            ));
244
        }
245
        
246 1 1. getSource : replaced return value with "" for com/jsql/util/ConnectionUtil::getSource → NO_COVERAGE
        return pageSource.trim();
247
    }
248
249
    public void setCustomUserAgent(Builder httpRequest) {
250 1 1. setCustomUserAgent : negated conditional → NO_COVERAGE
        if (this.injectionModel.getMediatorUtils().preferencesUtil().isUserAgentRandom()) {
251
            String agents = this.injectionModel.getMediatorUtils().userAgentUtil().getCustomUserAgent();
252
            List<String> listAgents = Stream.of(agents.split("[\\r\\n]+"))
253 2 1. lambda$setCustomUserAgent$4 : replaced boolean return with true for com/jsql/util/ConnectionUtil::lambda$setCustomUserAgent$4 → NO_COVERAGE
2. lambda$setCustomUserAgent$4 : negated conditional → NO_COVERAGE
                .filter(q -> !q.matches("^#.*"))
254
                .toList();
255
            String randomElement = listAgents.get(this.randomForUserAgent.nextInt(listAgents.size()));
256
            httpRequest.setHeader("User-Agent", randomElement);
257
        }
258
    }
259
    
260
    
261
    // Builder
262
263
    public ConnectionUtil withMethodInjection(AbstractMethodInjection methodInjection) {
264
        this.methodInjection = methodInjection;
265 1 1. withMethodInjection : replaced return value with null for com/jsql/util/ConnectionUtil::withMethodInjection → NO_COVERAGE
        return this;
266
    }
267
    
268
    public ConnectionUtil withTypeRequest(String typeRequest) {
269
        this.typeRequest = typeRequest;
270 1 1. withTypeRequest : replaced return value with null for com/jsql/util/ConnectionUtil::withTypeRequest → NO_COVERAGE
        return this;
271
    }
272
    
273
    
274
    // Getters and setters
275
    
276
    public String getUrlByUser() {
277 1 1. getUrlByUser : replaced return value with "" for com/jsql/util/ConnectionUtil::getUrlByUser → NO_COVERAGE
        return this.urlByUser;
278
    }
279
280
    public void setUrlByUser(String urlByUser) {
281
        this.urlByUser = urlByUser;
282
    }
283
    
284
    public String getUrlBase() {
285 1 1. getUrlBase : replaced return value with "" for com/jsql/util/ConnectionUtil::getUrlBase → SURVIVED
        return this.urlBase;
286
    }
287
288
    public void setUrlBase(String urlBase) {
289
        this.urlBase = urlBase;
290
    }
291
    
292
    public AbstractMethodInjection getMethodInjection() {
293 1 1. getMethodInjection : replaced return value with null for com/jsql/util/ConnectionUtil::getMethodInjection → KILLED
        return this.methodInjection;
294
    }
295
296
    public void setMethodInjection(AbstractMethodInjection methodInjection) {
297
        this.methodInjection = methodInjection;
298
    }
299
    
300
    public String getTypeRequest() {
301 1 1. getTypeRequest : replaced return value with "" for com/jsql/util/ConnectionUtil::getTypeRequest → NO_COVERAGE
        return this.typeRequest;
302
    }
303
304
    public void setTypeRequest(String typeRequest) {
305
        this.typeRequest = typeRequest;
306
    }
307
308
    /**
309
     * Default timeout used by the jcifs fix. It's the default value used usually by the JVM.
310
     */
311
    public Integer getTimeout() {
312 1 1. getTimeout : replaced Integer return value with 0 for com/jsql/util/ConnectionUtil::getTimeout → NO_COVERAGE
        return this.injectionModel.getMediatorUtils().preferencesUtil().countConnectionTimeout();
313
    }
314
315
    public CookieManager getCookieManager() {
316 1 1. getCookieManager : replaced return value with null for com/jsql/util/ConnectionUtil::getCookieManager → NO_COVERAGE
        return this.cookieManager;
317
    }
318
}

Mutations

58

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

62

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

65

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

68

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

72

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

79

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

86

1.1
Location : getHeadersMap
Killed by : none
replaced return value with Collections.emptyMap for com/jsql/util/ConnectionUtil::getHeadersMap → NO_COVERAGE

94

1.1
Location : lambda$getHeadersMap$0
Killed by : none
replaced return value with null for com/jsql/util/ConnectionUtil::lambda$getHeadersMap$0 → NO_COVERAGE

102

1.1
Location : getHeadersMap
Killed by : none
replaced return value with Collections.emptyMap for com/jsql/util/ConnectionUtil::getHeadersMap → NO_COVERAGE

114

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

122

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

124

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

126

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

145

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

146

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

152

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

155

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

165

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

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

166

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

168

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

169

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

172

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

174

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

180

1.1
Location : testConnection
Killed by : none
changed conditional boundary → NO_COVERAGE

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

3.3
Location : testConnection
Killed by : none
negated conditional → NO_COVERAGE

191

1.1
Location : getSourceLineFeed
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getSourceLineFeed → NO_COVERAGE

195

1.1
Location : getSource
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getSource → NO_COVERAGE

199

1.1
Location : getSource
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getSource → NO_COVERAGE

213

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

225

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

230

1.1
Location : getSource
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

233

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

246

1.1
Location : getSource
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getSource → NO_COVERAGE

250

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

253

1.1
Location : lambda$setCustomUserAgent$4
Killed by : none
replaced boolean return with true for com/jsql/util/ConnectionUtil::lambda$setCustomUserAgent$4 → NO_COVERAGE

2.2
Location : lambda$setCustomUserAgent$4
Killed by : none
negated conditional → NO_COVERAGE

265

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

270

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

277

1.1
Location : getUrlByUser
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getUrlByUser → NO_COVERAGE

285

1.1
Location : getUrlBase
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getUrlBase → SURVIVED
Covering tests

293

1.1
Location : getMethodInjection
Killed by : ParameterUtilSpock.[engine:spock]/[spec:ParameterUtilSpock]/[feature:$spock_feature_0_1]
replaced return value with null for com/jsql/util/ConnectionUtil::getMethodInjection → KILLED

301

1.1
Location : getTypeRequest
Killed by : none
replaced return value with "" for com/jsql/util/ConnectionUtil::getTypeRequest → NO_COVERAGE

312

1.1
Location : getTimeout
Killed by : none
replaced Integer return value with 0 for com/jsql/util/ConnectionUtil::getTimeout → NO_COVERAGE

316

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

Active mutators

Tests examined


Report generated by PIT 1.22.1