Просмотр исходного кода

Web Crawler v0.4 (New Keyboard and HTTP Methods)

- Updated the text input to match the text input from the UART Terminal app (big thanks to xMasterx)
- Added POST and PUT requests (with payloads)
- Added headers
- Added more error handling
jblanked 1 год назад
Родитель
Сommit
6a3877b40c

+ 6 - 0
CHANGELOG.md

@@ -1,3 +1,9 @@
+## 0.4 (New Keyboard and HTTP Methods)
+- Updated the text input to match the text input from the UART Terminal app (big thanks to xMasterx)
+- Added POST and PUT requests (with payloads)
+- Added headers
+- Added more error handling
+
 ## 0.3 (New Features)
 - Updated the progress messages displayed after sending a GET request.
 - Renamed the "Config" main submenu option to "Settings."

+ 33 - 20
README.md

@@ -1,63 +1,76 @@
 ## Overview
 
-**Web Crawler** is a custom application designed for the Flipper Zero device, allowing users to configure and manage GET requests directly from their Flipper Zero.
+**Web Crawler** is a custom application designed for the Flipper Zero device, allowing users to configure and manage HTTP requests directly from their Flipper Zero.
 
 ## Requirements
-- WiFi Dev Board for Flipper Zero with WebCrawler Flash: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+- WiFi Dev Board for Flipper Zero with FlipperHTTP Flash: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
 - WiFi Access Point
 
-## Features
+## Installation
+- Download from the Official Flipper App Store: https://lab.flipper.net/apps/web_crawler
 
-- **Configurable Request**: Specify the URL of the website you want to send a GET request to.
+## Features
+- **Configurable Request**: Specify the URL of the website you want to send a HTTP request to.
 - **Wi-Fi Configuration**: Enter your Wi-Fi SSID and password to enable network communication.
 - **File Management**: Automatically saves and manages received data on the device's storage, allowing users to view, rename, and delete the received data at any time.
 
 ## Usage
-
-1. **Connection**: Turn off your Flipper, connect the WiFi Dev Board, then turn your Flipper back on.
+1. **Connection**: After installing the app, turn off your Flipper, connect the WiFi Dev Board, then turn your Flipper back on.
 
 2. **Launch the Web Crawler App**: Navigate to the Apps menu on your Flipper Zero, select GPIO, then scroll down and select **Web Crawler**.
 
 3. **Main Menu**: Upon launching, you'll see a submenu containing the following options:
-   - **Run**: Initiate the GET request.
+   - **Run**: Initiate the HTTP request.
    - **About**: View information about the Web Crawler app.
    - **Settings**: Set up parameters or perform file operations.
 
-4. **Settings**: Select **Settings** and navigate to WiFi Settings. Use the Flipper Zero's navigation buttons to input and confirm your settings. Do the same for the Request settings. Once configured, these settings will be saved and used for subsequent GET request operations.
+4. **Settings**: Select **Settings** and navigate to WiFi Settings. Use the Flipper Zero's navigation buttons to input and confirm your settings. Do the same for the Request settings. Once configured, these settings will be saved and used for subsequent HTTP request operations.
 
-5. **Running the Crawler**: Select **Run** from the main submenu to start the GET request process. The app will:
-   - **Send Settings**: Transmit the GET request via serial to the WiFi Dev Board.
+   For testing purposes:
+   - https://httpbin.org/get Returns GET data.
+   - https://httpbin.org/post Returns POST data.
+   - https://httpbin.org/put Returns PUT data.
+
+5. **Running the Request**: Select **Run** from the main submenu to start the HTTP request process. The app will:
+   - **Send Request**: Transmit the HTTP request via serial to the WiFi Dev Board.
    - **Receive Data**: Listen for incoming data.
    - **Store Data**: Save the received data to the device's storage for later retrieval.
    - **Log**: Display detailed analysis of the operation status on the screen.
 
-6. **Accessing Received Data**: After the GET request operation completes, you can access the received data by:
+6. **Accessing Received Data**: After the HTTP request operation completes, you can access the received data by either:
+   - Navigating to File Settings and selecting Read File (preferred method)
    - Connecting to Flipper and opening the SD/apps_data/web_crawler_app/ storage directory to access the received_data.txt file (or the file name/type customized in the settings).
-   - Navigating to File Settings and selecting Read File.
 
 ## Setting Up Parameters
-
 1. **Path (URL)**
    - Enter the complete URL of the website you intend to crawl (e.g., https://www.example.com/).
 
-2. **SSID (WiFi Network)**
+2. **HTTP Method**
+   - Choose between GET, POST, and PUT.
+
+3. **Headers**
+   - Add your required headers to be used in your HTTP requests
+
+4. **Payload**
+   - Type in the JSON content to be sent with your POST or PUT requests.
+
+5. **SSID (WiFi Network)**
    - Provide the name of your WiFi network to enable the Flipper Zero to communicate over the network.
 
-3. **Password (WiFi Network)**
+6. **Password (WiFi Network)**
    - Input the corresponding password for your WiFi network.
 
-4. **Set File Type**
+7. **Set File Type**
    - Enter your desired file extension. After saving, the app will rename your file, applying the new extension.
 
-5. **Rename File**
+8. **Rename File**
    - Provide your desired file name. After saving, the app will rename your file with the new name.
 
-## Saving Settings
 
-After entering the desired configuration parameters, the app automatically saves these settings for use during the GET request process. You can update these settings at any time by navigating back to the **Settings** menu.
+## Saving Settings
+After entering the desired configuration parameters, the app automatically saves these settings for use during the HTTP request process. You can update these settings at any time by navigating back to the **Settings** menu.
 
 ## Logging and Debugging
-
 The Web Crawler app uses logging to help identify issues:
 
 - **Info Logs**: Provide general information about the app's operations (e.g., UART initialization, sending settings).

+ 2 - 0
app.c

@@ -1,3 +1,4 @@
+#include <uart_text_input.h>
 #include <web_crawler_e.h>
 #include <flipper_http.h>
 #include <web_crawler_storage.h>
@@ -24,6 +25,7 @@ int32_t web_crawler_app(void *p)
     if (!flipper_http_connect_wifi())
     {
         FURI_LOG_E(TAG, "Failed to connect to WiFi");
+        return -1;
     }
 
     if (!flipper_http_ping())

+ 2 - 2
application.fam

@@ -8,10 +8,10 @@ App(
         "gui", 
     ],
     order=10,
-    fap_version = (0, 3),
+    fap_version = (0, 4),
     fap_icon="app.png",
     fap_category="GPIO",
     fap_author="JBlanked",
     fap_icon_assets="assets",
-    fap_description="Use Wifi to access the internet and web scrape",
+    fap_description="Use Wi-Fi to access the internet and scrape data from the web.",
 )

BIN
assets/.DS_Store


BIN
assets/FlipperHTTP/.DS_Store


+ 32 - 53
assets/FlipperHTTP/README.md

