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