diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index d6bdcf95b..a0a7dcefb 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -80,6 +80,7 @@ add_imhex_plugin( source/content/tools/regex_replacer.cpp source/content/tools/tcp_client_server.cpp source/content/tools/wiki_explainer.cpp + source/content/tools/http_requests.cpp source/content/pl_visualizers/hex_viewer.cpp source/content/pl_visualizers/chunk_entropy.cpp diff --git a/plugins/builtin/include/content/tools_entries.hpp b/plugins/builtin/include/content/tools_entries.hpp index 6a5a7faa4..61ef1f8ac 100644 --- a/plugins/builtin/include/content/tools_entries.hpp +++ b/plugins/builtin/include/content/tools_entries.hpp @@ -23,4 +23,6 @@ namespace hex::plugin::builtin { void drawFileToolSplitter(); void drawFileToolCombiner(); + void drawHTTPRequestMaker(); + } diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 4b4ed6ebd..2dee50e87 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -561,6 +561,12 @@ "hex.builtin.tools.format.standard": "Standard", "hex.builtin.tools.graphing": "Graphing Calculator", "hex.builtin.tools.history": "History", + "hex.builtin.tools.http_requests": "HTTP Requests", + "hex.builtin.tools.http_requests.enter_url": "Enter URL", + "hex.builtin.tools.http_requests.send": "Send", + "hex.builtin.tools.http_requests.headers": "Headers", + "hex.builtin.tools.http_requests.response": "Response", + "hex.builtin.tools.http_requests.body": "Body", "hex.builtin.tools.ieee754": "IEEE 754 Floating Point Encoder and Decoder", "hex.builtin.tools.ieee754.clear": "Clear", "hex.builtin.tools.ieee754.description": "IEEE754 is a standard for representing floating point numbers which is used by most modern CPUs.\n\nThis tool visualizes the internal representation of a floating point number and allows decoding and encoding of numbers with a non-standard number of mantissa or exponent bits.", diff --git a/plugins/builtin/source/content/tools/http_requests.cpp b/plugins/builtin/source/content/tools/http_requests.cpp new file mode 100644 index 000000000..e949d57a1 --- /dev/null +++ b/plugins/builtin/source/content/tools/http_requests.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include + +namespace hex::plugin::builtin { + + using namespace std::literals::chrono_literals; + + void drawHTTPRequestMaker() { + static std::string url, body, output; + static std::vector> headers; + static int method = 0; + static TextEditor responseEditor, bodyEditor; + static HttpRequest request("", ""); + static std::future> response; + + AT_FIRST_TIME { + responseEditor.SetReadOnly(true); + responseEditor.SetShowLineNumbers(false); + responseEditor.SetShowWhitespaces(true); + responseEditor.SetShowCursor(false); + + auto languageDef = TextEditor::LanguageDefinition(); + for (auto &[name, identifier] : languageDef.mIdentifiers) + identifier.mDeclaration = ""; + + responseEditor.SetLanguageDefinition(languageDef); + + bodyEditor.SetShowLineNumbers(true); + bodyEditor.SetShowWhitespaces(true); + bodyEditor.SetShowCursor(true); + + bodyEditor.SetLanguageDefinition(languageDef); + }; + + constexpr static auto Methods = std::array{ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS", + "CONNECT", + "TRACE" + }; + ImGui::SetNextItemWidth(100_scaled); + ImGui::Combo("##method", &method, Methods.data(), Methods.size()); + + ImGui::SameLine(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 75_scaled); + if (ImGui::InputTextWithHint("##url", "hex.builtin.tools.http_requests.enter_url"_lang, url)) { + output.clear(); + } + + ImGui::SameLine(); + + ImGui::SetNextItemWidth(75_scaled); + if (ImGui::Button("hex.builtin.tools.http_requests.send"_lang)) { + request.setMethod(Methods[method]); + request.setUrl(url); + request.setBody(body); + + for (const auto &[key, value] : headers) + request.addHeader(key, value); + + response = request.execute(); + } + + if (ImGui::BeginChild("Settings", ImVec2(ImGui::GetContentRegionAvail().x, 200_scaled), ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::BeginTabBar("SettingsTabs")) { + if (ImGui::BeginTabItem("hex.builtin.tools.http_requests.headers"_lang)) { + if (ImGui::BeginTable("Headers", 3, ImGuiTableFlags_Borders, ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 2))) { + ImGui::TableSetupColumn("hex.ui.common.key"_lang, ImGuiTableColumnFlags_NoSort); + ImGui::TableSetupColumn("hex.ui.common.value"_lang, ImGuiTableColumnFlags_NoSort); + ImGui::TableSetupColumn("##remove", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, 20_scaled); + ImGui::TableSetupScrollFreeze(0, 1); + + ImGui::TableHeadersRow(); + + auto elementToRemove = headers.end(); + for (auto it = headers.begin(); it != headers.end(); ++it) { + auto &[key, value] = *it; + + ImGui::TableNextRow(); + ImGui::PushID(&key); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1); + ImGui::InputTextWithHint("##key", "hex.ui.common.key"_lang, key); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1); + ImGui::InputTextWithHint("##value", "hex.ui.common.value"_lang, value); + + ImGui::TableNextColumn(); + if (ImGuiExt::IconButton(ICON_VS_REMOVE, ImGui::GetStyleColorVec4(ImGuiCol_Text))) + elementToRemove = it; + + ImGui::PopID(); + } + + if (elementToRemove != headers.end()) + headers.erase(elementToRemove); + + ImGui::TableNextColumn(); + + ImGui::Dummy(ImVec2(0, 0)); + + ImGui::EndTable(); + } + if (ImGuiExt::DimmedButton("hex.ui.common.add"_lang)) headers.emplace_back(); + + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("hex.builtin.tools.http_requests.body"_lang)) { + bodyEditor.Render("Body", ImGui::GetContentRegionAvail(), true); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + } + ImGui::EndChild(); + + ImGuiExt::Header("hex.builtin.tools.http_requests.response"_lang); + responseEditor.Render("Response", ImVec2(ImGui::GetContentRegionAvail().x, 150_scaled), true); + + if (response.valid() && response.wait_for(0s) != std::future_status::timeout) { + const auto result = response.get(); + const auto data = result.getData(); + + if (const auto status = result.getStatusCode(); status != 0) + responseEditor.SetText("Status: " + std::to_string(result.getStatusCode()) + "\n\n" + data); + else + responseEditor.SetText("Status: No Response"); + } + + } + +} diff --git a/plugins/builtin/source/content/tools_entries.cpp b/plugins/builtin/source/content/tools_entries.cpp index 43fb8f265..2d880ff63 100644 --- a/plugins/builtin/source/content/tools_entries.cpp +++ b/plugins/builtin/source/content/tools_entries.cpp @@ -47,6 +47,7 @@ namespace hex::plugin::builtin { ContentRegistry::Tools::add("hex.builtin.tools.invariant_multiplication", drawInvariantMultiplicationDecoder); ContentRegistry::Tools::add("hex.builtin.tools.tcp_client_server", drawTCPClientServer); ContentRegistry::Tools::add("hex.builtin.tools.euclidean_algorithm", drawEuclidianAlgorithm); + ContentRegistry::Tools::add("hex.builtin.tools.http_requests", drawHTTPRequestMaker); } } diff --git a/plugins/ui/romfs/lang/en_US.json b/plugins/ui/romfs/lang/en_US.json index e779fc27b..73d5fab6f 100644 --- a/plugins/ui/romfs/lang/en_US.json +++ b/plugins/ui/romfs/lang/en_US.json @@ -4,6 +4,7 @@ "country": "United States", "fallback": true, "translations": { + "hex.ui.common.add": "Add", "hex.ui.common.address": "Address", "hex.ui.common.allow": "Allow", "hex.ui.common.begin": "Begin", @@ -34,6 +35,7 @@ "hex.ui.common.hexadecimal": "Hexadecimal", "hex.ui.common.info": "Information", "hex.ui.common.instruction": "Instruction", + "hex.ui.common.key": "Key", "hex.ui.common.link": "Link", "hex.ui.common.little": "Little", "hex.ui.common.little_endian": "Little Endian",