@@ -21,63 +21,42 @@ HTTP library for Flipper Zero. Compatible with Wifi Dev Board for Flipper Zero (
 15. Click on FLASH - slow. If successful, you will see three green LED blinks on the Dev board.
 16. On the Dev Board, press the RESET button once.
 
-You should be all set. Here's the initial guide: https://www.youtube.com/watch?v=Y2lUVTMTABE
+You are all set. Here's the initial guide: https://www.youtube.com/watch?v=Y2lUVTMTABE
 
+Star the repository (https://github.com/jblanked/WebCrawler-FlipperZero) and follow me for updates and upcoming Flipper apps.
 
-## Usage in `C` (flipper_http.h)
 
-**General**
-- Init:
-    - `flipper_http_init(FlipperHTTP_Callback callback, void *context)`
-- DeInit:
-    - `flipper_http_deinit()`
+## Usage in `C` (flipper_http.h)
 
-**WiFi**
-- Connect To Wifi: 
-    - `flipper_http_connect_wifi()`
-- Disconnect: 
-    - `flipper_http_disconnect_wifi()`
-- Ping: 
-    - `flipper_http_ping()`
-- Save Wifi: 
-    - `flipper_http_save_wifi(const char *ssid, const char *password)`
+| **Function Name**                                | **Return Value** | **Parameters**                                                                                                | **Description**                                                                                   |
+|--------------------------------------------------|------------------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
+| `flipper_http_init`                              | `bool`           | `FlipperHTTP_Callback callback`, `void *context`                                                               | Initializes the HTTP module with a callback and context.                                           |
+| `flipper_http_deinit`                            | `void`           | None                                                                                                           | Deinitializes the HTTP module.                                                                    |
+| `flipper_http_connect_wifi`                      | `bool`           | None                                                                                                           | Connects to WiFi using previously saved credentials.                                               |
+| `flipper_http_disconnect_wifi`                   | `bool`           | None                                                                                                           | Disconnects from the current WiFi network.                                                         |
+| `flipper_http_ping`                              | `bool`           | None                                                                                                           | Sends a ping request to test connectivity.                                                         |
+| `flipper_http_save_wifi`                         | `bool`           | `const char *ssid`, `const char *password`                                                                     | Saves WiFi credentials for future connections.                                                     |
+| `flipper_http_send_data`                         | `bool`           | `const char *data`                                                                                             | Sends the specified data to the server.                                                            |
+| `flipper_http_rx_callback`                       | `void`           | `const char *line`, `void *context`                                                                            | Callback function for handling received data.                                                      |
+| `flipper_http_get_request`                       | `bool`           | `const char *url`                                                                                              | Sends a GET request to the specified URL.                                                          |
+| `flipper_http_get_request_with_headers`          | `bool`           | `const char *url`, `const char *headers`                                                                       | Sends a GET request with custom headers to the specified URL.                                      |
+| `flipper_http_post_request_with_headers`         | `bool`           | `const char *url`, `const char *headers`, `const char *payload`                                                 | Sends a POST request with custom headers and a payload to the specified URL.                       |
+| `flipper_http_put_request_with_headers`          | `bool`           | `const char *url`, `const char *headers`, `const char *payload`                                                 | Sends a PUT request with custom headers and a payload to the specified URL.                        |
+| `flipper_http_save_received_data`                | `bool`           | `size_t bytes_received`, `const char line_buffer[]`                                                            | Saves the received data to the SD card, with the specified size and buffer.                        |
 
-**Extras**
-- Send Data:
-    - `flipper_http_send_data(const char *data)`
-- Rx Callback:
-    - `flipper_http_rx_callback(const char *line, void *context)`
-- GET request:
-    - `flipper_http_get_request(const char *url)`
-- Save Data to SD:
-    - `flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])`
 
 ## Usage in `JavaScript` (flipper_http.js):
-**General**
-- Init:
-    - `fhttp.init()`
-- DeInit:
-    - `fhttp.deinit()`
-
-**WiFi**
-- Connect To Wifi: 
-    - `fhttp.connect_wifi()`
-- Disconnect: 
-    - `fhttp.disconnect_wifi()`
-- Ping: 
-    - `fhttp.ping()`
-- Save Wifi: 
-    - `fhttp.save_wifi(ssid, password)`
-
-**Extras**
-- Send Data:
-    - `fhttp.send_data(data)`
-- Read Data:
-    - `fhttp.read_data(delay_ms)`
-- GET request:
-    - `fhttp.get_request(url)`
-    - `fhttp.get_request_with_headers(url, headers)`
-- POST request:
-    - `fhttp.post_request_with_headers(url, headers, payload)`
-- PUT request:
-    - `fhttp.put_request_with_headers(url, headers, payload)`
+| **Function Name**                      | **Return Value** | **Parameters**                                       | **Description**                                                                                      |
+|----------------------------------------|------------------|-----------------------------------------------------|------------------------------------------------------------------------------------------------------|
+| `fhttp.init`                           | `void`           | None                                                | Initializes the serial connection with the correct settings.                                          |
+| `fhttp.deinit`                         | `void`           | None                                                | Deinitializes and ends the serial connection.                                                         |
+| `fhttp.connect_wifi`                   | `bool`           | None                                                | Sends a command to connect to WiFi and returns whether the connection was successful.                 |
+| `fhttp.disconnect_wifi`                | `bool`           | None                                                | Sends a command to disconnect from WiFi and returns whether the disconnection was successful.          |
+| `fhttp.ping`                           | `bool`           | None                                                | Sends a ping request to test connectivity and returns whether a response was received.                |
+| `fhttp.save_wifi`                      | `bool`           | `ssid: string`, `password: string`                  | Saves WiFi credentials and returns whether the save operation was successful.                         |
+| `fhttp.send_data`                      | `void`           | `data: string`                                      | Sends the specified data to the serial port.                                                          |
+| `fhttp.read_data`                      | `string`         | `delay_ms: number`                                  | Reads data from the serial port with a specified delay and returns the response received.      |
+| `fhttp.get_request`                    | `string`         | `url: string`                                       | Sends a GET request to the specified URL and returns the response of the response.             |
+| `fhttp.get_request_with_headers`       | `string`         | `url: string`, `headers: string`                    | Sends a GET request with headers and returns the response of the response.                    |
+| `fhttp.post_request_with_headers`      | `string`         | `url: string`, `headers: string`, `payload: string` | Sends a POST request with headers and payload, and returns the response of the response.       |
+| `fhttp.put_request_with_headers`       | `string`         | `url: string`, `headers: string`, `payload: string` | Sends a PUT request with headers and payload, and returns the response of the response.        |

+ 287 - 22
assets/FlipperHTTP/flipper_http.h

@@ -12,7 +12,7 @@
 
 #define HTTP_TAG "WebCrawler"             // change this to your app name
 #define http_tag "web_crawler_app"        // change this to your app id
-#define UART_CH (FuriHalSerialIdUsart)    // UART channel
+#define UART_CH (FuriHalSerialIdUsart)    // UART channel (switched from FuriHalSerialIdUsart to FuriHalSerialIdLpuart)
 #define TIMEOUT_DURATION_TICKS (2 * 1000) // 2 seconds
 #define BAUDRATE (115200)                 // UART baudrate
 #define RX_BUF_SIZE 1024                  // UART RX buffer size
@@ -33,6 +33,9 @@ bool flipper_http_ping();
 bool flipper_http_save_wifi(const char *ssid, const char *password);
 //---
 bool flipper_http_get_request(const char *url);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
+bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload);
+bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload);
 //---
 bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
 
@@ -75,20 +78,21 @@ typedef struct
     char *last_response;
 
     // Timer-related members
-    FuriTimer *get_timeout_timer; // Timer for GET request timeout
-    bool started_receiving;       // Indicates if a GET request has started
-    bool just_started;            // Indicates if data reception has just started
+    FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
     char *received_data;          // Buffer to store received data
+
+    bool started_receiving_get; // Indicates if a GET request has started
+    bool just_started_get;      // Indicates if GET data reception has just started
+
+    bool started_receiving_post; // Indicates if a POST request has started
+    bool just_started_post;      // Indicates if POST data reception has just started
+
+    bool started_receiving_put; // Indicates if a PUT request has started
+    bool just_started_put;      // Indicates if PUT data reception has just started
 } FlipperHTTP;
 
 // Declare uart as extern to prevent multiple definitions
-FlipperHTTP fhttp;
-
-typedef struct
-{
-    char *key;
-    char *value;
-} FlipperHTTPHeader;
+static FlipperHTTP fhttp;
 
 // Timer callback function
 /**
@@ -103,7 +107,7 @@ void get_timeout_timer_callback(void *context)
     FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving [GET/END]...");
 
     // Reset the state
-    fhttp.started_receiving = false;
+    fhttp.started_receiving_get = false;
 
     // Free received data if any
     if (fhttp.received_data)
@@ -192,6 +196,16 @@ static int32_t flipper_http_worker(void *context)
  */
 bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
 {
+    if (!context)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init.");
+        return false;
+    }
+    if (!callback)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init.");
+        return false;
+    }
     fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
     if (!fhttp.flipper_http_stream)
     {
@@ -222,10 +236,15 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
     furi_hal_gpio_init_simple(&test_pins[0], GpioModeInput);
     furi_hal_gpio_init_simple(&test_pins[1], GpioModeOutputPushPull);
 
-    fhttp.serial_handle = NULL;
+    // handle when the UART control is busy to avoid furi_check failed
+    if (furi_hal_serial_control_is_busy(UART_CH))
+    {
+        FURI_LOG_E(HTTP_TAG, "UART control is busy.");
+        return false;
+    }
 
     fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH);
-    if (fhttp.serial_handle == NULL)
+    if (!fhttp.serial_handle)
     {
         FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL");
         // Cleanup resources
@@ -494,7 +513,113 @@ bool flipper_http_get_request(const char *url)
     // The response will be handled asynchronously via the callback
     return true;
 }
+// Function to send a GET request with headers
+/**
+ * @brief      Send a GET request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the GET request to.
+ * @param      headers  The headers to send with the GET request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_get_request_with_headers(const char *url, const char *headers)
+{
+    if (!url || !headers)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_with_headers.");
+        return false;
+    }
+
+    // Prepare GET request command with headers
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers.");
+        return false;
+    }
+
+    // Send GET request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a POST request with headers
+/**
+ * @brief      Send a POST request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the POST request to.
+ * @param      headers  The headers to send with the POST request.
+ * @param      data  The data to send with the POST request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_with_headers.");
+        return false;
+    }
+
+    // Prepare POST request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data.");
+        return false;
+    }
+
+    // Send POST request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a PUT request with headers
+/**
+ * @brief      Send a PUT request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the PUT request to.
+ * @param      headers  The headers to send with the PUT request.
+ * @param      data  The data to send with the PUT request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_put_request_with_headers.");
+        return false;
+    }
+
+    // Prepare PUT request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format PUT request command with headers and data.");
+        return false;
+    }
 
+    // Send PUT request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
 // Function to handle received data asynchronously
 /**
  * @brief      Callback function to handle received data asynchronously.
@@ -524,7 +649,7 @@ void flipper_http_rx_callback(const char *line, void *context)
     // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
 
     // Check if we've started receiving data from a GET request
-    if (fhttp.started_receiving)
+    if (fhttp.started_receiving_get)
     {
         // Restart the timeout timer each time new data is received
         furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
@@ -540,16 +665,16 @@ void flipper_http_rx_callback(const char *line, void *context)
                 flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
                 free(fhttp.received_data);
                 fhttp.received_data = NULL;
-                fhttp.started_receiving = false;
-                fhttp.just_started = false;
+                fhttp.started_receiving_get = false;
+                fhttp.just_started_get = false;
                 fhttp.state = IDLE;
                 return;
             }
             else
             {
                 FURI_LOG_E(HTTP_TAG, "No data received.");
-                fhttp.started_receiving = false;
-                fhttp.just_started = false;
+                fhttp.started_receiving_get = false;
+                fhttp.just_started_get = false;
                 fhttp.state = IDLE;
                 return;
             }
@@ -579,9 +704,135 @@ void flipper_http_rx_callback(const char *line, void *context)
             }
         }
 
-        if (!fhttp.just_started)
+        if (!fhttp.just_started_get)
         {
-            fhttp.just_started = true;
+            fhttp.just_started_get = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a POST request
+    else if (fhttp.started_receiving_post)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[POST/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "POST request completed.");
+            // Stop the timer since we've completed the POST request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                free(fhttp.received_data);
+                fhttp.received_data = NULL;
+                fhttp.started_receiving_post = false;
+                fhttp.just_started_post = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_post = false;
+                fhttp.just_started_post = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_post)
+        {
+            fhttp.just_started_post = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a PUT request
+    else if (fhttp.started_receiving_put)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[PUT/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "PUT request completed.");
+            // Stop the timer since we've completed the PUT request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                free(fhttp.received_data);
+                fhttp.received_data = NULL;
+                fhttp.started_receiving_put = false;
+                fhttp.just_started_put = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_put = false;
+                fhttp.just_started_put = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_put)
+        {
+            fhttp.just_started_put = true;
         }
         return;
     }
@@ -603,11 +854,25 @@ void flipper_http_rx_callback(const char *line, void *context)
     else if (strstr(line, "[GET/SUCCESS]") != NULL)
     {
         FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
-        fhttp.started_receiving = true;
+        fhttp.started_receiving_get = true;
         furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
         fhttp.state = RECEIVING;
         return;
     }
+    else if (strstr(line, "[POST/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "POST request succeeded.");
+        fhttp.started_receiving_post = true;
+        fhttp.state = RECEIVING;
+        return;
+    }
+    else if (strstr(line, "[PUT/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "PUT request succeeded.");
+        fhttp.started_receiving_put = true;
+        fhttp.state = RECEIVING;
+        return;
+    }
     else if (strstr(line, "[DISCONNECTED]") != NULL)
     {
         FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");

BIN
assets/KeyBackspaceSelected_16x9.png


BIN
assets/KeyBackspace_16x9.png


BIN
assets/KeySaveSelected_24x11.png


BIN
assets/KeySave_24x11.png


BIN
assets/WarningDolphin_45x42.png


+ 287 - 22
flipper_http.h

@@ -12,7 +12,7 @@
 
 #define HTTP_TAG "WebCrawler"             // change this to your app name
 #define http_tag "web_crawler_app"        // change this to your app id
-#define UART_CH (FuriHalSerialIdUsart)    // UART channel
+#define UART_CH (FuriHalSerialIdUsart)    // UART channel (switched from FuriHalSerialIdUsart to FuriHalSerialIdLpuart)
 #define TIMEOUT_DURATION_TICKS (2 * 1000) // 2 seconds
 #define BAUDRATE (115200)                 // UART baudrate
 #define RX_BUF_SIZE 1024                  // UART RX buffer size
@@ -33,6 +33,9 @@ bool flipper_http_ping();
 bool flipper_http_save_wifi(const char *ssid, const char *password);
 //---
 bool flipper_http_get_request(const char *url);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
+bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload);
+bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload);
 //---
 bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
 
@@ -75,20 +78,21 @@ typedef struct
     char *last_response;
 
     // Timer-related members
-    FuriTimer *get_timeout_timer; // Timer for GET request timeout
-    bool started_receiving;       // Indicates if a GET request has started
-    bool just_started;            // Indicates if data reception has just started
+    FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
     char *received_data;          // Buffer to store received data
+
+    bool started_receiving_get; // Indicates if a GET request has started
+    bool just_started_get;      // Indicates if GET data reception has just started
+
+    bool started_receiving_post; // Indicates if a POST request has started
+    bool just_started_post;      // Indicates if POST data reception has just started
+
+    bool started_receiving_put; // Indicates if a PUT request has started
+    bool just_started_put;      // Indicates if PUT data reception has just started
 } FlipperHTTP;
 
 // Declare uart as extern to prevent multiple definitions
-FlipperHTTP fhttp;
-
-typedef struct
-{
-    char *key;
-    char *value;
-} FlipperHTTPHeader;
+static FlipperHTTP fhttp;
 
 // Timer callback function
 /**
@@ -103,7 +107,7 @@ void get_timeout_timer_callback(void *context)
     FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving [GET/END]...");
 
     // Reset the state
-    fhttp.started_receiving = false;
+    fhttp.started_receiving_get = false;
 
     // Free received data if any
     if (fhttp.received_data)
@@ -192,6 +196,16 @@ static int32_t flipper_http_worker(void *context)
  */
 bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
 {
+    if (!context)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init.");
+        return false;
+    }
+    if (!callback)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init.");
+        return false;
+    }
     fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
     if (!fhttp.flipper_http_stream)
     {
@@ -222,10 +236,15 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
     furi_hal_gpio_init_simple(&test_pins[0], GpioModeInput);
     furi_hal_gpio_init_simple(&test_pins[1], GpioModeOutputPushPull);
 
-    fhttp.serial_handle = NULL;
+    // handle when the UART control is busy to avoid furi_check failed
+    if (furi_hal_serial_control_is_busy(UART_CH))
+    {
+        FURI_LOG_E(HTTP_TAG, "UART control is busy.");
+        return false;
+    }
 
     fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH);
