Configuration window, allowdowngrade

This commit is contained in:
Alexis Delhaie
2020-09-18 19:42:50 +02:00
parent 1794b6d8b9
commit 36f66d1cb1
18 changed files with 390 additions and 83 deletions

View File

@@ -1,6 +1,3 @@
Manifest-Version: 1.0 Manifest-Version: 1.0
Main-Class: ovh.alexisdelhaie.endpoint.Main Main-Class: ovh.alexisdelhaie.endpoint.Main
Class-Path: src.zip javafx-swt.jar javafx.web.jar javafx.base.jar javafx
.fxml.jar javafx.media.jar javafx.swing.jar javafx.controls.jar javafx.
graphics.jar

View File

@@ -6,6 +6,7 @@ import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import ovh.alexisdelhaie.endpoint.controllers.Controller;
public class Main extends Application { public class Main extends Application {

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<AnchorPane prefHeight="556.0" prefWidth="410.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ovh.alexisdelhaie.endpoint.Configuration">
<children>
<Label layoutX="12.0" layoutY="11.0" text="SSL">
<font>
<Font name="System Bold" size="12.0" />
</font>
</Label>
<Label layoutX="13.0" layoutY="35.0" text="Allow invalid SSL certificate" />
<CheckBox layoutX="381.0" layoutY="31.0" mnemonicParsing="false" />
<Line endX="100.0" layoutX="271.0" layoutY="48.0" startX="-100.0" stroke="#bfbfbf" strokeLineCap="ROUND" />
</children>
</AnchorPane>

View File

@@ -0,0 +1,127 @@
package ovh.alexisdelhaie.endpoint.configuration;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.scene.control.Alert;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class ConfigurationProperties {
private Map<String, String> properties;
private String osname;
private String filepath;
private ObjectMapper mapper;
@SuppressWarnings("unchecked")
public ConfigurationProperties() {
osname = System.getProperty("os.name").toUpperCase();
properties = new HashMap<>();
mapper = new ObjectMapper();
filepath = new StringBuilder(getAppData())
.append("EndPoint")
.append(getSeparator())
.append("settings.json")
.toString();
createAppFolder();
load();
}
public void setProperty(String key, String value) {
properties.put(key, value);
save();
}
public String getStringProperty(String key, String defaultS) {
if (properties.containsKey(key)) {
return properties.get(key);
}
return defaultS;
}
public boolean getBooleanProperty(String key, boolean defaultB) {
if (properties.containsKey(key)) {
return Boolean.parseBoolean(properties.get(key));
}
return defaultB;
}
private void save() {
try {
mapper.writeValue(new File(filepath), properties);
} catch (Exception e) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Cannot save settings");
alert.setHeaderText("There was an error while saving settings file");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
private void load() {
File f = new File(filepath);
try {
if (f.exists()) {
properties = mapper.readValue(f, new TypeReference<Map<String, String>>() { });
}
} catch (IOException e) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Cannot initialize settings");
alert.setHeaderText("There was an error while initializing settings file");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
private void createAppFolder() {
try {
Path path = Paths.get(new StringBuilder(getAppData())
.append("EndPoint")
.append(getSeparator()).toString());
if (!Files.exists(path)) {
Files.createDirectories(path);
}
} catch (IOException e) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Cannot create app folder");
alert.setHeaderText("There was an error while creating appdata folder");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
private String getAppData() {
String path = "";
if (osname.contains("WIN")) {
path = System.getenv("APPDATA");
path = (path.endsWith("\\") ? path : path + "\\");
}
else if (osname.contains("MAC")) {
path = System.getProperty("user.home") + "/Library/";
}
else if (osname.contains("NUX")) {
path = System.getProperty("user.home");
path = (path.endsWith("/") ? path : path + "/");
}
else {
path = System.getProperty("user.dir");
path = (path.endsWith("/") ? path : path + "/");
}
return path;
}
private String getSeparator() {
if (osname.contains("WIN")) {
return "\\";
}
return "/";
}
}

View File

@@ -0,0 +1,73 @@
package ovh.alexisdelhaie.endpoint.controllers;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.CheckBox;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.stage.Modality;
import javafx.stage.Stage;
import ovh.alexisdelhaie.endpoint.configuration.ConfigurationProperties;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class ConfigurationController
{
private ConfigurationProperties configurationProperties;
private Stage primaryStage;
@FXML
private CheckBox allowInvalidSsl;
@FXML
private CheckBox allowDowngrade;
public void setStageAndSetupListeners(Stage s) {
primaryStage = s;
}
public void setConfigurationProperties(ConfigurationProperties properties) {
configurationProperties = properties;
allowInvalidSsl.setSelected(properties.getBooleanProperty("allowInvalidSsl", false));
allowDowngrade.setSelected(properties.getBooleanProperty("allowDowngrade", true));
}
@FXML
private void onBooleanValueChanged(MouseEvent event) {
CheckBox c = (CheckBox) event.getSource();
configurationProperties.setProperty(c.getId(), String.valueOf(c.isSelected()));
}
@FXML
private void showAboutDialog() {
try {
Stage dialog = new Stage();
Parent xml = FXMLLoader.load(getClass().getResource("about.fxml"));
dialog.initOwner(primaryStage);
dialog.setScene(new Scene(xml, 677, 365));
dialog.setMaxHeight(365);
dialog.setMinHeight(365);
dialog.setMaxWidth(707);
dialog.setMinWidth(707);
dialog.setResizable(false);
dialog.setTitle("About EndPoint");
dialog.getIcons().add( new Image(
Controller.class.getResourceAsStream( "icon.png" )));
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.showAndWait();
} catch (IOException e) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Cannot initialize About");
alert.setHeaderText("There was an error while initializing this dialog");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
}

View File

@@ -1,4 +1,4 @@
package ovh.alexisdelhaie.endpoint; package ovh.alexisdelhaie.endpoint.controllers;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
@@ -13,18 +13,23 @@ import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.apache.commons.validator.routines.UrlValidator;
import ovh.alexisdelhaie.endpoint.configuration.ConfigurationProperties;
import ovh.alexisdelhaie.endpoint.http.HttpClient; import ovh.alexisdelhaie.endpoint.http.HttpClient;
import ovh.alexisdelhaie.endpoint.http.Request; import ovh.alexisdelhaie.endpoint.http.Request;
import ovh.alexisdelhaie.endpoint.http.RequestBuilder; import ovh.alexisdelhaie.endpoint.http.RequestBuilder;
import ovh.alexisdelhaie.endpoint.http.Response; import ovh.alexisdelhaie.endpoint.http.Response;
import ovh.alexisdelhaie.endpoint.impl.EditCell; import ovh.alexisdelhaie.endpoint.impl.EditCell;
import ovh.alexisdelhaie.endpoint.model.Param; import ovh.alexisdelhaie.endpoint.model.Param;
import ovh.alexisdelhaie.endpoint.url.URLGenerator;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -80,8 +85,11 @@ public class Controller implements Initializable {
private Stage primaryStage; private Stage primaryStage;
private HashMap<Integer, String> requests; private HashMap<Integer, String> requests;
private ConfigurationProperties properties;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
properties = new ConfigurationProperties();
requests = new HashMap<>(); requests = new HashMap<>();
String[] method = { "GET", "POST", "HEAD", "PUT", "DELETE" }; String[] method = { "GET", "POST", "HEAD", "PUT", "DELETE" };
httpMethod.setItems(FXCollections.observableArrayList(method)); httpMethod.setItems(FXCollections.observableArrayList(method));
@@ -184,6 +192,13 @@ public class Controller implements Initializable {
@FXML @FXML
private void start() { private void start() {
if (requestInput.getText().isBlank()) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("URL field empty");
alert.setContentText("Enter your URL in the field at the top of the window.");
alert.showAndWait();
return;
}
runningIndicatorPane.setVisible(true); runningIndicatorPane.setVisible(true);
new Thread(() -> { new Thread(() -> {
final String method = httpMethod.getValue(); final String method = httpMethod.getValue();
@@ -194,7 +209,10 @@ public class Controller implements Initializable {
Request r = new RequestBuilder(requestInput.getText()) Request r = new RequestBuilder(requestInput.getText())
.setCustomHeaders(getCustomHeaders()) .setCustomHeaders(getCustomHeaders())
.build(); .build();
HttpClient hc = new HttpClient(); HttpClient hc = new HttpClient(
properties.getBooleanProperty("allowInvalidSsl", false),
properties.getBooleanProperty("allowDowngrade", true)
);
switch (method) { switch (method) {
case "GET" -> response = hc.get(r); case "GET" -> response = hc.get(r);
case "POST" -> response = hc.post(r, getBody()); case "POST" -> response = hc.post(r, getBody());
@@ -213,7 +231,6 @@ public class Controller implements Initializable {
} }
} catch (IOException | NoSuchAlgorithmException | KeyManagementException e) { } catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
e.printStackTrace();
textArea.ifPresent(area -> Platform.runLater(() -> { textArea.ifPresent(area -> Platform.runLater(() -> {
resetResponseTab(); resetResponseTab();
area.setStyle("-fx-text-fill: red"); area.setStyle("-fx-text-fill: red");
@@ -255,6 +272,8 @@ public class Controller implements Initializable {
raw.setText(res.getRawResponse()); raw.setText(res.getRawResponse());
TextArea request = (TextArea) responseTab.get().getContent().lookup("#request"); TextArea request = (TextArea) responseTab.get().getContent().lookup("#request");
request.setText(res.getRequest().getRawRequest()); request.setText(res.getRequest().getRawRequest());
GridPane downgradedIndicator = (GridPane) responseTab.get().getContent().lookup("#downgraded_indicator");
downgradedIndicator.setVisible(res.isDowngraded());
TableView<Param> headers = (TableView<Param>) responseTab.get().getContent().lookup("#headers"); TableView<Param> headers = (TableView<Param>) responseTab.get().getContent().lookup("#headers");
headers.getItems().clear(); headers.getItems().clear();
for (Map.Entry<String, String> entry : res.getHeaders().entrySet()) { for (Map.Entry<String, String> entry : res.getHeaders().entrySet()) {
@@ -279,6 +298,10 @@ public class Controller implements Initializable {
time.setText("... ms"); time.setText("... ms");
TextArea raw = (TextArea) responseTab.get().getContent().lookup("#raw"); TextArea raw = (TextArea) responseTab.get().getContent().lookup("#raw");
raw.setText(""); raw.setText("");
TextArea request = (TextArea) responseTab.get().getContent().lookup("#request");
request.setText("");
GridPane downgradedIndicator = (GridPane) responseTab.get().getContent().lookup("#downgraded_indicator");
downgradedIndicator.setVisible(false);
TableView<Param> headers = (TableView<Param>) responseTab.get().getContent().lookup("#headers"); TableView<Param> headers = (TableView<Param>) responseTab.get().getContent().lookup("#headers");
headers.getItems().clear(); headers.getItems().clear();
} }
@@ -384,25 +407,29 @@ public class Controller implements Initializable {
} }
@FXML @FXML
private void showAboutDialog() { private void showConfigurationDialog() {
try { try {
Stage dialog = new Stage(); Stage dialog = new Stage();
Parent xml = FXMLLoader.load(getClass().getResource("about.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("configuration.fxml"));
Parent xml = loader.load();
ConfigurationController controller = loader.getController();
controller.setStageAndSetupListeners(primaryStage);
controller.setConfigurationProperties(properties);
dialog.initOwner(primaryStage); dialog.initOwner(primaryStage);
dialog.setScene(new Scene(xml, 677, 365)); dialog.setScene(new Scene(xml, 412, 556));
dialog.setMaxHeight(365); dialog.setMaxHeight(556);
dialog.setMinHeight(365); dialog.setMinHeight(556);
dialog.setMaxWidth(707); dialog.setMaxWidth(412);
dialog.setMinWidth(707); dialog.setMinWidth(412);
dialog.setResizable(false); dialog.setResizable(false);
dialog.setTitle("About EndPoint"); dialog.setTitle("Settings");
dialog.getIcons().add( new Image( dialog.getIcons().add( new Image(
Controller.class.getResourceAsStream( "icon.png" ))); Controller.class.getResourceAsStream( "icon.png" )));
dialog.initModality(Modality.APPLICATION_MODAL); dialog.initModality(Modality.APPLICATION_MODAL);
dialog.showAndWait(); dialog.showAndWait();
} catch (IOException e) { } catch (IOException e) {
Alert alert = new Alert(Alert.AlertType.ERROR); Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Cannot initialize About"); alert.setTitle("Cannot initialize Settings");
alert.setHeaderText("There was an error while initializing this dialog"); alert.setHeaderText("There was an error while initializing this dialog");
alert.setContentText(e.getMessage()); alert.setContentText(e.getMessage());
alert.showAndWait(); alert.showAndWait();
@@ -412,7 +439,22 @@ public class Controller implements Initializable {
@FXML @FXML
private void requestInputOnKeyPressed() { private void requestInputOnKeyPressed() {
Tab tab = tabs.getSelectionModel().getSelectedItem(); Tab tab = tabs.getSelectionModel().getSelectedItem();
requests.put(tab.hashCode(), requestInput.getText()); String url = requestInput.getText();
requests.put(tab.hashCode(), url);
if (url.isBlank()) {
tab.setText("Untitled");
} else {
url = URLGenerator.addSchemaToUrl(url);
UrlValidator urlValidator = new UrlValidator();
if (urlValidator.isValid(url)) {
try {
URL u = new URL(url);
tab.setText(u.getHost());
} catch (MalformedURLException e) {
System.err.println(e.getMessage());
}
}
}
} }
} }

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<AnchorPane maxHeight="556.0" maxWidth="412.0" minHeight="556.0" minWidth="412.0" prefHeight="556.0" prefWidth="412.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ovh.alexisdelhaie.endpoint.controllers.ConfigurationController">
<children>
<Label layoutX="12.0" layoutY="11.0" text="SSL">
<font>
<Font name="System Bold" size="12.0" />
</font>
</Label>
<Label layoutX="13.0" layoutY="35.0" text="Allow invalid SSL certificate" />
<CheckBox fx:id="allowInvalidSsl" layoutX="381.0" layoutY="31.0" mnemonicParsing="false" onMouseClicked="#onBooleanValueChanged" AnchorPane.rightAnchor="14.333333333333332" AnchorPane.topAnchor="32.0" />
<Label layoutX="13.0" layoutY="64.0" text="Downgrade when SSL failed" />
<CheckBox fx:id="allowDowngrade" layoutX="381.0" layoutY="60.0" mnemonicParsing="false" onMouseClicked="#onBooleanValueChanged" selected="true" AnchorPane.rightAnchor="14.333333333333332" AnchorPane.topAnchor="60.0" />
<Button layoutX="14.0" layoutY="517.0" mnemonicParsing="false" onMouseClicked="#showAboutDialog" text="About" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" />
</children>
</AnchorPane>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -54,5 +54,22 @@
</font> </font>
</TextArea> </TextArea>
<Label text="Sended request" GridPane.columnIndex="4" GridPane.rowIndex="4" /> <Label text="Sended request" GridPane.columnIndex="4" GridPane.rowIndex="4" />
<GridPane fx:id="downgraded_indicator" visible="false" GridPane.columnIndex="4" GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="382.0" minWidth="10.0" prefWidth="30.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="644.0" minWidth="10.0" prefWidth="644.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label prefHeight="17.0" prefWidth="354.0" text="This request was downgraded from SSL to plain text" textFill="#cfa600" GridPane.columnIndex="1" />
<Label alignment="CENTER" contentDisplay="CENTER" prefHeight="22.0" prefWidth="37.0" text="" textAlignment="CENTER" textFill="#cfa600">
<font>
<Font name="Segoe MDL2 Assets" size="20.0" />
</font>
</Label>
</children>
</GridPane>
</children> </children>
</GridPane> </GridPane>

View File

@@ -20,9 +20,15 @@ public class HttpClient {
public final static int DEFAULT_TIMEOUT = 10000; public final static int DEFAULT_TIMEOUT = 10000;
private final boolean allowInvalidSsl; private final boolean allowInvalidSsl;
private final boolean allowDowngrade;
private boolean downgraded;
public HttpClient() { this(false); } public HttpClient() { this(false, true); }
public HttpClient(boolean allowInvalidSsl) { this.allowInvalidSsl = allowInvalidSsl; } public HttpClient(boolean allowInvalidSsl, boolean allowDowngrade) {
this.allowInvalidSsl = allowInvalidSsl;
this.allowDowngrade = allowDowngrade;
this.downgraded = false;
}
public Optional<Response> get(Request r) throws IOException, KeyManagementException, NoSuchAlgorithmException { public Optional<Response> get(Request r) throws IOException, KeyManagementException, NoSuchAlgorithmException {
return process("GET", r, ""); return process("GET", r, "");
@@ -51,10 +57,13 @@ public class HttpClient {
} }
private Optional<Response> process(String method, Request r, String body) throws IOException, NoSuchAlgorithmException, KeyManagementException { private Optional<Response> process(String method, Request r, String body) throws IOException, NoSuchAlgorithmException, KeyManagementException {
String headers = buildHeaders(method, r);
Socket s = (r.getScheme().equals("https")) ? Socket s = (r.getScheme().equals("https")) ?
buildSSLSocket(resolve(r.getHost()).getHostAddress(), r.getPort()) buildSSLSocket(resolve(r.getHost()).getHostAddress(), r.getPort())
: buildSocket(resolve(r.getHost()).getHostAddress(), r.getPort()); : buildSocket(resolve(r.getHost()).getHostAddress(), r.getPort());
if (allowDowngrade && (s.getPort() != r.getPort())) {
r.setPort(s.getPort());
}
String headers = buildHeaders(method, r);
String request = (method.equals("POST") || method.equals("PUT")) ? String request = (method.equals("POST") || method.equals("PUT")) ?
new StringBuilder(headers).append(body).toString() : headers; new StringBuilder(headers).append(body).toString() : headers;
if (s.isConnected()) { if (s.isConnected()) {
@@ -68,7 +77,7 @@ public class HttpClient {
Instant end = Instant.now(); Instant end = Instant.now();
long time = end.toEpochMilli() - start.toEpochMilli(); long time = end.toEpochMilli() - start.toEpochMilli();
r.setRawRequest(request); r.setRawRequest(request);
return Optional.of(new Response(b, time, r)); return Optional.of(new Response(b, time, downgraded, r));
} finally { } finally {
s.close(); s.close();
} }
@@ -89,6 +98,7 @@ public class HttpClient {
} }
private Socket buildSSLSocket(String host, int port) throws IOException, KeyManagementException, NoSuchAlgorithmException { private Socket buildSSLSocket(String host, int port) throws IOException, KeyManagementException, NoSuchAlgorithmException {
System.setProperty("com.sun.net.ssl.rsaPreMasterSecretFix", "true");
SSLSocketFactory factory; SSLSocketFactory factory;
if(!allowInvalidSsl) { if(!allowInvalidSsl) {
factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
@@ -104,11 +114,23 @@ public class HttpClient {
sc.init(null, trustAllCerts, new java.security.SecureRandom()); sc.init(null, trustAllCerts, new java.security.SecureRandom());
factory = sc.getSocketFactory(); factory = sc.getSocketFactory();
} }
Socket s = factory.createSocket(host, port);
((SSLSocket)s).setEnabledProtocols(new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }); SSLSocket s = (SSLSocket) factory.createSocket(host, port);
s.setEnabledProtocols(new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" });
s.setKeepAlive(false); s.setKeepAlive(false);
s.setSoTimeout(DEFAULT_TIMEOUT); s.setSoTimeout(DEFAULT_TIMEOUT);
((SSLSocket)s).startHandshake(); if (allowDowngrade) {
try {
s.startHandshake();
return s;
} catch (Exception e) {
System.err.println(e.getMessage());
System.out.println("Downgrade to Non-SSL socket");
downgraded = true;
return buildSocket(host, (port == 443) ? 80 : port);
}
}
s.startHandshake();
return s; return s;
} }

View File

@@ -68,14 +68,13 @@ public enum HttpStatus {
private final int code; private final int code;
private final String message; private final String message;
private static final Map<Integer, HttpStatus> map;
HttpStatus(int code, String message) { HttpStatus(int code, String message) {
this.code = code; this.code = code;
this.message = message; this.message = message;
} }
private static final Map<Integer, HttpStatus> map;
static { static {
map = new HashMap<>(); map = new HashMap<>();
for (HttpStatus v : HttpStatus.values()) { for (HttpStatus v : HttpStatus.values()) {

View File

@@ -1,6 +1,6 @@
package ovh.alexisdelhaie.endpoint.http; package ovh.alexisdelhaie.endpoint.http;
import ovh.alexisdelhaie.endpoint.URLGenerator; import ovh.alexisdelhaie.endpoint.url.URLGenerator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects; import java.util.Objects;
@@ -10,7 +10,7 @@ public class Request {
private final String host; private final String host;
private final String scheme; private final String scheme;
private final String path; private final String path;
private final int port; private int port;
private final HashMap<String, String> params; private final HashMap<String, String> params;
private final HashMap<String, String> customHeaders; private final HashMap<String, String> customHeaders;
private final String body; private final String body;
@@ -67,6 +67,8 @@ public class Request {
rawRequest = r; rawRequest = r;
} }
void setPort(int p) { this.port = p; }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

@@ -20,8 +20,9 @@ public class Response {
private String status; private String status;
private final long time; private final long time;
private final Request request; private final Request request;
private final boolean downgraded;
Response(byte[] res, long time, Request r) throws UnsupportedEncodingException { Response(byte[] res, long time, boolean downgraded, Request r) throws UnsupportedEncodingException {
headers = new HashMap<>(); headers = new HashMap<>();
rawResponse = new String(res, StandardCharsets.UTF_8); rawResponse = new String(res, StandardCharsets.UTF_8);
int crlf = rawResponse.indexOf(DOUBLE_CRLF); int crlf = rawResponse.indexOf(DOUBLE_CRLF);
@@ -31,6 +32,7 @@ public class Response {
body = rawResponse.substring(crlf + DOUBLE_CRLF.length()); body = rawResponse.substring(crlf + DOUBLE_CRLF.length());
this.time = time; this.time = time;
request = r; request = r;
this.downgraded = downgraded;
} }
private void parseHeaders() { private void parseHeaders() {
@@ -106,6 +108,10 @@ public class Response {
public Request getRequest() { return request; } public Request getRequest() { return request; }
public boolean isDowngraded() {
return downgraded;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

@@ -3,7 +3,7 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ovh.alexisdelhaie.endpoint.Controller"> <AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ovh.alexisdelhaie.endpoint.controllers.Controller">
<TextField fx:id="requestInput" layoutX="109.0" layoutY="39.0" onKeyTyped="#requestInputOnKeyPressed" prefHeight="25.0" prefWidth="861.0" AnchorPane.leftAnchor="109.0" AnchorPane.rightAnchor="96.99999999999989" AnchorPane.topAnchor="39.0" /> <TextField fx:id="requestInput" layoutX="109.0" layoutY="39.0" onKeyTyped="#requestInputOnKeyPressed" prefHeight="25.0" prefWidth="861.0" AnchorPane.leftAnchor="109.0" AnchorPane.rightAnchor="96.99999999999989" AnchorPane.topAnchor="39.0" />
<Button layoutX="979.0" layoutY="39.0" mnemonicParsing="false" onMouseClicked="#start" prefHeight="25.0" prefWidth="73.0" text="Send" AnchorPane.rightAnchor="16.0" AnchorPane.topAnchor="39.0" /> <Button layoutX="979.0" layoutY="39.0" mnemonicParsing="false" onMouseClicked="#start" prefHeight="25.0" prefWidth="73.0" text="Send" AnchorPane.rightAnchor="16.0" AnchorPane.topAnchor="39.0" />
<ChoiceBox id="httpMethod" fx:id="httpMethod" layoutX="14.0" layoutY="39.0" prefHeight="25.0" prefWidth="85.0" AnchorPane.leftAnchor="14.0" AnchorPane.topAnchor="39.0" /> <ChoiceBox id="httpMethod" fx:id="httpMethod" layoutX="14.0" layoutY="39.0" prefHeight="25.0" prefWidth="85.0" AnchorPane.leftAnchor="14.0" AnchorPane.topAnchor="39.0" />
@@ -11,7 +11,7 @@
<TabPane fx:id="tabs" layoutX="-1.0" layoutY="104.0" prefHeight="540.0" prefWidth="1067.0" tabClosingPolicy="ALL_TABS" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="104.0"> <TabPane fx:id="tabs" layoutX="-1.0" layoutY="104.0" prefHeight="540.0" prefWidth="1067.0" tabClosingPolicy="ALL_TABS" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="104.0">
</TabPane> </TabPane>
<Button layoutX="1022.0" layoutY="71.0" mnemonicParsing="false" onMouseClicked="#createNewTab" prefHeight="25.0" prefWidth="31.0" text="+" AnchorPane.rightAnchor="16.0" AnchorPane.topAnchor="71.0" /> <Button layoutX="1022.0" layoutY="71.0" mnemonicParsing="false" onMouseClicked="#createNewTab" prefHeight="25.0" prefWidth="31.0" text="+" AnchorPane.rightAnchor="16.0" AnchorPane.topAnchor="71.0" />
<Button layoutX="1000.0" layoutY="71.0" mnemonicParsing="false" onMouseClicked="#showAboutDialog" prefHeight="25.0" prefWidth="31.0" text="?" AnchorPane.rightAnchor="58.0" AnchorPane.topAnchor="71.0" /> <Button layoutX="1020.0" layoutY="8.0" mnemonicParsing="false" onMouseClicked="#showConfigurationDialog" prefHeight="25.0" prefWidth="73.0" text="Settings" AnchorPane.rightAnchor="16.0" AnchorPane.topAnchor="8.0" />
<Pane fx:id="runningIndicatorPane" layoutX="109.0" layoutY="74.0" prefHeight="17.0" prefWidth="200.0" visible="false"> <Pane fx:id="runningIndicatorPane" layoutX="109.0" layoutY="74.0" prefHeight="17.0" prefWidth="200.0" visible="false">
<children> <children>
<ProgressIndicator layoutX="2.0" layoutY="1.0" prefHeight="17.0" prefWidth="18.0" /> <ProgressIndicator layoutX="2.0" layoutY="1.0" prefHeight="17.0" prefWidth="18.0" />

View File

@@ -0,0 +1,44 @@
package ovh.alexisdelhaie.endpoint.url;
public enum SpecialChar {
SPACE(' ', "%20"),
HASH('#', "%23"),
LEFT_BRACE('{', "%7B"),
RIGHT_BRACE('}', "%7D"),
LEFT_BRACKET('[', "%5B"),
RIGHT_BRACKET(']', "%5D"),
AT('@', "%40"),
CIRCUMFLEX('^', "%5E"),
SLASH('/', "%2F"),
BACK_SLASH('\\', "%5C"),
DOLLAR('$', "%24"),
LEFT_CHEVRON('<', "%3C"),
RIGHT_CHEVRON('>', "%3E"),
PIPE('|', "%7C"),
TILDE('~', "%7E"),
BACK_QUOTE('`', "%60"),
INTERROGATION('?', "%3F"),
EQUAL('=', "%3D"),
CR('\r', "%0D"),
LF('\n', "%0A"),
SEMICOLON(';', "%3B"),
COLON(':', "%3A"),
AND('&', "%26");
private char decodedChar;
private String encodedChar;
SpecialChar(char decodedChar, String encodedChar) {
this.decodedChar = decodedChar;
this.encodedChar = encodedChar;
}
public static String encodeString(String decodedString) {
String encodedString = decodedString.replace("%", "%25");
for (SpecialChar v : SpecialChar.values()) {
encodedString = encodedString.replace(Character.toString(v.decodedChar), v.encodedChar);
}
return encodedString;
}
}

View File

@@ -1,29 +1,16 @@
package ovh.alexisdelhaie.endpoint; package ovh.alexisdelhaie.endpoint.url;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class URLGenerator { public class URLGenerator {
public static final String HTTP_START = "http://"; public static final String HTTP_START = "http://";
public static final String HTTPS_START = "https://"; public static final String HTTPS_START = "https://";
public static final String[][] SPECIAL_CHARS = new String[][] {
{ " ", "%20" }, { "<", "%3C" }, { ">", "%3E" },
{ "#", "%23" }, { "{", "%7B" },
{ "}", "%7D" }, { "|", "%7C" }, { "\\", "%5C" },
{ "^", "%5E" }, { "~", "%7E" }, { "[", "%5B" },
{ "]", "%5D" }, { "`", "%60" }, { ";", "%3B" },
{ "/", "%2F" }, { "?", "%3F" }, { ":", "%3A" },
{ "@", "%40" }, { "=", "%3D" }, { "&", "%26" },
{ "$", "%24" }, { "\r", "%0D" }, { "\n", "%0A" }
};
static public String processNewUrl(HashMap<String, String> p, String url) { public static String processNewUrl(HashMap<String, String> p, String url) {
try { try {
if (!url.startsWith(HTTP_START) && !url.startsWith(HTTPS_START)) { if (!url.startsWith(HTTP_START) && !url.startsWith(HTTPS_START)) {
url = HTTP_START + url; url = HTTP_START + url;
@@ -43,12 +30,7 @@ public class URLGenerator {
return url; return url;
} }
public static Map<String, String> getSpecialCharRef() { public static String generateParamsPartWithEncoding(HashMap<String, String> p) {
return Stream.of(SPECIAL_CHARS)
.collect(Collectors.toMap(data -> (String) data[0], data -> (String) data[1]));
}
static public String generateParamsPartWithEncoding(HashMap<String, String> p) {
String result = "?"; String result = "?";
for (Map.Entry<String, String> entry : p.entrySet()) { for (Map.Entry<String, String> entry : p.entrySet()) {
result = new StringBuilder(result) result = new StringBuilder(result)
@@ -65,13 +47,11 @@ public class URLGenerator {
return result; return result;
} }
static public String generateParamsPart(HashMap<String, String> p) { public static String generateParamsPart(HashMap<String, String> p) {
Map<String, String> specialCharRef = getSpecialCharRef();
String result = "?"; String result = "?";
for (Map.Entry<String, String> entry : p.entrySet()) { for (Map.Entry<String, String> entry : p.entrySet()) {
String key = escapeText(entry.getKey(), specialCharRef); String key = SpecialChar.encodeString(entry.getKey());
String value = escapeText(entry.getValue(), specialCharRef); String value = SpecialChar.encodeString(entry.getValue());
result = new StringBuilder(result) result = new StringBuilder(result)
.append(key) .append(key)
.append("=") .append("=")
@@ -86,14 +66,11 @@ public class URLGenerator {
return result; return result;
} }
static public String escapeText(String s, Map<String, String> scr) { public static String addSchemaToUrl(String url) {
String result = s.replace("%", "%25"); if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) {
for (Map.Entry<String, String> entry : scr.entrySet()) { url = "http://" + url;
if (result.contains(entry.getKey())) {
result = result.replace(entry.getKey(), entry.getValue());
} }
} return url;
return result;
} }
} }