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

Merge web_crawler from https://github.com/jblanked/WebCrawler-FlipperZero

Willy-JL 1 год назад
Родитель
Сommit
20148ddfe4

+ 1 - 0
web_crawler/CHANGELOG.md

@@ -1,5 +1,6 @@
 ## 0.7
 - Improved memory allocation
+- App restructured
 
 ## 0.6
 - Added a DOWNLOAD method, which downloads the file at the specified path. This is best used for downloading images, binary files, and other non-text files.

+ 0 - 21
web_crawler/LICENSE

@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2024 jblanked
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.

+ 9 - 2
web_crawler/README.md

@@ -12,7 +12,7 @@
 - Video tutorial: https://www.youtube.com/watch?v=lkui2Smckq4
 
 ## Features
-- **Configurable Request**: Specify the URL of the website you want to send a HTTP request to.
+- **Configurable Request**: Specify the URL of the website you want to send a HTTP request to or download (tested up to 100Mbs)
 - **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.
 
@@ -33,6 +33,9 @@
    - https://httpbin.org/post Returns POST data.
    - https://httpbin.org/put Returns PUT data.
    - https://httpbin.org/delete Returns DELETE data.
+   - https://httpbin.org/bytes/1024 Returns BYTES data (DOWNLOAD method)
+   - https://proof.ovh.net/files/1Mb.dat Returns BYTES data (DOWNLOAD method - it can download the whole file)
+   - https://proof.ovh.net/files/10Mb.dat Returns BYTES data (DOWNLOAD method - it can download the whole file)
 
 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.