-    if (fhttp.serial_handle == NULL)
+    if (!fhttp.serial_handle)
     {
         FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL");
         // Cleanup resources
@@ -494,7 +513,113 @@ bool flipper_http_get_request(const char *url)
     // The response will be handled asynchronously via the callback
     return true;
 }
+// Function to send a GET request with headers
+/**
+ * @brief      Send a GET request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the GET request to.
+ * @param      headers  The headers to send with the GET request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_get_request_with_headers(const char *url, const char *headers)
+{
+    if (!url || !headers)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_with_headers.");
+        return false;
+    }
+
+    // Prepare GET request command with headers
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers.");
+        return false;
+    }
+
+    // Send GET request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a POST request with headers
+/**
+ * @brief      Send a POST request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the POST request to.
+ * @param      headers  The headers to send with the POST request.
+ * @param      data  The data to send with the POST request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_with_headers.");
+        return false;
+    }
+
+    // Prepare POST request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data.");
+        return false;
+    }
+
+    // Send POST request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a PUT request with headers
+/**
+ * @brief      Send a PUT request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the PUT request to.
+ * @param      headers  The headers to send with the PUT request.
+ * @param      data  The data to send with the PUT request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_put_request_with_headers.");
+        return false;
+    }
+
+    // Prepare PUT request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format PUT request command with headers and data.");
+        return false;
+    }
 
+    // Send PUT request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
 // Function to handle received data asynchronously
 /**
  * @brief      Callback function to handle received data asynchronously.
@@ -524,7 +649,7 @@ void flipper_http_rx_callback(const char *line, void *context)
     // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
 
     // Check if we've started receiving data from a GET request
-    if (fhttp.started_receiving)
+    if (fhttp.started_receiving_get)
     {
         // Restart the timeout timer each time new data is received
         furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
@@ -540,16 +665,16 @@ void flipper_http_rx_callback(const char *line, void *context)
                 flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
                 free(fhttp.received_data);
                 fhttp.received_data = NULL;
-                fhttp.started_receiving = false;
-                fhttp.just_started = false;
+                fhttp.started_receiving_get = false;
+                fhttp.just_started_get = false;
                 fhttp.state = IDLE;
                 return;
             }
             else
             {
                 FURI_LOG_E(HTTP_TAG, "No data received.");
-                fhttp.started_receiving = false;
-                fhttp.just_started = false;
+                fhttp.started_receiving_get = false;
+                fhttp.just_started_get = false;
                 fhttp.state = IDLE;
                 return;
             }
@@ -579,9 +704,135 @@ void flipper_http_rx_callback(const char *line, void *context)
             }
         }
 
-        if (!fhttp.just_started)
+        if (!fhttp.just_started_get)
         {
-            fhttp.just_started = true;
+            fhttp.just_started_get = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a POST request
+    else if (fhttp.started_receiving_post)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[POST/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "POST request completed.");
+            // Stop the timer since we've completed the POST request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                free(fhttp.received_data);
+                fhttp.received_data = NULL;
+                fhttp.started_receiving_post = false;
+                fhttp.just_started_post = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_post = false;
+                fhttp.just_started_post = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_post)
+        {
+            fhttp.just_started_post = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a PUT request
+    else if (fhttp.started_receiving_put)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[PUT/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "PUT request completed.");
+            // Stop the timer since we've completed the PUT request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                free(fhttp.received_data);
+                fhttp.received_data = NULL;
+                fhttp.started_receiving_put = false;
+                fhttp.just_started_put = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_put = false;
+                fhttp.just_started_put = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_put)
+        {
+            fhttp.just_started_put = true;
         }
         return;
     }
@@ -603,11 +854,25 @@ void flipper_http_rx_callback(const char *line, void *context)
     else if (strstr(line, "[GET/SUCCESS]") != NULL)
     {
         FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
-        fhttp.started_receiving = true;
+        fhttp.started_receiving_get = true;
         furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
         fhttp.state = RECEIVING;
         return;
     }
+    else if (strstr(line, "[POST/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "POST request succeeded.");
+        fhttp.started_receiving_post = true;
+        fhttp.state = RECEIVING;
+        return;
+    }
+    else if (strstr(line, "[PUT/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "PUT request succeeded.");
+        fhttp.started_receiving_put = true;
+        fhttp.state = RECEIVING;
+        return;
+    }
     else if (strstr(line, "[DISCONNECTED]") != NULL)
     {
         FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");

+ 803 - 0
uart_text_input.h

@@ -0,0 +1,803 @@
+// from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c
+// all credits to xMasterX for the code
+#ifndef UART_TEXT_INPUT_H
+#define UART_TEXT_INPUT_H
+
+#include <gui/elements.h>
+#include "web_crawler_icons.h"
+#include <furi.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <core/common_defines.h>
+
+/** Text input anonymous structure */
+typedef struct UART_TextInput UART_TextInput;
+typedef void (*UART_TextInputCallback)(void *context);
+typedef bool (*UART_TextInputValidatorCallback)(const char *text, FuriString *error, void *context);
+
+UART_TextInputValidatorCallback
+uart_text_input_get_validator_callback(UART_TextInput *uart_text_input);
+
+void uart_text_input_reset(UART_TextInput *uart_text_input);
+
+struct UART_TextInput
+{
+    View *view;
+    FuriTimer *timer;
+};
+
+typedef struct
+{
+    const char text;
+    const uint8_t x;
+    const uint8_t y;
+} UART_TextInputKey;
+
+typedef struct
+{
+    const char *header;
+    char *text_buffer;
+    size_t text_buffer_size;
+    bool clear_default_text;
+
+    UART_TextInputCallback callback;
+    void *callback_context;
+
+    uint8_t selected_row;
+    uint8_t selected_column;
+
+    UART_TextInputValidatorCallback validator_callback;
+    void *validator_callback_context;
+    FuriString *validator_text;
+    bool valadator_message_visible;
+} UART_TextInputModel;
+
+static const uint8_t keyboard_origin_x = 1;
+static const uint8_t keyboard_origin_y = 29;
+static const uint8_t keyboard_row_count = 4;
+
+#define mode_AT "Send AT command to UART"
+
+#define ENTER_KEY '\r'
+#define BACKSPACE_KEY '\b'
+
+static const UART_TextInputKey keyboard_keys_row_1[] = {
+    {'{', 1, 0},
+    {'(', 9, 0},
+    {'[', 17, 0},
+    {'|', 25, 0},
+    {'@', 33, 0},
+    {'&', 41, 0},
+    {'#', 49, 0},
+    {';', 57, 0},
+    {'^', 65, 0},
+    {'*', 73, 0},
+    {'`', 81, 0},
+    {'"', 89, 0},
+    {'~', 97, 0},
+    {'\'', 105, 0},
+    {'.', 113, 0},
+    {'/', 120, 0},
+};
+
+static const UART_TextInputKey keyboard_keys_row_2[] = {
+    {'q', 1, 10},
+    {'w', 9, 10},
+    {'e', 17, 10},
+    {'r', 25, 10},
+    {'t', 33, 10},
+    {'y', 41, 10},
+    {'u', 49, 10},
+    {'i', 57, 10},
+    {'o', 65, 10},
+    {'p', 73, 10},
+    {'0', 81, 10},
+    {'1', 89, 10},
+    {'2', 97, 10},
+    {'3', 105, 10},
+    {'=', 113, 10},
+    {'-', 120, 10},
+};
+
+static const UART_TextInputKey keyboard_keys_row_3[] = {
+    {'a', 1, 21},
+    {'s', 9, 21},
+    {'d', 18, 21},
+    {'f', 25, 21},
+    {'g', 33, 21},
+    {'h', 41, 21},
+    {'j', 49, 21},
+    {'k', 57, 21},
+    {'l', 65, 21},
+    {BACKSPACE_KEY, 72, 13},
+    {'4', 89, 21},
+    {'5', 97, 21},
+    {'6', 105, 21},
+    {'$', 113, 21},
+    {'%', 120, 21},
+
+};
+
+static const UART_TextInputKey keyboard_keys_row_4[] = {
+    {'z', 1, 33},
+    {'x', 9, 33},
+    {'c', 18, 33},
+    {'v', 25, 33},
+    {'b', 33, 33},
+    {'n', 41, 33},
+    {'m', 49, 33},
+    {'_', 57, 33},
+    {ENTER_KEY, 64, 24},
+    {'7', 89, 33},
+    {'8', 97, 33},
+    {'9', 105, 33},
+    {'!', 113, 33},
+    {'+', 120, 33},
+};
+
+static uint8_t get_row_size(uint8_t row_index)
+{
+    uint8_t row_size = 0;
+
+    switch (row_index + 1)
+    {
+    case 1:
+        row_size = sizeof(keyboard_keys_row_1) / sizeof(UART_TextInputKey);
+        break;
+    case 2:
+        row_size = sizeof(keyboard_keys_row_2) / sizeof(UART_TextInputKey);
+        break;
+    case 3:
+        row_size = sizeof(keyboard_keys_row_3) / sizeof(UART_TextInputKey);
+        break;
+    case 4:
+        row_size = sizeof(keyboard_keys_row_4) / sizeof(UART_TextInputKey);
+        break;
+    }
+
+    return row_size;
+}
+
+static const UART_TextInputKey *get_row(uint8_t row_index)
+{
+    const UART_TextInputKey *row = NULL;
+
+    switch (row_index + 1)
+    {
+    case 1:
+        row = keyboard_keys_row_1;
+        break;
+    case 2:
+        row = keyboard_keys_row_2;
+        break;
+    case 3:
+        row = keyboard_keys_row_3;
+        break;
+    case 4:
+        row = keyboard_keys_row_4;
+        break;
+    }
+
+    return row;
+}
+
+static char get_selected_char(UART_TextInputModel *model)
+{
+    return get_row(model->selected_row)[model->selected_column].text;
+}
+
+static bool char_is_lowercase(char letter)
+{
+    return (letter >= 0x61 && letter <= 0x7A);
+}
+
+static bool char_is_uppercase(char letter)
+{
+    return (letter >= 0x41 && letter <= 0x5A);
+}
+
+static char char_to_lowercase(const char letter)
+{
+    switch (letter)
+    {
+    case ' ':
+        return 0x5f;
+        break;
+    case ')':
+        return 0x28;
+        break;
+    case '}':
+        return 0x7b;
+        break;
+    case ']':
+        return 0x5b;
+        break;
+    case '\\':
+        return 0x2f;
+        break;
+    case ':':
+        return 0x3b;
+        break;
+    case ',':
+        return 0x2e;
+        break;
+    case '?':
+        return 0x21;
+        break;
+    case '>':
+        return 0x3c;
+        break;
+    }
+    if (char_is_uppercase(letter))
+    {
+        return (letter + 0x20);
+    }
+    else
+    {
+        return letter;
+    }
+}
+
+static char char_to_uppercase(const char letter)
+{
+    switch (letter)
+    {
+    case '_':
+        return 0x20;
+        break;
+    case '(':
+        return 0x29;
+        break;
+    case '{':
+        return 0x7d;
+        break;
+    case '[':
+        return 0x5d;
+        break;
+    case '/':
+        return 0x5c;
+        break;
+    case ';':
+        return 0x3a;
+        break;
+    case '.':
+        return 0x2c;
+        break;
+    case '!':
+        return 0x3f;
+        break;
+    case '<':
+        return 0x3e;
+        break;
+    }
+    if (char_is_lowercase(letter))
+    {
+        return (letter - 0x20);
+    }
+    else
+    {
+        return letter;
+    }
+}
+
+static void uart_text_input_backspace_cb(UART_TextInputModel *model)
+{
+    uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
+    if (text_length > 0)
+    {
+        model->text_buffer[text_length - 1] = 0;
+    }
+}
+
+static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
+{
+    UART_TextInputModel *model = _model;
+    // uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
+    uint8_t needed_string_width = canvas_width(canvas) - 8;
+    uint8_t start_pos = 4;
+
+    const char *text = model->text_buffer;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_draw_str(canvas, 2, 7, model->header);
+    elements_slightly_rounded_frame(canvas, 1, 8, 126, 12);
+
+    if (canvas_string_width(canvas, text) > needed_string_width)
+    {
+        canvas_draw_str(canvas, start_pos, 17, "...");
+        start_pos += 6;
+        needed_string_width -= 8;
+    }
+
+    while (text != 0 && canvas_string_width(canvas, text) > needed_string_width)
+    {
+        text++;
+    }
+
+    if (model->clear_default_text)
+    {
+        elements_slightly_rounded_box(
+            canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    else
+    {
+        canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|");
+        canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|");
+    }
+    canvas_draw_str(canvas, start_pos, 17, text);
+
+    canvas_set_font(canvas, FontKeyboard);
+
+    for (uint8_t row = 0; row <= keyboard_row_count; row++)
+    {
+        const uint8_t column_count = get_row_size(row);
+        const UART_TextInputKey *keys = get_row(row);
+
+        for (size_t column = 0; column < column_count; column++)
+        {
+            if (keys[column].text == ENTER_KEY)
+            {
+                canvas_set_color(canvas, ColorBlack);
+                if (model->selected_row == row && model->selected_column == column)
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySaveSelected_24x11);
+                }
+                else
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySave_24x11);
+                }
+            }
+            else if (keys[column].text == BACKSPACE_KEY)
+            {
+                canvas_set_color(canvas, ColorBlack);
+                if (model->selected_row == row && model->selected_column == column)
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspaceSelected_16x9);
+                }
+                else
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspace_16x9);
+                }
+            }
+            else
+            {
+                if (model->selected_row == row && model->selected_column == column)
+                {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_box(
+                        canvas,
+                        keyboard_origin_x + keys[column].x - 1,
+                        keyboard_origin_y + keys[column].y - 8,
+                        7,
+                        10);
+                    canvas_set_color(canvas, ColorWhite);
+                }
+                else
+                {
+                    canvas_set_color(canvas, ColorBlack);
+                }
+                if (0 == strcmp(model->header, mode_AT))
+                {
+                    canvas_draw_glyph(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        char_to_uppercase(keys[column].text));
+                }
+                else
+                {
+                    canvas_draw_glyph(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        keys[column].text);
+                }
+            }
+        }
+    }
+    if (model->valadator_message_visible)
+    {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_box(canvas, 8, 10, 110, 48);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
+        canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
+        canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
+        elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
+        canvas_set_font(canvas, FontKeyboard);
+    }
+}
+
+static void
+uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_row > 0)
+    {
+        model->selected_row--;
+        if (model->selected_column > get_row_size(model->selected_row) - 6)
+        {
+            model->selected_column = model->selected_column + 1;
+        }
+    }
+}
+
+static void
+uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_row < keyboard_row_count - 1)
+    {
+        model->selected_row++;
+        if (model->selected_column > get_row_size(model->selected_row) - 4)
+        {
+            model->selected_column = model->selected_column - 1;
+        }
+    }
+}
+
+static void
+uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_column > 0)
+    {
+        model->selected_column--;
+    }
+    else
+    {
+        model->selected_column = get_row_size(model->selected_row) - 1;
+    }
+}
+
+static void
+uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_column < get_row_size(model->selected_row) - 1)
+    {
+        model->selected_column++;
+    }
+    else
+    {
+        model->selected_column = 0;
+    }
+}
+
+static void uart_text_input_handle_ok(
+    UART_TextInput *uart_text_input,
+    UART_TextInputModel *model,
+    bool shift)
+{
+    char selected = get_selected_char(model);
+    uint8_t text_length = strlen(model->text_buffer);
+
+    if (0 == strcmp(model->header, mode_AT))
+    {
+        selected = char_to_uppercase(selected);
+    }
+
+    if (shift)
+    {
+        if (0 == strcmp(model->header, mode_AT))
+        {
+            selected = char_to_lowercase(selected);
+        }
+        else
+        {
+            selected = char_to_uppercase(selected);
+        }
+    }
+
+    if (selected == ENTER_KEY)
+    {
+        if (model->validator_callback &&
+            (!model->validator_callback(
+                model->text_buffer, model->validator_text, model->validator_callback_context)))
+        {
+            model->valadator_message_visible = true;
+            furi_timer_start(uart_text_input->timer, furi_kernel_get_tick_frequency() * 4);
+        }
+        else if (model->callback != 0 && text_length > 0)
+        {
+            model->callback(model->callback_context);
+        }
+    }
+    else if (selected == BACKSPACE_KEY)
+    {
+        uart_text_input_backspace_cb(model);
+    }
+    else
+    {
+        if (model->clear_default_text)
+        {
+            text_length = 0;
+        }
+        if (text_length < (model->text_buffer_size - 1))
+        {
+            model->text_buffer[text_length] = selected;
+            model->text_buffer[text_length + 1] = 0;
+        }
+    }
+    model->clear_default_text = false;
+}
+
+static bool uart_text_input_view_input_callback(InputEvent *event, void *context)
+{
+    UART_TextInput *uart_text_input = context;
+    furi_assert(uart_text_input);
+
+    bool consumed = false;
+
+    // Acquire model
+    UART_TextInputModel *model = view_get_model(uart_text_input->view);
+
+    if ((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
+        model->valadator_message_visible)
+    {
+        model->valadator_message_visible = false;
+        consumed = true;
+    }
+    else if (event->type == InputTypeShort)
+    {
+        consumed = true;
+        switch (event->key)
+        {
+        case InputKeyUp:
+            uart_text_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_text_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_text_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_text_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyOk:
+            uart_text_input_handle_ok(uart_text_input, model, false);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+    else if (event->type == InputTypeLong)
+    {
+        consumed = true;
+        switch (event->key)
+        {
+        case InputKeyUp:
+            uart_text_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_text_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_text_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_text_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyOk:
+            uart_text_input_handle_ok(uart_text_input, model, true);
+            break;
+        case InputKeyBack:
+            uart_text_input_backspace_cb(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+    else if (event->type == InputTypeRepeat)
+    {
+        consumed = true;
+        switch (event->key)
+        {
+        case InputKeyUp:
+            uart_text_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_text_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_text_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_text_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyBack:
+            uart_text_input_backspace_cb(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+
+    // Commit model
+    view_commit_model(uart_text_input->view, consumed);
+
+    return consumed;
+}
+
+void uart_text_input_timer_callback(void *context)
+{
+    furi_assert(context);
+    UART_TextInput *uart_text_input = context;
+
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { model->valadator_message_visible = false; },
+        true);
+}
+
+UART_TextInput *uart_text_input_alloc()
+{
+    UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput));
+    uart_text_input->view = view_alloc();
+    view_set_context(uart_text_input->view, uart_text_input);
+    view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel));
+    view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback);
+    view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback);
+
+    uart_text_input->timer =
+        furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
+
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { model->validator_text = furi_string_alloc(); },
+        false);
+
+    uart_text_input_reset(uart_text_input);
+
+    return uart_text_input;
+}
+
+void uart_text_input_free(UART_TextInput *uart_text_input)
+{
+    furi_assert(uart_text_input);
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { furi_string_free(model->validator_text); },
+        false);
+
+    // Send stop command
+    furi_timer_stop(uart_text_input->timer);
+    // Release allocated memory
+    furi_timer_free(uart_text_input->timer);
+
+    view_free(uart_text_input->view);
+
+    free(uart_text_input);
+}
+
+void uart_text_input_reset(UART_TextInput *uart_text_input)
+{
+    furi_assert(uart_text_input);
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->text_buffer_size = 0;
+            model->header = "";
+            model->selected_row = 0;
+            model->selected_column = 0;
+            model->clear_default_text = false;
+            model->text_buffer = NULL;
+            model->text_buffer_size = 0;
+            model->callback = NULL;
+            model->callback_context = NULL;
+            model->validator_callback = NULL;
+            model->validator_callback_context = NULL;
+            furi_string_reset(model->validator_text);
+            model->valadator_message_visible = false;
+        },
+        true);
+}
+
+View *uart_text_input_get_view(UART_TextInput *uart_text_input)
+{
+    furi_assert(uart_text_input);
+    return uart_text_input->view;
+}
+
+void uart_text_input_set_result_callback(
+    UART_TextInput *uart_text_input,
+    UART_TextInputCallback callback,
+    void *callback_context,
+    char *text_buffer,
+    size_t text_buffer_size,
+    bool clear_default_text)
+{
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->callback = callback;
+            model->callback_context = callback_context;
+            model->text_buffer = text_buffer;
+            model->text_buffer_size = text_buffer_size;
+            model->clear_default_text = clear_default_text;
+            if (text_buffer && text_buffer[0] != '\0')
+            {
+                // Set focus on Save
+                model->selected_row = 2;
+                model->selected_column = 8;
+            }
+        },
+        true);
+}
+
+void uart_text_input_set_validator(
+    UART_TextInput *uart_text_input,
+    UART_TextInputValidatorCallback callback,
+    void *callback_context)
+{
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->validator_callback = callback;
+            model->validator_callback_context = callback_context;
+        },
+        true);
+}
+
+UART_TextInputValidatorCallback
+uart_text_input_get_validator_callback(UART_TextInput *uart_text_input)
+{
+    UART_TextInputValidatorCallback validator_callback = NULL;
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { validator_callback = model->validator_callback; },
+        false);
+    return validator_callback;
+}
+
+void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_input)
+{
+    void *validator_callback_context = NULL;
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { validator_callback_context = model->validator_callback_context; },
+        false);
+    return validator_callback_context;
+}
+
+void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char *text)
+{
+    with_view_model(
+        uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true);
+}
+
+#endif // UART_TEXT_INPUT_H

