mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 23:57:39 +03:00
Added support for authentication in Calibre
This commit is contained in:
parent
79dc134b78
commit
da3abbacd5
@ -12,9 +12,9 @@
|
||||
CrossPointSettings CrossPointSettings::instance;
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 2;
|
||||
// Increment this when adding new persisted settings fields
|
||||
constexpr uint8_t SETTINGS_COUNT = 17;
|
||||
constexpr uint8_t SETTINGS_COUNT = 19;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
@ -47,6 +47,8 @@ bool CrossPointSettings::saveToFile() const {
|
||||
serialization::writeString(outputFile, std::string(opdsServerUrl));
|
||||
serialization::writePod(outputFile, textAntiAliasing);
|
||||
serialization::writePod(outputFile, hideBatteryPercentage);
|
||||
serialization::writeString(outputFile, std::string(calibreUsername));
|
||||
serialization::writeString(outputFile, std::string(calibrePassword));
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||
@ -113,6 +115,20 @@ bool CrossPointSettings::loadFromFile() {
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, hideBatteryPercentage);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
{
|
||||
std::string usernameStr;
|
||||
serialization::readString(inputFile, usernameStr);
|
||||
strncpy(calibreUsername, usernameStr.c_str(), sizeof(calibreUsername) - 1);
|
||||
calibreUsername[sizeof(calibreUsername) - 1] = '\0';
|
||||
}
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
{
|
||||
std::string passwordStr;
|
||||
serialization::readString(inputFile, passwordStr);
|
||||
strncpy(calibrePassword, passwordStr.c_str(), sizeof(calibrePassword) - 1);
|
||||
calibrePassword[sizeof(calibrePassword) - 1] = '\0';
|
||||
}
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
} while (false);
|
||||
|
||||
inputFile.close();
|
||||
|
||||
@ -88,6 +88,8 @@ class CrossPointSettings {
|
||||
uint8_t screenMargin = 5;
|
||||
// OPDS browser settings
|
||||
char opdsServerUrl[128] = "";
|
||||
char calibreUsername[64] = "";
|
||||
char calibrePassword[64] = "";
|
||||
// Hide battery percentage
|
||||
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@ inline std::vector<SettingInfo> getSettingsList() {
|
||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||
SettingInfo::String("opdsServerUrl", "Calibre Web URL", SETTINGS.opdsServerUrl,
|
||||
sizeof(SETTINGS.opdsServerUrl) - 1),
|
||||
SettingInfo::String("calibreUsername", "Calibre Username", SETTINGS.calibreUsername,
|
||||
sizeof(SETTINGS.calibreUsername) - 1),
|
||||
SettingInfo::String("calibrePassword", "Calibre Password", SETTINGS.calibrePassword,
|
||||
sizeof(SETTINGS.calibrePassword) - 1),
|
||||
SettingInfo::Action("Calibre Settings"),
|
||||
SettingInfo::Action("Check for updates"),
|
||||
};
|
||||
|
||||
@ -13,8 +13,8 @@
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
constexpr int MENU_ITEMS = 2;
|
||||
const char* menuNames[MENU_ITEMS] = {"Calibre Web URL", "Connect as Wireless Device"};
|
||||
constexpr int MENU_ITEMS = 4;
|
||||
const char* menuNames[MENU_ITEMS] = {"Calibre Web URL", "Username", "Password", "Connect as Wireless Device"};
|
||||
} // namespace
|
||||
|
||||
void CalibreSettingsActivity::taskTrampoline(void* param) {
|
||||
@ -98,6 +98,42 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
updateRequired = true;
|
||||
}));
|
||||
} else if (selectedIndex == 1) {
|
||||
// Username
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Username", SETTINGS.calibreUsername, 10,
|
||||
63, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& username) {
|
||||
strncpy(SETTINGS.calibreUsername, username.c_str(), sizeof(SETTINGS.calibreUsername) - 1);
|
||||
SETTINGS.calibreUsername[sizeof(SETTINGS.calibreUsername) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
} else if (selectedIndex == 2) {
|
||||
// Password
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Password", SETTINGS.calibrePassword, 10,
|
||||
63, // maxLength
|
||||
true, // is password
|
||||
[this](const std::string& password) {
|
||||
strncpy(SETTINGS.calibrePassword, password.c_str(), sizeof(SETTINGS.calibrePassword) - 1);
|
||||
SETTINGS.calibrePassword[sizeof(SETTINGS.calibrePassword) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
} else if (selectedIndex == 3) {
|
||||
// Wireless Device - launch the activity (handles WiFi connection internally)
|
||||
exitActivity();
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
@ -153,11 +189,19 @@ void CalibreSettingsActivity::render() {
|
||||
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected);
|
||||
|
||||
// Draw status for URL setting
|
||||
// Draw status for URL, username, and password settings
|
||||
if (i == 0) {
|
||||
const char* status = (strlen(SETTINGS.opdsServerUrl) > 0) ? "[Set]" : "[Not Set]";
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||
} else if (i == 1) {
|
||||
const char* status = (strlen(SETTINGS.calibreUsername) > 0) ? "[Set]" : "[Not Set]";
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||
} else if (i == 2) {
|
||||
const char* status = (strlen(SETTINGS.calibrePassword) > 0) ? "[Set]" : "[Not Set]";
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
#include "UrlUtils.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace UrlUtils {
|
||||
|
||||
std::string ensureProtocol(const std::string& url) {
|
||||
@ -38,4 +41,76 @@ std::string buildUrl(const std::string& serverUrl, const std::string& path) {
|
||||
return urlWithProtocol + "/" + path;
|
||||
}
|
||||
|
||||
std::string urlEncode(const std::string& value) {
|
||||
std::ostringstream escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex;
|
||||
|
||||
for (char c : value) {
|
||||
// Keep alphanumeric and other safe characters intact
|
||||
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
||||
escaped << c;
|
||||
} else {
|
||||
// Encode special characters
|
||||
escaped << std::uppercase;
|
||||
escaped << '%' << std::setw(2) << int((unsigned char)c);
|
||||
escaped << std::nouppercase;
|
||||
}
|
||||
}
|
||||
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
std::string buildUrlWithAuth(const std::string& serverUrl, const std::string& path,
|
||||
const std::string& username, const std::string& password) {
|
||||
// If no credentials, use regular buildUrl
|
||||
if (username.empty() && password.empty()) {
|
||||
return buildUrl(serverUrl, path);
|
||||
}
|
||||
|
||||
std::string urlWithProtocol = ensureProtocol(serverUrl);
|
||||
|
||||
// Find protocol end
|
||||
const size_t protocolEnd = urlWithProtocol.find("://");
|
||||
if (protocolEnd == std::string::npos) {
|
||||
return buildUrl(serverUrl, path); // Fallback if no protocol
|
||||
}
|
||||
|
||||
// Extract protocol and host parts
|
||||
std::string protocol = urlWithProtocol.substr(0, protocolEnd + 3); // Include ://
|
||||
std::string hostAndPath = urlWithProtocol.substr(protocolEnd + 3);
|
||||
|
||||
// Check if auth already exists in URL
|
||||
const size_t atPos = hostAndPath.find('@');
|
||||
if (atPos != std::string::npos) {
|
||||
// Auth already in URL, remove it
|
||||
hostAndPath = hostAndPath.substr(atPos + 1);
|
||||
}
|
||||
|
||||
// Build auth string with URL encoding
|
||||
std::string auth;
|
||||
if (!username.empty() || !password.empty()) {
|
||||
auth = urlEncode(username) + ":" + urlEncode(password) + "@";
|
||||
}
|
||||
|
||||
// Reconstruct URL with auth
|
||||
std::string authenticatedUrl = protocol + auth + hostAndPath;
|
||||
|
||||
// Now apply path logic
|
||||
if (path.empty()) {
|
||||
return authenticatedUrl;
|
||||
}
|
||||
if (path[0] == '/') {
|
||||
// Absolute path - extract just protocol + auth + host
|
||||
const size_t firstSlash = hostAndPath.find('/');
|
||||
std::string hostOnly = (firstSlash == std::string::npos) ? hostAndPath : hostAndPath.substr(0, firstSlash);
|
||||
return protocol + auth + hostOnly + path;
|
||||
}
|
||||
// Relative path
|
||||
if (authenticatedUrl.back() == '/') {
|
||||
return authenticatedUrl + path;
|
||||
}
|
||||
return authenticatedUrl + "/" + path;
|
||||
}
|
||||
|
||||
} // namespace UrlUtils
|
||||
|
||||
@ -20,4 +20,17 @@ std::string extractHost(const std::string& url);
|
||||
*/
|
||||
std::string buildUrl(const std::string& serverUrl, const std::string& path);
|
||||
|
||||
/**
|
||||
* URL encode a string (percent encoding for special characters)
|
||||
*/
|
||||
std::string urlEncode(const std::string& value);
|
||||
|
||||
/**
|
||||
* Build URL with basic authentication embedded.
|
||||
* If username and password are provided, adds them to the URL.
|
||||
* Example: https://username:password@example.com/path
|
||||
*/
|
||||
std::string buildUrlWithAuth(const std::string& serverUrl, const std::string& path,
|
||||
const std::string& username, const std::string& password);
|
||||
|
||||
} // namespace UrlUtils
|
||||
|
||||
Loading…
Reference in New Issue
Block a user