CallableHttpHead.java

package com.jsql.model.accessible;

import com.jsql.model.InjectionModel;
import com.jsql.model.bean.util.Header;
import com.jsql.model.bean.util.Interaction;
import com.jsql.model.bean.util.Request;
import com.jsql.util.ConnectionUtil;
import com.jsql.util.LogLevelUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.AbstractMap.SimpleEntry;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.stream.Stream;

/**
 * Thread unit to test if an administration page exists on the server.
 * The process can be cancelled by the user.
 */
public class CallableHttpHead implements Callable<CallableHttpHead> {
    
    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();
    
    /**
     * URL to an administration page on the website to get tested.
     */
    private final String urlAdminPage;
    
    /**
     * HTTP header response code.
     */
    private String responseCodeHttp = StringUtils.EMPTY;

    private final InjectionModel injectionModel;

    private final String metadataInjectionProcess;
    
    /**
     * Create a callable to find admin page.
     * @param urlAdminPage URL of admin page
     */
    public CallableHttpHead(String urlAdminPage, InjectionModel injectionModel, String metadataInjectionProcess) {
        
        this.urlAdminPage = urlAdminPage;
        this.injectionModel= injectionModel;
        this.metadataInjectionProcess= metadataInjectionProcess;
    }

    /**
     * Call URL to a administration page in HEAD mode and send the result back to view.
     */
    @Override
    public CallableHttpHead call() {
        
        if (this.injectionModel.getResourceAccess().isSearchAdminStopped()) {
            return this;
        }
        
        try {
            var builderHttpRequest = HttpRequest.newBuilder()
                .uri(URI.create(this.urlAdminPage))
                .method("HEAD", BodyPublishers.noBody())
                .timeout(Duration.ofSeconds(4));
            
            Stream.of(
                this.injectionModel.getMediatorUtils().getParameterUtil().getHeaderFromEntries()
                .split("\\\\r\\\\n")
            )
            .map(e -> {
                
                if (e.split(":").length == 2) {
                    
                    return new SimpleEntry<>(
                        e.split(":")[0],
                        e.split(":")[1]
                    );
                } else {
                    
                    return null;
                }
            })
            .filter(Objects::nonNull)
            .forEach(e -> builderHttpRequest.header(e.getKey(), e.getValue()));
            
            var httpRequest = builderHttpRequest.build();
            
            var httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(4))
                .build();
            
            HttpResponse<Void> response = httpClient.send(httpRequest, BodyHandlers.discarding());

            this.responseCodeHttp = ""+ response.statusCode();

            Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
            msgHeader.put(Header.URL, this.urlAdminPage);
            msgHeader.put(Header.POST, StringUtils.EMPTY);
            msgHeader.put(Header.HEADER, ConnectionUtil.getHeadersMap(httpRequest.headers()));
            msgHeader.put(Header.RESPONSE, ConnectionUtil.getHeadersMap(response));
            msgHeader.put(Header.METADATA_PROCESS, this.metadataInjectionProcess);
            
            var request = new Request();
            request.setMessage(Interaction.MESSAGE_HEADER);
            request.setParameters(msgHeader);
            this.injectionModel.sendToViews(request);
            
        } catch (InterruptedException e) {
            
            LOGGER.log(LogLevelUtil.IGNORE, e, e);
            Thread.currentThread().interrupt();
            
        } catch (Exception e) {
            
            var eMessageImplicit = String.format(
                "Problem connecting to %s (implicit reason): %s", 
                this.urlAdminPage,
                InjectionModel.getImplicitReason(e)
            );
            
            String eMessage = Optional.ofNullable(e.getMessage()).orElse(eMessageImplicit);
            
            LOGGER.log(LogLevelUtil.CONSOLE_ERROR, eMessage);
        }
        
        return this;
    }

    /**
     * Check if HTTP response is either 2xx or 3xx, which corresponds to
     * a acceptable response from the website.
     * @return true if HTTP code start with 2 or 3
     */
    public boolean isHttpResponseOk() {
        return this.responseCodeHttp.matches("[23]\\d\\d");
    }
    
    
    // Getters
    
    public String getUrl() {
        return this.urlAdminPage;
    }
}