+ 286 - 36
web_crawler_callback.h

@@ -1,11 +1,13 @@
 // web_crawler_callback.h
-static bool sent_get_request = false;
+static bool sent_http_request = false;
 static bool get_success = false;
 static bool already_success = false;
 static WebCrawlerApp *app_instance = NULL;
 
 // Forward declaration of callback functions
 static void web_crawler_setting_item_path_clicked(void *context, uint32_t index);
+static void web_crawler_setting_item_headers_clicked(void *context, uint32_t index);
+static void web_crawler_setting_item_payload_clicked(void *context, uint32_t index);
 static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index);
 static void web_crawler_setting_item_password_clicked(void *context, uint32_t index);
 static void web_crawler_setting_item_file_type_clicked(void *context, uint32_t index);
@@ -13,6 +15,29 @@ static void web_crawler_setting_item_file_rename_clicked(void *context, uint32_t
 static void web_crawler_setting_item_file_delete_clicked(void *context, uint32_t index);
 static void web_crawler_setting_item_file_read_clicked(void *context, uint32_t index);
 
+static void web_crawler_http_method_change(VariableItem *item)
+{
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, http_method_names[index]);
+
+    // save the http method
+    if (app_instance)
+    {
+        strncpy(app_instance->http_method, http_method_names[index], strlen(http_method_names[index]) + 1);
+
+        // save the settings
+        save_settings(
+            app_instance->path,
+            app_instance->ssid,
+            app_instance->password,
+            app_instance->file_rename,
+            app_instance->file_type,
+            app_instance->http_method,
+            app_instance->headers,
+            app_instance->payload);
+    }
+}
+
 static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
 {
     UNUSED(context);
@@ -43,13 +68,50 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
 
     if (app_instance->path)
     {
-        if (!sent_get_request)
+        if (!sent_http_request)
         {
+            if (strstr(app_instance->http_method, "GET") != NULL)
+            {
+                canvas_draw_str(canvas, 0, 10, "Sending GET request...");
+
+                // Perform GET request and handle the response
+                if (app_instance->headers == NULL || app_instance->headers[0] == '\0' || strstr(app_instance->headers, " ") == NULL)
+                {
+                    get_success = flipper_http_get_request(app_instance->path);
+                }
+                else
+                {
+                    get_success = flipper_http_get_request_with_headers(app_instance->path, app_instance->headers);
+                }
+            }
+            else if (strstr(app_instance->http_method, "POST") != NULL)
+            {
+                canvas_draw_str(canvas, 0, 10, "Sending POST request...");
 
-            canvas_draw_str(canvas, 0, 10, "Sending GET request...");
+                // Perform POST request and handle the response
+                get_success = flipper_http_post_request_with_headers(app_instance->path, app_instance->headers, app_instance->payload);
+            }
+            else if (strstr(app_instance->http_method, "PUT") != NULL)
+            {
+                canvas_draw_str(canvas, 0, 10, "Sending PUT request...");
+
+                // Perform PUT request and handle the response
+                get_success = flipper_http_put_request_with_headers(app_instance->path, app_instance->headers, app_instance->payload);
+            }
+            else
+            {
+                canvas_draw_str(canvas, 0, 10, "Sending GET request...");
 
-            // Perform GET request and handle the response
-            get_success = flipper_http_get_request(app_instance->path);
+                // Perform GET request and handle the response
+                if (app_instance->headers == NULL || app_instance->headers[0] == '\0' || strstr(app_instance->headers, " ") == NULL)
+                {
+                    get_success = flipper_http_get_request(app_instance->path);
+                }
+                else
+                {
+                    get_success = flipper_http_get_request_with_headers(app_instance->path, app_instance->headers);
+                }
+            }
 
             canvas_draw_str(canvas, 0, 20, "Sent!");
 
@@ -64,7 +126,7 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
                 canvas_draw_str(canvas, 0, 30, "Failed.");
             }
 
-            sent_get_request = true;
+            sent_http_request = true;
         }
         else
         {
@@ -105,7 +167,7 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
                 }
                 else
                 {
-                    canvas_draw_str(canvas, 0, 10, "GET request failed.");
+                    canvas_draw_str(canvas, 0, 10, "HTTP request failed.");
                     canvas_draw_str(canvas, 0, 20, "Press BACK to return.");
                 }
                 get_success = false;
@@ -143,7 +205,7 @@ static uint32_t web_crawler_back_to_main_callback(void *context)
 {
     UNUSED(context);
     // reset GET request flags
-    sent_get_request = false;
+    sent_http_request = false;
     get_success = false;
     already_success = false;
     // free file read widget if it exists
@@ -197,7 +259,7 @@ static void web_crawler_submenu_callback(void *context, uint32_t index)
         switch (index)
         {
         case WebCrawlerSubmenuIndexRun:
-            sent_get_request = false; // Reset the flag
+            sent_http_request = false; // Reset the flag
             view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewRun);
             break;
         case WebCrawlerSubmenuIndexAbout:
@@ -282,6 +344,17 @@ static void web_crawler_request_enter_callback(void *context, uint32_t index)
     case 0: // URL
         web_crawler_setting_item_path_clicked(context, index);
         break;
+    case 1:
+        // HTTP Method
+        break;
+    case 2:
+        // Headers
+        web_crawler_setting_item_headers_clicked(context, index);
+        break;
+    case 3:
+        // Payload
+        web_crawler_setting_item_payload_clicked(context, index);
+        break;
     default:
         FURI_LOG_E(TAG, "Unknown configuration item index");
         break;
@@ -313,16 +386,71 @@ static void web_crawler_set_path_updated(void *context)
         variable_item_set_current_value_text(app->path_item, app->path);
 
         // Save the URL to the settings
-        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type);
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
+    }
 
