crosspoint-reader/src/util/UrlUtils.cpp

117 lines
3.5 KiB
C++
Raw Normal View History

Calibre Web Epub Downloading + Calibre Wireless Device Syncing (#219) ## Summary Adds support for browsing and downloading books from a Calibre-web server via OPDS. How it works 1. Configure server URL in Settings → Calibre Web URL (e.g., https://myserver.com:port I use Cloudflare tunnel to make my server accessible anywhere fwiw) 2. "Calibre Library" will now show on the the home screen 3. Browse the catalog - navigate through categories like "By Newest", "By Author", "By Series", etc. 4. Download books - select a book and press Confirm to download the EPUB to your device Navigation - Up/Down - Move through entries - Confirm - Open folder or download book - Back - Go to parent catalog, or exit to home if at root - Navigation entries show with > prefix, books show title and author - Button hints update dynamically ("Open" for folders, "Download" for books) Technical details - Fetches OPDS catalog from {server_url}/opds - Parses both navigation feeds (catalog links) and acquisition feeds (downloadable books) - Maintains navigation history stack for back navigation - Handles absolute paths in OPDS links correctly (e.g., /books/opds/navcatalog/...) - Downloads EPUBs directly to the SD card root Note The server URL should be typed to include https:// if the server requires it - HTTP→HTTPS redirects may cause SSL errors on ESP32. ## Additional Context * I also changed the home titles to use uppercase for each word and added a setting to change the size of the side margins --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-07 03:58:37 -05:00
#include "UrlUtils.h"
#include <iomanip>
2026-01-13 13:25:15 +00:00
#include <sstream>
Calibre Web Epub Downloading + Calibre Wireless Device Syncing (#219) ## Summary Adds support for browsing and downloading books from a Calibre-web server via OPDS. How it works 1. Configure server URL in Settings → Calibre Web URL (e.g., https://myserver.com:port I use Cloudflare tunnel to make my server accessible anywhere fwiw) 2. "Calibre Library" will now show on the the home screen 3. Browse the catalog - navigate through categories like "By Newest", "By Author", "By Series", etc. 4. Download books - select a book and press Confirm to download the EPUB to your device Navigation - Up/Down - Move through entries - Confirm - Open folder or download book - Back - Go to parent catalog, or exit to home if at root - Navigation entries show with > prefix, books show title and author - Button hints update dynamically ("Open" for folders, "Download" for books) Technical details - Fetches OPDS catalog from {server_url}/opds - Parses both navigation feeds (catalog links) and acquisition feeds (downloadable books) - Maintains navigation history stack for back navigation - Handles absolute paths in OPDS links correctly (e.g., /books/opds/navcatalog/...) - Downloads EPUBs directly to the SD card root Note The server URL should be typed to include https:// if the server requires it - HTTP→HTTPS redirects may cause SSL errors on ESP32. ## Additional Context * I also changed the home titles to use uppercase for each word and added a setting to change the size of the side margins --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-07 03:58:37 -05:00
namespace UrlUtils {
std::string ensureProtocol(const std::string& url) {
if (url.find("://") == std::string::npos) {
return "http://" + url;
}
return url;
}
std::string extractHost(const std::string& url) {
const size_t protocolEnd = url.find("://");
if (protocolEnd == std::string::npos) {
// No protocol, find first slash
const size_t firstSlash = url.find('/');
return firstSlash == std::string::npos ? url : url.substr(0, firstSlash);
}
// Find the first slash after the protocol
const size_t hostStart = protocolEnd + 3;
const size_t pathStart = url.find('/', hostStart);
return pathStart == std::string::npos ? url : url.substr(0, pathStart);
}
std::string buildUrl(const std::string& serverUrl, const std::string& path) {
const std::string urlWithProtocol = ensureProtocol(serverUrl);
if (path.empty()) {
return urlWithProtocol;
}
if (path[0] == '/') {
// Absolute path - use just the host
return extractHost(urlWithProtocol) + path;
}
// Relative path - append to server URL
if (urlWithProtocol.back() == '/') {
return urlWithProtocol + 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();
}
2026-01-13 13:25:15 +00:00
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);
2026-01-13 13:25:15 +00:00
// 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;
}
Calibre Web Epub Downloading + Calibre Wireless Device Syncing (#219) ## Summary Adds support for browsing and downloading books from a Calibre-web server via OPDS. How it works 1. Configure server URL in Settings → Calibre Web URL (e.g., https://myserver.com:port I use Cloudflare tunnel to make my server accessible anywhere fwiw) 2. "Calibre Library" will now show on the the home screen 3. Browse the catalog - navigate through categories like "By Newest", "By Author", "By Series", etc. 4. Download books - select a book and press Confirm to download the EPUB to your device Navigation - Up/Down - Move through entries - Confirm - Open folder or download book - Back - Go to parent catalog, or exit to home if at root - Navigation entries show with > prefix, books show title and author - Button hints update dynamically ("Open" for folders, "Download" for books) Technical details - Fetches OPDS catalog from {server_url}/opds - Parses both navigation feeds (catalog links) and acquisition feeds (downloadable books) - Maintains navigation history stack for back navigation - Handles absolute paths in OPDS links correctly (e.g., /books/opds/navcatalog/...) - Downloads EPUBs directly to the SD card root Note The server URL should be typed to include https:// if the server requires it - HTTP→HTTPS redirects may cause SSL errors on ESP32. ## Additional Context * I also changed the home titles to use uppercase for each word and added a setting to change the size of the side margins --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-07 03:58:37 -05:00
} // namespace UrlUtils