Chunk parsing algo, HTTP/1.0, fixing http status with tomcat

This commit is contained in:
Alexis Delhaie
2020-10-02 21:27:37 +02:00
parent 36f66d1cb1
commit 20959bad2d
8 changed files with 121 additions and 40 deletions

View File

@@ -1,12 +1,14 @@
package ovh.alexisdelhaie.endpoint.controllers; package ovh.alexisdelhaie.endpoint.controllers;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.stage.Modality; import javafx.stage.Modality;
@@ -14,8 +16,6 @@ import javafx.stage.Stage;
import ovh.alexisdelhaie.endpoint.configuration.ConfigurationProperties; import ovh.alexisdelhaie.endpoint.configuration.ConfigurationProperties;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class ConfigurationController public class ConfigurationController
{ {
@@ -27,15 +27,21 @@ public class ConfigurationController
private CheckBox allowInvalidSsl; private CheckBox allowInvalidSsl;
@FXML @FXML
private CheckBox allowDowngrade; private CheckBox allowDowngrade;
@FXML
private ChoiceBox<String> httpVersion;
public void setStageAndSetupListeners(Stage s) { public void setStageAndSetupListeners(Stage s) {
primaryStage = s; primaryStage = s;
} }
public void setConfigurationProperties(ConfigurationProperties properties) { public void setConfigurationProperties(ConfigurationProperties properties) {
String[] versions = { "HTTP/1.1", "HTTP/1.0" };
httpVersion.setItems(FXCollections.observableArrayList(versions));
configurationProperties = properties; configurationProperties = properties;
allowInvalidSsl.setSelected(properties.getBooleanProperty("allowInvalidSsl", false)); allowInvalidSsl.setSelected(properties.getBooleanProperty("allowInvalidSsl", false));
allowDowngrade.setSelected(properties.getBooleanProperty("allowDowngrade", true)); allowDowngrade.setSelected(properties.getBooleanProperty("allowDowngrade", true));
httpVersion.setValue(properties.getStringProperty("httpVersion", versions[0]));
httpVersion.setOnAction(this::onChoiceBoxValueChanged);
} }
@FXML @FXML
@@ -44,13 +50,19 @@ public class ConfigurationController
configurationProperties.setProperty(c.getId(), String.valueOf(c.isSelected())); configurationProperties.setProperty(c.getId(), String.valueOf(c.isSelected()));
} }
@SuppressWarnings("unchecked")
private void onChoiceBoxValueChanged(ActionEvent event) {
ChoiceBox<String> c = (ChoiceBox<String>) event.getSource();
configurationProperties.setProperty(c.getId(), c.getValue());
}
@FXML @FXML
private void showAboutDialog() { private void showAboutDialog() {
try { try {
Stage dialog = new Stage(); Stage dialog = new Stage();
Parent xml = FXMLLoader.load(getClass().getResource("about.fxml")); Parent xml = FXMLLoader.load(getClass().getResource("about.fxml"));
dialog.initOwner(primaryStage); dialog.initOwner(primaryStage);
dialog.setScene(new Scene(xml, 677, 365)); dialog.setScene(new Scene(xml, 707, 365));
dialog.setMaxHeight(365); dialog.setMaxHeight(365);
dialog.setMinHeight(365); dialog.setMinHeight(365);
dialog.setMaxWidth(707); dialog.setMaxWidth(707);

View File

@@ -84,6 +84,7 @@ public class Controller implements Initializable {
private Stage primaryStage; private Stage primaryStage;
private HashMap<Integer, String> requests; private HashMap<Integer, String> requests;
private HashMap<Integer, String> methods;
private ConfigurationProperties properties; private ConfigurationProperties properties;
@@ -91,11 +92,16 @@ public class Controller implements Initializable {
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
properties = new ConfigurationProperties(); properties = new ConfigurationProperties();
requests = new HashMap<>(); requests = new HashMap<>();
methods = 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));
httpMethod.setValue(method[0]); httpMethod.setValue(method[0]);
httpMethod.setOnAction(actionEvent -> httpMethodChanged());
tabs.getSelectionModel().selectedItemProperty().addListener( tabs.getSelectionModel().selectedItemProperty().addListener(
(ov, t, t1) -> requestInput.setText((t1 != null) ? requests.get(t1.hashCode()) : "") (ov, t, t1) -> {
requestInput.setText((t1 != null) ? requests.get(t1.hashCode()) : "");
httpMethod.setValue((t1 != null) ? methods.get(t1.hashCode()) : httpMethod.getValue());
}
); );
createNewTab(); createNewTab();
} }
@@ -111,6 +117,7 @@ public class Controller implements Initializable {
TabPane options = new TabPane(); TabPane options = new TabPane();
TextArea ta = new TextArea(); TextArea ta = new TextArea();
ta.setEditable(false);
options.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); options.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
options.getTabs().addAll(params, auth, headers, body, response); options.getTabs().addAll(params, auth, headers, body, response);
@@ -121,6 +128,7 @@ public class Controller implements Initializable {
Tab tab = new Tab("untitled", sp); Tab tab = new Tab("untitled", sp);
tab.setOnCloseRequest(arg0 -> { tab.setOnCloseRequest(arg0 -> {
requests.remove(tab.hashCode()); requests.remove(tab.hashCode());
methods.remove(tab.hashCode());
}); });
return tab; return tab;
@@ -209,10 +217,7 @@ 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);
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());
@@ -395,6 +400,7 @@ public class Controller implements Initializable {
new Thread(() -> { new Thread(() -> {
Tab t = newTab(); Tab t = newTab();
requests.put(t.hashCode(), ""); requests.put(t.hashCode(), "");
methods.put(t.hashCode(), httpMethod.getValue());
Platform.runLater(() -> { Platform.runLater(() -> {
tabs.getTabs().add(t); tabs.getTabs().add(t);
tabs.getSelectionModel().select(t); tabs.getSelectionModel().select(t);
@@ -436,6 +442,11 @@ public class Controller implements Initializable {
} }
} }
private void httpMethodChanged() {
Tab tab = tabs.getSelectionModel().getSelectedItem();
methods.put(tab.hashCode(), httpMethod.getValue());
}
@FXML @FXML
private void requestInputOnKeyPressed() { private void requestInputOnKeyPressed() {
Tab tab = tabs.getSelectionModel().getSelectedItem(); Tab tab = tabs.getSelectionModel().getSelectedItem();

View File

@@ -11,11 +11,6 @@
<image> <image>
<Image url="@banner.png" /> <Image url="@banner.png" />
</image></ImageView> </image></ImageView>
<Label layoutX="537.0" layoutY="327.0" text="Version 0.1.1 (Not finished)" AnchorPane.bottomAnchor="16.0" AnchorPane.rightAnchor="16.0">
<font>
<Font size="13.0" />
</font>
</Label>
<GridPane layoutX="32.0" layoutY="123.0" prefHeight="224.0" prefWidth="348.0"> <GridPane layoutX="32.0" layoutY="123.0" prefHeight="224.0" prefWidth="348.0">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
@@ -25,6 +20,7 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<Label text="Author: Alexis Delhaie (@alexlegarnd)"> <Label text="Author: Alexis Delhaie (@alexlegarnd)">
@@ -32,17 +28,22 @@
<Font size="13.0" /> <Font size="13.0" />
</font> </font>
</Label> </Label>
<Label text="Made with Java 14.0.2 (GUI with JavaFX 15)" GridPane.rowIndex="1"> <Label text="Software: Java 14.0.2 (GUI: JavaFX 15)" GridPane.rowIndex="2">
<font> <font>
<Font size="13.0" /> <Font size="13.0" />
</font> </font>
</Label> </Label>
<Label text="Installer made with Delphi 10.3 Community" GridPane.rowIndex="2"> <Label text="Installer: Delphi 10.3.3 Community" GridPane.rowIndex="3">
<font> <font>
<Font size="13.0" /> <Font size="13.0" />
</font> </font>
</Label> </Label>
<Label text="Installer Bootstrap made with Python 3" GridPane.rowIndex="3"> <Label text="Installer Bootstrap: Python 3.8.5 [MSC v.1924 (AMD64)]" GridPane.rowIndex="4">
<font>
<Font size="13.0" />
</font>
</Label>
<Label text="Version: 0.1.2 (Not finished)" GridPane.rowIndex="1">
<font> <font>
<Font size="13.0" /> <Font size="13.0" />
</font> </font>

View File

@@ -1,20 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.Button?>
<?import javafx.scene.layout.*?> <?import javafx.scene.control.CheckBox?>
<?import javafx.scene.text.*?> <?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<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"> <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="20.0" layoutY="14.0" text="SSL">
<Label layoutX="12.0" layoutY="11.0" text="SSL">
<font> <font>
<Font name="System Bold" size="12.0" /> <Font name="System Bold" size="12.0" />
</font> </font>
</Label> </Label>
<Label layoutX="13.0" layoutY="35.0" text="Allow invalid SSL certificate" /> <Label layoutX="20.0" layoutY="40.0" prefHeight="18.0" prefWidth="148.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" /> <CheckBox fx:id="allowInvalidSsl" layoutX="381.0" layoutY="41.0" mnemonicParsing="false" onMouseClicked="#onBooleanValueChanged" AnchorPane.rightAnchor="14.199999999999989" AnchorPane.topAnchor="41.0" />
<Label layoutX="13.0" layoutY="64.0" text="Downgrade when SSL failed" /> <Label layoutX="20.0" layoutY="59.0" prefHeight="18.0" prefWidth="148.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" /> <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" /> <Button layoutX="14.0" layoutY="515.0" mnemonicParsing="false" onMouseClicked="#showAboutDialog" prefHeight="26.0" prefWidth="58.0" text="About" AnchorPane.bottomAnchor="15.0" AnchorPane.leftAnchor="14.0" />
</children> <Label layoutX="20.0" layoutY="97.0" prefHeight="18.0" prefWidth="72.0" text="Requests">
<font>
<Font name="System Bold" size="12.0" />
</font>
</Label>
<Label layoutX="20.0" layoutY="126.0" prefHeight="18.0" prefWidth="72.0" text="HTTP version" />
<ChoiceBox fx:id="httpVersion" layoutX="308.0" layoutY="123.0" prefHeight="26.0" prefWidth="90.0" />
</AnchorPane> </AnchorPane>

View File

@@ -53,7 +53,7 @@
<Font name="Consolas" size="12.0" /> <Font name="Consolas" size="12.0" />
</font> </font>
</TextArea> </TextArea>
<Label text="Sended request" GridPane.columnIndex="4" GridPane.rowIndex="4" /> <Label text="Sent request" GridPane.columnIndex="4" GridPane.rowIndex="4" />
<GridPane fx:id="downgraded_indicator" visible="false" GridPane.columnIndex="4" GridPane.rowIndex="1"> <GridPane fx:id="downgraded_indicator" visible="false" GridPane.columnIndex="4" GridPane.rowIndex="1">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="382.0" minWidth="10.0" prefWidth="30.0" /> <ColumnConstraints hgrow="SOMETIMES" maxWidth="382.0" minWidth="10.0" prefWidth="30.0" />

View File

@@ -1,5 +1,7 @@
package ovh.alexisdelhaie.endpoint.http; package ovh.alexisdelhaie.endpoint.http;
import ovh.alexisdelhaie.endpoint.configuration.ConfigurationProperties;
import javax.net.ssl.*; import javax.net.ssl.*;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@@ -18,15 +20,19 @@ public class HttpClient {
public final static String CRLF = "\r\n"; public final static String CRLF = "\r\n";
public final static int DEFAULT_TIMEOUT = 10000; public final static int DEFAULT_TIMEOUT = 10000;
public final static boolean DEFAULT_ALLOW_INVALID_SSL = false;
public final static boolean DEFAULT_ALLOW_DOWNGRADE = true;
public final static String DEFAULT_HTTP_VERSION = "HTTP/1.1";
private final boolean allowInvalidSsl; private final boolean allowInvalidSsl;
private final boolean allowDowngrade; private final boolean allowDowngrade;
private final String httpVersion;
private boolean downgraded; private boolean downgraded;
public HttpClient() { this(false, true); } public HttpClient(ConfigurationProperties props) {
public HttpClient(boolean allowInvalidSsl, boolean allowDowngrade) { this.allowInvalidSsl = props.getBooleanProperty("allowInvalidSsl", DEFAULT_ALLOW_INVALID_SSL);
this.allowInvalidSsl = allowInvalidSsl; this.allowDowngrade = props.getBooleanProperty("allowDowngrade", DEFAULT_ALLOW_DOWNGRADE);
this.allowDowngrade = allowDowngrade; this.httpVersion = props.getStringProperty("httpVersion", DEFAULT_HTTP_VERSION);
this.downgraded = false; this.downgraded = false;
} }
@@ -139,8 +145,9 @@ public class HttpClient {
String path = (r.getParams().isEmpty()) ? String path = (r.getParams().isEmpty()) ?
r.getPath() : r.getPathWithParams(); r.getPath() : r.getPathWithParams();
final StringBuilder sb = new StringBuilder(method).append(" ").append(path) final StringBuilder sb = new StringBuilder(method).append(" ").append(path)
.append(" HTTP/1.1").append(CRLF); .append(" ").append(httpVersion).append(CRLF);
sb.append("host: ").append(r.getHost()).append(":").append(r.getPort()).append(CRLF); sb.append("host: ").append(r.getHost()).append(":").append(r.getPort()).append(CRLF);
sb.append("connection: close").append(CRLF); sb.append("connection: close").append(CRLF);
if (!custom.containsKey("accept")) { if (!custom.containsKey("accept")) {
sb.append("accept: */*").append(CRLF); sb.append("accept: */*").append(CRLF);

View File

@@ -1,7 +1,10 @@
package ovh.alexisdelhaie.endpoint.http; package ovh.alexisdelhaie.endpoint.http;
import ovh.alexisdelhaie.endpoint.http.parsers.Chunked;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@@ -15,7 +18,7 @@ public class Response {
private final HashMap<String, String> headers; private final HashMap<String, String> headers;
private final String rawHeaders; private final String rawHeaders;
private String rawResponse; private String rawResponse;
private final String body; private String body;
private int statusCode; private int statusCode;
private String status; private String status;
private final long time; private final long time;
@@ -33,6 +36,7 @@ public class Response {
this.time = time; this.time = time;
request = r; request = r;
this.downgraded = downgraded; this.downgraded = downgraded;
parseBody();
} }
private void parseHeaders() { private void parseHeaders() {
@@ -53,7 +57,7 @@ public class Response {
statusCode = Integer.parseInt(m.group(2)); statusCode = Integer.parseInt(m.group(2));
status = m.group(3); status = m.group(3);
} else { } else {
p = Pattern.compile("^(HTTP/1.1)\\s([0-9]{3})$"); p = Pattern.compile("^(HTTP/1.1)\\s([0-9]{3})?(.*)$");
m = p.matcher(l); m = p.matcher(l);
if (m.matches()) { if (m.matches()) {
statusCode = Integer.parseInt(m.group(2)); statusCode = Integer.parseInt(m.group(2));
@@ -66,6 +70,19 @@ public class Response {
} }
} }
private void parseBody() {
if (headers.containsKey("transfer-encoding")) {
if (headers.get("transfer-encoding").toLowerCase().contains("chunked")) {
ArrayList<String> chunks = Chunked.parse(body);
final StringBuilder sb = new StringBuilder();
for (String chunk : chunks) {
sb.append(chunk);
}
body = sb.toString();
}
}
}
private String getEncoding() { private String getEncoding() {
Pattern p = Pattern.compile("(.+);\\s(.+)=(.+)"); Pattern p = Pattern.compile("(.+);\\s(.+)=(.+)");
if (headers.containsKey("content-type")) { if (headers.containsKey("content-type")) {

View File

@@ -0,0 +1,25 @@
package ovh.alexisdelhaie.endpoint.http.parsers;
import java.math.BigInteger;
import java.util.ArrayList;
public class Chunked {
public static ArrayList<String> parse(String body) {
ArrayList<String> result = new ArrayList<>();
try {
body = body.strip();
int length; int pos;
do {
pos = body.indexOf("\r\n");
length = Integer.parseInt(body.substring(0, pos), 16);
result.add(body.substring(pos + 2, length));
body = body.substring(pos + 2 + length);
} while (!body.isEmpty());
} catch (NumberFormatException e) {
result.add(body);
}
return result;
}
}