-        // send to UART
-        if (!flipper_http_save_wifi(app->ssid, app->password))
-        {
-            FURI_LOG_E(TAG, "Failed to save wifi settings via UART");
-            FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
-        }
+    // Return to the Configure view
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListRequest);
+}
 
-        FURI_LOG_D(TAG, "URL saved: %s", app->path);
+/**
+ * @brief      Callback for when the user finishes entering the headers
+ * @param      context   The context - WebCrawlerApp object.
+ */
+static void web_crawler_set_headers_updated(void *context)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->temp_buffer_headers || !app->temp_buffer_size_headers || !app->headers_item)
+    {
+        FURI_LOG_E(TAG, "Invalid headers buffer");
+        return;
+    }
+    // Store the entered headers from temp_buffer_headers to headers
+    strncpy(app->headers, app->temp_buffer_headers, app->temp_buffer_size_headers - 1);
+
+    if (app->headers_item)
+    {
+        variable_item_set_current_value_text(app->headers_item, app->headers);
+
+        // Save the headers to the settings
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
+    }
+
+    // Return to the Configure view
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListRequest);
+}
+
+/**
+ * @brief      Callback for when the user finishes entering the payload.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+static void web_crawler_set_payload_updated(void *context)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->temp_buffer_payload || !app->temp_buffer_size_payload || !app->payload_item)
+    {
+        FURI_LOG_E(TAG, "Invalid payload buffer");
+        return;
+    }
+    // Store the entered payload from temp_buffer_payload to payload
+    strncpy(app->payload, app->temp_buffer_payload, app->temp_buffer_size_payload - 1);
+
+    if (app->payload_item)
+    {
+        variable_item_set_current_value_text(app->payload_item, app->payload);
+
+        // Save the payload to the settings
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
     }
 
     // Return to the Configure view
@@ -354,7 +482,7 @@ static void web_crawler_set_ssid_updated(void *context)
         variable_item_set_current_value_text(app->ssid_item, app->ssid);
 
         // Save the SSID to the settings
-        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type);
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
 
         // send to UART
         if (!flipper_http_save_wifi(app->ssid, app->password))
@@ -395,7 +523,7 @@ static void web_crawler_set_password_update(void *context)
         variable_item_set_current_value_text(app->password_item, app->password);
 
         // Save the Password to the settings
-        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type);
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
 
         // send to UART
         if (!flipper_http_save_wifi(app->ssid, app->password))
@@ -438,6 +566,9 @@ static void web_crawler_set_file_type_update(void *context)
     if (app->file_type_item)
     {
         variable_item_set_current_value_text(app->file_type_item, app->file_type);
+
+        // Save the File Type to the settings
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
     }
 
     rename_received_data(app->file_rename, app->file_rename, app->file_type, old_file_type);
@@ -477,6 +608,9 @@ static void web_crawler_set_file_rename_update(void *context)
     if (app->file_rename_item)
     {
         variable_item_set_current_value_text(app->file_rename_item, app->file_rename);
+
+        // Save the File Rename to the settings
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type, app->http_method, app->headers, app->payload);
     }
 
     rename_received_data(old_name, app->file_rename, app->file_type, app->file_type);
@@ -506,7 +640,7 @@ static void web_crawler_setting_item_path_clicked(void *context, uint32_t index)
 
     UNUSED(index);
     // Set up the text input
-    text_input_set_header_text(app->text_input_path, "Enter URL");
+    uart_text_input_set_header_text(app->text_input_path, "Enter URL");
 
     // Initialize temp_buffer with existing path
     if (app->path && strlen(app->path) > 0)
@@ -515,14 +649,14 @@ static void web_crawler_setting_item_path_clicked(void *context, uint32_t index)
     }
     else
     {
-        strncpy(app->temp_buffer_path, "https://www.google.com/", app->temp_buffer_size_path - 1);
+        strncpy(app->temp_buffer_path, "https://httpbin.org/get", app->temp_buffer_size_path - 1);
     }
 
     app->temp_buffer_path[app->temp_buffer_size_path - 1] = '\0';
 
     // Configure the text input
     bool clear_previous_text = false;
-    text_input_set_result_callback(
+    uart_text_input_set_result_callback(
         app->text_input_path,
         web_crawler_set_path_updated,
         app,
@@ -532,13 +666,129 @@ static void web_crawler_setting_item_path_clicked(void *context, uint32_t index)
 
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
-        text_input_get_view(app->text_input_path),
+        uart_text_input_get_view(app->text_input_path),
         web_crawler_back_to_request_callback);
 
     // Show text input dialog
     view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInput);
 }
 
+/**
+ * @brief      Handler for headers configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+static void web_crawler_setting_item_headers_clicked(void *context, uint32_t index)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    UNUSED(index);
+    if (!app->text_input_headers)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
+    if (!app->headers)
+    {
+        FURI_LOG_E(TAG, "Headers is NULL");
+        return;
+    }
+    if (!app->temp_buffer_headers)
+    {
+        FURI_LOG_E(TAG, "Temp buffer headers is NULL");
+        return;
+    }
+    // Set up the text input
+    uart_text_input_set_header_text(app->text_input_headers, "Enter Headers");
+
+    // Initialize temp_buffer with existing headers
+    if (app->headers && strlen(app->headers) > 0)
+    {
+        strncpy(app->temp_buffer_headers, app->headers, app->temp_buffer_size_headers - 1);
+    }
+    else
+    {
+        strncpy(app->temp_buffer_headers, "{\"Content-Type\":\"application/json\",\"key\":\"value\"}", app->temp_buffer_size_headers - 1);
+    }
+
+    app->temp_buffer_headers[app->temp_buffer_size_headers - 1] = '\0';
+
+    // Configure the text input
+    bool clear_previous_text = false;
+    uart_text_input_set_result_callback(
+        app->text_input_headers,
+        web_crawler_set_headers_updated,
+        app,
+        app->temp_buffer_headers,
+        app->temp_buffer_size_headers,
+        clear_previous_text);
+
+    // Set the previous callback to return to Configure screen
+    view_set_previous_callback(
+        uart_text_input_get_view(app->text_input_headers),
+        web_crawler_back_to_request_callback);
+
+    // Show text input dialog
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInputHeaders);
+}
+
+/**
+ * @brief      Handler for payload configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+static void web_crawler_setting_item_payload_clicked(void *context, uint32_t index)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    UNUSED(index);
+    if (!app->text_input_payload)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
+    // Set up the text input
+    uart_text_input_set_header_text(app->text_input_payload, "Enter Payload");
+
+    // Initialize temp_buffer with existing payload
+    if (app->payload && strlen(app->payload) > 0)
+    {
+        strncpy(app->temp_buffer_payload, app->payload, app->temp_buffer_size_payload - 1);
+    }
+    else
+    {
+        strncpy(app->temp_buffer_payload, "{\"key\":\"value\"}", app->temp_buffer_size_payload - 1);
+    }
+
+    app->temp_buffer_payload[app->temp_buffer_size_payload - 1] = '\0';
+
+    // Configure the text input
+    bool clear_previous_text = false;
+    uart_text_input_set_result_callback(
+        app->text_input_payload,
+        web_crawler_set_payload_updated,
+        app,
+        app->temp_buffer_payload,
+        app->temp_buffer_size_payload,
+        clear_previous_text);
+
+    // Set the previous callback to return to Configure screen
+    view_set_previous_callback(
+        uart_text_input_get_view(app->text_input_payload),
+        web_crawler_back_to_request_callback);
+
+    // Show text input dialog
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInputPayload);
+}
+
 /**
  * @brief      Handler for SSID configuration item click.
  * @param      context  The context - WebCrawlerApp object.
@@ -559,7 +809,7 @@ static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index)
         return;
     }
     // Set up the text input
-    text_input_set_header_text(app->text_input_ssid, "Enter SSID");
+    uart_text_input_set_header_text(app->text_input_ssid, "Enter SSID");
 
     // Initialize temp_buffer with existing SSID
     if (app->ssid && strlen(app->ssid) > 0)
@@ -568,14 +818,14 @@ static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index)
     }
     else
     {
-        strncpy(app->temp_buffer_ssid, "SSID-2G-", app->temp_buffer_size_ssid - 1);
+        strncpy(app->temp_buffer_ssid, "", app->temp_buffer_size_ssid - 1);
     }
 
     app->temp_buffer_ssid[app->temp_buffer_size_ssid - 1] = '\0';
 
     // Configure the text input
     bool clear_previous_text = false;
-    text_input_set_result_callback(
+    uart_text_input_set_result_callback(
         app->text_input_ssid,
         web_crawler_set_ssid_updated,
         app,
@@ -585,7 +835,7 @@ static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index)
 
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
-        text_input_get_view(app->text_input_ssid),
+        uart_text_input_get_view(app->text_input_ssid),
         web_crawler_back_to_wifi_callback);
 
     // Show text input dialog
@@ -612,7 +862,7 @@ static void web_crawler_setting_item_password_clicked(void *context, uint32_t in
         return;
     }
     // Set up the text input
-    text_input_set_header_text(app->text_input_password, "Enter Password");
+    uart_text_input_set_header_text(app->text_input_password, "Enter Password");
 
     // Initialize temp_buffer with existing password
     strncpy(app->temp_buffer_password, app->password, app->temp_buffer_size_password - 1);
@@ -620,7 +870,7 @@ static void web_crawler_setting_item_password_clicked(void *context, uint32_t in
 
     // Configure the text input
     bool clear_previous_text = false;
-    text_input_set_result_callback(
+    uart_text_input_set_result_callback(
         app->text_input_password,
         web_crawler_set_password_update,
         app,
@@ -630,7 +880,7 @@ static void web_crawler_setting_item_password_clicked(void *context, uint32_t in
 
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
-        text_input_get_view(app->text_input_password),
+        uart_text_input_get_view(app->text_input_password),
         web_crawler_back_to_wifi_callback);
 
     // Show text input dialog
@@ -657,7 +907,7 @@ static void web_crawler_setting_item_file_type_clicked(void *context, uint32_t i
         return;
     }
     // Set up the text input
-    text_input_set_header_text(app->text_input_file_type, "Enter File Type");
+    uart_text_input_set_header_text(app->text_input_file_type, "Enter File Type");
 
     // Initialize temp_buffer with existing file_type
     if (app->file_type && strlen(app->file_type) > 0)
@@ -673,7 +923,7 @@ static void web_crawler_setting_item_file_type_clicked(void *context, uint32_t i
 
     // Configure the text input
     bool clear_previous_text = false;
-    text_input_set_result_callback(
+    uart_text_input_set_result_callback(
         app->text_input_file_type,
         web_crawler_set_file_type_update,
         app,
@@ -683,7 +933,7 @@ static void web_crawler_setting_item_file_type_clicked(void *context, uint32_t i
 
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
-        text_input_get_view(app->text_input_file_type),
+        uart_text_input_get_view(app->text_input_file_type),
         web_crawler_back_to_file_callback);
 
     // Show text input dialog
@@ -710,7 +960,7 @@ static void web_crawler_setting_item_file_rename_clicked(void *context, uint32_t
         return;
     }
     // Set up the text input
-    text_input_set_header_text(app->text_input_file_rename, "Enter File Rename");
+    uart_text_input_set_header_text(app->text_input_file_rename, "Enter File Rename");
 
     // Initialize temp_buffer with existing file_rename
     if (app->file_rename && strlen(app->file_rename) > 0)
@@ -726,7 +976,7 @@ static void web_crawler_setting_item_file_rename_clicked(void *context, uint32_t
 
     // Configure the text input
     bool clear_previous_text = false;
-    text_input_set_result_callback(
+    uart_text_input_set_result_callback(
         app->text_input_file_rename,
         web_crawler_set_file_rename_update,
         app,
@@ -736,7 +986,7 @@ static void web_crawler_setting_item_file_rename_clicked(void *context, uint32_t
 
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
-        text_input_get_view(app->text_input_file_rename),
+        uart_text_input_get_view(app->text_input_file_rename),
         web_crawler_back_to_file_callback);
 
     // Show text input dialog

+ 27 - 5
web_crawler_e.h

@@ -16,6 +16,7 @@
 #include <storage/storage.h>
 
 #define TAG "WebCrawler"
+static char *http_method_names[] = {"GET", "POST", "PUT"};
 
 // Define the submenu items for our WebCrawler application
 typedef enum
@@ -44,6 +45,8 @@ typedef enum
     WebCrawlerViewFileRead,                // Text input for File Read
     WebCrawlerViewTextInputFileType,       // Text input for File Type
     WebCrawlerViewTextInputFileRename,     // Text input for File Rename
+    WebCrawlerViewTextInputHeaders,        // Text input for Headers
+    WebCrawlerViewTextInputPayload,        // Text input for Payload
     WebCrawlerViewFileDelete,              // File Delete
 } WebCrawlerViewIndex;
 
@@ -57,11 +60,14 @@ typedef struct
     Submenu *submenu_config;
     Widget *widget_about;
 
-    TextInput *text_input_path;
-    TextInput *text_input_ssid;
-    TextInput *text_input_password;
-    TextInput *text_input_file_type;
-    TextInput *text_input_file_rename;
+    UART_TextInput *text_input_path;
+    UART_TextInput *text_input_ssid;
+    UART_TextInput *text_input_password;
+    UART_TextInput *text_input_file_type;
+    UART_TextInput *text_input_file_rename;
+    //
+    UART_TextInput *text_input_headers;
+    UART_TextInput *text_input_payload;
 
     Widget *widget_file_read;
     Widget *widget_file_delete;
@@ -77,12 +83,19 @@ typedef struct
     VariableItem *file_rename_item;
     VariableItem *file_read_item;
     VariableItem *file_delete_item;
+    //
+    VariableItem *http_method_item;
+    VariableItem *headers_item;
+    VariableItem *payload_item;
 
     char *path;
     char *ssid;
     char *password;
     char *file_type;
     char *file_rename;
+    char *http_method;
+    char *headers;
+    char *payload;
 
     char *temp_buffer_path;
     uint32_t temp_buffer_size_path;
@@ -98,5 +111,14 @@ typedef struct
 
     char *temp_buffer_file_rename;
     uint32_t temp_buffer_size_file_rename;
+
+    char *temp_buffer_http_method;
+    uint32_t temp_buffer_size_http_method;
+
+    char *temp_buffer_headers;
+    uint32_t temp_buffer_size_headers;
+
+    char *temp_buffer_payload;
+    uint32_t temp_buffer_size_payload;
 } WebCrawlerApp;
 #endif // WEB_CRAWLER_E

+ 62 - 6
web_crawler_free.h

@@ -66,6 +66,42 @@ static void free_buffers(WebCrawlerApp *app)
         free(app->temp_buffer_file_rename);
         app->temp_buffer_file_rename = NULL;
     }
+
+    if (app->temp_buffer_http_method)
+    {
+        free(app->temp_buffer_http_method);
+        app->temp_buffer_http_method = NULL;
+    }
+
+    if (app->temp_buffer_headers)
+    {
+        free(app->temp_buffer_headers);
+        app->temp_buffer_headers = NULL;
+    }
+
+    if (app->temp_buffer_payload)
+    {
+        free(app->temp_buffer_payload);
+        app->temp_buffer_payload = NULL;
+    }
+
+    if (app->http_method)
+    {
+        free(app->http_method);
+        app->http_method = NULL;
+    }
+
+    if (app->headers)
+    {
+        free(app->headers);
+        app->headers = NULL;
+    }
+
+    if (app->payload)
+    {
+        free(app->payload);
+        app->payload = NULL;
+    }
 }
 
 static void free_resources(WebCrawlerApp *app)
@@ -112,15 +148,19 @@ static void free_all(WebCrawlerApp *app, char *reason)
     if (app->widget_file_delete)
         widget_free(app->widget_file_delete);
     if (app->text_input_path)
-        text_input_free(app->text_input_path);
+        uart_text_input_free(app->text_input_path);
     if (app->text_input_ssid)
-        text_input_free(app->text_input_ssid);
+        uart_text_input_free(app->text_input_ssid);
     if (app->text_input_password)
-        text_input_free(app->text_input_password);
+        uart_text_input_free(app->text_input_password);
     if (app->text_input_file_type)
-        text_input_free(app->text_input_file_type);
+        uart_text_input_free(app->text_input_file_type);
     if (app->text_input_file_rename)
-        text_input_free(app->text_input_file_rename);
+        uart_text_input_free(app->text_input_file_rename);
+    if (app->text_input_headers)
+        uart_text_input_free(app->text_input_headers);
+    if (app->text_input_payload)
+        uart_text_input_free(app->text_input_payload);
 
     furi_record_close(RECORD_GUI);
     free_resources(app);
@@ -175,7 +215,7 @@ static void web_crawler_app_free(WebCrawlerApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewSubmenuConfig);
         submenu_free(app->submenu_config);
     }
-    // Remove and free Configuration screen
+    // Remove and free Variable Item Lists
     if (app->variable_item_list_wifi)
     {
         view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewVariableItemListWifi);
@@ -198,6 +238,22 @@ static void web_crawler_app_free(WebCrawlerApp *app)
     view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputPassword);
     view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputFileType);
     view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputFileRename);
+    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputHeaders);
+    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputPayload);
+    if (app->text_input_path)
+        uart_text_input_free(app->text_input_path);
+    if (app->text_input_ssid)
+        uart_text_input_free(app->text_input_ssid);
+    if (app->text_input_password)
+        uart_text_input_free(app->text_input_password);
+    if (app->text_input_file_type)
+        uart_text_input_free(app->text_input_file_type);
+    if (app->text_input_file_rename)
+        uart_text_input_free(app->text_input_file_rename);
+    if (app->text_input_headers)
+        uart_text_input_free(app->text_input_headers);
+    if (app->text_input_payload)
+        uart_text_input_free(app->text_input_payload);
 
     // Remove and free Widgets
     if (app->widget_about)

+ 151 - 20
web_crawler_i.h

@@ -1,5 +1,6 @@
 #ifndef WEB_CRAWLER_I_H
 #define WEB_CRAWLER_I_H
+
 /**
  * @brief      Function to allocate resources for the WebCrawlerApp.
  * @return     Pointer to the initialized WebCrawlerApp, or NULL on failure.
@@ -42,7 +43,6 @@ WebCrawlerApp *web_crawler_app_alloc()
     if (!flipper_http_init(flipper_http_rx_callback, app))
     {
         FURI_LOG_E(TAG, "Failed to initialize UART");
-        free_all(app, "Failed to initialize UART");
         return NULL;
     }
 
@@ -149,13 +149,72 @@ WebCrawlerApp *web_crawler_app_alloc()
     }
     app->file_rename[0] = '\0';
 
+    // Allocate and initialize temp_buffer_http_method
+    app->temp_buffer_size_http_method = 128;
+    app->temp_buffer_http_method = malloc(app->temp_buffer_size_http_method);
+    if (!app->temp_buffer_http_method)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate temp_buffer_http_method");
+        free_all(app, "Failed to allocate temp_buffer_http_method");
+        return NULL;
+    }
+
+    // Allocate http_method
+    app->http_method = malloc(app->temp_buffer_size_http_method);
+    if (!app->http_method)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate http_method");
+        free_all(app, "Failed to allocate http_method");
+        return NULL;
+    }
+
+    // Allocate and initialize temp_buffer_headers
+    app->temp_buffer_size_headers = 256;
+    app->temp_buffer_headers = malloc(app->temp_buffer_size_headers);
+    if (!app->temp_buffer_headers)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate temp_buffer_headers");
+        free_all(app, "Failed to allocate temp_buffer_headers");
+        return NULL;
+    }
+
+    // Allocate headers
+    app->headers = malloc(app->temp_buffer_size_headers);
+    if (!app->headers)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate headers");
+        free_all(app, "Failed to allocate headers");
+        return NULL;
+    }
+
+    // Allocate and initialize temp_buffer_payload
+    app->temp_buffer_size_payload = 128;
+    app->temp_buffer_payload = malloc(app->temp_buffer_size_payload);
+    if (!app->temp_buffer_payload)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate temp_buffer_payload");
+        free_all(app, "Failed to allocate temp_buffer_payload");
+        return NULL;
+    }
+
+    // Allocate payload
+    app->payload = malloc(app->temp_buffer_size_payload);
+    if (!app->payload)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate payload");
+        free_all(app, "Failed to allocate payload");
+        return NULL;
+    }
+
     // Allocate TextInput views
-    app->text_input_path = text_input_alloc();
-    app->text_input_ssid = text_input_alloc();
-    app->text_input_password = text_input_alloc();
-    app->text_input_file_type = text_input_alloc();
-    app->text_input_file_rename = text_input_alloc();
-    if (!app->text_input_path || !app->text_input_ssid || !app->text_input_password || !app->text_input_file_type || !app->text_input_file_rename)
+    app->text_input_path = uart_text_input_alloc();
+    app->text_input_ssid = uart_text_input_alloc();
+    app->text_input_password = uart_text_input_alloc();
+    app->text_input_file_type = uart_text_input_alloc();
+    app->text_input_file_rename = uart_text_input_alloc();
+    app->text_input_headers = uart_text_input_alloc();
+    app->text_input_payload = uart_text_input_alloc();
+    if (!app->text_input_path || !app->text_input_ssid || !app->text_input_password || !app->text_input_file_type || !app->text_input_file_rename || !app->text_input_headers || !app->text_input_payload)
     {
         FURI_LOG_E(TAG, "Failed to allocate TextInput");
         free_all(app, "Failed to allocate TextInput");
@@ -163,18 +222,22 @@ WebCrawlerApp *web_crawler_app_alloc()
     }
 
     // Add TextInput views with unique view IDs
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInput, text_input_get_view(app->text_input_path));
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputSSID, text_input_get_view(app->text_input_ssid));
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputPassword, text_input_get_view(app->text_input_password));
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputFileType, text_input_get_view(app->text_input_file_type));
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputFileRename, text_input_get_view(app->text_input_file_rename));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInput, uart_text_input_get_view(app->text_input_path));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputSSID, uart_text_input_get_view(app->text_input_ssid));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputPassword, uart_text_input_get_view(app->text_input_password));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputFileType, uart_text_input_get_view(app->text_input_file_type));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputFileRename, uart_text_input_get_view(app->text_input_file_rename));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputHeaders, uart_text_input_get_view(app->text_input_headers));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputPayload, uart_text_input_get_view(app->text_input_payload));
 
     // Set previous callback for TextInput views to return to Configure screen
-    view_set_previous_callback(text_input_get_view(app->text_input_path), web_crawler_back_to_request_callback);
-    view_set_previous_callback(text_input_get_view(app->text_input_ssid), web_crawler_back_to_wifi_callback);
-    view_set_previous_callback(text_input_get_view(app->text_input_password), web_crawler_back_to_wifi_callback);
-    view_set_previous_callback(text_input_get_view(app->text_input_file_type), web_crawler_back_to_file_callback);
-    view_set_previous_callback(text_input_get_view(app->text_input_file_rename), web_crawler_back_to_file_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_path), web_crawler_back_to_request_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_headers), web_crawler_back_to_request_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_payload), web_crawler_back_to_request_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_ssid), web_crawler_back_to_wifi_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_password), web_crawler_back_to_wifi_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_file_type), web_crawler_back_to_file_callback);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_file_rename), web_crawler_back_to_file_callback);
 
     // Allocate Configuration screen
     app->variable_item_list_wifi = variable_item_list_alloc();
@@ -192,6 +255,9 @@ WebCrawlerApp *web_crawler_app_alloc()
 
     // Add item to the configuration screen
     app->path_item = variable_item_list_add(app->variable_item_list_request, "Path", 0, NULL, NULL);
+    app->http_method_item = variable_item_list_add(app->variable_item_list_request, "HTTP Method", 3, web_crawler_http_method_change, app);
+    app->headers_item = variable_item_list_add(app->variable_item_list_request, "Headers", 0, NULL, NULL);
+    app->payload_item = variable_item_list_add(app->variable_item_list_request, "Payload", 0, NULL, NULL);
     //
     app->ssid_item = variable_item_list_add(app->variable_item_list_wifi, "SSID", 0, NULL, NULL);         // index 0
     app->password_item = variable_item_list_add(app->variable_item_list_wifi, "Password", 0, NULL, NULL); // index 1
@@ -201,13 +267,16 @@ WebCrawlerApp *web_crawler_app_alloc()
     app->file_rename_item = variable_item_list_add(app->variable_item_list_file, "Rename File", 0, NULL, NULL); // index 2
     app->file_delete_item = variable_item_list_add(app->variable_item_list_file, "Delete File", 0, NULL, NULL); // index 3
 
-    if (!app->ssid_item || !app->password_item || !app->file_type_item || !app->file_rename_item || !app->path_item || !app->file_read_item || !app->file_delete_item)
+    if (!app->ssid_item || !app->password_item || !app->file_type_item || !app->file_rename_item || !app->path_item || !app->file_read_item || !app->file_delete_item || !app->http_method_item || !app->headers_item || !app->payload_item)
     {
         free_all(app, "Failed to add items to VariableItemList");
         return NULL;
     }
 
     variable_item_set_current_value_text(app->path_item, "");        // Initialize
+    variable_item_set_current_value_text(app->http_method_item, ""); // Initialize
+    variable_item_set_current_value_text(app->headers_item, "");     // Initialize
+    variable_item_set_current_value_text(app->payload_item, "");     // Initialize
     variable_item_set_current_value_text(app->ssid_item, "");        // Initialize
     variable_item_set_current_value_text(app->password_item, "");    // Initialize
     variable_item_set_current_value_text(app->file_type_item, "");   // Initialize
@@ -242,7 +311,7 @@ WebCrawlerApp *web_crawler_app_alloc()
     }
 
     // Set header
-    submenu_set_header(app->submenu_main, "Web Crawler v0.3");
+    submenu_set_header(app->submenu_main, "Web Crawler v0.4");
     submenu_set_header(app->submenu_config, "Settings");
 
     // Add items
@@ -322,7 +391,24 @@ WebCrawlerApp *web_crawler_app_alloc()
     view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewFileDelete, widget_get_view(app->widget_file_delete));
 
     // Load Settings and Update Views
-    if (!load_settings(app->path, app->temp_buffer_size_path, app->ssid, app->temp_buffer_size_ssid, app->password, app->temp_buffer_size_password, app->file_rename, app->temp_buffer_size_file_rename, app->file_type, app->temp_buffer_size_file_type, app))
+    if (!load_settings(
+            app->path,
+            app->temp_buffer_size_path,
+            app->ssid,
+            app->temp_buffer_size_ssid,
+            app->password,
+            app->temp_buffer_size_password,
+            app->file_rename,
+            app->temp_buffer_size_file_rename,
+            app->file_type,
+            app->temp_buffer_size_file_type,
+            app->http_method,
+            app->temp_buffer_size_http_method,
+            app->headers,
+            app->temp_buffer_size_headers,
+            app->payload,
+            app->temp_buffer_size_payload,
+            app))
     {
         FURI_LOG_E(TAG, "Failed to load settings");
     }
@@ -347,6 +433,51 @@ WebCrawlerApp *web_crawler_app_alloc()
             variable_item_set_current_value_text(app->ssid_item, ""); // Initialize
         }
 
+        if (app->file_type[0] != '\0')
+        {
+            variable_item_set_current_value_text(app->file_type_item, app->file_type);
+        }
+        else
+        {
+            variable_item_set_current_value_text(app->file_type_item, ".txt"); // Initialize
+        }
+
+        if (app->file_rename[0] != '\0')
+        {
+            variable_item_set_current_value_text(app->file_rename_item, app->file_rename);
+        }
+        else
+        {
+            variable_item_set_current_value_text(app->file_rename_item, "received_data"); // Initialize
+        }
+
+        if (app->http_method[0] != '\0')
+        {
+            variable_item_set_current_value_text(app->http_method_item, app->http_method);
+        }
+        else
+        {
+            variable_item_set_current_value_text(app->http_method_item, "GET"); // Initialize
+        }
+
+        if (app->headers[0] != '\0')
+        {
+            variable_item_set_current_value_text(app->headers_item, app->headers);
+        }
+        else
+        {
+            variable_item_set_current_value_text(app->headers_item, "{\n\t\"Content-Type\": \"application/json\"\n}"); // Initialize
+        }
+
+        if (app->payload[0] != '\0')
+        {
+            variable_item_set_current_value_text(app->payload_item, app->payload);
+        }
+        else
+        {
+            variable_item_set_current_value_text(app->payload_item, "{\n\t\"key\": \"value\"\n}"); // Initialize
+        }
+
         // Password handling can be omitted for security or handled securely
     }
 

+ 78 - 1
web_crawler_storage.h

@@ -17,7 +17,15 @@
 #define TRUNCATION_NOTICE "\n\n[Data truncated due to size limits]"
 
 // Function to save settings: path, SSID, and password
-static void save_settings(const char *path, const char *ssid, const char *password, const char *file_rename, const char *file_type)
+static void save_settings(
+    const char *path,
+    const char *ssid,
+    const char *password,
+    const char *file_rename,
+    const char *file_type,
+    const char *http_method,
+    const char *headers,
+    const char *payload)
 {
     // Create the directory for saving settings
     char directory_path[256];
@@ -82,6 +90,30 @@ static void save_settings(const char *path, const char *ssid, const char *passwo
         FURI_LOG_E(TAG, "Failed to write file type");
     }
 
+    // Save the http method length and data
+    size_t http_method_length = strlen(http_method) + 1; // Include null terminator
+    if (storage_file_write(file, &http_method_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, http_method, http_method_length) != http_method_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write http method");
+    }
+
+    // Save the headers length and data
+    size_t headers_length = strlen(headers) + 1; // Include null terminator
+    if (storage_file_write(file, &headers_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, headers, headers_length) != headers_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write headers");
+    }
+
+    // Save the payload length and data
+    size_t payload_length = strlen(payload) + 1; // Include null terminator
+    if (storage_file_write(file, &payload_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, payload, payload_length) != payload_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write payload");
+    }
+
     storage_file_close(file);
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);
@@ -99,6 +131,12 @@ static bool load_settings(
     size_t file_rename_size,
     char *file_type,
     size_t file_type_size,
+    char *http_method,
+    size_t http_method_size,
+    char *headers,
+    size_t headers_size,
+    char *payload,
+    size_t payload_size,
     WebCrawlerApp *app)
 {
     if (!app)
@@ -182,12 +220,51 @@ static bool load_settings(
     }
     file_type[file_type_length - 1] = '\0'; // Ensure null-termination
 
+    // Load the http method
+    size_t http_method_length;
+    if (storage_file_read(file, &http_method_length, sizeof(size_t)) != sizeof(size_t) || http_method_length > http_method_size ||
+        storage_file_read(file, http_method, http_method_length) != http_method_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read http method");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Load the headers
+    size_t headers_length;
+    if (storage_file_read(file, &headers_length, sizeof(size_t)) != sizeof(size_t) || headers_length > headers_size ||
+        storage_file_read(file, headers, headers_length) != headers_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read headers");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Load the payload
+    size_t payload_length;
+    if (storage_file_read(file, &payload_length, sizeof(size_t)) != sizeof(size_t) || payload_length > payload_size ||
+        storage_file_read(file, payload, payload_length) != payload_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read payload");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
     // set the path, ssid, and password
     strncpy(app->path, path, path_size);
     strncpy(app->ssid, ssid, ssid_size);
     strncpy(app->password, password, password_size);
     strncpy(app->file_rename, file_rename, file_rename_size);
     strncpy(app->file_type, file_type, file_type_size);
+    strncpy(app->http_method, http_method, http_method_size);
+    strncpy(app->headers, headers, headers_size);
+    strncpy(app->payload, payload, payload_size);
 
     storage_file_close(file);
     storage_file_free(file);