From d133db07e164212c756c772007c03f281aef74e3 Mon Sep 17 00:00:00 2001 From: szeka9 Date: Sat, 29 Aug 2026 17:15:33 +0200 Subject: [PATCH] v0.9.0 release --- Makefile | 2 +- dist/pyrobusta/__init__.py | 6 + dist/pyrobusta/application.mpy | Bin 0 -> 1717 bytes dist/pyrobusta/assets/www/concepts.html | 12 +- dist/pyrobusta/assets/www/configuration.html | 131 +++-- dist/pyrobusta/assets/www/error_handling.html | 152 ++++++ dist/pyrobusta/assets/www/file_server.html | 2 +- dist/pyrobusta/assets/www/index.html | 4 +- dist/pyrobusta/assets/www/introduction.html | 58 +- dist/pyrobusta/assets/www/request.html | 26 +- dist/pyrobusta/assets/www/response.html | 79 ++- dist/pyrobusta/assets/www/routing.html | 2 +- dist/pyrobusta/assets/www/security.html | 499 ++++++++++++++++++ dist/pyrobusta/assets/www/static_content.html | 44 +- dist/pyrobusta/bindings/http_connection.mpy | Bin 2010 -> 2099 bytes dist/pyrobusta/connectivity/__init__.py | 0 dist/pyrobusta/connectivity/wifi.mpy | Bin 645 -> 0 bytes dist/pyrobusta/protocol/__init__.py | 21 + dist/pyrobusta/protocol/http.mpy | Bin 8173 -> 9047 bytes dist/pyrobusta/protocol/http_basic_auth.mpy | Bin 0 -> 2752 bytes dist/pyrobusta/protocol/http_cookie.mpy | Bin 0 -> 1208 bytes dist/pyrobusta/protocol/http_file_server.mpy | Bin 3190 -> 3181 bytes dist/pyrobusta/protocol/http_multipart.mpy | Bin 2345 -> 2291 bytes dist/pyrobusta/protocol/http_security.mpy | Bin 0 -> 536 bytes dist/pyrobusta/server/http_server.mpy | Bin 3111 -> 2425 bytes dist/pyrobusta/stream/buffer.mpy | Bin 1549 -> 1457 bytes dist/pyrobusta/transport/connection.mpy | Bin 1079 -> 1133 bytes dist/pyrobusta/utils/assets.mpy | Bin 719 -> 721 bytes dist/pyrobusta/utils/config.mpy | Bin 1143 -> 1810 bytes dist/pyrobusta/utils/crypto.mpy | Bin 0 -> 1867 bytes dist/pyrobusta/utils/helpers.mpy | Bin 696 -> 0 bytes dist/pyrobusta/utils/iam.mpy | Bin 0 -> 2844 bytes dist/pyrobusta/utils/lexpath.mpy | Bin 0 -> 771 bytes dist/pyrobusta/utils/logging.mpy | Bin 428 -> 582 bytes dist/pyrobusta/utils/patch.mpy | Bin 460 -> 465 bytes docs/application_development/concepts.md | 2 +- docs/application_development/configuration.md | 2 +- .../application_development/error_handling.md | 2 +- docs/application_development/file_server.md | 2 +- docs/application_development/index.md | 2 +- docs/application_development/introduction.md | 2 +- docs/application_development/performance.md | 2 +- docs/application_development/reference.md | 2 +- docs/application_development/request.md | 2 +- docs/application_development/response.md | 2 +- docs/application_development/routing.md | 2 +- docs/application_development/security.md | 2 +- .../server_lifecycle.md | 2 +- .../application_development/static_content.md | 2 +- package.json | 42 +- src/pyrobusta/__init__.py | 2 +- 51 files changed, 964 insertions(+), 146 deletions(-) create mode 100644 dist/pyrobusta/application.mpy create mode 100644 dist/pyrobusta/assets/www/error_handling.html create mode 100644 dist/pyrobusta/assets/www/security.html delete mode 100644 dist/pyrobusta/connectivity/__init__.py delete mode 100644 dist/pyrobusta/connectivity/wifi.mpy create mode 100644 dist/pyrobusta/protocol/http_basic_auth.mpy create mode 100644 dist/pyrobusta/protocol/http_cookie.mpy create mode 100644 dist/pyrobusta/protocol/http_security.mpy create mode 100644 dist/pyrobusta/utils/crypto.mpy delete mode 100644 dist/pyrobusta/utils/helpers.mpy create mode 100644 dist/pyrobusta/utils/iam.mpy create mode 100644 dist/pyrobusta/utils/lexpath.mpy diff --git a/Makefile b/Makefile index 5ff15b4..b6f2dcd 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PYROBUSTA_VERSION := v0.8.0 +PYROBUSTA_VERSION := v0.9.0 DEVICE ?= u0 SRC_DIR := src diff --git a/dist/pyrobusta/__init__.py b/dist/pyrobusta/__init__.py index e69de29..a82c93f 100644 --- a/dist/pyrobusta/__init__.py +++ b/dist/pyrobusta/__init__.py @@ -0,0 +1,6 @@ +"""Global server attributes""" + +from os import getcwd + +PYROBUSTA_VERSION = "v0.9.0" +WORKING_DIR = getcwd().rstrip("/") diff --git a/dist/pyrobusta/application.mpy b/dist/pyrobusta/application.mpy new file mode 100644 index 0000000000000000000000000000000000000000..07d5ada2c0a45ff7f75bbb917ea806a9ad092c32 GIT binary patch literal 1717 zcmZWo?NZxT5MA4dIF9WoSBe5gNn9%i>^LT&s!P(Q1#BQ5NC2BQbvmg~WZB$CmNb$K zp})+OAD!t;A0f{mNND~xA8lSCeStneo}gEfF?2efv3K{LyLZpotM|;DnXrlxLRATK3I|})f%7y2vWA{NSe`>G<93m zAf{TDVL=q@Ed$7;{rQVQFM?}hy$vbJlpSSD>KM(grb5bNw;R+{JK(jT6Ql}=^p=|v zmOFraMsnBL05UK2@zy<2;a)u=xeJs4<;Vov;WY|tXaLR8|v z{y2>beC64L;Le@41vRoL6Lp$t$4Gk?z_NyaQqu$i)M(belkQ+LfZk5m>($ECU<&H8 zcP49=DmQo2{cV#j*u49F)_x2W=baDgg_DDerw+*9G~M!_2P%2;H!iCBHc;n-Re5Wa zd^=OLU-6cCya+1cmj-+MB|ee~ZO$$hR+b8d@7nv({p5b8w>}o;!aX{Y4~-4+5iZi` z(G-b?d5WVN7sBkB5|C1ql+N-|E=uAUiO=ydE=J-wouHHX$k=&4&c*ozm*A6J^7z^` zaP)MDW2VF0=yZe=)|H{3N6LtvxqYX{X4VVn3jy4Z=t~jMT@i$kIP~@fdjU}lIyfvv z8Yp;tUD;o|onmhH_)PYeAS_NyOsv0l8Mb1P`{BXuh&c4-kiFc@$}_< zcHscuh(stN!n>F3H2tYJ$b!p=^JSO$%wConcepts

← Back

-

This chapter introduces the architectural concepts and design principles that underpin PyRobusta. +

This page introduces the architectural concepts and design principles that underpin PyRobusta. Detailed information about configuration, routing, and request handling is covered in the corresponding chapters.


Table of Contents

@@ -67,8 +67,9 @@

Execution Model

and associates each connection with a StreamReader and StreamWriter pair for network I/O.

Each client connection executes as an independent asynchronous task, allowing multiple connections to make progress concurrently. HttpServer maintains the set of active connections and accepts -new connections until the configured maximum number of concurrent connections is reached. HttpServer -closes idle connections after the configured timeout to reclaim resources. Because each connection is +new connections until the configured maximum number of concurrent connections is reached. When the +server is at capacity, new connections wait for resources to become available until a configurable +timeout expires. If the timeout is reached, the connection is rejected. Because each connection is processed independently, slow clients do not block unrelated connections. However, requests within the same connection are always processed sequentially.

HandlerHttpEngineHttpConnectionHttpServerClientHandlerHttpEngineHttpConnectionHttpServerClientloop[Until request complete]alt[Keep-Alive][Connection closed]New connectionReserve stream buffersCreate async connection taskProcess received bytesNeed more dataRead socketInvoke route handlerReturn responseResponse readySend responseWait for next requestRelease stream buffers

@@ -79,7 +80,8 @@

Memory Model

for normal request processing is reserved when a connection is accepted, rather than during request handling.

For each new connection, the server reserves a request and response stream buffer from the shared buffer pool. When all stream buffers are reserved, new connections are blocked until a buffer pair becomes available -(either through connection closure or timeout). HttpServer automatically closes connections that remain inactive for too long.

+(either through connection closure or timeout). Connections that remain inactive for too long will +automatically time out, enforced by HttpConnection.

Stream buffers are preallocated during server initialization based on the configured memory limits. Their size and number remain fixed throughout the lifetime of the server. Preallocating stream buffers reduces heap fragmentation, improving memory stability and reducing the risk of runtime allocation failures.

@@ -121,7 +123,7 @@

Resource Usage

Together, these architectural decisions enable PyRobusta to handle HTTP requests with predictable resource usage, incremental request processing, and deterministic behavior suitable for resource-constrained embedded systems.


-

PyRobusta v0.8.0 Web Server

+

PyRobusta v0.9.0 Web Server

diff --git a/dist/pyrobusta/assets/www/configuration.html b/dist/pyrobusta/assets/www/configuration.html index ede073f..617fee5 100644 --- a/dist/pyrobusta/assets/www/configuration.html +++ b/dist/pyrobusta/assets/www/configuration.html @@ -18,7 +18,7 @@

Table of Contents

  • Configuration
  • Configuration Format & Deployment
  • Parameter Description
  • -
  • Configuration API
  • +
  • Configuration Loading

  • Configuration Format & Deployment

    @@ -32,8 +32,8 @@

    Configuration Format & Deployment

    Perform a soft reset and upload pyrobusta.env using mpremote.

    -
    $ mpremote a0 soft-reset
    -$ mpremote a0 cp pyrobusta.env :/pyrobusta.env
    +
    $ mpremote connect /dev/ttyACM1 soft-reset
    +$ mpremote connect /dev/ttyACM1 cp pyrobusta.env :/pyrobusta.env
     

    Parameter Description

    @@ -52,10 +52,55 @@

    Parameter Description

    - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -67,65 +112,63 @@

    Parameter Description

    - + - - - - - - - - - - - - - + + + - - + + + + + + + - - - + + + + + + + +
    wifi_passwordPassword of the Wi-Fi network. When empty, Wi-Fi is not initialized by the built-in wifi.py module.Password of the Wi-Fi network. None
    tlsEnables or disables TLS. When enabled, cert.der and key.der must be installed at the server root.False
    tls_cert_fileAlternative path to the TLS certificate./cert.der
    tls_key_fileAlternative path to the TLS private key./key.der
    passwd_filePath to the file containing user credentials used for authentication./pyrobusta.passwd
    roles_filePath to the file containing RBAC role definitions used for authorization./pyrobusta.roles
    log_levelLogging level. Can be one of: error, warning, info, debug.info
    socket_max_conMaximum number of simultaneous socket connections.2
    http_served_pathsSpace-separated list of filesystem paths that may be served over HTTP./www
    http_mem_capFraction of available heap memory reserved for stream buffers. Valid range: (0, 1].0.1
    http_port Port number for HTTP. 80
    http_multipartEnables or disables multipart request and response processing. Enabling multipart support increases memory usage.Enables or disables multipart request and response processing (Content-Type: multipart/*). False
    http_mem_capFraction of available heap memory reserved for stream buffers. Valid range: (0, 1].0.1
    http_served_pathsSpace-separated list of filesystem paths that may be served over HTTP./www /lib/pyrobusta
    http_files_api Enables or disables the file management API endpoint (/files), allowing upload, download, and listing of files. False
    socket_max_conMaximum number of simultaneous socket connections.2http_authSelects the type of authentication method enforced by the server. Currently, basic authentication (basic) is supported.None
    tlsEnables or disables TLS. When enabled, cert.der and key.der must be installed at the server root.http_browser_securityEnables or disables browser security features including browser security headers (Content Security Policy, referrer policy) and CSRF protection (if authentication is enabled). Disabling browser security is only recommended when using non-browser clients or during local development and testing.True
    http_insecure_authAllows clients to authenticate over unsecured HTTP (without TLS). This may expose credentials or authentication tokens in transit. False
    log_levelLogging level. Can be one of: warning, info, debug.infohttp_sessionsEnables browser session cookies for authenticated clients. When enabled, successful authentication establishes a session that can be reused without resending authentication credentials.True
    http_session_ttl_secDuration of validity of session cookies in seconds.900
    -

    Configuration API

    -

    Configuration values can be accessed through the -pyrobusta.utils.config module. -Values are loaded from pyrobusta.env during server initialization. -Configuration values can be retrieved using -get_config() together with one of the -predefined CONF_* constants.

    -

    After initialization, configuration values are retrieved from an internal cache. -The cached values are normalized to their expected runtime types to avoid repeated -parsing of environment strings.

    -

    Configuration values are treated as immutable during runtime. -Changes are applied only when the configuration cache is reloaded. -The configuration cache can be reloaded by calling -read_config(), which re-reads pyrobusta.env -and rebuilds the internal normalized cache.

    -
    from pyrobusta.utils.config import get_config, CONF_TLS
    +

    Configuration Loading

    +

    Configuration is represented by the Config class in +pyrobusta.utils.config. During application initialization, +pyrobusta.application creates and loads a Config instance from +pyrobusta.env, converting values to their expected runtime types.

    +

    Configuration values are exposed as attributes on the Config instance:

    +
    config = Config("/pyrobusta.env")
     
    -@HttpEngine.route("/tls", "GET")
    -def tls_status(http_ctx, _):
    -    enabled = get_config(CONF_TLS)
    -    return "text/plain", f"TLS enabled: {enabled}"
    +if config.tls:
    +    ...
     
    +

    The application uses the configuration to initialize and specialize the +relevant subsystems during startup. The Config instance is then discarded; +runtime components do not access configuration directly.

    +

    Configuration is immutable for the lifetime of an application. Changes +to pyrobusta.env therefore require a new application instance and, under +normal operation, an application restart.

    +

    The pyrobusta.utils.config module does not maintain a global configuration +object or runtime configuration cache. It provides the Config definition +and configuration-loading functionality.


    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/error_handling.html b/dist/pyrobusta/assets/www/error_handling.html new file mode 100644 index 0000000..92a5756 --- /dev/null +++ b/dist/pyrobusta/assets/www/error_handling.html @@ -0,0 +1,152 @@ + + + + + + + + +

    Logging & Error Handling

    +

    ← Back

    +

    This page describes how the server provides logging with different levels of verbosity +and handles HTTP status codes for client and server errors, including exceptions raised by applications.

    +
    +

    Table of Contents

    + +
    +

    Logging

    +

    The server provides built-in logging constructs implemented by the pyrobusta.utils.logging +module. The logging module supports several log levels, controlled by the log_level server +configuration. log_level can be set to one of error, warning, info, or debug, listed in increasing +order of verbosity. Selecting a log level enables logging for that level and all less verbose levels. +For example, setting log_level to info enables info, warning, and error messages.

    +

    Logging methods expect a mandatory format string as the first positional argument, +followed by optional positional arguments substituted in the format string.

    +
    import asyncio
    +
    +from pyrobusta import application
    +from pyrobusta.utils import logging
    +
    +
    +async def main():
    +  await application.run()
    +  logging.info("%s: %s", __name__, "the application started")
    +  while True:
    +    await asyncio.sleep(1)
    +
    +asyncio.run(main())
    +
    +
    29972 INFO pyrobusta.application: connected, ip=[192.168.1.101]
    +29983 INFO pyrobusta.server.http_server: 1 connection(s) allowed
    +30029 INFO pyrobusta.server.http_server: started
    +30041 INFO __main__: the application started
    +
    +

    Client Errors (4xx)

    +

    As described in the Response Handling guide, route +handlers can set the status codes directly with the terminate method of the HTTP +context. Invalid requests should be rejected by the application with a 4xx status +code. The specific status code is application-specific.

    +
    @HttpEngine.route("/app/action", "POST")
    +def handler(http_ctx, _):
    +    content_type = http_ctx.headers.get("content-type")
    +    if content_type != "application/json":
    +      http_ctx.terminate(415)
    +      return "text/plain", "Unsupported media"
    +    # <rest of the application route>
    +
    +

    The application must use one of the following status codes supported by PyRobusta:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Status CodeHTTP Status
    400Bad Request
    401Unauthorized
    403Forbidden
    404Not Found
    405Method Not Allowed
    408Request Timeout
    413Content Too Large
    415Unsupported Media Type
    +

    Server Errors (5xx)

    +

    5xx status codes indicate errors preventing the server from processing a valid request. +The following list of 5xx status codes are supported by PyRobusta:

    + + + + + + + + + + + + + + + + + + + + + +
    Status CodeHTTP Status
    500Internal Server Error
    503Service Unavailable
    505Version Not Supported
    +

    Exception Handling

    +

    Applications may raise exceptions to signal a failure. Uncaught exceptions are +automatically mapped to the 500 status code sent in the response.

    +

    Additionally, when the server catches an exception raised by the application, +it logs an error message.

    +
    @HttpEngine.route("/app/action", "POST")
    +def handler(http_ctx, _):
    +    # [...]
    +    raise RuntimeError("failed to execute action")
    +    # [...]
    +
    +
    2287169 INFO pyrobusta.application: already connected ip=[192.168.1.101]
    +2287179 INFO pyrobusta.server.http_server: 1 connection(s) allowed
    +2287222 INFO pyrobusta.server.http_server: started
    +[...]
    +2292844 ERROR pyrobusta.protocol.http.run: error=[failed to execute action]
    +
    +
    +

    PyRobusta v0.9.0 Web Server

    + + + diff --git a/dist/pyrobusta/assets/www/file_server.html b/dist/pyrobusta/assets/www/file_server.html index a54fe4f..4ea4d72 100644 --- a/dist/pyrobusta/assets/www/file_server.html +++ b/dist/pyrobusta/assets/www/file_server.html @@ -139,7 +139,7 @@

    Example Request

    192.168.1.100/files/www/user_data/test.txt

    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/index.html b/dist/pyrobusta/assets/www/index.html index 78418ba..84a685e 100644 --- a/dist/pyrobusta/assets/www/index.html +++ b/dist/pyrobusta/assets/www/index.html @@ -23,10 +23,12 @@

    Available Resources

  • Response Processing
  • Static Content
  • File Server API
  • +
  • Authentication & Security
  • +
  • Logging & Error Handling
  • Source Code

  • -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/introduction.html b/dist/pyrobusta/assets/www/introduction.html index b6b1d97..fed0328 100644 --- a/dist/pyrobusta/assets/www/introduction.html +++ b/dist/pyrobusta/assets/www/introduction.html @@ -21,9 +21,10 @@

    Demo Application

    The following application demonstrates the basic structure of a PyRobusta application, including route registration, response generation, and server initialization. The application implements a simple HTTP API that returns the application version. The example includes boot.py, which starts the application, and app.py, which registers routes and starts the HTTP server.

    -
    import asyncio
    +
    # /app.py
    +import asyncio
     
    -from pyrobusta.server import http_server
    +from pyrobusta import application
     from pyrobusta.protocol.http import HttpEngine
     
     APP_VERSION = "v0.1.0"
    @@ -34,61 +35,52 @@ 

    Demo Application

    return "application/json", { "version": APP_VERSION } - return "text/plain", f"{APP_VERSION}\n" async def main(): - server = http_server.HttpServer() - await server.start_socket_server() - + await application.run() while True: await asyncio.sleep(1)
    -
    # /boot.py
    +
    # /boot.py
     # This file is executed on every boot
     
    +import asyncio
     import machine
    -from os import listdir
    -
    -from pyrobusta.connectivity import wifi
    -
    -connected = wifi.initialize()
    -if connected and not machine.reset_cause() == machine.SOFT_RESET:
    -    if "app.py" in listdir():
    -        import app
    +import app
     
    -        asyncio.run(app.main())
    +if machine.reset_cause() != machine.SOFT_RESET:
    +    asyncio.run(app.main())
     

    In the example, boot.py conditionally starts the server when no REPL session is active. This allows mpremote to connect after a soft reset and upload files during development.

    Deployment with mpremote

    -

    Perform a soft reset and upload app.py and boot.py using mpremote.

    -
    $ mpremote a0 soft-reset
    -$ mpremote a0 cp app.py :/app.py
    -$ mpremote a0 cp boot.py :/boot.py
    +

    Perform a soft reset and upload app.py and boot.py using mpremote:

    +
    $ mpremote connect /dev/ttyACM1 soft-reset
    +$ mpremote connect /dev/ttyACM1 cp app.py :/app.py
    +$ mpremote connect /dev/ttyACM1 cp boot.py :/boot.py
     
    -

    Perform a hard reset to start the application and connect to the REPL.

    -
    $ mpremote a0 reset repl
    -Connected to MicroPython at /dev/ttyACM0
    -...
    -[INFO] pyrobusta.con.wifi: network b'Home-Wi-Fi' found!
    -[INFO] pyrobusta.con.wifi: connected, available at 192.168.1.101
    -[WARN] pyrobusta.server.http_server.init_pools: low-memory mode with reduced buffer size
    -[INFO] pyrobusta.server.http_server.init_pools: 2 connection(s) allowed
    -[INFO] pyrobusta.server.http_server: started
    +

    Perform a hard reset to start the application and connect to the REPL:

    +
    $ mpremote connect /dev/ttyACM1 reset sleep 1 repl
    +Connected to MicroPython at /dev/ttyACM1
    +Use Ctrl-] or Ctrl-x to exit this shell
    +[...]
    +2711 INFO pyrobusta.application: connected, ip=[192.168.1.101]
    +2732 INFO pyrobusta.server.http_server: 4 connection(s) allowed
    +2762 INFO pyrobusta.server.http_server: started
     
    -# You can now reach the device at 192.168.1.101 (replace with your IP)
    +# You can now reach the device at the indicated IP address
     # Press Ctrl-x to exit
     
    -

    Use curl to test the application.

    -
    $ curl "http://192.168.1.101/version"
    +

    Use curl to test the application:

    +
    $ curl "http://192.168.1.101/version"
     v0.1.0
     
     $ curl -H "Accept: application/json" "http://192.168.1.101/version"
     {"version": "v0.1.0"}
     

    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/request.html b/dist/pyrobusta/assets/www/request.html index 7aed513..21f7aa2 100644 --- a/dist/pyrobusta/assets/www/request.html +++ b/dist/pyrobusta/assets/www/request.html @@ -101,9 +101,8 @@

    Streamed Requests

    request chunks are processed individually by calling registered route handlers for each chunk received. As a result, the application must process the request body incrementally rather than assuming the full payload is available at once.

    -
    import pyrobusta.server.http_server as http_server
    +
    from pyrobusta import application
     from pyrobusta.protocol.http import HttpEngine
    -from pyrobusta.utils.helpers import normalize_path
     
     @HttpEngine.route("/app/chunks", "POST")
     def upload_chunks(http_ctx, payload: bytes):
    @@ -116,7 +115,7 @@ 

    Streamed Requests

    if payload: # Wait for more chunks before setting response status - with open(normalize_path("/tmp/chunks.txt"), "ab") as f: + with open("/tmp/chunks.txt", "ab") as f: f.write(payload) return @@ -125,8 +124,7 @@

    Streamed Requests

    return "text/plain", "OK" async def main(): - server = http_server.HttpServer() - await server.start_socket_server() + await application.run() while True: await asyncio.sleep(1)
    @@ -137,8 +135,8 @@

    Multipart Requests

    Unlike regular request bodies, multipart parts are parsed by the server before being passed to the application. Preprocessed parts consist of headers (dictionary) and the raw part body (bytes), passed as a tuple.

    -

    Multipart state tracking -The HTTP context exposes the boolean attributes +

    Multipart State Tracking

    +

    The HTTP context exposes the boolean attributes mp_is_first and mp_is_last to identify the first and final part of a multipart request. This allows the handler to maintain per-request @@ -151,9 +149,8 @@

    Multipart Requests

    import asyncio from os import listdir, remove, rename, mkdir -import pyrobusta.server.http_server as http_server +from pyrobusta import application from pyrobusta.protocol.http import HttpEngine -from pyrobusta.utils.helpers import normalize_path @HttpEngine.route("/app/parts", "POST") def handle_parts(http_ctx, payload: tuple): @@ -163,8 +160,8 @@

    Multipart Requests

    _part_headers, part_body = payload - tmp_path = normalize_path(f"/tmp/parts.{http_ctx.id}.txt") - target_path = normalize_path(f"/www/user_data/parts.{http_ctx.id}.txt") + tmp_path = f"/tmp/parts.{http_ctx.id}.txt" + target_path = f"/www/user_data/parts.{http_ctx.id}.txt" with open(tmp_path, "ab") as f: f.write(part_body) @@ -176,14 +173,13 @@

    Multipart Requests

    return "text/plain", "OK" async def main(): - mkdir(normalize_path("/tmp")) - server = http_server.HttpServer() - await server.start_socket_server() + mkdir("/tmp") + await application.run() while True: await asyncio.sleep(1)

    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/response.html b/dist/pyrobusta/assets/www/response.html index 42f3be9..883ea2b 100644 --- a/dist/pyrobusta/assets/www/response.html +++ b/dist/pyrobusta/assets/www/response.html @@ -47,6 +47,73 @@

    Status Codes

    http_ctx.terminate(404) return "text/plain", "Resource not found"
    +

    The following list of status codes are supported by PyRobusta:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Status CodeHTTP Status
    200OK
    201Created
    204No Content
    400Bad Request
    401Unauthorized
    403Forbidden
    404Not Found
    405Method Not Allowed
    408Request Timeout
    413Content Too Large
    415Unsupported Media Type
    500Internal Server Error
    503Service Unavailable
    505Version Not Supported

    Response Headers

    Response headers and response bodies can be configured through methods exposed by the HTTP context (set_response_header(), set_response_body()). @@ -108,7 +175,7 @@

    Streamed Responses

    # /app.py
     import asyncio
     
    -from pyrobusta.server import http_server
    +from pyrobusta import application
     from pyrobusta.protocol.http import HttpEngine
     
     @HttpEngine.route("/stream", "GET")
    @@ -133,8 +200,7 @@ 

    Streamed Responses

    http_ctx.resp_handler = generate_chunks async def main(): - server = http_server.HttpServer() - await server.start_socket_server() + await application.run() while True: await asyncio.sleep(1)
    @@ -164,7 +230,7 @@

    Streamed Multipart Responses

    # /app.py
     import asyncio
     
    -from pyrobusta.server import http_server
    +from pyrobusta import application
     from pyrobusta.protocol.http import HttpEngine
     
     def multipart_response(num_responses, part_size):
    @@ -185,8 +251,7 @@ 

    Streamed Multipart Responses

    return "multipart/form-data", multipart_response(part_count, part_size) async def main(): - server = http_server.HttpServer() - await server.start_socket_server() + await application.run() while True: await asyncio.sleep(1)
    @@ -207,7 +272,7 @@

    Streamed Multipart Responses

    --pyrobusta-boundary--

    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/routing.html b/dist/pyrobusta/assets/www/routing.html index 8d8e415..dd5673f 100644 --- a/dist/pyrobusta/assets/www/routing.html +++ b/dist/pyrobusta/assets/www/routing.html @@ -130,7 +130,7 @@

    Route Registration & Deregistrati return "text/plain", "OK"


    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/assets/www/security.html b/dist/pyrobusta/assets/www/security.html new file mode 100644 index 0000000..b665e88 --- /dev/null +++ b/dist/pyrobusta/assets/www/security.html @@ -0,0 +1,499 @@ + + + + + + + + +

    Authentication & Security

    +

    ← Back

    +

    This page documents the security features provided by PyRobusta, +including HTTP Basic Authentication, role-based authorization, and TLS.

    +

    Note: Authentication verifies the client's identity. Authorization determines +whether the authenticated user is permitted to access a resource.

    +
    +

    Table of Contents

    + +
    +

    Basic Authentication

    +

    PyRobusta supports HTTP Basic Authentication with per-user credentials +stored on the device. Passwords are stored as PBKDF2-HMAC-SHA256 hashes +along with randomly generated salt values. Basic authentication is +disabled by default. It can be enabled by setting +http_auth=basic in pyrobusta.env.

    +

    During initialization, PyRobusta reads pyrobusta.passwd by default +from the server's working directory. Each entry specifies a username, +one or more access role names, password hash and other related metadata. +Set passwd_file in the configuration to specify an alternative user +credentials file to load. For more information, see Server Configuration.

    +

    PyRobusta defines a single realm (realm="Device") to authenticate against. +The server response includes the WWW-Authenticate: Basic realm="Device" header +when a request does not contain valid credentials.

    +

    pyrobusta.passwd

    +

    pyrobusta.passwd utilizes a passwd-like format, specifying one user per line, +with each element of the user data separated by :. The expected format +of a user entry is:

    +
    <username>:<roles>:<password-hash>:<salt>:<iterations>:<algorithm>:<version>
    +
    +
      +
    • username: name of the user (case insensitive)
    • +
    • roles: comma-separated role names (case insensitive)
    • +
    • password-hash: Base64-encoded PBKDF2 output
    • +
    • salt: Base64-encoded random salt
    • +
    • iterations: number of PBKDF2 iterations
    • +
    • algorithm: password hashing algorithm (currently PBKDF2-HMAC-SHA256)
    • +
    • version: version of PyRobusta when the user was created
    • +
    +
    # Example
    +# File: /pyrobusta.passwd
    +szeka9:api_admin:JlSousyaBB87FIIS/xoQIWTCH7+Z/yjQao5NGE7O/ww=:Zz1n+l9UN89hKlpnnvcXAg==:5000:PBKDF2-HMAC-SHA256:v0.8.0
    +alice:api_user,app_viewer:mKmmf5wBEtlkty7LEcphieciOd3Pl0yY7r3WmDiZnzg=:XTQgg3Has79lDTNYVW+aPw==:5000:PBKDF2-HMAC-SHA256:v0.8.0
    +bob:api_user,app_maintainer:sOqLqi48jCQUiR+VpcCcfMgKcKCspbE902y0yFe0DV4=:5PzMbQQJtQRP8aZ9C7t8qQ==:5000:PBKDF2-HMAC-SHA256:v0.8.0
    +[...]
    +
    +

    New users can be added programmatically through the IAM API:

    +
    from pyrobusta.utils.iam import IAMDatabase
    +iam_db = IAMDatabase("pyrobusta.passwd", "pyrobusta.roles")
    +iam_db.load()
    +iam_db.create_user("john", "john's-secret-password", ["role-1", "role-2"])
    +
    +

    Password verification uses PBKDF2-HMAC-SHA256 with a default iteration count of 5000. +The iteration count is intentionally chosen as a compromise between resistance against +offline attacks and the computational limitations of microcontroller-class hardware. +Because lower iteration counts reduce the computational cost of each password guess, +PyRobusta enforces strong password requirements to increase the password search +space and improve resistance against brute-force attacks.

    +
    iam_db.create_user("john", "secret", ["role-1", "role-2"])
    +Traceback (most recent call last):
    +  File "<stdin>", line 1, in <module>
    +  File "src/pyrobusta/utils/iam.py", line 198, in create_user
    +  File "src/pyrobusta/utils/crypto.py", line 125, in validate_password
    +ValueError: Password must be at least 16 characters
    +
    +

    HTTP Sessions

    +

    Without session authentication, each HTTP request authenticated with Basic Auth requires a +new password verification operation. Since PBKDF2 is intentionally computationally expensive, +this can introduce noticeable latency on microcontroller-class hardware.

    +

    To avoid repeated password verification, PyRobusta can issue stateless session cookies +after a successful authentication. Subsequent requests are authenticated using the session +cookie instead of recomputing the PBKDF2 password hash.

    +

    Session cookies are cryptographically signed and validated by the server without maintaining +per-session state. Session signing keys are derived from per-user secrets that are randomly +generated during server startup and stored only in memory. Consequently, all existing +sessions are invalidated automatically when the server restarts.

    +

    Stateless sessions are enabled by setting http_sessions=true in pyrobusta.env. +Sessions expire after 15 minutes by default. The session lifetime can be configured with +http_session_ttl_sec.

    +

    Authorization

    +

    When HTTP Basic Authentication is enabled, PyRobusta provides role-based access control (RBAC). +Resource permissions are determined by the authenticated user's assigned roles. +Each user can be assigned one or more roles, configured in pyrobusta.passwd.

    +

    While pyrobusta.passwd assigns roles to users, pyrobusta.roles maps those roles to +HTTP methods and server resources. Authorization follows a least-privilege model. +Users have no permissions unless explicitly granted by one or more authorization rules. +Each matching rule grants additional permissions. Requests that do not match an authorization +rule receive HTTP 403 Forbidden.

    +

    Set roles_file in the configuration to specify an alternative role configuration file to load. +For more information, see Server Configuration.

    +

    pyrobusta.roles

    +

    pyrobusta.roles consists of one or more authorization blocks. Each block defines one or +more path patterns, followed by one or more authorization rules. Every authorization rule +applies to every path pattern declared within the same block.

    +
    <path-pattern>
    +<path-pattern>
    +...
    +    <HTTP-method>: <role>,<role>,...
    +    <HTTP-method>: <role>,<role>,...
    +    ...
    +
    +<path-pattern>
    +    ...
    +
    +

    Authorization rules are selected based on pattern specificity rather than their +order in pyrobusta.roles. Path patterns support the following forms, ordered +from least to most specific. Each request is matched against the most specific +pattern, and only the roles associated with the selected pattern are considered +during authorization.

    +
    /**                        # Match any path
    +/*                         # Match direct children of /
    +/app                       # Exact match
    +/app/**                    # Match any descendant of /app
    +/app/*                     # Match direct children of /app
    +/app/*/resources           # Match a single wildcard segment
    +/app/endpoint/resources    # Exact match
    +
    +

    Note: recursive globs (**) are only supported as the final path segment. +Allowing ** in intermediate segments would introduce ambiguous matches +and is therefore not supported.

    +
    # File: /pyrobusta.roles
    +
    +# ---------------------------------------------------------
    +# Example: public resources (/, /index.html, /styles.css, ...)
    +/
    +/*
    +    GET,HEAD: *
    +
    +# ---------------------------------------------------------
    +# Example: apply rule to parent and child resources
    +/app/resource
    +/app/resource/*
    +    GET: resource_viewer
    +    POST,PUT,DELETE: resource_maintainer
    +
    +# ---------------------------------------------------------
    +# Example: apply rule to paths of any length with **
    +/app/api/**
    +    GET: api_viewer
    +    POST,PUT,DELETE: api_maintainer
    +
    +# ---------------------------------------------------------
    +# Example: require api_admin access for all HTTP methods
    +/app/api/management
    +    *: api_admin
    +
    +# ---------------------------------------------------------
    +# Example: restrict all access to a resource
    +/app/secret
    +    *:
    +
    +# ---------------------------------------------------------
    +# Example: assign multiple roles
    +/app/logs
    +    GET: api_admin,api_maintainer
    +
    +

    Note: The special role * grants public access regardless of the authenticated user's roles. +Rules that specify * do not require authentication.

    +

    CSRF Protection

    +

    Credentials used with HTTP Basic Authentication may be automatically attached to requests by a browser. +This makes HTTP Basic Authentication vulnerable to CSRF (Cross-Site Request Forgery) attacks, +allowing an attacker-controlled website to cause the browser to issue requests to the server on +behalf of an authenticated user.

    +

    To prevent CSRF attacks, PyRobusta applies the Signed Double-Submit Cookie pattern. For unsafe HTTP methods +(POST, PUT, PATCH, and DELETE), the client must return the CSRF token received from the server as +a cookie. The client must send the same token in the X-CSRF-Token request header. The server +rejects requests where the cookie value and header value do not match.

    +

    A malicious website cannot read the CSRF cookie from another origin because of the browser's +same-origin policy. Therefore, a malicious website cannot provide a valid CSRF token in the request header. +Cryptographic signing prevents attackers from forging valid CSRF tokens without knowledge of the server-side secret.

    +

    PyRobusta generates CSRF tokens using HMAC-SHA256 with per-user secrets generated during server +initialization. User secrets exist only in memory, so restarting the server invalidates all +previous CSRF tokens. The server issues a CSRF cookie after successful authentication. Clients +must include the same token in the X-CSRF-Token header for subsequent unsafe requests.

    +

    Tokens generated by PyRobusta have the following format:

    +
    <hex-encoded-random-nonce>.<hex-encoded-HMAC-SHA256-signature>
    +
    +

    During verification, PyRobusta recalculates the HMAC-SHA256 signature over the nonce using the +user secret and compares it with the signature provided in the token. Requests are rejected with HTTP +403 Forbidden if the token signature is invalid or if the cookie and X-CSRF-Token header values do not match.

    +

    Authentication & Authorization Flow

    +

    Public

    Protected

    Yes

    No

    Yes

    No

    No

    Yes

    No

    Yes

    No

    Yes

    No

    Yes

    Yes

    No

    Incoming request

    Resolve resource

    Resource security policy

    Process request

    Read cookie data

    Session cookie?

    Validate session

    Use configured auth method

    Session valid?

    Authenticated identity

    Authorization header?

    401 Unauthorized

    Validate credentials

    Credentials valid?

    CSRF required?

    Authorize

    CSRF token valid?

    403 Forbidden

    Permission granted?

    +

    PyRobusta returns the following HTTP status codes for authentication and authorization failures:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConditionResponse
    Missing authentication credentials401 Unauthorized
    Invalid authentication credentials401 Unauthorized
    Authenticated, but CSRF validation failed403 Forbidden
    Authenticated, but authorization failed403 Forbidden
    +

    Content Serving & Browser Security Headers

    +

    Unsafe Content Types

    +

    To lower the risk of cross-site scripting (XSS) attacks, browser-interpreted or +active content types like text/html and application/javascript are served +as attachments by setting Content-Disposition: attachment. This rule applies +to every file stored under /www/user_data. The following file extensions are +allowlisted and are served inline with their corresponding content types:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ExtensionContent-Type header
    .csvtext/csv
    .gifimage/gif
    .icoimage/x-icon
    .jpeg/.jpgimage/jpeg
    .jsonapplication/json
    .logtext/plain
    .pngimage/png
    .txttext/plain
    .webpimage/webp
    +

    Unless overridden by the application, responses include X-Content-Type-Options: nosniff +to prevent browsers from interpreting content using a different MIME type than the one +provided by the server.

    +

    Browser Security Headers

    +

    Content Security Policy

    +

    Content Security Policy controls which resources the browser is allowed to +load, execute, and embed, acting as a defense mechanism against XSS +(cross-site scripting) vulnerabilities. PyRobusta includes a default +Content-Security-Policy header in every response. Applications may override +this header when required, but doing so can reduce browser-side security +protections.

    +

    The following defaults are applicable to the Content-Security-Policy header:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PolicySettingMeaning
    default-src'self'Resources are only allowed from the same origin by default.
    script-src'self'JavaScript may only be loaded from the same origin. Inline scripts are blocked.
    style-src'self' 'unsafe-inline'Stylesheets may be loaded from the same origin. Inline styles are allowed.
    object-src'none'Embedded plugin content such as <object> and <embed> is disabled.
    base-uri'self'The document base URL can only be set to the same origin.
    frame-ancestors'none'Prevents the application from being embedded in frames.
    +

    Inline styles are permitted for compatibility with embedded resources such as +SVG files. Inline JavaScript remains disabled by the default policy.

    +

    Referrer Policy

    +

    Referrer Policy controls the amount of referrer information sent by the browser. +The following default is applicable to the Referrer-Policy header:

    + + + + + + + + + + + + + + + +
    HeaderSettingMeaning
    Referrer-Policyno-referrerPrevents browsers from sending the originating URL in the Referer header.
    +

    HTTPS / TLS

    +

    TLS is enabled by the tls configuration parameter in pyrobusta.env. When enabled, the certificate and private +key specified by the tls_cert_file and tls_key_file configuration are loaded. The underlying asyncio server +accepts a configured MicroPython ssl.SSLContext through the ssl parameter of asyncio.start_server().

    +

    TLS capabilities, including supported certificate and private-key formats, key types, cipher suites, and cryptographic +algorithms, depend on the SSL implementation and configuration provided by the target MicroPython port. The configurations +documented here are those supported and tested for PyRobusta on the ESP32 port. Larger RSA key sizes and larger elliptic +curves require additional RAM and computational resources during TLS initialization and connection handling. +The recommended configurations provide a balance between compatibility, security, and resource usage.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AlgorithmSize/curveKey/Certificate formatComment
    ECDSAP-256DERRecommended ECDSA configuration
    ECDSAP-384DER
    ECDSAP-256PEM
    RSA2048DERRecommended RSA configuration
    RSA3072DER
    RSA4096DER
    RSA2048PEM
    +

    Certificate Creation & Installation

    +

    The following section provides examples for certificate creation. +The examples below use ECDSA P-256, which is the recommended configuration. +RSA certificates can be generated similarly by replacing the EC key-generation commands +with RSA key generation.

    +

    Replace device.local with the hostname used to access the device, as well as +the certificate subject (subjectAltName) values according to your deployment. +If the device is accessed by IP address, use an IP SAN instead, for +example subjectAltName=IP:192.168.1.101.

    +

    Self-signed Certificate

    +
    # Create private key for the server
    +openssl genpkey -algorithm EC \
    +  -pkeyopt ec_paramgen_curve:P-256 \
    +  -outform DER -out key.der
    +
    +# Create certificate for the server, signed by itself
    +openssl req -x509 -new \
    +  -key key.der -keyform DER \
    +  -out cert.der -outform DER \
    +  -days 365 \
    +  -subj /CN=device.local \
    +  -addext 'basicConstraints=critical,CA:FALSE' \
    +  -addext 'keyUsage=critical,digitalSignature' \
    +  -addext 'subjectAltName=DNS:device.local'
    +
    +

    The above commands produce:

    +
    key.der          ECDSA P-256 private key
    +cert.der         self-signed certificate
    +
    +

    CA-signed Certificate

    +
    # Optional: generate CA private key and certificate if does not exist
    +openssl genpkey -algorithm EC \
    +  -pkeyopt ec_paramgen_curve:P-256 \
    +  -outform DER -out ca.key.der
    +
    +openssl req -new -x509 \
    +  -key ca.key.der -keyform DER \
    +  -out ca.crt.der -outform DER \
    +  -days 3650 -subj /CN=TestCA \
    +  -addext 'basicConstraints=critical,CA:TRUE' \
    +  -addext 'keyUsage=critical,keyCertSign,cRLSign'
    +
    +# Create server private key certificate signing request (CSR)
    +openssl genpkey -algorithm EC \
    +  -pkeyopt ec_paramgen_curve:P-256 \
    +  -outform DER -out key.der
    +
    +openssl req -new \
    +  -key key.der -keyform DER \
    +  -out server.csr \
    +  -subj /CN=device.local
    +
    +# Create certificate for the server, signed by the CA
    +openssl x509 -req \
    +  -in server.csr \
    +  -CA ca.crt.der -CAform DER \
    +  -CAkey ca.key.der -CAkeyform DER \
    +  -CAcreateserial \
    +  -out cert.der -outform DER \
    +  -days 365 \
    +  -extfile <(printf '%s\n' \
    +    'basicConstraints=critical,CA:FALSE' \
    +    'keyUsage=critical,digitalSignature' \
    +    'subjectAltName=DNS:device.local')
    +
    +

    The above commands produce:

    +
    ca.key.der       CA private key
    +ca.crt.der       CA certificate
    +server.csr       Server CSR
    +key.der          Server private key
    +cert.der         CA-signed server certificate
    +
    +

    Install certificates into the root directory with mpremote:

    +
    $ mpremote connect /dev/ttyACM1 soft-reset
    +$ mpremote connect /dev/ttyACM1 cp key.der :/key.der
    +$ mpremote connect /dev/ttyACM1 cp cert.der :/cert.der
    +
    +
    +

    PyRobusta v0.9.0 Web Server

    + + + diff --git a/dist/pyrobusta/assets/www/static_content.html b/dist/pyrobusta/assets/www/static_content.html index 801e137..2b22f49 100644 --- a/dist/pyrobusta/assets/www/static_content.html +++ b/dist/pyrobusta/assets/www/static_content.html @@ -60,12 +60,28 @@

    MIME Type Handling

    +.css +text/css + + +.csv +text/csv + + +.gif +image/gif + + .html text/html -.css -text/css +.ico +image/x-icon + + +.jpeg/.jpg +image/jpeg .js @@ -76,37 +92,37 @@

    MIME Type Handling

    application/json -.ico -image/x-icon +.log +text/plain -.jpeg -image/jpeg +.png +image/png -.jpg -image/jpeg +.raw +application/octet-stream -.png -image/png +.svg +image/svg+xml .txt text/plain -.gif -image/gif +.webp +image/webp -.raw, unknown extensions +unknown extensions application/octet-stream
    -

    PyRobusta v0.8.0 Web Server

    +

    PyRobusta v0.9.0 Web Server

    diff --git a/dist/pyrobusta/bindings/http_connection.mpy b/dist/pyrobusta/bindings/http_connection.mpy index 8d77afb21b91f16869d5bda6b2888e13ea3d37e3..0b757c51d2d491d46a6a5a079558b6c39329d036 100644 GIT binary patch delta 1341 zcmYjQOH`v>G&_~8LOE3^ZEPUF{W?t4{vc8=&swRd*Mj6r za#yJRL|(6bB(L6~qUCFxp?|ldmn%0Gqva*wrYDQ}(zn|My`rCcfGdFGbX@OQRM%v& zq8r840(Vz8kRGlfrZ($XukJg8G_k_PW(Efb4O8nSA!$;M1kp=|9MF+7DJR0NkP|u) zb|c&tazi)59)vH1JkW!%m+~dZ-u93edP6?w%iR+J8 z<=xM=-#dE(*V8w?yqPh32K)Q_>9HuVy8`t?napW+umio+ksa)OZ3k&B|AITnKqhVW ztqeqjq!_0?60m|Diea&=3?5ds%^dBpm@bKUh@i!Euuul-eGEQBUb_wjwR6HsHPpq$)zb`3A-6ok4(Hh zRPQO%Edx4a66&z>zW`NH9yFw&@w9{~KUXnHQU=*$6C*s-rx*;P{-azL+ucyu$j=OE zr@yL;m?wpg%O}}6iyg*rs5v$*f3WgbMj1{InOpJ8iBTcRCu3hF1U@!A6yf3nd_)lD zCL{gyIGVru18QamJHoqUf_I;&znL~lDiO=mQ+Or#HoO98#Q!30ynpAlAQ0j`&4hJT z!?z2~TMj+ST&upLsYc?yRyD#=I{rrqlCL}`F^8W#!sb<*# delta 1235 zcmYjP-*4Jh7`?WMLn#o)c5f*GLNFu{C|QW>W$9Rnak7N`kes!PP?bUII4K&1iV$hj zG~uMAsb1p#a(SUvtJZ*bzh2X~pCli3dEk?`_w{VGQqi?~sagTilds1D z-lw%{y{c8q$;b8jQ{XOF_x4JaJ>WQ~m&*IrUjf3tqdz*>1BbDei=1*h^@>*A(Sc)o zUn`Yb^W=-xPvl+lXsSh1vmBQ!ppH9*dbNO>&IKQS_IYcay6L8!8;B0-*w$RI^^)3c z9N+h_L@_wNnn)zdhB`t*(qO1bVubWjkjfu1HiQR!HfTebMtIOiLmFW_!q7fA_bkQMl@IL6V4azVkRPFmFI&^=T5QdYDU1Y2;X6xa4z&^LxkQV z$bSLD0)ThTARwSCML;%1LQjf<{*(=JsR5WWHTs$TN1NC6Zu-4@nR&yTm46E9){Uig~v@uo;hhp(mR5d+Aa?Rv934EkJ&a;%+ z;CPY1*yP;&4GAa@Fn|cGVsKLed3@>WbU@Xb%pV!ICvDu0PTw)PVg8l`Ua?t&RVET|nX{hWdRv@w%x2$7F-v1yZIxBy!CQ3?G0 z%E~UVHWBD1A7sWXCZsZg$%Uoo*mMV1jkF~q&pP-BD+&ZZj&^S_p|nLpYb~A2vs~Jk zk8gxxLi~ed^uu@~7+(oRpUlK#!O1m~!_VO+&a)dP*BDYSl&=miTW60BmjsjdcgKTl zbcYcQJ!6nM9zjeX5z1|CvbNXCdY3|{e#|XzvLrXz$9gb3v&k;JR?3~_5WbFT>;EZH z>>{$;;Nwi9XJ1u2QF1FsY~fNCIqH^PdR1f@o8cx<7$~-@)MO49-PNk;|y)`=MZ@iB}RlY&v zaq}~RO(KMWbLCgVLgFG(Sjpj>1sdmya{H&J@qbHnHrV6Zii#`kyFHN^UQ5L(!YyHIm`NCkAkTFgauKlijXDo}@!5s;FZ}HNpMEr1;mE{8F-ahM} dLBEJgwfI@|FK)>j?6TnC$1fEnLL_um`xkBwMR@=K diff --git a/dist/pyrobusta/connectivity/__init__.py b/dist/pyrobusta/connectivity/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/dist/pyrobusta/connectivity/wifi.mpy b/dist/pyrobusta/connectivity/wifi.mpy deleted file mode 100644 index d89dfb263a3418bb63d234c69e5ae8e9084b4d28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 645 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 N5iHMOFT{k9{6GIjJXQbz diff --git a/dist/pyrobusta/protocol/http_basic_auth.mpy b/dist/pyrobusta/protocol/http_basic_auth.mpy new file mode 100644 index 0000000000000000000000000000000000000000..c7a1976fd4de6f6997ab2596f521ea590b8758fc GIT binary patch literal 2752 zcmZux-E-4c7Qg;X2*FXV#C1pnxR&i0kqsz|E+AvIMx~^-cCTrE{bTv<+1O>>Wi{*S6kuhk9t)(AN&2p&s~@>sxhG18^xT=CYZ2t_;w8a*>xz z2@g#HFrUq&#l>V{0l-|cP!Q*mg}I(tNM?#XbH13)15_%c^I{>*<YM65bzSq%gd9=w~=_uRLc6I1@QN( zx_LFoD|b{`2?EdHy~#tn_6}gNO!oBT3B6O>Z0I#y8?(5p;&Kf$U@5~RaE1JA0FT5} zWUG8YH3AjYkXA9#_XCG=;8?k;;%1D1q$`0tjcQHNEm*+R0(KI~G$nb%0H4#BW*p5U zk;v??S7Mh=f4Z`g37^R`3?u(;=)vd%|AVmyLQ@!XA}9WLAs7C4BX^VV5MH8>=x5zy zM`#c7&|c)F`%oX!d-0W?4#MMtsc7~vy^Sl1XqA4A7zAM(*dXbAg< z2}qCxMI0v(;b;5CM(ANQOhW`|5|K28DEc@$P9ub9Kk{Sz3Dz+-N}oU{%E2%D(c-Y1 z8v8uqKug0u%lv)9F}xIPR^l#{75q8DF9`m;;4c(ofS}_}M8_QHWXy?zF&CPQxluIc zL7X6aw~Jtqel)QX;D?K-M9}CX2B75_KnsHGxIgl(8!p_G%fD(~2=PK|C&K#6Q~@8sCAbK0!0dq&9^2cEJ70B#pT7I%3C}&|GVla- znsKxf&T{HnyTdv$qrAmT{_RLA61;h&wfGr{oL@z5+bQpy>u^7-KO`MEQp zsk0N>IFs3&V?vPy9v?>DObgGgH?0@Bec>e(W=C7pFNjMC$MUP$A?SVFtqTir=km+_ zU2ebGA}@0_IN6FjzInoxTGSQB#kldJTGac&-wfMod2-JA?Oz}bPk!~sxT`gC8HbSX zaXfy=mKnec`Tv1?IPkA9$8T@%a{tAZm=^g#i@e$*f62P=-tNMFi`<9AOv2ga)m?$- zt>bIxFU4Klg3X4s*`&-jiCE{4{F|hcC*k<}Q;u)GPL+0^Vu}k-Eon6q&etja=}zhOpTCA= z>0iws_`{64`odzqcOk}!D^m0Ol`zLL;p?;EkTMfs!r^o@x`PRFa8+rOJa?;2rumPV zz87tBmcNcK$2DBHoSKEJowx%d=D2yw$@F9Ij=z8%{#=`!_X}H;A}6)RPanGD2&#F0Hg zM5?r5i-h{tpOSdH7C>zPA z8d$(KF(}a;^RP-vI<<~S6)c<(+dOEz55_)}!gixZEf5_l5XUjC>O&ZCmJg6Bwn_v{ zmdWWclLM1=+o+bT3J8?UL*h^n9nSfHx(MslA?dXmD3QL-nI1eORIfY4*3IgH1;P)L zv11<$$A%vy|BE4gb89n`(+e9fb0EmBrcl_}+SE5T*SCPAX9_#(gIaW@s|LH!fJ2xz z1(TE>>#Xhrb(?_K3E6qyY|yCFvd;E-VwQ*vth2SiV4VQbOW3w*7t}TI! z1Htd=uJkv^Bp&~-1i1MAQJ5<#3+rDbPm5}}bRImPJ)dmmXL!W3rw4hOqE{Rdg{Ws{ zRQ4jTJc34K0SS9e(I<{Y`I&LqhkWuF8e@oG42To#%SqXf{Bi&V_SRu2hrW4EQ&(kdlsiPYArt|98Y&wsh zMW9Feq?PZ)`CW~Bdnr9qd5wGGxxKsZN{=-l1vPN{eEeIu^hY+cm&f0=rJ1hAy}QXU zGW6fTvE8AyU*N>gtyd{ezcb?Ig3DJaF2y&0b0>9gqCEbgoDRyNaP#Y;vK(7nQX|8p zZ=k=`7gCrD7`W`D_*>Q2?WtqEyLL8r#bVuE;qzi9JRAKi)O^X78(NBlBB59`)XHYs nne;Yp52C%jbfazKR{MAN1^w<7NstNO!ijDh&bb!1@eck6%Ee$; literal 0 HcmV?d00001 diff --git a/dist/pyrobusta/protocol/http_file_server.mpy b/dist/pyrobusta/protocol/http_file_server.mpy index 1c30f91d93c874f4089fe1223be9d02683125845..f0399156f79aff4318208568dfda776036ecea9e 100644 GIT binary patch delta 1896 zcmZ8hO-$Qn7`7cqCBsXfboD;el3cfvX>`t=zno&LlC@^2*v$axwLh7++0h79M=p$yibuaV?n<<7shz zNnFmPG1sen*R4!yG3{CuZ>!lad-GAet=^PMtJSDt?E`Txe;a4dD85VN=d=A)yYgIDJ&>tO16q-y!oEKhr<&I^SNRBCy6k5-__4Kj%*9$kf#iX z@xMz&@*L>o@Z8~U=~QtPbRL2((p4-e>}6v&M;z`I`0H>sy9s(&V&KgF7U?b^CZQWh zJE+}M=dnbrKMH463_PdO6ghH(W%4Jeo?v=^;TzD`iv$Jzp zHU=6au*NtDf`^S!nH-`GU|7A46g2V*jg}D@1LG+_?4CAWYo2kh$rJ>e1Who#uHbg3-cdHk)>#I zj;Nboj;JopiY!ZsYtqcU8;us%i`vH_26yu6QpN;oGVQ>d`4Cu&PD+bnd`8NB1N6X1 z1#!E9#n}9!lzB{Mn~Vj?_Xs2{u3VK8(WFGszLnWoDG^8{;t8U##J#$@>R!RUMrNYP zXy$9(Y=JHjUCsFmDnQkzjm@n<5Nr*7jUNfaiHr7IhP9@(Gi#1DZ)TrTRnwzr$NKNc3OEwSq)s!1 zj9y!ws!0cNIO@dNip3TZgQwQ1YIr8ik*Tpgg{o1^%qCT?VV@-uz_W2hTU zk?I3eDz*VlX$5bNvEVQshvB04j~7zRXBBg*?kV4crgN~KC2`RnY63-`@%{PkDYg3( z;#5eyZSQQhV~qv`VWh^PDpc=Kg{pJ(IFJd8lb7VCstyabgTgxHKocBL#lRxv;f}<> zo1n1)q=$$DmctZ_o*JEhzeizyuQ!RmVPDMoMAzZXPy^XQ^(udR;UJ! z@OPn74COyo@1&>=@}S}T#zCvWG*GBUj^KZh*QqAx;y`+TC-+M7By{aVH`TnJQ`sx# zH#u^DH_MNsX=wv`ShDNH-X`VEVj`i-SW2Yd(CV>d*f)u$wKZg42~*Wbcjvjkp6OlT zOW4+enSPOZZm3M<$-TrX+e~in1P!Pa^W9Yki;v5}U~MY0Nt{A(<7F*MM8(YUb4Z6K zSrF;9;m*&&^Hj6S3UGK#s1T`qH}~;SeKu{aa_`;9^L~h?@8>x`dO&$|dCrRYE&h>w z1oPt|x?M&87Nbrf{Q)YPfUeBXC#cv9`XTYoT-4qgm46bdojzM_z}d~Xnp)V}5rul2 zDTVKFwE_c8-7-pYj28nI)uy2s}n@mFX+Cp6`G@ z4(y>zn=H8ggcmmDe2)JKKf0OL)>J#>Ie@_he&~y3 z&Er{Wlq3Fq^uQlLF|BEDCSUX={gUD#^p?Vnz`|#6pWb_|52969k2!{@h5Cc&K e&eG+xRMQUokDE?7aAd+k)B=q|s@=sBnE(H*JNR?} diff --git a/dist/pyrobusta/protocol/http_multipart.mpy b/dist/pyrobusta/protocol/http_multipart.mpy index d5ad1568fd4693eca101334a063f8a172f5695f6..e8d17a5af4c81a20229e83e006d4a600d53ec7ac 100644 GIT binary patch delta 1266 zcmZWo&vV;E6!uEC>?SG5l2A=+8pUhHvSOz}6)FjBaAi3^nx@2=b_Sp7jAEcOE=(F= z!byt9&6FuKz=+|@aACNu?3yIKku)Ujt-~P`NazLr0rkL@m8jq*nG8`nM}j7oMkS$Dkx4`%q197K@G9J+lcFfpv9e}U(o<0ytbzi6(1vE z*k7@)B9=Zht{^V%wlxAJn6kNAm%jt?v80`~qZ4Z|M8b&cWk}Q(Lc~_f1YfS)pS!4C zzO38lt5u-{B?;=Hkb=^poJz@1%sNlPeJ8L%8mP3m3(m7JZ*}K;u*duDmkz{IZ_ZrW zwfFSfE%xmem(xPeA8!^*Oi>Nh);Bgby(b&x(fc;oBJ?W?iYJ>pf|0u zdHKe1I#l5D5IG#SGfudeET`obt zu7R!AY8g+uIkU7)pKc%hZs@w&=FZ@q3HtP^MvM@Tb{=$d+)XDBah>b<`A!FOy`cf4 z%8YkZ(AY0dcet}Yf<+KmL}%5)#5?-6W7r7r}j#ya*e@@UY2ZKZd7&~}m?J(ocL{zd~C`a6IyWGL1 zXkfS9X?ni#dx$%S*b)2)HRADu;!}GQt#8PLN<0bJ(kgyKh)o}2`;K+{Q+%ALVBLd= z%_8>AgW}W+1N5s8jcLRw5Anm%3s@Q_TJG_={O3N@2$UZf6+EPYe~Q?Fam_chL7$)3 z8bn3E>SuP4VS_U-j}Dwq20bg8fHTFbifOGbu?^k}Tt|653M0HWNiHm8dXL z3$^_iL-arp2ZNaLBZZirAhPsZD%nNiFIW9RO$uTjN7#!Jy=9YFeLvop9<`rkQivTW b|K(pLjNqjQW1i_3viAad0xTdYtrEJS! zZ*ij(#D zimOHygvt4(^-ni$Ue}fvv>UgzTSenHaJP$@MrspIqe2o8S5RS*Ct++1;^mFk$ z6}mD~2~^mMw=$}exB&Vbew@W~I_V@`Bq2EZ+pJ0ich_N0KustHqdPN7@ zFFM(%=wcHh!OB_Hy)`o1?-?7vtW)`XWGcjEg(&roDS^}!^)81=K!bG00c1Yv72IS1#o5q%EQubic?K&Hp6$vE_pS#@(=xDR^9FX&<2J^3YvIXIVT zMI5je!0?Av6?}c?US|AlMM>$y#JG;AkRsvr>^40&D#DJ83#3`@Q$ zgNN14Eb4S^_KO43S9|_6tte8B=|xPI^li%c2>$4ByAh+)F>pQL8$jNi^6n{nnf8Pn>UB_w^QWEzEe#C=(5bJQmJD{M5_KlYw}JD=pIGDAut zB7{Pm8`1;M8{_~Iq`WAUfITxqCI(}+To~mcbJ47p?Ex-8UnB#4Jfx%0CuG$v&!{ho zRPPx&;r}3J$)I$0P)$wLYzu1XtB{VHw)Yki=G==XX_SU^qH*?nUzxm`K!<0xH1maQX?WN=igS1fxH$Bq2!kQUJj%J`G*)Kh&A?(7P4~GWq6$T*hEXb62p>=zO2 zQ!c86o(&~Dhaf_xjR+f&gKZdTL*9l?n?QNm&`Z80Ia>Li{|EhTQnU>d2y$Jvim$sP mtKypko`ZHtS)7u*x)Bi%h3O$(p!yZ59LeSz|mijM>x?aBk diff --git a/dist/pyrobusta/protocol/http_security.mpy b/dist/pyrobusta/protocol/http_security.mpy new file mode 100644 index 0000000000000000000000000000000000000000..4a537f6e39c7e4e3fe0d0775bf5375dc819ab484 GIT binary patch literal 536 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 literal 1549 zcmZvbT~FIq7{`z87)%Huaop21?MfQwh47Znq!el0#2`R7)M;sHA=NZHIEOWBf-~E^ zWSTS^vW{KvayQd1Crye2`v9f=4uzZD^?rhWfIW7g)wIQm{ha@Ko^$@s@7VWx(9mu6 zj-ji$MpM_;HVw0!GfbV7>$$bf^>w08G@5Ae`m7f(Y}RV?x~}O+xJT-m-uztCYAE)g zR;^SwzUt;6(Jwb#IVi8yH1(;0xOJTn6x4}9^euwyZy>CZvO!Q2a=e96K!%NV6LB)x zsGuIXzJ)?+xlvZDW)pEn^;?2M+j`Y3uhj^OfxU_w$y-&jjbaxQ-C;;pwK`P7Vhuu` zqTNq|sKh~&m^@v>~xG}Dk}v>Oq;4rFh8 z+>KRjbHhZzZN1!R5FOc+-Ly-7Zw>{{n-S_B9M7D+LT{iVS1FriWY5`bUE@rm*HOQp zVc9Myw)cV*>+?|gSy$?UVSCQWus#nB%eZV zvl4~-BpUZa`QCSJ_7{p3lc}^ND9YF~ZV6EgpGi@jH+d?BqCIWrAdVP*xRa+mp_k95 zP^8z3xuQz_fd2O3_om=yUCYEjOpKxfdFg|fDZ2PqVTB)q+@3sjn4;bs+NW@zpdZeb zQuO~$;e^!fo`>!u5`zWFhc8Ji#vYj-d|B$nQ7`83 z6{!!$BtO0?_2X+rHL$xH<5_XkT1qC9BThRyV6~$`_z&`9?Z`l$YDYu-xZ^&2+VLxXLA1UEAp{Aa0T)lbBwCfO3YB43ZB2h1!=m-I z63--(!|BN+6j&#P*`f;jK0n{#sAJ83w(`P(xr5V(&cP%5_3GnSbK|9wYqJA3i|XF$ z?-af)PFOoYHa(GY;Di?10zZYGJKp1XZr-t873TR|9+3-YLFd$U@#FOUcvQS;?J2oT zdNe(nN~S0I_Z?p1GyK>Hyq0r|{G>?dDdJH$>YTTFTzJ$L@DwV&6Xto^TYun%XZodc z`0xPQI6Qr4y1fz^?7V*Lx^7MXM#Zjx>z|cWI-AL6lmGQySmg5}1A9YV(1AK2>Ows! z+`Tv@X#Ytf8iRFy1(1u`bQV~JkG)DCR04s>6ng9SV(2dHPQzz4od{ip@;{W(3@q)O z{;S+I>~8EDMxXN6#9P*%iX`PA)8lbwLw<1fiVz_0)S diff --git a/dist/pyrobusta/transport/connection.mpy b/dist/pyrobusta/transport/connection.mpy index e74282a15007e8310476538d1b33933e03db874b..827ee325e10c4569e5f87f9217ec40464fb0c58b 100644 GIT binary patch delta 742 zcmZXQU2oE06vz9tv{VMHwB@n9Y%41-9YZb21(p~u7k8mw!5cq;H~R#(FcNil=bZm}e!r9F3;CNQ9uUaNHRq5hBo1nN zyV-6auDZI{Yar=Yhq|`c>vU`BUbormq+42}VdcZ!H^ZOe#|7}UABc5s^1529^DlJm zC9kxaYP);qS*%k*aL&#S^k!G(yV}3evK{@k%-32r4p?SA!<-q5`-LK3}UiD;S`ZXL(VV`@h|}i`AG($ z$Sev;Ndd=oGEBa6SNY<=Q?Gvr3_QLixJ;37ML-~*L7*!GAd^8M+o!G=N%Zk`48n{9 z1~WMHOA0X{Zv|bERl#7YdGSUx5goG&lsgB$(b0*qlLeIL+^UYO>fGeKk4$cn#?#XCU5(5pgYPh=wz^ zok|GN*C`=kGK=@N>2(3HS53y716iQbgn-gZR+q)(3)Q5!L!&HbGCmfLa$u4}E$&&N zG#}XAxOo4B4UBW(>_i!qKj*n%BxT&K@|J6DeX8kS@?dk1$&wyrP# zCalwSW;ET3+A WXYuW6o%&x3``Mlm>I59!1g-B$CRRr0cU{=36fYFB#v4Qiz;;(DG4%hO2fi5 zOfANb9q~`N9H5Cwca8i2x32sHcEd05^08=Q+?{jYd!F~c=YsxDdmeg-^;@2Srx{b`^jHJrGB7Z|FYT-gx26X zw;m$4I>H#c}@59={u1XQHtAKtsviyr{W{t$6Qgt z4>mEjcXEL7PjPjOtEnRd9y>yi?YVMWQvh7V&vMDM4HY24RE_koPW+cEvhYP462Mm_tud!L1j^XQfYBX;>3t|j18OJ8I>8;Eh3}@8|)L* n)b!nSJ#>}SgxHiBJ{=HZ6A=1zP(WCS8OY-QWWqSPkI4}LNBI}| delta 82 zcmcb}dY*NHgM>_8eo<~>PG(hVd_iJK1_NJ4YED6FQSrp^cZ`jj-58Y_RTLtm1RLxV m)YY}zbUk#H)P&fS8B7=t2(bwWnJ^v{5Ef!KVVFFD$q@ig#1_K< diff --git a/dist/pyrobusta/utils/config.mpy b/dist/pyrobusta/utils/config.mpy index cf1ccad26d3d67ff34de798c89bf656d7bb99a3e..7f68b037eb1ec53872d13735bd52ee80bc8ef290 100644 GIT binary patch literal 1810 zcma)*T~FIq7{`yD9Fmwo9b=3avbBpL2^l1irP&6hqru~)>;=QtD$6)$SR$4JCTUt3)-2s=pWd(4zzgb&Z{(4asm)8JuA_kF)VQv?7 z?FUt^7VUcg6?FTgaG#W}KEw;bdE5{PejGA&ZA+6aODlmecB@6psvBkr^w_!un6#pr zcB-VBAdF(U9m!jot^zuJU){kAe0u=}ojivdlsW-x8oFwAe7{!NG616+Wm#7rs5*#P zhH_uE~fm*^xu>g-#+v>Kg6sv&2QPnVQz_wjW#(tzD zs_C{?Euytf+}^4s7pq#gtXM;{Nn+_;)2O4*pie0^Q?qx#2u^DioKR&f4da}pq5&Bd z3nb2lvTf__YCy@RS}XxtttduGtCWE=?}WU46llgt0XS2RK+b}u%*e8(8@46OfR#^h zfquo%bycx}-!^M1V6K(b3Swnm2kD8c&X_aM+;)bG&2{IC<}9_P8$}y9GriOx^kRT8 za7qyNoks{gZHBP#JUJLV2MqR}B?hT8$Y9`<7VLiu7YucWafTEO;@m%|U;u}h52V_D z7Z&XA=uT*L6Q{Vkr5FMn#Oi+mgPp5zo*3-I;uB;LUlPO#BRpXSL#KaQsxtrp=`%Sz z2H^Khrlu$o5^(^DD4=@YVdw9(tN%L^CPsNLnB3bK{`-8W#RXh1z7goW7hI~xK{`k@ zd<)}xLmDPIvO#;2#IQHw;XH)~4UyjyfgCJ+n*p>hOL9I*A)Jz*_dV<1oSS>XJqbS< z3T3#DX&;vr05_2VoIgWw(a_ZlnI*WCqOq*`45nP8QCk*r(T!;&}E}JP>__LR)1kq(OhFen2aTaXks)T zkB%qfvFUi~*3z#8^NBkUpaps)>kx}S6E9ox%2VQCmmQ^JIb`Sa`SdOurxQ8i)r-a- z;^MCKB1L|O1Y!@YKr9nXQ+d+xx*u~^%0Cl2?eCMprU$KtL1 zM-p;7i6e1wzxfD-hs>w$p3A28_H$&`Q(#Cojdq^@>qqJPJ2$D@%x8a!^M#wl9Z7i> z%w$s2GmVYS8|vox4fV=cED@VRHW3qJt^kk4FI(;J)00OB`QIC^&~q#5=+a4d{$Ea26T5j;{X5v literal 1143 zcmYk3T~iuo6vv-uK|w^btlP&V(nwhaBNPy~#I#MvWp|ZT2n)+gm}zDqD=ISx44{s^ z=;Jsf zRDzA2X8p-#t6d6iwky?EP^s5eD{Fzq4!EH?v^8g~-rOivE8mpmMyb6HhS$s0M!DGn z_N`E0E)W9N;P&=5uvTiTb#Sv$YPQPqg$2-LYrDBw20DeB<+LOzEY{J0iD$y z<{=xvD4@?^4}gtLK3tH2&R|}Exq)_nhg>VSuXN;>)k>?a)HfPmfrMKuq?6IClwsr? zpO%E=LvW|778d1rl4UYo;OyDug#|&DB!L4H*R{wG`@LwZ|Pv>|}7MV;;0w3rXgg7tBj3`_}DakJL89Bi` zk_|Dydk&enbor=w(%6O1@p0hn_AgdrIGzKyuWFWIV?4lWK$CP{dth6w)=O<*zxw|h z+KiSj4fYMazj2^k+X7ZoC@|AE9IRHJTwfp9OcqF6;5`ex-%Z07IBS7(Hp~J;7C7^r z^Jkoc#iDz2%@vG?r4#^8;4wQ+2ST z?9*=7Kp?2{MbBN2XVNn{?e%mh(-|imk+ulH2Tzp_Wj^B;h|m!YmCpXDk91av!8-`QW>LQka}DNvhZtj>FC(g!;7vh6itHZ(ypN-N*H4oB#ru$n8nsDLNr zbx#GS1fQ4ox;#l?Dj)k560;qA7{5X5aPcSj`bb{>5&rgq#BoOil6IVkKs9UBBN4zx zqtpunkrDVi{?SV{+l3^Gfb&0I5i|{qExO@V_DwWRd`9?bsQ;^-zd7!~+)M3Zegq#i zj7RXB5%`uEI6k4Vx5tG4jC(}*$y>(}nEzA%Pd(*?6T<(CcAkrh2ET3aLN}fmF?#lG z>g&f#wmcDe$FT+Do+(9NzeSD()UD#YD>O+Fe!cULcAicB&2of~jS)jMD5OVyS6ulY Dk|a7V diff --git a/dist/pyrobusta/utils/crypto.mpy b/dist/pyrobusta/utils/crypto.mpy new file mode 100644 index 0000000000000000000000000000000000000000..c52dd7fba30483deb5e87c199324522df7e38b90 GIT binary patch literal 1867 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 literal 0 HcmV?d00001 diff --git a/dist/pyrobusta/utils/lexpath.mpy b/dist/pyrobusta/utils/lexpath.mpy new file mode 100644 index 0000000000000000000000000000000000000000..be39b0e6bdec90b61322236b3acb421a4e79bac3 GIT binary patch literal 771 zcmX|--%i?49LEm_|FoOVW9=%NPFV|x|4N(crZ=|E zWqXW0LR;>3Cp?71%*(xiPq5SCcG2Y5&*|@TzTdtb0Mk5p-KgWG+FAYh<4L2bFP${Y zmBv!V_*~PQhl$!57-9Y&f^fNM)b*yJHH?F*anx)8YvwX(O$R5Bj_XyuQa(2{Zx0N^ z1n5g7fc5FjQn}rC`P}B#hNf)hiK3z5!*ZqMf2bYrg9st}}s4mL#8?bIVb`yCcF)Hr=8v3~w4o*y(RI$kOd^{Rk zSd5!(vm;@f?Q`4|?6fYrN``r1uLL@_+e*M4wm$_93N7ot?fUpeW^H!BNd*)I4IUI! z{0*sbYRa4_3W6{{%g2Q5Y+RU&3AQYwK|BwxnV^m66{zK6Z5KW2%Caj@21W}gs$r=`4M;MfSF?_2lSn~0G12Yi)%#iOZdQ!K6tQ9e8y zjm$@RTTUa|xx;3gltDCU({{}pMX?F9RhM*0LqvC4&YdFVv1|QlOPq?oWLMeIu$e9< z_~n_1ZSElYx11q~yNdK4lkApCHhTqzt{KKz3CQF?67%>wwH{$!nrd-DT9BgQWHK3x zWzx2?>Hnx7zdV8MH6}Vz)_c=(xob4$?0&e-^dt6GlJ*MnDSG+v6vq#Rl~U)py|9bj7A0;j#w^2Bd@(*SY)h6PgeKGM!4&8UX#~2oWebUk zmx+lVr`w*Ez4Ikd+iNRc;oVIy=Sn;qCA_skuE=nn=UyFuUwuy)=HCd>ztHyQXK zpSb-g8~8QIhGn%akUkz*EszddW*cPPs2??8%Wk$CcGLJ^H0`!&LV0OUOgl>(?muib z;PKM#U)r)Q30_S?K~6sP@tGU+{m_eMY}M=Y=Lh2m=I4tq((_ce+}vjM?qspP$RL35&95d4i46kIB5Kr&j2$eKVzElsjohDh29$#op@`%C3%W}TvL zxwd;(W{g&T=_byXiuGxXM9zq@7o#j^h4H5tNt{`Fy%VDx=K^B`lPb6!=c0B?X${Mz zM=E``yS;;p@o(L_*7d9FKR@vX7OHT0RZSmvoO$hrUCN?L_qtfd2t9}|f4t88SF$R7 c#T5mu-cux%zTpZ+5AP{Cl^$@VfY$DmzdtUV`2YX_ literal 428 zcmY+7UrWMZ6vodsoy+EYdy)6Tq)emGi-aLvbyNOBu!%y2NKBn`48|sN%Orv={Xl+% zzDD$A+Emau=i)gJzvn!UESzJwo7qZfy0ER0J9CC5*U^nx$*?p{H?^DT0+K0Bb!5dd zC%OiiN?WSSt#(6h)t+lD;CFsbacx^Q9XSZfhWe%&5Fe`}R|8HrCl(0vp=|~a0ry|- zyV7g+;}iS>?l+}+dvm1v)!M^TW1}zk1#(C?r3RcGcwq>#2gW7}Y|stQjA=aGa@gfEg5fkZ`$a3W1&B171Y5?LZdq8>lEzRHJ# zY0Q+V?=@n7sui46)+iPVArM?B5=yZC`vuP-FFh#WXc=(gx7xxd=qMFm-%@3&*VzCu XkMDOVOe1U?Z{DRb?l1c`Syu{p82C0g@cc=fxVNjgI$10i!s=kL5w*NL^F!9foUc&_P}K3 z7KYu763jfjd<`544r)4T0h`0hp^ckNn1HGRlbdobGtOXS=MffZkWENPb4Yby zWZJk%fr07pMg>O3BS7`4j6y=2w?LIMHE9?NaEOYEHK?d6ZxZHoc2;FfHUTnRe1L=q tmnwrQqZv~&(9#NR0d7$-@dizG)s4cOf%jZobr?2EaBc!}R2jkolL1*FQkMV# delta 300 zcmcb}e1@6TmyJPQaw4le8&`5pVsY_A=NQ38?tFoQqWpr?qLNAmj=aR&iF?KCSvdF@ z8`wJqI@kr6v>1bp8N`?aK{TTnyAhKZM_@8@tHW+a8D?HS{sxW&2Q?iv1GRun0!%ku ze1rrxY!qhMXu=%nRJ=)m^`=VzL_|n%qX{EOGQVe7;(6s zk&m61lczx<0jgJI<0ca(pyt5jCL1qN4qnj)`GkbjR0qb53Ji>gHYzYO9tNsZWfT(G zv>B?9snJ1+i&IRzL2aWjmx~vW5aCv3Fk?&x+EAe_z#}Fu(V(fW3KS2#=i;ivuu+0* M6Og0I5EhsW0FD+*M*si- diff --git a/docs/application_development/concepts.md b/docs/application_development/concepts.md index a8cacd7..f55d310 100644 --- a/docs/application_development/concepts.md +++ b/docs/application_development/concepts.md @@ -149,4 +149,4 @@ incremental request processing, and deterministic behavior suitable for resource --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/configuration.md b/docs/application_development/configuration.md index e6737be..c045598 100644 --- a/docs/application_development/configuration.md +++ b/docs/application_development/configuration.md @@ -95,4 +95,4 @@ and configuration-loading functionality. --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/error_handling.md b/docs/application_development/error_handling.md index f8d7217..01b546a 100644 --- a/docs/application_development/error_handling.md +++ b/docs/application_development/error_handling.md @@ -118,4 +118,4 @@ def handler(http_ctx, _): --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/file_server.md b/docs/application_development/file_server.md index b605dc2..ff5484e 100644 --- a/docs/application_development/file_server.md +++ b/docs/application_development/file_server.md @@ -136,4 +136,4 @@ $ curl -X DELETE \ --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/index.md b/docs/application_development/index.md index 2f5db58..dd12fba 100644 --- a/docs/application_development/index.md +++ b/docs/application_development/index.md @@ -23,4 +23,4 @@ Then continue with the remaining guides in the order listed below. --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/introduction.md b/docs/application_development/introduction.md index 6784293..111cc29 100644 --- a/docs/application_development/introduction.md +++ b/docs/application_development/introduction.md @@ -95,4 +95,4 @@ $ curl -H "Accept: application/json" "http://192.168.1.101/version" --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/performance.md b/docs/application_development/performance.md index 9439173..2c5d1e1 100644 --- a/docs/application_development/performance.md +++ b/docs/application_development/performance.md @@ -23,4 +23,4 @@ This page is work in progress. Refer to the [main page](index.md) for available --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/reference.md b/docs/application_development/reference.md index b934d48..1c8dcf5 100644 --- a/docs/application_development/reference.md +++ b/docs/application_development/reference.md @@ -26,4 +26,4 @@ This page is work in progress. Refer to the [main page](index.md) for available --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/request.md b/docs/application_development/request.md index 5832b48..83306e3 100644 --- a/docs/application_development/request.md +++ b/docs/application_development/request.md @@ -202,4 +202,4 @@ async def main(): --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/response.md b/docs/application_development/response.md index ec91e1e..a9f0c6e 100644 --- a/docs/application_development/response.md +++ b/docs/application_development/response.md @@ -248,4 +248,4 @@ XXXXXXXXXX --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/routing.md b/docs/application_development/routing.md index 3c9ec18..8a1c9fb 100644 --- a/docs/application_development/routing.md +++ b/docs/application_development/routing.md @@ -148,4 +148,4 @@ def enable_sensor(http_ctx, enabled): --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/security.md b/docs/application_development/security.md index ff6390d..2429aaa 100644 --- a/docs/application_development/security.md +++ b/docs/application_development/security.md @@ -468,4 +468,4 @@ $ mpremote connect /dev/ttyACM1 cp cert.der :/cert.der --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/server_lifecycle.md b/docs/application_development/server_lifecycle.md index 7ec47c3..9cf47d9 100644 --- a/docs/application_development/server_lifecycle.md +++ b/docs/application_development/server_lifecycle.md @@ -26,4 +26,4 @@ This page is work in progress. Refer to the [main page](index.md) for available --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/docs/application_development/static_content.md b/docs/application_development/static_content.md index 7147103..0f57980 100644 --- a/docs/application_development/static_content.md +++ b/docs/application_development/static_content.md @@ -76,4 +76,4 @@ The following mapping between extensions and content types is maintained by the --- -PyRobusta v0.8.0 Web Server +PyRobusta v0.9.0 Web Server diff --git a/package.json b/package.json index 09a56af..1ee75e8 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,9 @@ { - "version": "v0.8.0", + "version": "v0.9.0", "urls": [ [ - "pyrobusta/connectivity/wifi.mpy", - "github:szeka9/PyRobusta/dist/pyrobusta/connectivity/wifi.mpy" - ], - [ - "pyrobusta/connectivity/__init__.py", - "github:szeka9/PyRobusta/dist/pyrobusta/connectivity/__init__.py" + "pyrobusta/application.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/application.mpy" ], [ "pyrobusta/transport/__init__.py", @@ -25,6 +21,10 @@ "pyrobusta/assets/www/request.html", "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/request.html" ], + [ + "pyrobusta/assets/www/error_handling.html", + "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/error_handling.html" + ], [ "pyrobusta/assets/www/index.html", "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/index.html" @@ -37,6 +37,10 @@ "pyrobusta/assets/www/file_server.html", "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/file_server.html" ], + [ + "pyrobusta/assets/www/security.html", + "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/security.html" + ], [ "pyrobusta/assets/www/introduction.html", "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/introduction.html" @@ -58,8 +62,12 @@ "github:szeka9/PyRobusta/dist/pyrobusta/assets/www/response.html" ], [ - "pyrobusta/utils/helpers.mpy", - "github:szeka9/PyRobusta/dist/pyrobusta/utils/helpers.mpy" + "pyrobusta/utils/lexpath.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/utils/lexpath.mpy" + ], + [ + "pyrobusta/utils/crypto.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/utils/crypto.mpy" ], [ "pyrobusta/utils/__init__.py", @@ -81,6 +89,10 @@ "pyrobusta/utils/assets.mpy", "github:szeka9/PyRobusta/dist/pyrobusta/utils/assets.mpy" ], + [ + "pyrobusta/utils/iam.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/utils/iam.mpy" + ], [ "pyrobusta/protocol/http_multipart.mpy", "github:szeka9/PyRobusta/dist/pyrobusta/protocol/http_multipart.mpy" @@ -93,10 +105,22 @@ "pyrobusta/protocol/__init__.py", "github:szeka9/PyRobusta/dist/pyrobusta/protocol/__init__.py" ], + [ + "pyrobusta/protocol/http_basic_auth.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/protocol/http_basic_auth.mpy" + ], + [ + "pyrobusta/protocol/http_security.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/protocol/http_security.mpy" + ], [ "pyrobusta/protocol/http_file_server.mpy", "github:szeka9/PyRobusta/dist/pyrobusta/protocol/http_file_server.mpy" ], + [ + "pyrobusta/protocol/http_cookie.mpy", + "github:szeka9/PyRobusta/dist/pyrobusta/protocol/http_cookie.mpy" + ], [ "pyrobusta/stream/__init__.py", "github:szeka9/PyRobusta/dist/pyrobusta/stream/__init__.py" diff --git a/src/pyrobusta/__init__.py b/src/pyrobusta/__init__.py index f47476b..a82c93f 100644 --- a/src/pyrobusta/__init__.py +++ b/src/pyrobusta/__init__.py @@ -2,5 +2,5 @@ from os import getcwd -PYROBUSTA_VERSION = "v0.8.0" +PYROBUSTA_VERSION = "v0.9.0" WORKING_DIR = getcwd().rstrip("/")