FastLED 3.9.15
Loading...
Searching...
No Matches
url.h
Go to the documentation of this file.
1#pragma once
2
7
8#include "fl/stl/int.h"
9#include "fl/stl/string.h"
10#include "fl/stl/string_view.h"
11#include "fl/stl/noexcept.h"
12
13namespace fl {
14
15class url {
16 public:
17 url() FL_NOEXCEPT : mValid(false), mRepaired(false) { zeroOffsets(); }
18
19 explicit url(const char *url) FL_NOEXCEPT
20 : mUrl(url), mValid(false), mRepaired(false) {
22 parse();
23 }
24
25 explicit url(const fl::string &u) FL_NOEXCEPT
26 : mUrl(u), mValid(false), mRepaired(false) {
28 parse();
29 }
30
32 : mUrl(url.data(), url.size()), mValid(false), mRepaired(false) {
34 parse();
35 }
36
37 bool isValid() const FL_NOEXCEPT { return mValid; }
38 explicit operator bool() const FL_NOEXCEPT { return mValid; }
39
41 bool wasRepaired() const FL_NOEXCEPT { return mRepaired; }
42
43 // ---- Component accessors (return views into mUrl) ----
52
55 fl::u16 port() const FL_NOEXCEPT {
57 if (p.empty()) {
58 return defaultPort();
59 }
60 fl::u16 result = 0;
61 for (fl::size i = 0; i < p.size(); ++i) {
62 result = static_cast<fl::u16>(result * 10 +
63 static_cast<fl::u16>(p[i] - '0'));
64 }
65 return result;
66 }
67
68 // ---- Whole-URL access ----
70 return fl::string_view(mUrl.c_str(), mUrl.size());
71 }
72 const fl::string &string() const FL_NOEXCEPT { return mUrl; }
73 const char *c_str() const FL_NOEXCEPT { return mUrl.c_str(); }
74
75 // ---- Comparison ----
76 bool operator==(const url &o) const FL_NOEXCEPT { return mUrl == o.mUrl; }
77 bool operator!=(const url &o) const FL_NOEXCEPT { return !(mUrl == o.mUrl); }
78
79 private:
80 // A (offset, length) pair stored as uint16_t.
81 struct Span {
82 fl::u16 off;
83 fl::u16 len;
84 };
85
87 if (s.len == 0)
88 return fl::string_view();
89 return fl::string_view(mUrl.c_str() + s.off, s.len);
90 }
91
93 mScheme = {0, 0};
94 mUserinfo = {0, 0};
95 mHost = {0, 0};
96 mPort = {0, 0};
97 mPath = {0, 0};
98 mQuery = {0, 0};
99 mFragment = {0, 0};
100 mAuthority = {0, 0};
101 }
102
103 static Span makeSpan(fl::size off, fl::size len) FL_NOEXCEPT {
104 Span s;
105 s.off = static_cast<fl::u16>(off);
106 s.len = static_cast<fl::u16>(len);
107 return s;
108 }
109
110 fl::u16 defaultPort() const FL_NOEXCEPT {
112 if (s == "https" || s == "wss")
113 return 443;
114 if (s == "http" || s == "ws")
115 return 80;
116 if (s == "ftp")
117 return 21;
118 return 0;
119 }
120
122 if (mUrl.empty()) {
123 return;
124 }
125
126 fl::string_view src(mUrl.c_str(), mUrl.size());
127 fl::size pos = 0;
128
129 // 1. Scheme — look for "://"
130 fl::size sep = src.find("://");
131 if (sep == fl::string_view::npos) {
132 // No scheme found — assume https:// and retry
133 mUrl = fl::string("https://") + mUrl;
134 mRepaired = true;
135 src = fl::string_view(mUrl.c_str(), mUrl.size());
136 sep = src.find("://");
137 }
138 mScheme = makeSpan(0, sep);
139 pos = sep + 3; // skip past "://"
140
141 // 2. Authority — everything up to first '/', '?', or '#'
142 fl::size authStart = pos;
143 fl::size authEnd = src.size();
144 for (fl::size i = pos; i < src.size(); ++i) {
145 char c = src[i];
146 if (c == '/' || c == '?' || c == '#') {
147 authEnd = i;
148 break;
149 }
150 }
151 mAuthority = makeSpan(authStart, authEnd - authStart);
152
153 // 3. Parse authority into userinfo, host, port
154 parseAuthority(src.substr(authStart, authEnd - authStart), authStart);
155
156 pos = authEnd;
157
158 // 4. Path — up to '?' or '#'
159 if (pos < src.size() && src[pos] == '/') {
160 fl::size pathStart = pos;
161 fl::size pathEnd = src.size();
162 for (fl::size i = pos; i < src.size(); ++i) {
163 if (src[i] == '?' || src[i] == '#') {
164 pathEnd = i;
165 break;
166 }
167 }
168 mPath = makeSpan(pathStart, pathEnd - pathStart);
169 pos = pathEnd;
170 }
171
172 // 5. Query — after '?' up to '#'
173 if (pos < src.size() && src[pos] == '?') {
174 ++pos; // skip '?'
175 fl::size qStart = pos;
176 fl::size qEnd = src.size();
177 fl::size hashPos = src.find('#', pos);
178 if (hashPos != fl::string_view::npos) {
179 qEnd = hashPos;
180 }
181 mQuery = makeSpan(qStart, qEnd - qStart);
182 pos = qEnd;
183 }
184
185 // 6. Fragment — after '#'
186 if (pos < src.size() && src[pos] == '#') {
187 ++pos; // skip '#'
188 mFragment = makeSpan(pos, src.size() - pos);
189 }
190
191 mValid = true;
192 }
193
194 void parseAuthority(fl::string_view auth, fl::size baseOff) FL_NOEXCEPT {
195 fl::size pos = 0;
196
197 // userinfo — everything before '@'
198 fl::size at = auth.find('@');
199 if (at != fl::string_view::npos) {
200 mUserinfo = makeSpan(baseOff + pos, at);
201 pos = at + 1; // skip '@'
202 }
203
204 // host (with IPv6 bracket support) and optional port
205 if (pos < auth.size() && auth[pos] == '[') {
206 // IPv6 literal
207 fl::size closeBracket = auth.find(']', pos);
208 if (closeBracket == fl::string_view::npos) {
209 // Malformed, take rest as host
210 mHost = makeSpan(baseOff + pos, auth.size() - pos);
211 return;
212 }
213 // Include brackets in host
214 mHost = makeSpan(baseOff + pos, closeBracket - pos + 1);
215 pos = closeBracket + 1;
216 if (pos < auth.size() && auth[pos] == ':') {
217 ++pos;
218 mPort = makeSpan(baseOff + pos, auth.size() - pos);
219 }
220 } else {
221 // Regular host — find last ':' that separates host:port
222 fl::size colon = auth.find(':', pos);
223 if (colon != fl::string_view::npos) {
224 mHost = makeSpan(baseOff + pos, colon - pos);
225 fl::size portStart = colon + 1;
226 mPort = makeSpan(baseOff + portStart, auth.size() - portStart);
227 } else {
228 mHost = makeSpan(baseOff + pos, auth.size() - pos);
229 }
230 }
231 }
232
234 bool mValid;
244};
245
246using Url = url;
247
256
257 bool isValid() const FL_NOEXCEPT { return primary.isValid(); }
258 explicit operator bool() const FL_NOEXCEPT { return primary.isValid(); }
259};
260
275 fl::size pos = 0;
276 while (pos < content.size()) {
277 fl::size eol = content.find('\n', pos);
278 fl::size lineEnd = (eol == fl::string_view::npos) ? content.size() : eol;
279 fl::string_view line = content.substr(pos, lineEnd - pos);
280 pos = (eol == fl::string_view::npos) ? content.size() : eol + 1;
281
282 // Strip a trailing '\r' from CRLF line endings.
283 if (!line.empty() && line[line.size() - 1] == '\r') {
284 line = line.substr(0, line.size() - 1);
285 }
286
287 // Trim leading whitespace.
288 fl::size s = 0;
289 while (s < line.size() &&
290 (line[s] == ' ' || line[s] == '\t')) {
291 ++s;
292 }
293 // Trim trailing whitespace.
294 fl::size e = line.size();
295 while (e > s &&
296 (line[e - 1] == ' ' || line[e - 1] == '\t')) {
297 --e;
298 }
299 line = line.substr(s, e - s);
300
301 if (line.empty()) {
302 continue;
303 }
304 if (line[0] == '#') {
305 continue;
306 }
307 return url(line);
308 }
309 return url();
310}
311
326 LnkMetadata out;
327 bool gotPrimary = false;
328 fl::size pos = 0;
329 while (pos < content.size()) {
330 fl::size eol = content.find('\n', pos);
331 fl::size lineEnd = (eol == fl::string_view::npos) ? content.size() : eol;
332 fl::string_view line = content.substr(pos, lineEnd - pos);
333 pos = (eol == fl::string_view::npos) ? content.size() : eol + 1;
334
335 if (!line.empty() && line[line.size() - 1] == '\r') {
336 line = line.substr(0, line.size() - 1);
337 }
338
339 fl::size s = 0;
340 while (s < line.size() &&
341 (line[s] == ' ' || line[s] == '\t')) {
342 ++s;
343 }
344 fl::size e = line.size();
345 while (e > s &&
346 (line[e - 1] == ' ' || line[e - 1] == '\t')) {
347 --e;
348 }
349 line = line.substr(s, e - s);
350
351 if (line.empty() || line[0] == '#') {
352 continue;
353 }
354
355 if (!gotPrimary) {
356 out.primary = url(line);
357 gotPrimary = true;
358 continue;
359 }
360
361 // Subsequent lines: look for "key=value" metadata.
362 fl::size eq = line.find('=');
363 if (eq == fl::string_view::npos) {
364 // Unknown non-kv line, ignore (forward-compat).
365 continue;
366 }
367 fl::string_view key = line.substr(0, eq);
368 fl::string_view value = line.substr(eq + 1);
369 if (key == "sha256") {
370 out.sha256 = fl::string(value.data(), value.size());
371 } else if (key == "fallback") {
372 out.fallback = url(value);
373 }
374 // else: unknown key, ignore.
375 }
376 return out;
377}
378
379} // namespace fl
uint8_t pos
Definition Blur.ino:11
constexpr bool empty() const FL_NOEXCEPT
static constexpr fl::size npos
Definition string_view.h:35
constexpr fl::size size() const FL_NOEXCEPT
Definition string_view.h:99
fl::size find(char ch, fl::size pos=0) const FL_NOEXCEPT
string_view substr(fl::size pos=0, fl::size count=npos) const FL_NOEXCEPT
fl::string_view host() const FL_NOEXCEPT
Definition url.h:46
fl::u16 port() const FL_NOEXCEPT
Numeric port.
Definition url.h:55
fl::string_view port_str() const FL_NOEXCEPT
Definition url.h:47
Span mScheme
Definition url.h:236
fl::string_view str() const FL_NOEXCEPT
Definition url.h:69
Span mUserinfo
Definition url.h:237
url(const char *url) FL_NOEXCEPT
Definition url.h:19
fl::string_view scheme() const FL_NOEXCEPT
Definition url.h:44
void parse() FL_NOEXCEPT
Definition url.h:121
Span mQuery
Definition url.h:241
fl::string_view userinfo() const FL_NOEXCEPT
Definition url.h:45
const fl::string & string() const FL_NOEXCEPT
Definition url.h:72
bool isValid() const FL_NOEXCEPT
Definition url.h:37
bool wasRepaired() const FL_NOEXCEPT
True if the URL was missing a scheme and "https://" was assumed.
Definition url.h:41
Span mAuthority
Definition url.h:243
url(const fl::string &u) FL_NOEXCEPT
Definition url.h:25
fl::string_view query() const FL_NOEXCEPT
Definition url.h:49
fl::string_view path() const FL_NOEXCEPT
Definition url.h:48
url(fl::string_view url) FL_NOEXCEPT
Definition url.h:31
fl::string_view authority() const FL_NOEXCEPT
Definition url.h:51
bool operator!=(const url &o) const FL_NOEXCEPT
Definition url.h:77
bool mRepaired
Definition url.h:235
fl::string_view fragment() const FL_NOEXCEPT
Definition url.h:50
Span mFragment
Definition url.h:242
static Span makeSpan(fl::size off, fl::size len) FL_NOEXCEPT
Definition url.h:103
fl::u16 defaultPort() const FL_NOEXCEPT
Definition url.h:110
void parseAuthority(fl::string_view auth, fl::size baseOff) FL_NOEXCEPT
Definition url.h:194
Span mPort
Definition url.h:239
bool operator==(const url &o) const FL_NOEXCEPT
Definition url.h:76
void zeroOffsets() FL_NOEXCEPT
Definition url.h:92
fl::string_view view(const Span &s) const FL_NOEXCEPT
Definition url.h:86
url() FL_NOEXCEPT
Definition url.h:17
Span mPath
Definition url.h:240
const char * c_str() const FL_NOEXCEPT
Definition url.h:73
bool mValid
Definition url.h:234
Span mHost
Definition url.h:238
fl::string mUrl
Definition url.h:233
Definition url.h:15
fl::u16 len
Definition url.h:83
fl::u16 off
Definition url.h:82
LnkMetadata parse_lnk_with_metadata(fl::string_view content) FL_NOEXCEPT
Parse a .lnk file into URL + metadata (forward-compat).
Definition url.h:325
constexpr int type_rank< T >::value
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
url parse_lnk(fl::string_view content) FL_NOEXCEPT
Parse the contents of a .lnk file into a fl::url.
Definition url.h:274
url Url
Definition url.h:246
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
fl::url primary
First non-comment URL in the file.
Definition url.h:253
bool isValid() const FL_NOEXCEPT
Definition url.h:257
fl::url fallback
Mirror URL used if primary fails (reserved).
Definition url.h:255
fl::string sha256
Hex-encoded sha256 of the expected asset (reserved).
Definition url.h:254
Metadata extracted from a .lnk asset link file.
Definition url.h:252