Fix web radio streaming: HTTPS URLs were rejected outright
POST /api/streaming's JSON parser only accepted "http://" URLs, rejecting any "https://" URL with invalid_json before ever attempting a connection. Nearly every real internet radio stream today is HTTPS- only, so this made the feature fail for essentially any station a user would actually try. Two changes were needed together: the parser now accepts both http:// and https://, and web_radio_stream.cpp's esp_http_client now attaches ESP-IDF's built-in CA certificate bundle (crt_bundle_attach) so the TLS handshake actually verifies -- CONFIG_MBEDTLS_CERTIFICATE_BUNDLE was already enabled in sdkconfig but never wired up here. Requires adding mbedtls to main's PRIV_REQUIRES (esp_crt_bundle.h lives there). Confirmed live: an https:// stream URL now gets accepted by the API and produces a real HTTP response (404, from a guessed-wrong path) -- before this fix it never reached the network at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ namespace {
|
|||||||
|
|
||||||
constexpr std::size_t kMaxUrlLength = 200U;
|
constexpr std::size_t kMaxUrlLength = 200U;
|
||||||
constexpr std::string_view kHttpPrefix = "http://";
|
constexpr std::string_view kHttpPrefix = "http://";
|
||||||
|
constexpr std::string_view kHttpsPrefix = "https://";
|
||||||
|
|
||||||
[[nodiscard]] std::string_view extractJsonString(std::string_view json,
|
[[nodiscard]] std::string_view extractJsonString(std::string_view json,
|
||||||
std::string_view key)
|
std::string_view key)
|
||||||
@@ -84,8 +85,9 @@ std::expected<WebRadioConfig, ParseError> parseWebRadioConfigJson(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::string_view url = extractJsonString(json, "url");
|
const std::string_view url = extractJsonString(json, "url");
|
||||||
if (url.empty() || url.size() > kMaxUrlLength
|
const bool isHttp = url.substr(0, kHttpPrefix.size()) == kHttpPrefix;
|
||||||
|| url.substr(0, kHttpPrefix.size()) != kHttpPrefix) {
|
const bool isHttps = url.substr(0, kHttpsPrefix.size()) == kHttpsPrefix;
|
||||||
|
if (url.empty() || url.size() > kMaxUrlLength || !(isHttp || isHttps)) {
|
||||||
return std::unexpected(ParseError::InvalidJson);
|
return std::unexpected(ParseError::InvalidJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,18 @@ namespace {
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
const auto badScheme = core::parseWebRadioConfigJson(
|
const auto badScheme = core::parseWebRadioConfigJson(
|
||||||
R"({"enabled":true,"url":"https://example.com/x.mp3"})");
|
R"({"enabled":true,"url":"ftp://example.com/x.mp3"})");
|
||||||
if (badScheme) {
|
if (badScheme) {
|
||||||
std::cerr << "non-http scheme should fail\n";
|
std::cerr << "non-http(s) scheme should fail\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
// Most public internet radio streams are HTTPS-only; rejecting the
|
||||||
|
// scheme outright (as this parser used to) made streaming unusable for
|
||||||
|
// essentially any real station.
|
||||||
|
const auto httpsOk = core::parseWebRadioConfigJson(
|
||||||
|
R"({"enabled":true,"url":"https://example.com/x.mp3"})");
|
||||||
|
if (!httpsOk || httpsOk->url != "https://example.com/x.mp3") {
|
||||||
|
std::cerr << "https url should parse\n";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
const auto malformed = core::parseWebRadioConfigJson("not json");
|
const auto malformed = core::parseWebRadioConfigJson("not json");
|
||||||
|
|||||||
@@ -176,26 +176,6 @@ quando arriva un nuovo nome/messaggio RDS durante la riproduzione.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Stile UI — Apple, minimalista ma con una sezione grafica curata
|
|
||||||
|
|
||||||
- Componenti nativi SwiftUI: `Picker` segmented per la sorgente, `Slider` con
|
|
||||||
`.tint()` per volume/bass/stereo, `List`/`Form` in stile Impostazioni per le
|
|
||||||
stazioni e le opzioni tecniche. Niente controlli custom pesanti o griglie di
|
|
||||||
bottoni non standard.
|
|
||||||
- Organizza per tab/sezione logica, non tutto in una schermata:
|
|
||||||
- **Ascolto**: sorgente attiva, volume, stazione corrente.
|
|
||||||
- **Suono**: EQ 6 bande, Bass Boost, Stereo Spread.
|
|
||||||
- **Stazioni**: lista unificata FM+DAB (vedi §4), con scan.
|
|
||||||
- **Bluetooth**: pairing, dispositivo connesso.
|
|
||||||
- **Diagnostica**: tono di test, dettagli tecnici/versione firmware — non
|
|
||||||
mescolare con i controlli quotidiani.
|
|
||||||
- Una sezione "grafica" curata è benvenuta (es. una card "Now Playing" con
|
|
||||||
sfondo sfumato/blur, animazione leggera sul cambio sorgente). Ora **puoi**
|
|
||||||
agganciarla a dati reali di livello audio — vedi §7, l'endpoint VU-meter è
|
|
||||||
disponibile da oggi.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. VU-meter — nuovo, disponibile da oggi
|
## 7. VU-meter — nuovo, disponibile da oggi
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -225,7 +205,44 @@ uscita), non un'animazione finta.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Checklist di autoverifica prima di considerare il lavoro finito
|
## 8. Scan FM completo — NON è bloccato, è solo lento (86s misurati)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/tuner/scan/full
|
||||||
|
```
|
||||||
|
|
||||||
|
Fa una scansione dell'intera banda FM (fino a 60 canali candidati, con
|
||||||
|
pausa+lettura RDS per ciascuno) e risponde **una sola volta alla fine**,
|
||||||
|
misurato: **~86 secondi** per uno scan completo. Non è un bug, è il tempo
|
||||||
|
reale che serve per farlo bene (RDS incluso).
|
||||||
|
|
||||||
|
**Se l'app usa un timeout HTTP standard (30-60s), questa richiesta scade
|
||||||
|
prima che il firmware finisca** — la request fallisce lato client, ma il
|
||||||
|
firmware nel frattempo continua e completa comunque (il risultato però va
|
||||||
|
perso perché il client non lo aspetta più). Sembra "bloccato", ma non lo è.
|
||||||
|
|
||||||
|
**Azione richiesta**: per questa chiamata specifica, imposta un timeout di
|
||||||
|
almeno **120 secondi** sulla request HTTP, e mostra un indicatore "scansione
|
||||||
|
in corso..." per tutta la durata (non un caricamento breve). `POST
|
||||||
|
/api/tuner/scan` (senza `/full`, per una singola stazione con filtro nome)
|
||||||
|
è invece rapido, timeout normale va bene.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8bis. Streaming web radio — bug corretto, ora accetta HTTPS
|
||||||
|
|
||||||
|
`POST /api/streaming {"enabled":true,"url":"..."}` **prima rifiutava
|
||||||
|
categoricamente qualsiasi URL `https://`** (errore `invalid_json`), accettando
|
||||||
|
solo `http://` — dato che quasi tutte le radio via internet reali sono
|
||||||
|
HTTPS-only, questo probabilmente era il motivo per cui "qualsiasi cosa si
|
||||||
|
faccia" dava errore. Corretto oggi: ora accetta sia `http://` che `https://`,
|
||||||
|
e il firmware verifica il certificato TLS con la CA bundle integrata di
|
||||||
|
ESP-IDF. Nessun cambio di contratto per l'app — stessa forma JSON di prima,
|
||||||
|
semplicemente ora funziona anche con URL HTTPS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Checklist di autoverifica prima di considerare il lavoro finito
|
||||||
|
|
||||||
- [ ] Cambiare sorgente da Radio a Bluetooth nell'app cambia davvero l'audio
|
- [ ] Cambiare sorgente da Radio a Bluetooth nell'app cambia davvero l'audio
|
||||||
sul dispositivo reale (non solo lo stato locale dell'app).
|
sul dispositivo reale (non solo lo stato locale dell'app).
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ idf_component_register(
|
|||||||
"$<$<BOOL:${CONFIG_ESP32_I2S_TEST_TONE}>:esp32_i2s_test_tone.cpp>"
|
"$<$<BOOL:${CONFIG_ESP32_I2S_TEST_TONE}>:esp32_i2s_test_tone.cpp>"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES core net secure_store adau1701 si4684 tuner audio bt1035 bluetooth station integration ota eeprom24aa webradio driver
|
REQUIRES core net secure_store adau1701 si4684 tuner audio bt1035 bluetooth station integration ota eeprom24aa webradio driver
|
||||||
PRIV_REQUIRES esp_timer esp_http_client
|
PRIV_REQUIRES esp_timer esp_http_client mbedtls
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CONFIG_TEST_FIRMWARE)
|
if(CONFIG_TEST_FIRMWARE)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "esp32_i2s_sink.hpp"
|
#include "esp32_i2s_sink.hpp"
|
||||||
#include "webradio/WebRadioService.hpp"
|
#include "webradio/WebRadioService.hpp"
|
||||||
|
|
||||||
|
#include "esp_crt_bundle.h"
|
||||||
#include "esp_http_client.h"
|
#include "esp_http_client.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@@ -48,6 +49,12 @@ struct InputBuffer {
|
|||||||
esp_http_client_config_t cfg{};
|
esp_http_client_config_t cfg{};
|
||||||
cfg.url = url.c_str();
|
cfg.url = url.c_str();
|
||||||
cfg.timeout_ms = kHttpTimeoutMs;
|
cfg.timeout_ms = kHttpTimeoutMs;
|
||||||
|
// Most public internet radio streams are HTTPS-only today; esp_http_client
|
||||||
|
// needs an explicit trust anchor for TLS verification or the handshake
|
||||||
|
// fails outright. CONFIG_MBEDTLS_CERTIFICATE_BUNDLE is already enabled
|
||||||
|
// (sdkconfig), so attach ESP-IDF's built-in CA bundle -- this is a no-op
|
||||||
|
// for plain http:// URLs.
|
||||||
|
cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||||
if (client == nullptr) {
|
if (client == nullptr) {
|
||||||
ESP_LOGE(kTag, "esp_http_client_init failed");
|
ESP_LOGE(kTag, "esp_http_client_init failed");
|
||||||
|
|||||||
Reference in New Issue
Block a user