FastLED 3.10.8
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_NO_EXCEPT : mValid(false), mRepaired(false) { zeroOffsets(); }
18
19 explicit url(const char *url) FL_NO_EXCEPT
20 : mUrl(url), mValid(false), mRepaired(false) {
22 parse();
23 }
24
25 explicit url(const fl::string &u) FL_NO_EXCEPT
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_NO_EXCEPT { return mValid; }
38 explicit operator bool() const FL_NO_EXCEPT { return mValid; }
39
41 bool wasRepaired() const FL_NO_EXCEPT { return mRepaired; }
42
43 // ---- Component accessors (return views into mUrl) ----
52
55 fl::u16 port() const FL_NO_EXCEPT {
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_NO_EXCEPT { return mUrl; }
73 const char *c_str() const FL_NO_EXCEPT { return mUrl.c_str(); }
74
75 // ---- Comparison ----
76 bool operator==(const url &o) const FL_NO_EXCEPT { return mUrl == o.mUrl; }
77 bool operator!=(const url &o) const FL_NO_EXCEPT { 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_NO_EXCEPT {
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_NO_EXCEPT {
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_NO_EXCEPT {
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_NO_EXCEPT { return primary.isValid(); }
258 explicit operator bool() const FL_NO_EXCEPT { 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
325 LnkMetadata out;
326 bool gotPrimary = false;
327 fl::size pos = 0;
328 while (pos < content.size()) {
329 fl::size eol = content.find('\n', pos);
330 fl::size lineEnd = (eol == fl::string_view::npos) ? content.size() : eol;
331 fl::string_view line = content.substr(pos, lineEnd - pos);
332 pos = (eol == fl::string_view::npos) ? content.size() : eol + 1;
333
334 if (!line.empty() && line[line.size() - 1] == '\r') {
335 line = line.substr(0, line.size() - 1);
336 }
337
338 fl::size s = 0;
339 while (s < line.size() &&
340 (line[s] == ' ' || line[s] == '\t')) {
341 ++s;
342 }
343 fl::size e = line.size();
344 while (e > s &&
345 (line[e - 1] == ' ' || line[e - 1] == '\t')) {
346 --e;
347 }
348 line = line.substr(s, e - s);
349
350 if (line.empty() || line[0] == '#') {
351 continue;
352 }
353
354 if (!gotPrimary) {
355 out.primary = url(line);
356 gotPrimary = true;
357 continue;
358 }
359
360 // Subsequent lines: look for "key=value" metadata.
361 fl::size eq = line.find('=');
362 if (eq == fl::string_view::npos) {
363 // Unknown non-kv line, ignore (forward-compat).
364 continue;
365 }
366 fl::string_view key = line.substr(0, eq);
367 fl::string_view value = line.substr(eq + 1);
368 if (key == "sha256") {
369 out.sha256 = fl::string(value.data(), value.size());
370 } else if (key == "fallback") {
371 out.fallback = url(value);
372 }
373 // else: unknown key, ignore.
374 }
375 return out;
376}
377
378} // namespace fl
uint8_t pos
Definition Blur.ino:11
string_view substr(fl::size pos=0, fl::size count=npos) const FL_NO_EXCEPT
constexpr fl::size size() const FL_NO_EXCEPT
Definition string_view.h:99
static constexpr fl::size npos
Definition string_view.h:35
fl::size find(char ch, fl::size pos=0) const FL_NO_EXCEPT
constexpr bool empty() const FL_NO_EXCEPT
url() FL_NO_EXCEPT
Definition url.h:17
Span mScheme
Definition url.h:236
void zeroOffsets() FL_NO_EXCEPT
Definition url.h:92
Span mUserinfo
Definition url.h:237
fl::u16 defaultPort() const FL_NO_EXCEPT
Definition url.h:110
Span mQuery
Definition url.h:241
bool operator!=(const url &o) const FL_NO_EXCEPT
Definition url.h:77
fl::string_view authority() const FL_NO_EXCEPT
Definition url.h:51
Span mAuthority
Definition url.h:243
static Span makeSpan(fl::size off, fl::size len) FL_NO_EXCEPT
Definition url.h:103
fl::string_view scheme() const FL_NO_EXCEPT
Definition url.h:44
bool isValid() const FL_NO_EXCEPT
Definition url.h:37
bool wasRepaired() const FL_NO_EXCEPT
True if the URL was missing a scheme and "https://" was assumed.
Definition url.h:41
bool operator==(const url &o) const FL_NO_EXCEPT
Definition url.h:76
bool mRepaired
Definition url.h:235
fl::string_view userinfo() const FL_NO_EXCEPT
Definition url.h:45
fl::u16 port() const FL_NO_EXCEPT
Numeric port.
Definition url.h:55
Span mFragment
Definition url.h:242
fl::string_view query() const FL_NO_EXCEPT
Definition url.h:49
fl::string_view path() const FL_NO_EXCEPT
Definition url.h:48
fl::string_view port_str() const FL_NO_EXCEPT
Definition url.h:47
fl::string_view host() const FL_NO_EXCEPT
Definition url.h:46
fl::string_view fragment() const FL_NO_EXCEPT
Definition url.h:50
Span mPort
Definition url.h:239
void parse() FL_NO_EXCEPT
Definition url.h:121
url(const char *url) FL_NO_EXCEPT
Definition url.h:19
const char * c_str() const FL_NO_EXCEPT
Definition url.h:73
url(fl::string_view url) FL_NO_EXCEPT
Definition url.h:31
fl::string_view view(const Span &s) const FL_NO_EXCEPT
Definition url.h:86
const fl::string & string() const FL_NO_EXCEPT
Definition url.h:72
Span mPath
Definition url.h:240
fl::string_view str() const FL_NO_EXCEPT
Definition url.h:69
void parseAuthority(fl::string_view auth, fl::size baseOff) FL_NO_EXCEPT
Definition url.h:194
url(const fl::string &u) FL_NO_EXCEPT
Definition url.h:25
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
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
LnkMetadata parse_lnk_with_metadata(fl::string_view content) FL_NO_EXCEPT
Parse a .lnk file into URL + metadata (forward-compat).
Definition url.h:324
constexpr int type_rank< T >::value
url parse_lnk(fl::string_view content) FL_NO_EXCEPT
Parse the contents of a .lnk file into a fl::url.
Definition url.h:274
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
url Url
Definition url.h:246
Base definition for an LED controller.
Definition crgb.hpp:179
fl::url primary
First non-comment URL in the file.
Definition url.h:253
bool isValid() const FL_NO_EXCEPT
Definition url.h:257
fl::url fallback
Mirror URL used if primary fails.
Definition url.h:255
fl::string sha256
Hex-encoded sha256 of the expected asset.
Definition url.h:254
Metadata extracted from a .lnk asset link file.
Definition url.h:252