@@ -49,7 +52,7 @@
    - Enter the complete URL of the website you intend to crawl (e.g., https://www.example.com/).
 
 2. **HTTP Method**
-   - Choose between GET, POST, DELETE, and PUT.
+   - Choose between GET, POST, DELETE, PUT, and DOWNLOAD.
 
 3. **Headers**
    - Add your required headers to be used in your HTTP requests
@@ -81,4 +84,8 @@ The Web Crawler app uses logging to help identify issues:
 
 Connect your Flipper Zero to a computer and use a serial terminal to view these logs for detailed troubleshooting.
 
+## Known Issues
+1. **Screen Delay**: Occasionally, the Run screen may get stuck on "Receiving Data".
+   - If it takes longer than 10 seconds, restart your Flipper Zero.
+
 *Happy Crawling! 🕷️* 

+ 1 - 6
web_crawler/web_crawler_i.h → web_crawler/alloc/web_crawler_alloc.c

@@ -1,5 +1,4 @@
-#ifndef WEB_CRAWLER_I_H
-#define WEB_CRAWLER_I_H
+#include <alloc/web_crawler_alloc.h>
 
 /**
  * @brief      Function to allocate resources for the WebCrawlerApp.
@@ -427,12 +426,8 @@ WebCrawlerApp* web_crawler_app_alloc() {
         // Password handling can be omitted for security or handled securely
     }
 
-    app_instance = app;
-
     // Start with the Submenu view
     view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewSubmenuMain);
 
     return app;
 }
-
-#endif // WEB_CRAWLER_I_H

+ 14 - 0
web_crawler/alloc/web_crawler_alloc.h

@@ -0,0 +1,14 @@
+#ifndef WEB_CRAWLER_I_H
+#define WEB_CRAWLER_I_H
+
+#include <web_crawler.h>
+#include <callback/web_crawler_callback.h>
+#include <flip_storage/web_crawler_storage.h>
+
+/**
+ * @brief      Function to allocate resources for the WebCrawlerApp.
+ * @return     Pointer to the initialized WebCrawlerApp, or NULL on failure.
+ */
+WebCrawlerApp* web_crawler_app_alloc();
+
+#endif // WEB_CRAWLER_I_H

+ 6 - 10
web_crawler/app.c

@@ -1,9 +1,5 @@
-#include <web_crawler_e.h>
-#include <flipper_http.h>
-#include <web_crawler_storage.h>
-#include <web_crawler_free.h>
-#include <web_crawler_callback.h>
-#include <web_crawler_i.h>
+#include <web_crawler.h>
+#include <alloc/web_crawler_alloc.h>
 /**
  * @brief      Entry point for the WebCrawler application.
  * @param      p  Input parameter - unused
@@ -12,8 +8,8 @@
 int32_t web_crawler_app(void* p) {
     UNUSED(p);
 
-    WebCrawlerApp* app = web_crawler_app_alloc();
-    if(!app) {
+    app_instance = web_crawler_app_alloc();
+    if(!app_instance) {
         FURI_LOG_E(TAG, "Failed to allocate WebCrawlerApp");
         return -1;
     }
@@ -24,10 +20,10 @@ int32_t web_crawler_app(void* p) {
     }
 
     // Run the application
-    view_dispatcher_run(app->view_dispatcher);
+    view_dispatcher_run(app_instance->view_dispatcher);
 
     // Free resources after the application loop ends
-    web_crawler_app_free(app);
+    web_crawler_app_free(app_instance);
 
     return 0;
 }

+ 127 - 90
web_crawler/web_crawler_callback.h → web_crawler/callback/web_crawler_callback.c

@@ -1,21 +1,62 @@
-// web_crawler_callback.h
-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);
-static void web_crawler_setting_item_file_rename_clicked(void* context, uint32_t index);
-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) {
+#include <callback/web_crawler_callback.h>
+bool sent_http_request = false;
+bool get_success = false;
+bool already_success = false;
+
+void web_crawler_draw_error(Canvas* canvas) {
+    if(!canvas) {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    canvas_clear(canvas);
+    canvas_set_font(canvas, FontSecondary);
+
+    if(fhttp.state == INACTIVE) {
+        canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
+        canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
+        canvas_draw_str(canvas, 0, 32, "If your board is connected,");
+        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
+        canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
+        canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
+        return;
+    }
+
+    if(fhttp.last_response) {
+        if(strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") !=
+           NULL) {
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            return;
+        }
+        if(strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL) {
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            return;
+        }
+        if(strstr(
+               fhttp.last_response, "[ERROR] GET request failed with error: connection refused") !=
+           NULL) {
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Connection refused.");
+            canvas_draw_str(canvas, 0, 50, "Choose another URL.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            return;
+        }
+
+        canvas_draw_str(canvas, 0, 10, "[ERROR] Failed to sync data.");
+        canvas_draw_str(canvas, 0, 30, "If this is your third attempt,");
+        canvas_draw_str(canvas, 0, 40, "it's likely your URL is not");
+        canvas_draw_str(canvas, 0, 50, "compabilbe or correct.");
+        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        return;
+    }
+
+    canvas_draw_str(canvas, 0, 10, "HTTP request failed.");
+    canvas_draw_str(canvas, 0, 20, "Press BACK to return.");
+}
+
+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]);
 
@@ -39,7 +80,7 @@ static void web_crawler_http_method_change(VariableItem* item) {
     }
 }
 
-static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
+void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
     UNUSED(context);
     if(!app_instance) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -53,28 +94,31 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
     canvas_clear(canvas);
     canvas_set_font(canvas, FontSecondary);
 
-    if(fhttp.state == INACTIVE) {
-        canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
-        canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
-        canvas_draw_str(canvas, 0, 32, "If your board is connected,");
-        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
-        canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
-        canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
+    if(fhttp.state == INACTIVE || fhttp.state == ISSUE) {
+        web_crawler_draw_error(canvas);
         return;
     }
 
     if(app_instance->path) {
         if(!sent_http_request) {
-            snprintf(
-                fhttp.file_path,
-                sizeof(fhttp.file_path),
-                STORAGE_EXT_PATH_PREFIX "/apps_data/web_crawler/received_data.txt");
-
-            fhttp.save_received_data = true;
+            if(app_instance->file_type && app_instance->file_rename) {
+                snprintf(
+                    fhttp.file_path,
+                    sizeof(fhttp.file_path),
+                    STORAGE_EXT_PATH_PREFIX "/apps_data/web_crawler/%s%s",
+                    app_instance->file_rename,
+                    app_instance->file_type);
+            } else {
+                snprintf(
+                    fhttp.file_path,
+                    sizeof(fhttp.file_path),
+                    STORAGE_EXT_PATH_PREFIX "/apps_data/web_crawler/received_data.txt");
+            }
 
             if(strstr(app_instance->http_method, "GET") != NULL) {
                 canvas_draw_str(canvas, 0, 10, "Sending GET request...");
 
+                fhttp.save_received_data = true;
                 fhttp.is_bytes_request = false;
 
                 // Perform GET request and handle the response
@@ -88,6 +132,7 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
             } else if(strstr(app_instance->http_method, "POST") != NULL) {
                 canvas_draw_str(canvas, 0, 10, "Sending POST request...");
 
+                fhttp.save_received_data = true;
                 fhttp.is_bytes_request = false;
 
                 // Perform POST request and handle the response
@@ -96,6 +141,7 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
             } else if(strstr(app_instance->http_method, "PUT") != NULL) {
                 canvas_draw_str(canvas, 0, 10, "Sending PUT request...");
 
+                fhttp.save_received_data = true;
                 fhttp.is_bytes_request = false;
 
                 // Perform PUT request and handle the response
@@ -104,6 +150,7 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
             } else if(strstr(app_instance->http_method, "DELETE") != NULL) {
                 canvas_draw_str(canvas, 0, 10, "Sending DELETE request...");
 
+                fhttp.save_received_data = true;
                 fhttp.is_bytes_request = false;
 
                 // Perform DELETE request and handle the response
@@ -113,6 +160,7 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
                 // download file
                 canvas_draw_str(canvas, 0, 10, "Downloading file...");
 
+                fhttp.save_received_data = false;
                 fhttp.is_bytes_request = true;
 
                 // Perform GET request and handle the response
@@ -124,10 +172,10 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
 
             if(get_success) {
                 canvas_draw_str(canvas, 0, 30, "Receiving data...");
-                // Set the state to RECEIVING to ensure we continue to see the receiving message
                 fhttp.state = RECEIVING;
             } else {
                 canvas_draw_str(canvas, 0, 30, "Failed.");
+                fhttp.state = ISSUE;
             }
 
             sent_http_request = true;
@@ -139,37 +187,7 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
                 canvas_draw_str(canvas, 0, 10, "Data saved to file.");
                 canvas_draw_str(canvas, 0, 20, "Press BACK to return.");
             } else {
-                if(fhttp.state == ISSUE) {
-                    if(strstr(
-                           fhttp.last_response,
-                           "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL) {
-                        canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-                        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-                        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-                    } else if(
-                        strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") !=
-                        NULL) {
-                        canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-                        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-                        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-                    } else if(
-                        strstr(
-                            fhttp.last_response,
-                            "[ERROR] GET request failed with error: connection refused") != NULL) {
-                        canvas_draw_str(canvas, 0, 10, "[ERROR] Connection refused.");
-                        canvas_draw_str(canvas, 0, 50, "Choose another URL.");
-                        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-                    } else {
-                        canvas_draw_str(canvas, 0, 10, "[ERROR] Failed to sync data.");
-                        canvas_draw_str(canvas, 0, 30, "If this is your third attempt,");
-                        canvas_draw_str(canvas, 0, 40, "it's likely your URL is not");
-                        canvas_draw_str(canvas, 0, 50, "compabilbe or correct.");
-                        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-                    }
-                } else {
-                    canvas_draw_str(canvas, 0, 10, "HTTP request failed.");
-                    canvas_draw_str(canvas, 0, 20, "Press BACK to return.");
-                }
+                web_crawler_draw_error(canvas);
                 get_success = false;
             }
         }
@@ -183,7 +201,7 @@ static void web_crawler_view_draw_callback(Canvas* canvas, void* context) {
  * @param      context   The context - WebCrawlerApp object.
  * @return     WebCrawlerViewSubmenu
  */
-static uint32_t web_crawler_back_to_configure_callback(void* context) {
+uint32_t web_crawler_back_to_configure_callback(void* context) {
     UNUSED(context);
     // free file read widget if it exists
     if(app_instance->widget_file_read) {
@@ -197,7 +215,7 @@ static uint32_t web_crawler_back_to_configure_callback(void* context) {
  * @param      context   The context - WebCrawlerApp object.
  * @return     WebCrawlerViewSubmenu
  */
-static uint32_t web_crawler_back_to_main_callback(void* context) {
+uint32_t web_crawler_back_to_main_callback(void* context) {
     UNUSED(context);
     // reset GET request flags
     sent_http_request = false;
@@ -210,17 +228,17 @@ static uint32_t web_crawler_back_to_main_callback(void* context) {
     return WebCrawlerViewSubmenuMain; // Return to the main submenu
 }
 
-static uint32_t web_crawler_back_to_file_callback(void* context) {
+uint32_t web_crawler_back_to_file_callback(void* context) {
     UNUSED(context);
     return WebCrawlerViewVariableItemListFile; // Return to the file submenu
 }
 
-static uint32_t web_crawler_back_to_wifi_callback(void* context) {
+uint32_t web_crawler_back_to_wifi_callback(void* context) {
     UNUSED(context);
     return WebCrawlerViewVariableItemListWifi; // Return to the wifi submenu
 }
 
-static uint32_t web_crawler_back_to_request_callback(void* context) {
+uint32_t web_crawler_back_to_request_callback(void* context) {
     UNUSED(context);
     return WebCrawlerViewVariableItemListRequest; // Return to the request submenu
 }
@@ -230,7 +248,7 @@ static uint32_t web_crawler_back_to_request_callback(void* context) {
  * @param      context   The context - unused
  * @return     VIEW_NONE to exit the app
  */
-static uint32_t web_crawler_exit_app_callback(void* context) {
+uint32_t web_crawler_exit_app_callback(void* context) {
     UNUSED(context);
     return VIEW_NONE;
 }
@@ -240,7 +258,7 @@ static uint32_t web_crawler_exit_app_callback(void* context) {
  * @param      context   The context - WebCrawlerApp object.
  * @param      index     The WebCrawlerSubmenuIndex item that was clicked.
  */
-static void web_crawler_submenu_callback(void* context, uint32_t index) {
+void web_crawler_submenu_callback(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
 
     if(app->view_dispatcher) {
@@ -279,7 +297,7 @@ static void web_crawler_submenu_callback(void* context, uint32_t index) {
  * @param      context   The context - WebCrawlerApp object.
  * @param      index     The index of the item that was clicked.
  */
-static void web_crawler_wifi_enter_callback(void* context, uint32_t index) {
+void web_crawler_wifi_enter_callback(void* context, uint32_t index) {
     switch(index) {
     case 0: // SSID
         web_crawler_setting_item_ssid_clicked(context, index);
@@ -298,7 +316,7 @@ static void web_crawler_wifi_enter_callback(void* context, uint32_t index) {
  * @param      context   The context - WebCrawlerApp object.
  * @param      index     The index of the item that was clicked.
  */
-static void web_crawler_file_enter_callback(void* context, uint32_t index) {
+void web_crawler_file_enter_callback(void* context, uint32_t index) {
     switch(index) {
     case 0: // File Read
         web_crawler_setting_item_file_read_clicked(context, index);
@@ -323,7 +341,7 @@ static void web_crawler_file_enter_callback(void* context, uint32_t index) {
  * @param      context   The context - WebCrawlerApp object.
  * @param      index     The index of the item that was clicked.
  */
-static void web_crawler_request_enter_callback(void* context, uint32_t index) {
+void web_crawler_request_enter_callback(void* context, uint32_t index) {
     switch(index) {
     case 0: // URL
         web_crawler_setting_item_path_clicked(context, index);
@@ -349,7 +367,7 @@ static void web_crawler_request_enter_callback(void* context, uint32_t index) {
  * @brief      Callback for when the user finishes entering the URL.
  * @param      context   The context - WebCrawlerApp object.
  */
-static void web_crawler_set_path_updated(void* context) {
+void web_crawler_set_path_updated(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -385,7 +403,7 @@ static void web_crawler_set_path_updated(void* context) {
  * @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) {
+void web_crawler_set_headers_updated(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -421,7 +439,7 @@ static void web_crawler_set_headers_updated(void* context) {
  * @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) {
+void web_crawler_set_payload_updated(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -457,7 +475,7 @@ static void web_crawler_set_payload_updated(void* context) {
  * @brief      Callback for when the user finishes entering the SSID.
  * @param      context   The context - WebCrawlerApp object.
  */
-static void web_crawler_set_ssid_updated(void* context) {
+void web_crawler_set_ssid_updated(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -499,7 +517,7 @@ static void web_crawler_set_ssid_updated(void* context) {
  * @brief      Callback for when the user finishes entering the Password.
  * @param      context   The context - WebCrawlerApp object.
  */
-static void web_crawler_set_password_update(void* context) {
+void web_crawler_set_password_update(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -542,7 +560,7 @@ static void web_crawler_set_password_update(void* context) {
  * @brief      Callback for when the user finishes entering the File Type.
  * @param      context   The context - WebCrawlerApp object.
  */
-static void web_crawler_set_file_type_update(void* context) {
+void web_crawler_set_file_type_update(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -600,7 +618,7 @@ static void web_crawler_set_file_type_update(void* context) {
  * @brief      Callback for when the user finishes entering the File Rename.
  * @param      context   The context - WebCrawlerApp object.
  */
-static void web_crawler_set_file_rename_update(void* context) {
+void web_crawler_set_file_rename_update(void* context) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -663,7 +681,7 @@ static void web_crawler_set_file_rename_update(void* context) {
  * @param      context  The context - WebCrawlerApp object.
  * @param      index    The index of the item that was clicked.
  */
-static void web_crawler_setting_item_path_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_path_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -708,7 +726,7 @@ static void web_crawler_setting_item_path_clicked(void* context, uint32_t index)
  * @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) {
+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");
@@ -763,7 +781,7 @@ static void web_crawler_setting_item_headers_clicked(void* context, uint32_t ind
  * @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) {
+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");
@@ -808,7 +826,7 @@ static void web_crawler_setting_item_payload_clicked(void* context, uint32_t ind
  * @param      context  The context - WebCrawlerApp object.
  * @param      index    The index of the item that was clicked.
  */
-static void web_crawler_setting_item_ssid_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_ssid_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -852,7 +870,7 @@ static void web_crawler_setting_item_ssid_clicked(void* context, uint32_t index)
  * @param      context  The context - WebCrawlerApp object.
  * @param      index    The index of the item that was clicked.
  */
-static void web_crawler_setting_item_password_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_password_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -891,7 +909,7 @@ static void web_crawler_setting_item_password_clicked(void* context, uint32_t in
  * @param      context  The context - WebCrawlerApp object.
  * @param      index    The index of the item that was clicked.
  */
-static void web_crawler_setting_item_file_type_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_file_type_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -935,7 +953,7 @@ static void web_crawler_setting_item_file_type_clicked(void* context, uint32_t i
  * @param      context  The context - WebCrawlerApp object.
  * @param      index    The index of the item that was clicked.
  */
-static void web_crawler_setting_item_file_rename_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_file_rename_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -981,7 +999,7 @@ static void web_crawler_setting_item_file_rename_clicked(void* context, uint32_t
  * @param      context  The context - WebCrawlerApp object.
  * @param      index    The index of the item that was clicked.
  */
-static void web_crawler_setting_item_file_delete_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_file_delete_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -1001,7 +1019,7 @@ static void web_crawler_setting_item_file_delete_clicked(void* context, uint32_t
     view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewFileDelete);
 }
 
-static void web_crawler_setting_item_file_read_clicked(void* context, uint32_t index) {
+void web_crawler_setting_item_file_read_clicked(void* context, uint32_t index) {
     WebCrawlerApp* app = (WebCrawlerApp*)context;
     if(!app) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
@@ -1009,6 +1027,25 @@ static void web_crawler_setting_item_file_read_clicked(void* context, uint32_t i
     }
     UNUSED(index);
     widget_reset(app->widget_file_read);
+
+    if(app->file_rename && app->file_type) {
+        snprintf(
+            fhttp.file_path,
+            sizeof(fhttp.file_path),
+            "%s%s%s",
+            RECEIVED_DATA_PATH,
+            app->file_rename,
+            app->file_type);
+    } else {
+        snprintf(
+            fhttp.file_path,
+            sizeof(fhttp.file_path),
+            "%s%s%s",
+            RECEIVED_DATA_PATH,
+            "received_data",
+            ".txt");
+    }
+
     // load the received data from the saved file
     FuriString* received_data = flipper_http_load_from_file(fhttp.file_path);
     if(received_data == NULL) {

+ 169 - 0
web_crawler/callback/web_crawler_callback.h

@@ -0,0 +1,169 @@
+#ifndef WEB_CRAWLER_CALLBACK_H
+#define WEB_CRAWLER_CALLBACK_H
+#include "web_crawler.h"
+#include <flip_storage/web_crawler_storage.h>
+
+extern bool sent_http_request;
+extern bool get_success;
+extern bool already_success;
+
+void web_crawler_draw_error(Canvas* canvas);
+
+void web_crawler_http_method_change(VariableItem* item);
+
+void web_crawler_view_draw_callback(Canvas* canvas, void* context);
+
+/**
+ * @brief      Navigation callback to handle exiting from other views to the submenu.
+ * @param      context   The context - WebCrawlerApp object.
+ * @return     WebCrawlerViewSubmenu
+ */
+uint32_t web_crawler_back_to_configure_callback(void* context);
+
+/**
+ * @brief      Navigation callback to handle returning to the Wifi Settings screen.
+ * @param      context   The context - WebCrawlerApp object.
+ * @return     WebCrawlerViewSubmenu
+ */
+uint32_t web_crawler_back_to_main_callback(void* context);
+uint32_t web_crawler_back_to_file_callback(void* context);
+
+uint32_t web_crawler_back_to_wifi_callback(void* context);
+
+uint32_t web_crawler_back_to_request_callback(void* context);
+
+/**
+ * @brief      Navigation callback to handle exiting the app from the main submenu.
+ * @param      context   The context - unused
+ * @return     VIEW_NONE to exit the app
+ */
+uint32_t web_crawler_exit_app_callback(void* context);
+
+/**
+ * @brief      Handle submenu item selection.
+ * @param      context   The context - WebCrawlerApp object.
+ * @param      index     The WebCrawlerSubmenuIndex item that was clicked.
+ */
+void web_crawler_submenu_callback(void* context, uint32_t index);
+
+/**
+ * @brief      Configuration enter callback to handle different items.
+ * @param      context   The context - WebCrawlerApp object.
+ * @param      index     The index of the item that was clicked.
+ */
+void web_crawler_wifi_enter_callback(void* context, uint32_t index);
+
+/**
+ * @brief      Configuration enter callback to handle different items.
+ * @param      context   The context - WebCrawlerApp object.
+ * @param      index     The index of the item that was clicked.
+ */
+void web_crawler_file_enter_callback(void* context, uint32_t index);
+
+/**
+ * @brief      Configuration enter callback to handle different items.
+ * @param      context   The context - WebCrawlerApp object.
+ * @param      index     The index of the item that was clicked.
+ */
+void web_crawler_request_enter_callback(void* context, uint32_t index);
+
+/**
+ * @brief      Callback for when the user finishes entering the URL.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_path_updated(void* context);
+
+/**
+ * @brief      Callback for when the user finishes entering the headers
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_headers_updated(void* context);
+
+/**
+ * @brief      Callback for when the user finishes entering the payload.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_payload_updated(void* context);
+
+/**
+ * @brief      Callback for when the user finishes entering the SSID.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_ssid_updated(void* context);
+
+/**
+ * @brief      Callback for when the user finishes entering the Password.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_password_update(void* context);
+
+/**
+ * @brief      Callback for when the user finishes entering the File Type.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_file_type_update(void* context);
+
+/**
+ * @brief      Callback for when the user finishes entering the File Rename.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+void web_crawler_set_file_rename_update(void* context);
+
+/**
+ * @brief      Handler for Path configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_path_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for headers configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_headers_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for payload configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_payload_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for SSID configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_ssid_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for Password configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_password_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for File Type configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_file_type_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for File Rename configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_file_rename_clicked(void* context, uint32_t index);
+
+/**
+ * @brief      Handler for File Delete configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+void web_crawler_setting_item_file_delete_clicked(void* context, uint32_t index);
+
+void web_crawler_setting_item_file_read_clicked(void* context, uint32_t index);
+#endif

+ 1 - 23
web_crawler/easy_flipper.h → web_crawler/easy_flipper/easy_flipper.c

@@ -1,24 +1,4 @@
-#ifndef EASY_FLIPPER_H
-#define EASY_FLIPPER_H
-
-#include <malloc.h>
-#include <furi.h>
-#include <furi_hal.h>
-#include <gui/gui.h>
-#include <gui/view.h>
-#include <gui/modules/submenu.h>
-#include <gui/view_dispatcher.h>
-#include <gui/modules/menu.h>
-#include <gui/modules/submenu.h>
-#include <gui/modules/widget.h>
-#include <gui/modules/text_input.h>
-#include <gui/modules/text_box.h>
-#include <gui/modules/variable_item_list.h>
-#include <gui/modules/dialog_ex.h>
-#include <gui/modules/popup.h>
-#include <gui/modules/loading.h>
-
-#define EASY_TAG "EasyFlipper"
+#include <easy_flipper/easy_flipper.h>
 
 /**
  * @brief Navigation callback for exiting the application
@@ -530,5 +510,3 @@ bool easy_flipper_set_char_to_furi_string(FuriString** furi_string, char* buffer
     furi_string_set_str(*furi_string, buffer);
     return true;
 }
-
-#endif // EASY_FLIPPER_H

+ 261 - 0
web_crawler/easy_flipper/easy_flipper.h

@@ -0,0 +1,261 @@
+#ifndef EASY_FLIPPER_H
+#define EASY_FLIPPER_H
+
+#include <malloc.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <stdio.h>
+#include <string.h>
+#include <jsmn/jsmn.h>
+
+#define EASY_TAG "EasyFlipper"
+
+/**
+ * @brief Navigation callback for exiting the application
+ * @param context The context - unused
+ * @return next view id (VIEW_NONE to exit the app)
+ */
+uint32_t easy_flipper_callback_exit_app(void* context);
+/**
+ * @brief Initialize a buffer
+ * @param buffer The buffer to initialize
+ * @param buffer_size The size of the buffer
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size);
+/**
+ * @brief Initialize a View object
+ * @param view The View object to initialize
+ * @param view_id The ID/Index of the view
+ * @param draw_callback The draw callback function (set to NULL if not needed)
+ * @param input_callback The input callback function (set to NULL if not needed)
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_view(
+    View** view,
+    int32_t view_id,
+    void draw_callback(Canvas*, void*),
+    bool input_callback(InputEvent*, void*),
+    uint32_t (*previous_callback)(void*),
+    ViewDispatcher** view_dispatcher,
+    void* context);
+
+/**
+ * @brief Initialize a ViewDispatcher object
+ * @param view_dispatcher The ViewDispatcher object to initialize
+ * @param gui The GUI object
+ * @param context The context to pass to the event callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui, void* context);
+
+/**
+ * @brief Initialize a Submenu object
+ * @note This does not set the items in the submenu
+ * @param submenu The Submenu object to initialize
+ * @param view_id The ID/Index of the view
+ * @param title The title/header of the submenu
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_submenu(
+    Submenu** submenu,
+    int32_t view_id,
+    char* title,
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher);
+
+/**
+ * @brief Initialize a Menu object
+ * @note This does not set the items in the menu
+ * @param menu The Menu object to initialize
+ * @param view_id The ID/Index of the view
+ * @param item_callback The item callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_menu(
+    Menu** menu,
+    int32_t view_id,
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher);
+
+/**
+ * @brief Initialize a Widget object
+ * @param widget The Widget object to initialize
+ * @param view_id The ID/Index of the view
+ * @param text The text to display in the widget
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_widget(
+    Widget** widget,
+    int32_t view_id,
+    char* text,
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher);
+
+/**
+ * @brief Initialize a VariableItemList object
+ * @note This does not set the items in the VariableItemList
+ * @param variable_item_list The VariableItemList object to initialize
+ * @param view_id The ID/Index of the view
+ * @param enter_callback The enter callback function (can be set to NULL)
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the enter callback (usually the app)
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_variable_item_list(
+    VariableItemList** variable_item_list,
+    int32_t view_id,
+    void (*enter_callback)(void*, uint32_t),
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher,
+    void* context);
+
+/**
+ * @brief Initialize a TextInput object
+ * @param text_input The TextInput object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_text_input(
+    TextInput** text_input,
+    int32_t view_id,
+    char* header_text,
+    char* text_input_temp_buffer,
+    uint32_t text_input_buffer_size,
+    void (*result_callback)(void*),
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher,
+    void* context);
+
+/**
+ * @brief Initialize a TextInput object with extra symbols
+ * @param uart_text_input The TextInput object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_uart_text_input(
+    TextInput** uart_text_input,
+    int32_t view_id,
+    char* header_text,
+    char* uart_text_input_temp_buffer,
+    uint32_t uart_text_input_buffer_size,
+    void (*result_callback)(void*),
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher,
+    void* context);
+
+/**
+ * @brief Initialize a DialogEx object
+ * @param dialog_ex The DialogEx object to initialize
+ * @param view_id The ID/Index of the view
+ * @param header The header of the dialog
+ * @param header_x The x coordinate of the header
+ * @param header_y The y coordinate of the header
+ * @param text The text of the dialog
+ * @param text_x The x coordinate of the dialog
+ * @param text_y The y coordinate of the dialog
+ * @param left_button_text The text of the left button
+ * @param right_button_text The text of the right button
+ * @param center_button_text The text of the center button
+ * @param result_callback The result callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the result callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_dialog_ex(
+    DialogEx** dialog_ex,
+    int32_t view_id,
+    char* header,
+    uint16_t header_x,
+    uint16_t header_y,
+    char* text,
+    uint16_t text_x,
+    uint16_t text_y,
+    char* left_button_text,
+    char* right_button_text,
+    char* center_button_text,
+    void (*result_callback)(DialogExResult, void*),
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher,
+    void* context);
+
+/**
+ * @brief Initialize a Popup object
+ * @param popup The Popup object to initialize
+ * @param view_id The ID/Index of the view
+ * @param header The header of the dialog
+ * @param header_x The x coordinate of the header
+ * @param header_y The y coordinate of the header
+ * @param text The text of the dialog
+ * @param text_x The x coordinate of the dialog
+ * @param text_y The y coordinate of the dialog
+ * @param result_callback The result callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the result callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_popup(
+    Popup** popup,
+    int32_t view_id,
+    char* header,
+    uint16_t header_x,
+    uint16_t header_y,
+    char* text,
+    uint16_t text_x,
+    uint16_t text_y,
+    void (*result_callback)(void*),
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher,
+    void* context);
+
+/**
+ * @brief Initialize a Loading object
+ * @param loading The Loading object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_loading(
+    Loading** loading,
+    int32_t view_id,
+    uint32_t(previous_callback)(void*),
+    ViewDispatcher** view_dispatcher);
+
+/**
+ * @brief Set a char butter to a FuriString
+ * @param furi_string The FuriString object
+ * @param buffer The buffer to copy the string to
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_char_to_furi_string(FuriString** furi_string, char* buffer);
+
+#endif

+ 6 - 27
web_crawler/web_crawler_storage.h → web_crawler/flip_storage/web_crawler_storage.c

@@ -1,25 +1,7 @@
-#ifndef WEB_CRAWLER_STORAGE_H
-#define WEB_CRAWLER_STORAGE_H
-
-#include <furi.h>
-#include <storage/storage.h>
-
-#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/settings.bin"
-#define RECEIVED_DATA_PATH                         \
-    STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag \
-                            "/" // add the file name to the end (e.g. "received_data.txt")
-
-// will need to make a duplicate of the file
-// one to save data, and the other for users to manipulate
-
-#define MAX_RECEIVED_DATA_SIZE 1024
-#define SHOW_MAX_FILE_SIZE     2048
-
-// Define the truncation notice
-#define TRUNCATION_NOTICE "\n\n[Data truncated due to size limits]"
+#include <flip_storage/web_crawler_storage.h>
 
 // Function to save settings: path, SSID, and password
-static void save_settings(
+void save_settings(
     const char* path,
     const char* ssid,
     const char* password,
@@ -31,9 +13,7 @@ static void save_settings(
     // Create the directory for saving settings
     char directory_path[256];
     snprintf(
-        directory_path,
-        sizeof(directory_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/web_crawler_app");
+        directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/web_crawler");
 
     // Create the directory
     Storage* storage = furi_record_open(RECORD_STORAGE);
@@ -113,7 +93,7 @@ static void save_settings(
 }
 
 // Function to load settings (the variables must be opened in the order they were saved)
-static bool load_settings(
+bool load_settings(
     char* path,
     size_t path_size,
     char* ssid,
@@ -260,7 +240,7 @@ static bool load_settings(
     return true;
 }
 
-static bool delete_received_data(WebCrawlerApp* app) {
+bool delete_received_data(WebCrawlerApp* app) {
     if(app == NULL) {
         FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
         return false;
@@ -310,7 +290,7 @@ static bool delete_received_data(WebCrawlerApp* app) {
     return true;
 }
 
-static bool rename_received_data(
+bool rename_received_data(
     const char* old_name,
     const char* new_name,
     const char* file_type,
@@ -379,4 +359,3 @@ static bool rename_received_data(
         return renamed;
     }
 }
-#endif // WEB_CRAWLER_STORAGE_H

+ 49 - 0
web_crawler/flip_storage/web_crawler_storage.h

@@ -0,0 +1,49 @@
+#ifndef WEB_CRAWLER_STORAGE_H
+#define WEB_CRAWLER_STORAGE_H
+#include <web_crawler.h>
+#include <furi.h>
+#include <storage/storage.h>
+
+#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/settings.bin"
+#define RECEIVED_DATA_PATH                         \
+    STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag \
+                            "/" // add the file name to the end (e.g. "received_data.txt")
+
+// Function to save settings: path, SSID, and password
+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);
+
+// Function to load settings (the variables must be opened in the order they were saved)
+bool load_settings(
+    char* path,
+    size_t path_size,
+    char* ssid,
+    size_t ssid_size,
+    char* password,
+    size_t password_size,
+    char* file_rename,
+    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);
+
+bool delete_received_data(WebCrawlerApp* app);
+bool rename_received_data(
+    const char* old_name,
+    const char* new_name,
+    const char* file_type,
+    const char* old_file_type);
+#endif // WEB_CRAWLER_STORAGE_H

+ 7 - 141
web_crawler/flipper_http.h → web_crawler/flipper_http/flipper_http.c

@@ -1,137 +1,7 @@
-// flipper_http.h
-#ifndef FLIPPER_HTTP_H
-#define FLIPPER_HTTP_H
-
-#include <furi.h>
-#include <furi_hal.h>
-#include <furi_hal_gpio.h>
-#include <furi_hal_serial.h>
-#include <storage/storage.h>
-
-// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
-
-#define HTTP_TAG               "WebCrawler" // change this to your app name
-#define http_tag               "web_crawler" // change this to your app id
-#define UART_CH                (FuriHalSerialIdUsart) // UART channel
-#define TIMEOUT_DURATION_TICKS (3 * 1000) // 3 seconds
-#define BAUDRATE               (115200) // UART baudrate
-#define RX_BUF_SIZE            2048 // UART RX buffer size
-#define RX_LINE_BUFFER_SIZE    2048 // UART RX line buffer size (increase for large responses)
-#define MAX_FILE_SHOW          5000 // Maximum data from file to show
-
-// Forward declaration for callback
-typedef void (*FlipperHTTP_Callback)(const char* line, void* context);
-
-// Functions
-bool flipper_http_init(FlipperHTTP_Callback callback, void* context);
-void flipper_http_deinit();
-//---
-void flipper_http_rx_callback(const char* line, void* context);
-bool flipper_http_send_data(const char* data);
-//---
-bool flipper_http_connect_wifi();
-bool flipper_http_disconnect_wifi();
-bool flipper_http_ping();
-bool flipper_http_scan_wifi();
-bool flipper_http_save_wifi(const char* ssid, const char* password);
-bool flipper_http_ip_wifi();
-bool flipper_http_ip_address();
-//---
-bool flipper_http_list_commands();
-bool flipper_http_led_on();
-bool flipper_http_led_off();
-bool flipper_http_parse_json(const char* key, const char* json_data);
-bool flipper_http_parse_json_array(const char* key, int index, const char* json_data);
-//---
-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_delete_request_with_headers(
-    const char* url,
-    const char* headers,
-    const char* payload);
-//---
-bool flipper_http_get_request_bytes(const char* url, const char* headers);
-bool flipper_http_post_request_bytes(const char* url, const char* headers, const char* payload);
-//
-bool flipper_http_append_to_file(
-    const void* data,
-    size_t data_size,
-    bool start_new_file,
-    char* file_path);
-
-FuriString* flipper_http_load_from_file(char* file_path);
-static char* trim(const char* str);
-//
-bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void));
-
-// State variable to track the UART state
-typedef enum {
-    INACTIVE, // Inactive state
-    IDLE, // Default state
-    RECEIVING, // Receiving data
-    SENDING, // Sending data
-    ISSUE, // Issue with connection
-} SerialState;
-
-// Event Flags for UART Worker Thread
-typedef enum {
-    WorkerEvtStop = (1 << 0),
-    WorkerEvtRxDone = (1 << 1),
-} WorkerEvtFlags;
-
-// FlipperHTTP Structure
-typedef struct {
-    FuriStreamBuffer* flipper_http_stream; // Stream buffer for UART communication
-    FuriHalSerialHandle* serial_handle; // Serial handle for UART communication
-    FuriThread* rx_thread; // Worker thread for UART
-    FuriThreadId rx_thread_id; // Worker thread ID
-    FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
-    void* callback_context; // Context for the callback
-    SerialState state; // State of the UART
-
-    // variable to store the last received data from the UART
-    char* last_response;
-    char file_path[256]; // Path to save the received data
-
-    // Timer-related members
-    FuriTimer* get_timeout_timer; // Timer for HTTP request timeout
-
-    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
-
-    bool started_receiving_delete; // Indicates if a DELETE request has started
-    bool just_started_delete; // Indicates if DELETE data reception has just started
-
-    // Buffer to hold the raw bytes received from the UART
-    uint8_t* received_bytes;
-    size_t received_bytes_len; // Length of the received bytes
-    bool is_bytes_request; // Flag to indicate if the request is for bytes
-    bool save_bytes; // Flag to save the received data to a file
-    bool save_received_data; // Flag to save the received data to a file
-} FlipperHTTP;
-
-static FlipperHTTP fhttp;
-// Global static array for the line buffer
-static char rx_line_buffer[RX_LINE_BUFFER_SIZE];
-#define FILE_BUFFER_SIZE 512
-static uint8_t file_buffer[FILE_BUFFER_SIZE];
-
-// fhttp.last_response holds the last received data from the UART, which could be [GET/END], [POST/END], [PUT/END], [DELETE/END], etc
-
+#include <flipper_http/flipper_http.h>
+FlipperHTTP fhttp;
+char rx_line_buffer[RX_LINE_BUFFER_SIZE];
+uint8_t file_buffer[FILE_BUFFER_SIZE];
 // Function to append received data to file
 // make sure to initialize the file path before calling this function
 bool flipper_http_append_to_file(
@@ -258,7 +128,7 @@ FuriString* flipper_http_load_from_file(char* file_path) {
  * @note       This function will handle received data asynchronously via the callback.
  */
 // UART worker thread
-static int32_t flipper_http_worker(void* context) {
+int32_t flipper_http_worker(void* context) {
     UNUSED(context);
     size_t rx_line_pos = 0;
     static size_t file_buffer_len = 0;
@@ -302,8 +172,6 @@ static int32_t flipper_http_worker(void* context) {
                         // Invoke the callback with the complete line
                         fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context);
 
-                        // save the received data
-
                         // Reset the line buffer position
                         rx_line_pos = 0;
                     } else {
@@ -376,7 +244,7 @@ void get_timeout_timer_callback(void* context) {
  * @param      context   The context to pass to the callback.
  * @note       This function will handle received data asynchronously via the callback.
  */
-static void _flipper_http_rx_callback(
+void _flipper_http_rx_callback(
     FuriHalSerialHandle* handle,
     FuriHalSerialRxEvent event,
     void* context) {
@@ -1123,7 +991,7 @@ void flipper_http_rx_callback(const char* line, void* context) {
     }
 
     // Uncomment below line to log the data received over UART
-    FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
+    // 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_get) {
@@ -1388,5 +1256,3 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
     }
     return true;
 }
-
-#endif // FLIPPER_HTTP_H

+ 360 - 0
web_crawler/flipper_http/flipper_http.h

@@ -0,0 +1,360 @@
+// flipper_http.h
+#ifndef FLIPPER_HTTP_H
+#define FLIPPER_HTTP_H
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_gpio.h>
+#include <furi_hal_serial.h>
+#include <storage/storage.h>
+
+// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
+
+#define HTTP_TAG               "Web Crawler" // change this to your app name
+#define http_tag               "web_crawler" // change this to your app id
+#define UART_CH                (FuriHalSerialIdUsart) // UART channel
+#define TIMEOUT_DURATION_TICKS (5 * 1000) // 5 seconds
+#define BAUDRATE               (115200) // UART baudrate
+#define RX_BUF_SIZE            2048 // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE    2048 // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW          4096 // Maximum data from file to show
+#define FILE_BUFFER_SIZE       512 // File buffer size
+
+// Forward declaration for callback
+typedef void (*FlipperHTTP_Callback)(const char* line, void* context);
+
+// State variable to track the UART state
+typedef enum {
+    INACTIVE, // Inactive state
+    IDLE, // Default state
+    RECEIVING, // Receiving data
+    SENDING, // Sending data
+    ISSUE, // Issue with connection
+} SerialState;
+
+// Event Flags for UART Worker Thread
+typedef enum {
+    WorkerEvtStop = (1 << 0),
+    WorkerEvtRxDone = (1 << 1),
+} WorkerEvtFlags;
+
+// FlipperHTTP Structure
+typedef struct {
+    FuriStreamBuffer* flipper_http_stream; // Stream buffer for UART communication
+    FuriHalSerialHandle* serial_handle; // Serial handle for UART communication
+    FuriThread* rx_thread; // Worker thread for UART
+    FuriThreadId rx_thread_id; // Worker thread ID
+    FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
+    void* callback_context; // Context for the callback
+    SerialState state; // State of the UART
+
+    // variable to store the last received data from the UART
+    char* last_response;
+    char file_path[256]; // Path to save the received data
+
+    // Timer-related members
+    FuriTimer* get_timeout_timer; // Timer for HTTP request timeout
+
+    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
+
+    bool started_receiving_delete; // Indicates if a DELETE request has started
+    bool just_started_delete; // Indicates if DELETE data reception has just started
+
+    // Buffer to hold the raw bytes received from the UART
+    uint8_t* received_bytes;
+    size_t received_bytes_len; // Length of the received bytes
+    bool is_bytes_request; // Flag to indicate if the request is for bytes
+    bool save_bytes; // Flag to save the received data to a file
+    bool save_received_data; // Flag to save the received data to a file
+} FlipperHTTP;
+
+extern FlipperHTTP fhttp;
+// Global static array for the line buffer
+extern char rx_line_buffer[RX_LINE_BUFFER_SIZE];
+extern uint8_t file_buffer[FILE_BUFFER_SIZE];
+
+// fhttp.last_response holds the last received data from the UART
+
+// Function to append received data to file
+// make sure to initialize the file path before calling this function
+bool flipper_http_append_to_file(
+    const void* data,
+    size_t data_size,
+    bool start_new_file,
+    char* file_path);
+
+FuriString* flipper_http_load_from_file(char* file_path);
+
+// UART worker thread
+/**
+ * @brief      Worker thread to handle UART data asynchronously.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
+// UART worker thread
+int32_t flipper_http_worker(void* context);
+
+// Timer callback function
+/**
+ * @brief      Callback function for the GET timeout timer.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will be called when the GET request times out.
+ */
+void get_timeout_timer_callback(void* context);
+
+// UART RX Handler Callback (Interrupt Context)
+/**
+ * @brief      A private callback function to handle received data asynchronously.
+ * @return     void
+ * @param      handle    The UART handle.
+ * @param      event     The event type.
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
+void _flipper_http_rx_callback(
+    FuriHalSerialHandle* handle,
+    FuriHalSerialRxEvent event,
+    void* context);
+
+// UART initialization function
+/**
+ * @brief      Initialize UART.
+ * @return     true if the UART was initialized successfully, false otherwise.
+ * @param      callback  The callback function to handle received data (ex. flipper_http_rx_callback).
+ * @param      context   The context to pass to the callback.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_init(FlipperHTTP_Callback callback, void* context);
+
+// Deinitialize UART
+/**
+ * @brief      Deinitialize UART.
+ * @return     void
+ * @note       This function will stop the asynchronous RX, release the serial handle, and free the resources.
+ */
+void flipper_http_deinit();
+
+// Function to send data over UART with newline termination
+/**
+ * @brief      Send data over UART with newline termination.
+ * @return     true if the data was sent successfully, false otherwise.
+ * @param      data  The data to send over UART.
+ * @note       The data will be sent over UART with a newline character appended.
+ */
+bool flipper_http_send_data(const char* data);
+
+// Function to send a PING request
+/**
+ * @brief      Send a PING request to check if the Wifi Dev Board is connected.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ * @note       This is best used to check if the Wifi Dev Board is connected.
+ * @note       The state will remain INACTIVE until a PONG is received.
+ */
+bool flipper_http_ping();
+
+// Function to list available commands
+/**
+ * @brief      Send a command to list available commands.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_list_commands();
+
+// Function to turn on the LED
+/**
+ * @brief      Allow the LED to display while processing.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_led_on();
+
+// Function to turn off the LED
+/**
+ * @brief      Disable the LED from displaying while processing.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_led_off();
+
+// Function to parse JSON data
+/**
+ * @brief      Parse JSON data.
+ * @return     true if the JSON data was parsed successfully, false otherwise.
+ * @param      key       The key to parse from the JSON data.
+ * @param      json_data The JSON data to parse.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_parse_json(const char* key, const char* json_data);
+
+// Function to parse JSON array data
+/**
+ * @brief      Parse JSON array data.
+ * @return     true if the JSON array data was parsed successfully, false otherwise.
+ * @param      key       The key to parse from the JSON array data.
+ * @param      index     The index to parse from the JSON array data.
+ * @param      json_data The JSON array data to parse.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_parse_json_array(const char* key, int index, const char* json_data);
+
+// Function to scan for WiFi networks
+/**
+ * @brief      Send a command to scan for WiFi networks.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_scan_wifi();
+
+// Function to save WiFi settings (returns true if successful)
+/**
+ * @brief      Send a command to save WiFi settings.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_save_wifi(const char* ssid, const char* password);
+
+// Function to get IP address of WiFi Devboard
+/**
+ * @brief      Send a command to get the IP address of the WiFi Devboard
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_ip_address();
+
+// Function to get IP address of the connected WiFi network
+/**
+ * @brief      Send a command to get the IP address of the connected WiFi network.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_ip_wifi();
+
+// Function to disconnect from WiFi (returns true if successful)
+/**
+ * @brief      Send a command to disconnect from WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_disconnect_wifi();
+
+// Function to connect to WiFi (returns true if successful)
+/**
+ * @brief      Send a command to connect to WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_connect_wifi();
+
+// Function to send a GET request
+/**
+ * @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.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_get_request(const char* url);
+
+// 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);
+
+// Function to send a GET request with headers and return bytes
+/**
+ * @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_bytes(const char* url, const char* headers);
+
+// 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);
+
+// Function to send a POST request with headers and return bytes
+/**
+ * @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      payload  The data to send with the POST request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_post_request_bytes(const char* url, const char* headers, const char* payload);
+
+// 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);
+
+// Function to send a DELETE request with headers
+/**
+ * @brief      Send a DELETE request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the DELETE request to.
+ * @param      headers  The headers to send with the DELETE request.
+ * @param      data  The data to send with the DELETE request.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_delete_request_with_headers(
+    const char* url,
+    const char* headers,
+    const char* payload);
+
+// Function to handle received data asynchronously
+/**
+ * @brief      Callback function to handle received data asynchronously.
+ * @return     void
+ * @param      line     The received line.
+ * @param      context  The context passed to the callback.
+ * @note       The received data will be handled asynchronously via the callback and handles the state of the UART.
+ */
+void flipper_http_rx_callback(const char* line, void* context);
+
+// Function to trim leading and trailing spaces and newlines from a constant string
+char* trim(const char* str);
+/**
+ * @brief Process requests and parse JSON data asynchronously
+ * @param http_request The function to send the request
+ * @param parse_json The function to parse the JSON
+ * @return true if successful, false otherwise
+ */
+bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void));
+
+#endif // FLIPPER_HTTP_H

+ 666 - 0
web_crawler/jsmn/jsmn.c

@@ -0,0 +1,666 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include <jsmn/jsmn.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t*
+    jsmn_alloc_token(jsmn_parser* parser, jsmntok_t* tokens, const size_t num_tokens) {
+    jsmntok_t* tok;
+
+    if(parser->toknext >= num_tokens) {
+        return NULL;
+    }
+    tok = &tokens[parser->toknext++];
+    tok->start = tok->end = -1;
+    tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+    tok->parent = -1;
+#endif
+    return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void
+    jsmn_fill_token(jsmntok_t* token, const jsmntype_t type, const int start, const int end) {
+    token->type = type;
+    token->start = start;
+    token->end = end;
+    token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(
+    jsmn_parser* parser,
+    const char* js,
+    const size_t len,
+    jsmntok_t* tokens,
+    const size_t num_tokens) {
+    jsmntok_t* token;
+    int start;
+
+    start = parser->pos;
+
+    for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+        switch(js[parser->pos]) {
+#ifndef JSMN_STRICT
+        /* In strict mode primitive must be followed by "," or "}" or "]" */
+        case ':':
+#endif
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+        case ',':
+        case ']':
+        case '}':
+            goto found;
+        default:
+            /* to quiet a warning from gcc*/
+            break;
+        }
+        if(js[parser->pos] < 32 || js[parser->pos] >= 127) {
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+        }
+    }
+#ifdef JSMN_STRICT
+    /* In strict mode primitive must be followed by a comma/object/array */
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+#endif
+
+found:
+    if(tokens == NULL) {
+        parser->pos--;
+        return 0;
+    }
+    token = jsmn_alloc_token(parser, tokens, num_tokens);
+    if(token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+    }
+    jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+    token->parent = parser->toksuper;
+#endif
+    parser->pos--;
+    return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(
+    jsmn_parser* parser,
+    const char* js,
+    const size_t len,
+    jsmntok_t* tokens,
+    const size_t num_tokens) {
+    jsmntok_t* token;
+
+    int start = parser->pos;
+
+    /* Skip starting quote */
+    parser->pos++;
+
+    for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+        char c = js[parser->pos];
+
+        /* Quote: end of string */
+        if(c == '\"') {
+            if(tokens == NULL) {
+                return 0;
+            }
+            token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if(token == NULL) {
+                parser->pos = start;
+                return JSMN_ERROR_NOMEM;
+            }
+            jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+            token->parent = parser->toksuper;
+#endif
+            return 0;
+        }
+
+        /* Backslash: Quoted symbol expected */
+        if(c == '\\' && parser->pos + 1 < len) {
+            int i;
+            parser->pos++;
+            switch(js[parser->pos]) {
+            /* Allowed escaped symbols */
+            case '\"':
+            case '/':
+            case '\\':
+            case 'b':
+            case 'f':
+            case 'r':
+            case 'n':
+            case 't':
+                break;
+            /* Allows escaped symbol \uXXXX */
+            case 'u':
+                parser->pos++;
+                for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+                    /* If it isn't a hex character we have an error */
+                    if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+                         (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+                         (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+                        parser->pos = start;
+                        return JSMN_ERROR_INVAL;
+                    }
+                    parser->pos++;
+                }
+                parser->pos--;
+                break;
+            /* Unexpected symbol */
+            default:
+                parser->pos = start;
+                return JSMN_ERROR_INVAL;
+            }
+        }
+    }
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser* parser) {
+    parser->pos = 0;
+    parser->toknext = 0;
+    parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+int jsmn_parse(
+    jsmn_parser* parser,
+    const char* js,
+    const size_t len,
+    jsmntok_t* tokens,
+    const unsigned int num_tokens) {
+    int r;
+    int i;
+    jsmntok_t* token;
+    int count = parser->toknext;
+
+    for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+        char c;
+        jsmntype_t type;
+
+        c = js[parser->pos];
+        switch(c) {
+        case '{':
+        case '[':
+            count++;
+            if(tokens == NULL) {
+                break;
+            }
+            token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if(token == NULL) {
+                return JSMN_ERROR_NOMEM;
+            }
+            if(parser->toksuper != -1) {
+                jsmntok_t* t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+                /* In strict mode an object or array can't become a key */
+                if(t->type == JSMN_OBJECT) {
+                    return JSMN_ERROR_INVAL;
+                }
+#endif
+                t->size++;
+#ifdef JSMN_PARENT_LINKS
+                token->parent = parser->toksuper;
+#endif
+            }
+            token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+            token->start = parser->pos;
+            parser->toksuper = parser->toknext - 1;
+            break;
+        case '}':
+        case ']':
+            if(tokens == NULL) {
+                break;
+            }
+            type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+            if(parser->toknext < 1) {
+                return JSMN_ERROR_INVAL;
+            }
+            token = &tokens[parser->toknext - 1];
+            for(;;) {
+                if(token->start != -1 && token->end == -1) {
+                    if(token->type != type) {
+                        return JSMN_ERROR_INVAL;
+                    }
+                    token->end = parser->pos + 1;
+                    parser->toksuper = token->parent;
+                    break;
+                }
+                if(token->parent == -1) {
+                    if(token->type != type || parser->toksuper == -1) {
+                        return JSMN_ERROR_INVAL;
+                    }
+                    break;
+                }
+                token = &tokens[token->parent];
+            }
+#else
+            for(i = parser->toknext - 1; i >= 0; i--) {
+                token = &tokens[i];
+                if(token->start != -1 && token->end == -1) {
+                    if(token->type != type) {
+                        return JSMN_ERROR_INVAL;
+                    }
+                    parser->toksuper = -1;
+                    token->end = parser->pos + 1;
+                    break;
+                }
+            }
+            /* Error if unmatched closing bracket */
+            if(i == -1) {
+                return JSMN_ERROR_INVAL;
+            }
+            for(; i >= 0; i--) {
+                token = &tokens[i];
+                if(token->start != -1 && token->end == -1) {
+                    parser->toksuper = i;
+                    break;
+                }
+            }
+#endif
+            break;
+        case '\"':
+            r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+            if(r < 0) {
+                return r;
+            }
+            count++;
+            if(parser->toksuper != -1 && tokens != NULL) {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+            break;
+        case ':':
+            parser->toksuper = parser->toknext - 1;
+            break;
+        case ',':
+            if(tokens != NULL && parser->toksuper != -1 &&
+               tokens[parser->toksuper].type != JSMN_ARRAY &&
+               tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+                parser->toksuper = tokens[parser->toksuper].parent;
+#else
+                for(i = parser->toknext - 1; i >= 0; i--) {
+                    if(tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+                        if(tokens[i].start != -1 && tokens[i].end == -1) {
+                            parser->toksuper = i;
+                            break;
+                        }
+                    }
+                }
+#endif
+            }
+            break;
+#ifdef JSMN_STRICT
+        /* In strict mode primitives are: numbers and booleans */
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 't':
+        case 'f':
+        case 'n':
+            /* And they must not be keys of the object */
+            if(tokens != NULL && parser->toksuper != -1) {
+                const jsmntok_t* t = &tokens[parser->toksuper];
+                if(t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) {
+                    return JSMN_ERROR_INVAL;
+                }
+            }
+#else
+        /* In non-strict mode every unquoted value is a primitive */
+        default:
+#endif
+            r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+            if(r < 0) {
+                return r;
+            }
+            count++;
+            if(parser->toksuper != -1 && tokens != NULL) {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+
+#ifdef JSMN_STRICT
+        /* Unexpected char in strict mode */
+        default:
+            return JSMN_ERROR_INVAL;
+#endif
+        }
+    }
+
+    if(tokens != NULL) {
+        for(i = parser->toknext - 1; i >= 0; i--) {
+            /* Unmatched opened object or array */
+            if(tokens[i].start != -1 && tokens[i].end == -1) {
+                return JSMN_ERROR_PART;
+            }
+        }
+    }
+
+    return count;
+}
+
+// Helper function to create a JSON object
+char* jsmn(const char* key, const char* value) {
+    int length = strlen(key) + strlen(value) + 8; // Calculate required length
+    char* result = (char*)malloc(length * sizeof(char)); // Allocate memory
+    if(result == NULL) {
+        return NULL; // Handle memory allocation failure
+    }
+    snprintf(result, length, "{\"%s\":\"%s\"}", key, value);
+    return result; // Caller is responsible for freeing this memory
+}
+
+// Helper function to compare JSON keys
+int jsoneq(const char* json, jsmntok_t* tok, const char* s) {
+    if(tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
+       strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+        return 0;
+    }
+    return -1;
+}
+
+// Return the value of the key in the JSON data
+char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
+    // Parse the JSON feed
+    if(json_data != NULL) {
+        jsmn_parser parser;
+        jsmn_init(&parser);
+
+        // Allocate tokens array on the heap
+        jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
+        if(tokens == NULL) {
+            FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+            return NULL;
+        }
+
+        int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, max_tokens);
+        if(ret < 0) {
+            // Handle parsing errors
+            FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+            free(tokens);
+            return NULL;
+        }
+
+        // Ensure that the root element is an object
+        if(ret < 1 || tokens[0].type != JSMN_OBJECT) {
+            FURI_LOG_E("JSMM.H", "Root element is not an object.");
+            free(tokens);
+            return NULL;
+        }
+
+        // Loop through the tokens to find the key
+        for(int i = 1; i < ret; i++) {
+            if(jsoneq(json_data, &tokens[i], key) == 0) {
+                // We found the key. Now, return the associated value.
+                int length = tokens[i + 1].end - tokens[i + 1].start;
+                char* value = malloc(length + 1);
+                if(value == NULL) {
+                    FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
+                    free(tokens);
+                    return NULL;
+                }
+                strncpy(value, json_data + tokens[i + 1].start, length);
+                value[length] = '\0'; // Null-terminate the string
+
+                free(tokens); // Free the token array
+                return value; // Return the extracted value
+            }
+        }
+
+        // Free the token array if key was not found
+        free(tokens);
+    } else {
+        FURI_LOG_E("JSMM.H", "JSON data is NULL");
+    }
+    FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+    return NULL; // Return NULL if something goes wrong
+}
+
+// Revised get_json_array_value function
+char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t max_tokens) {
+    // Retrieve the array string for the given key
+    char* array_str = get_json_value(key, json_data, max_tokens);
+    if(array_str == NULL) {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
+        return NULL;
+    }
+
+    // Initialize the JSON parser
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Allocate memory for JSON tokens
+    jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
+    if(tokens == NULL) {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        free(array_str);
+        return NULL;
+    }
+
+    // Parse the JSON array
+    int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+    if(ret < 0) {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Ensure the root element is an array
+    if(ret < 1 || tokens[0].type != JSMN_ARRAY) {
+        FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Check if the index is within bounds
+    if(index >= (uint32_t)tokens[0].size) {
+        FURI_LOG_E(
+            "JSMM.H",
+            "Index %lu out of bounds for array with size %d.",
+            (unsigned long)index,
+            tokens[0].size);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Locate the token corresponding to the desired array element
+    int current_token = 1; // Start after the array token
+    for(uint32_t i = 0; i < index; i++) {
+        if(tokens[current_token].type == JSMN_OBJECT) {
+            // For objects, skip all key-value pairs
+            current_token += 1 + 2 * tokens[current_token].size;
+        } else if(tokens[current_token].type == JSMN_ARRAY) {
+            // For nested arrays, skip all elements
+            current_token += 1 + tokens[current_token].size;
+        } else {
+            // For primitive types, simply move to the next token
+            current_token += 1;
+        }
+
+        // Safety check to prevent out-of-bounds
+        if(current_token >= ret) {
+            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            free(tokens);
+            free(array_str);
+            return NULL;
+        }
+    }
+
+    // Extract the array element
+    jsmntok_t element = tokens[current_token];
+    int length = element.end - element.start;
+    char* value = malloc(length + 1);
+    if(value == NULL) {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Copy the element value to a new string
+    strncpy(value, array_str + element.start, length);
+    value[length] = '\0'; // Null-terminate the string
+
+    // Clean up
+    free(tokens);
+    free(array_str);
+
+    return value;
+}
+
+// Revised get_json_array_values function with correct token skipping
+char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, int* num_values) {
+    // Retrieve the array string for the given key
+    char* array_str = get_json_value(key, json_data, max_tokens);
+    if(array_str == NULL) {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
+        return NULL;
+    }
+
+    // Initialize the JSON parser
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Allocate memory for JSON tokens
+    jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
+    if(tokens == NULL) {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        free(array_str);
+        return NULL;
+    }
+
+    // Parse the JSON array
+    int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+    if(ret < 0) {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Ensure the root element is an array
+    if(tokens[0].type != JSMN_ARRAY) {
+        FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    // Allocate memory for the array of values (maximum possible)
+    int array_size = tokens[0].size;
+    char** values = malloc(array_size * sizeof(char*));
+    if(values == NULL) {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+        free(tokens);
+        free(array_str);
+        return NULL;
+    }
+
+    int actual_num_values = 0;
+
+    // Traverse the array and extract all object values
+    int current_token = 1; // Start after the array token
+    for(int i = 0; i < array_size; i++) {
+        if(current_token >= ret) {
+            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            break;
+        }
+
+        jsmntok_t element = tokens[current_token];
+
+        if(element.type != JSMN_OBJECT) {
+            FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i);
+            // Skip this element
+            current_token += 1;
+            continue;
+        }
+
+        int length = element.end - element.start;
+
+        // Allocate a new string for the value and copy the data
+        char* value = malloc(length + 1);
+        if(value == NULL) {
+            FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
+            for(int j = 0; j < actual_num_values; j++) {
+                free(values[j]);
+            }
+            free(values);
+            free(tokens);
+            free(array_str);
+            return NULL;
+        }
+
+        strncpy(value, array_str + element.start, length);
+        value[length] = '\0'; // Null-terminate the string
+
+        values[actual_num_values] = value;
+        actual_num_values++;
+
+        // Skip all tokens related to this object to avoid misparsing
+        current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens
+    }
+
+    *num_values = actual_num_values;
+
+    // Reallocate the values array to actual_num_values if necessary
+    if(actual_num_values < array_size) {
+        char** reduced_values = realloc(values, actual_num_values * sizeof(char*));
+        if(reduced_values != NULL) {
+            values = reduced_values;
+        }
+
+        // Free the remaining values
+        for(int i = actual_num_values; i < array_size; i++) {
+            free(values[i]);
+        }
+    }
+
+    // Clean up
+    free(tokens);
+    free(array_str);
+    return values;
+}

+ 131 - 0
web_crawler/jsmn/jsmn.h

@@ -0,0 +1,131 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * [License text continues...]
+ */
+
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+     * JSON type identifier. Basic types are:
+     * 	o Object
+     * 	o Array
+     * 	o String
+     * 	o Other primitive: number, boolean (true/false) or null
+     */
+typedef enum {
+    JSMN_UNDEFINED = 0,
+    JSMN_OBJECT = 1 << 0,
+    JSMN_ARRAY = 1 << 1,
+    JSMN_STRING = 1 << 2,
+    JSMN_PRIMITIVE = 1 << 3
+} jsmntype_t;
+
+enum jsmnerr {
+    /* Not enough tokens were provided */
+    JSMN_ERROR_NOMEM = -1,
+    /* Invalid character inside JSON string */
+    JSMN_ERROR_INVAL = -2,
+    /* The string is not a full JSON packet, more bytes expected */
+    JSMN_ERROR_PART = -3
+};
+
+/**
+     * JSON token description.
+     * type		type (object, array, string etc.)
+     * start	start position in JSON data string
+     * end		end position in JSON data string
+     */
+typedef struct {
+    jsmntype_t type;
+    int start;
+    int end;
+    int size;
+#ifdef JSMN_PARENT_LINKS
+    int parent;
+#endif
+} jsmntok_t;
+
+/**
+     * JSON parser. Contains an array of token blocks available. Also stores
+     * the string being parsed now and current position in that string.
+     */
+typedef struct {
+    unsigned int pos; /* offset in the JSON string */
+    unsigned int toknext; /* next token to allocate */
+    int toksuper; /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+     * Create JSON parser over an array of tokens
+     */
+JSMN_API void jsmn_init(jsmn_parser* parser);
+
+/**
+     * Run JSON parser. It parses a JSON data string into and array of tokens, each
+     * describing a single JSON object.
+     */
+JSMN_API int jsmn_parse(
+    jsmn_parser* parser,
+    const char* js,
+    const size_t len,
+    jsmntok_t* tokens,
+    const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation has been moved to jsmn.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
+
+/* Custom Helper Functions */
+#ifndef JB_JSMN_EDIT
+#define JB_JSMN_EDIT
+/* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <furi.h>
+
+// Helper function to create a JSON object
+char* jsmn(const char* key, const char* value);
+// Helper function to compare JSON keys
+int jsoneq(const char* json, jsmntok_t* tok, const char* s);
+
+// Return the value of the key in the JSON data
+char* get_json_value(char* key, char* json_data, uint32_t max_tokens);
+
+// Revised get_json_array_value function
+char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t max_tokens);
+
+// Revised get_json_array_values function with correct token skipping
+char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, int* num_values);
+#endif /* JB_JSMN_EDIT */

+ 8 - 5
web_crawler/web_crawler_free.h → web_crawler/web_crawler.c

@@ -1,6 +1,6 @@
-// web_crawler_free.h
+#include <web_crawler.h>
 
-static void free_buffers(WebCrawlerApp* app) {
+void free_buffers(WebCrawlerApp* app) {
     if(!app) {
         FURI_LOG_E(TAG, "Invalid app context");
         return;
@@ -86,7 +86,7 @@ static void free_buffers(WebCrawlerApp* app) {
     }
 }
 
-static void free_resources(WebCrawlerApp* app) {
+void free_resources(WebCrawlerApp* app) {
     if(!app) {
         FURI_LOG_E(TAG, "Invalid app context");
         return;
@@ -95,7 +95,7 @@ static void free_resources(WebCrawlerApp* app) {
     free_buffers(app);
 }
 
-static void free_all(WebCrawlerApp* app, char* reason) {
+void free_all(WebCrawlerApp* app, char* reason) {
     if(!app) {
         FURI_LOG_E(TAG, "Invalid app context");
         return;
@@ -129,7 +129,7 @@ static void free_all(WebCrawlerApp* app, char* reason) {
  * @brief      Function to free the resources used by WebCrawlerApp.
  * @param      app  The WebCrawlerApp object to free.
  */
-static void web_crawler_app_free(WebCrawlerApp* app) {
+void web_crawler_app_free(WebCrawlerApp* app) {
     if(!app) {
         FURI_LOG_E(TAG, "Invalid app context");
         return;
@@ -213,3 +213,6 @@ static void web_crawler_app_free(WebCrawlerApp* app) {
         free(app);
     }
 }
+
+WebCrawlerApp* app_instance = NULL;
+char* http_method_names[] = {"GET", "POST", "PUT", "DELETE", "DOWNLOAD"};

+ 21 - 3
web_crawler/web_crawler_e.h → web_crawler/web_crawler.h

@@ -2,11 +2,14 @@
 #ifndef WEB_CRAWLER_E
 #define WEB_CRAWLER_E
 
-#include <easy_flipper.h>
+#include <easy_flipper/easy_flipper.h>
+#include <flipper_http/flipper_http.h>
+#include <jsmn/jsmn.h>
+#include "web_crawler_icons.h"
 #include <storage/storage.h>
 
-#define TAG "WebCrawler"
-static char* http_method_names[] = {"GET", "POST", "PUT", "DELETE", "DOWNLOAD"};
+#define TAG "Web Crawler"
+extern char* http_method_names[];
 
 // Define the submenu items for our WebCrawler application
 typedef enum {
@@ -108,4 +111,19 @@ typedef struct {
     char* temp_buffer_payload;
     uint32_t temp_buffer_size_payload;
 } WebCrawlerApp;
+
+void free_buffers(WebCrawlerApp* app);
+
+void free_resources(WebCrawlerApp* app);
+
+void free_all(WebCrawlerApp* app, char* reason);
+
+/**
+ * @brief      Function to free the resources used by WebCrawlerApp.
+ * @param      app  The WebCrawlerApp object to free.
+ */
+void web_crawler_app_free(WebCrawlerApp* app);
+
+extern WebCrawlerApp* app_instance;
+
 #endif // WEB_CRAWLER_E