mirror of
https://github.com/esphome/esphome.git
synced 2026-10-24 15:05:33 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4225a29f8 | ||
|
|
8e552b3cd5 | ||
|
|
baa70304c0 | ||
|
|
c1b493ffc9 | ||
|
|
9d30bcc721 |
No files matched your search
@@ -15,25 +15,34 @@ ESPHOME_LOG_TAG(TAG, "http_request.ota");
|
||||
|
||||
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
|
||||
|
||||
OtaHttpRequestComponent::Request &OtaHttpRequestComponent::get_request_() {
|
||||
if (this->request_ == nullptr)
|
||||
this->request_ = make_unique<Request>();
|
||||
return *this->request_;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->md5_url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
if (this->request_ != nullptr)
|
||||
this->request_->md5_url.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->md5_url_ = url;
|
||||
this->md5_expected_.clear(); // to be retrieved later
|
||||
Request &request = this->get_request_();
|
||||
request.md5_url = url;
|
||||
request.md5_expected.clear(); // to be retrieved later
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
if (this->request_ != nullptr)
|
||||
this->request_->url.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->url_ = url;
|
||||
this->get_request_().url = url;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::flash() {
|
||||
if (this->url_.empty()) {
|
||||
if (this->request_ == nullptr || this->request_->url.empty()) {
|
||||
ESP_LOGE(TAG, "URL not set; cannot start update");
|
||||
return;
|
||||
}
|
||||
@@ -58,8 +67,8 @@ void OtaHttpRequestComponent::flash() {
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
this->notify_state_(ota::OTA_ERROR, 0.0f, ota_status);
|
||||
#endif
|
||||
this->md5_computed_.clear(); // will be reset at next attempt
|
||||
this->md5_expected_.clear(); // will be reset at next attempt
|
||||
this->request_->md5_computed.clear(); // will be reset at next attempt
|
||||
this->request_->md5_expected.clear(); // will be reset at next attempt
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -81,18 +90,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
md5::MD5Digest md5_receive;
|
||||
char md5_receive_str[33];
|
||||
|
||||
if (this->md5_expected_.empty() && !this->http_get_md5_()) {
|
||||
if (this->request_->md5_expected.empty() && !this->http_get_md5_()) {
|
||||
return OTA_MD5_INVALID;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
|
||||
ESP_LOGD(TAG, "MD5 expected: %s", this->request_->md5_expected.c_str());
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->url_);
|
||||
auto url_with_auth = this->get_url_with_auth_(this->request_->url);
|
||||
if (url_with_auth.empty()) {
|
||||
return OTA_BAD_URL;
|
||||
}
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->request_->url.c_str());
|
||||
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
|
||||
@@ -179,9 +188,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
// verify MD5 is as expected and act accordingly
|
||||
md5_receive.calculate();
|
||||
md5_receive.get_hex(md5_receive_str);
|
||||
this->md5_computed_ = md5_receive_str;
|
||||
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
|
||||
this->request_->md5_computed = md5_receive_str;
|
||||
if (strncmp(this->request_->md5_computed.c_str(), this->request_->md5_expected.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->request_->md5_computed.c_str());
|
||||
this->cleanup_(std::move(backend), container, /*abort_backend=*/true);
|
||||
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
} else {
|
||||
@@ -223,11 +232,15 @@ static std::string url_encode(const std::string &str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_password(const std::string &password) { this->password_ = url_encode(password); }
|
||||
void OtaHttpRequestComponent::set_username(const std::string &username) { this->username_ = url_encode(username); }
|
||||
void OtaHttpRequestComponent::set_password(const std::string &password) {
|
||||
this->get_request_().password = url_encode(password);
|
||||
}
|
||||
void OtaHttpRequestComponent::set_username(const std::string &username) {
|
||||
this->get_request_().username = url_encode(username);
|
||||
}
|
||||
|
||||
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
|
||||
if (this->username_.empty() || this->password_.empty()) {
|
||||
if (this->request_->username.empty() || this->request_->password.empty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -240,23 +253,23 @@ std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url)
|
||||
ESP_LOGD(TAG, "Using basic HTTP authentication");
|
||||
|
||||
start_char += 3; // skip '://' characters
|
||||
auto url_with_auth =
|
||||
url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
|
||||
auto url_with_auth = url.substr(0, start_char) + this->request_->username + ":" + this->request_->password + "@" +
|
||||
url.substr(start_char);
|
||||
return url_with_auth;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::http_get_md5_() {
|
||||
if (this->md5_url_.empty()) {
|
||||
if (this->request_->md5_url.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
|
||||
auto url_with_auth = this->get_url_with_auth_(this->request_->md5_url);
|
||||
if (url_with_auth.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->request_->md5_url.c_str());
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
if (container == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MD5 URL");
|
||||
@@ -273,8 +286,8 @@ bool OtaHttpRequestComponent::http_get_md5_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->md5_expected_.resize(MD5_SIZE);
|
||||
auto result = http_read_fully(container.get(), (uint8_t *) this->md5_expected_.data(), MD5_SIZE, MD5_SIZE,
|
||||
this->request_->md5_expected.resize(MD5_SIZE);
|
||||
auto result = http_read_fully(container.get(), (uint8_t *) this->request_->md5_expected.data(), MD5_SIZE, MD5_SIZE,
|
||||
this->parent_->get_timeout());
|
||||
container->end();
|
||||
|
||||
|
||||
@@ -27,29 +27,36 @@ class OtaHttpRequestComponent final : public ota::OTAComponent, public Parented<
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_md5_url(const std::string &md5_url);
|
||||
void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
|
||||
void set_md5(const std::string &md5) { this->get_request_().md5_expected = md5; }
|
||||
void set_password(const std::string &password);
|
||||
void set_url(const std::string &url);
|
||||
void set_username(const std::string &username);
|
||||
|
||||
std::string md5_computed() { return this->md5_computed_; }
|
||||
std::string md5_expected() { return this->md5_expected_; }
|
||||
std::string md5_computed() { return this->request_ != nullptr ? this->request_->md5_computed : std::string(); }
|
||||
std::string md5_expected() { return this->request_ != nullptr ? this->request_->md5_expected : std::string(); }
|
||||
|
||||
void flash();
|
||||
|
||||
protected:
|
||||
/// Allocated by the first setter, which only runs before a flash, and never freed, so a flash never loses it.
|
||||
struct Request {
|
||||
std::string md5_computed;
|
||||
std::string md5_expected;
|
||||
std::string md5_url;
|
||||
std::string password;
|
||||
std::string username;
|
||||
std::string url;
|
||||
};
|
||||
Request &get_request_();
|
||||
|
||||
void cleanup_(ota::OTABackendPtr backend, const std::shared_ptr<HttpContainer> &container, bool abort_backend);
|
||||
// Only reached from flash(), which returns early while request_ is null
|
||||
uint8_t do_ota_();
|
||||
std::string get_url_with_auth_(const std::string &url);
|
||||
bool http_get_md5_();
|
||||
bool validate_url_(const std::string &url);
|
||||
|
||||
std::string md5_computed_{};
|
||||
std::string md5_expected_{};
|
||||
std::string md5_url_{};
|
||||
std::string password_{};
|
||||
std::string username_{};
|
||||
std::string url_{};
|
||||
std::unique_ptr<Request> request_;
|
||||
int status_ = -1;
|
||||
static const uint16_t HTTP_RECV_BUFFER = 256; // the firmware GET chunk size
|
||||
};
|
||||
|
||||
@@ -296,7 +296,9 @@ def _external_manifests() -> list[dict[str, Any]]:
|
||||
found[component_dir] = source
|
||||
break
|
||||
else:
|
||||
if not component_dir.is_relative_to(loader.CORE_COMPONENTS_PATH):
|
||||
# Built in means anywhere in the package: the esphome domain itself
|
||||
# is esphome/core, not under esphome/components
|
||||
if not component_dir.is_relative_to(loader.CORE_COMPONENTS_PATH.parent):
|
||||
_LOGGER.warning(
|
||||
"SBOM: component '%s' in %s matches no external component "
|
||||
"source and is listed as part of ESPHome",
|
||||
|
||||
@@ -72,6 +72,23 @@ button:
|
||||
id(ota_update).set_source_url(std::string("http://my.ha.net:8123/local/esphome/manifest.json"));
|
||||
ESP_LOGI("test", "Manifest is now %s", id(ota_update).get_source_url());
|
||||
- component.update: ota_update
|
||||
# The OTA component's public setters and getters as lambdas use them; every shape must compile.
|
||||
- platform: template
|
||||
name: Flash from lambda
|
||||
on_press:
|
||||
then:
|
||||
- lambda: |-
|
||||
std::string url = "http://my.ha.net:8123/local/esphome/";
|
||||
url += "firmware.bin";
|
||||
id(http_request_ota).set_md5_url("http://my.ha.net:8123/local/esphome/firmware.md5");
|
||||
id(http_request_ota).set_md5(std::string("0123456789abcdef0123456789abcdef"));
|
||||
id(http_request_ota).set_username("user");
|
||||
id(http_request_ota).set_password(std::string("pass"));
|
||||
id(http_request_ota).set_url(url);
|
||||
id(http_request_ota).set_url(url.c_str());
|
||||
ESP_LOGI("test", "MD5 %s / %s", id(http_request_ota).md5_expected().c_str(),
|
||||
id(http_request_ota).md5_computed().c_str());
|
||||
id(http_request_ota).flash();
|
||||
|
||||
update:
|
||||
- platform: http_request
|
||||
|
||||
@@ -386,7 +386,7 @@ def test_external_manifests_skip_other_components(
|
||||
components_dir = tmp_path / "components"
|
||||
get_sources().append(ExternalSource(components_dir=components_dir))
|
||||
_add_external_component(components_dir, "widget")
|
||||
_add_external_component(tmp_path / "builtin", "wifi")
|
||||
_add_external_component(tmp_path / "pkg" / "components", "wifi")
|
||||
CORE.loaded_integrations.update({"missing", "namespace"})
|
||||
sbom.loader._COMPONENT_CACHE["namespace"] = SimpleNamespace(
|
||||
module=SimpleNamespace()
|
||||
@@ -399,7 +399,9 @@ def test_external_manifests_skip_other_components(
|
||||
side_effect=sbom.loader._COMPONENT_CACHE.get,
|
||||
),
|
||||
patch.object(
|
||||
sbom.loader, "CORE_COMPONENTS_PATH", (tmp_path / "builtin").resolve()
|
||||
sbom.loader,
|
||||
"CORE_COMPONENTS_PATH",
|
||||
(tmp_path / "pkg" / "components").resolve(),
|
||||
),
|
||||
):
|
||||
manifests = sbom._external_manifests()
|
||||
@@ -703,3 +705,12 @@ def test_checkout_remote_detached(tmp_path: Path) -> None:
|
||||
"https://user:token@github.com/someone/esphome.git",
|
||||
)
|
||||
assert not any(call.args[0][1] == "config" for call in run.call_args_list)
|
||||
|
||||
|
||||
def test_external_manifests_esphome_core_is_built_in(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""The esphome domain resolves to esphome/core, which is still part of ESPHome."""
|
||||
CORE.loaded_integrations.add("esphome")
|
||||
assert sbom._external_manifests() == []
|
||||
assert "SBOM" not in caplog.text
|
||||
Reference in new issue
Block a user