diff options
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r-- | src/gmrequest.c | 152 |
1 files changed, 133 insertions, 19 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c index 0d69861d..137e8303 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -1,8 +1,31 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
1 | #include "gmrequest.h" | 23 | #include "gmrequest.h" |
2 | #include "gmutil.h" | 24 | #include "gmutil.h" |
3 | #include "gmcerts.h" | 25 | #include "gmcerts.h" |
4 | #include "app.h" /* dataDir_App() */ | 26 | #include "app.h" /* dataDir_App() */ |
5 | #include "embedded.h" | 27 | #include "embedded.h" |
28 | #include "ui/text.h" | ||
6 | 29 | ||
7 | #include <the_Foundation/file.h> | 30 | #include <the_Foundation/file.h> |
8 | #include <the_Foundation/mutex.h> | 31 | #include <the_Foundation/mutex.h> |
@@ -171,11 +194,22 @@ static void checkServerCertificate_GmRequest_(iGmRequest *d) { | |||
171 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); | 194 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); |
172 | d->resp.certFlags = 0; | 195 | d->resp.certFlags = 0; |
173 | if (cert) { | 196 | if (cert) { |
174 | const iRangecc domain = urlHost_String(&d->url); | 197 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); |
175 | d->resp.certFlags |= available_GmCertFlag; | 198 | d->resp.certFlags |= available_GmCertFlag; |
176 | if (!isExpired_TlsCertificate(cert)) { | 199 | if (!isExpired_TlsCertificate(cert)) { |
177 | d->resp.certFlags |= timeVerified_GmCertFlag; | 200 | d->resp.certFlags |= timeVerified_GmCertFlag; |
178 | } | 201 | } |
202 | /* TODO: Check for IP too (see below), because it may be specified in the SAN. */ | ||
203 | #if 0 | ||
204 | iString *ip = toStringFlags_Address(address_TlsRequest(d->req), noPort_SocketStringFlag, 0); | ||
205 | if (verifyIp_TlsCertificate(cert, ip)) { | ||
206 | printf("[GmRequest] IP address %s matches!\n", cstr_String(ip)); | ||
207 | } | ||
208 | else { | ||
209 | printf("[GmRequest] IP address %s not matched\n", cstr_String(ip)); | ||
210 | } | ||
211 | delete_String(ip); | ||
212 | #endif | ||
179 | if (verifyDomain_TlsCertificate(cert, domain)) { | 213 | if (verifyDomain_TlsCertificate(cert, domain)) { |
180 | d->resp.certFlags |= domainVerified_GmCertFlag; | 214 | d->resp.certFlags |= domainVerified_GmCertFlag; |
181 | } | 215 | } |
@@ -265,31 +299,92 @@ static void requestFinished_GmRequest_(iAnyObject *obj) { | |||
265 | 299 | ||
266 | static const iBlock *aboutPageSource_(iRangecc path) { | 300 | static const iBlock *aboutPageSource_(iRangecc path) { |
267 | const iBlock *src = NULL; | 301 | const iBlock *src = NULL; |
268 | if (equalCase_Rangecc(&path, "lagrange")) { | 302 | if (equalCase_Rangecc(path, "lagrange")) { |
269 | return &blobAbout_Embedded; | 303 | return &blobLagrange_Embedded; |
270 | } | 304 | } |
271 | if (equalCase_Rangecc(&path, "help")) { | 305 | if (equalCase_Rangecc(path, "help")) { |
272 | return &blobHelp_Embedded; | 306 | return &blobHelp_Embedded; |
273 | } | 307 | } |
274 | if (equalCase_Rangecc(&path, "version")) { | 308 | if (equalCase_Rangecc(path, "version")) { |
275 | return &blobVersion_Embedded; | 309 | return &blobVersion_Embedded; |
276 | } | 310 | } |
277 | return src; | 311 | return src; |
278 | } | 312 | } |
279 | 313 | ||
280 | static const iBlock *replaceVariables_(const iBlock *block) { | 314 | static const iBlock *replaceVariables_(const iBlock *block) { |
281 | iRegExp *var = new_RegExp("\\$\\{([A-Z_]+)\\}", 0); | 315 | iRegExp *var = new_RegExp("\\$\\{([^}]+)\\}", 0); |
282 | iRegExpMatch m; | 316 | iRegExpMatch m; |
317 | init_RegExpMatch(&m); | ||
283 | if (matchRange_RegExp(var, range_Block(block), &m)) { | 318 | if (matchRange_RegExp(var, range_Block(block), &m)) { |
284 | iBlock *replaced = collect_Block(copy_Block(block)); | 319 | iBlock *replaced = collect_Block(copy_Block(block)); |
285 | do { | 320 | do { |
286 | const iRangei span = m.range; | 321 | const iRangei span = m.range; |
287 | remove_Block(replaced, span.start, size_Range(&span)); | ||
288 | const iRangecc name = capturedRange_RegExpMatch(&m, 1); | 322 | const iRangecc name = capturedRange_RegExpMatch(&m, 1); |
289 | if (equal_Rangecc(&name, "APP_VERSION")) { | 323 | iRangecc repl = iNullRange; |
290 | insertData_Block(replaced, span.start, | 324 | if (equal_Rangecc(name, "APP_VERSION")) { |
291 | LAGRANGE_APP_VERSION, strlen(LAGRANGE_APP_VERSION)); | 325 | repl = range_CStr(LAGRANGE_APP_VERSION); |
326 | } | ||
327 | else if (startsWith_Rangecc(name, "BT:")) { /* block text */ | ||
328 | repl = range_String(collect_String(renderBlockChars_Text( | ||
329 | &fontFiraSansRegular_Embedded, | ||
330 | 11, /* should be larger if shaded */ | ||
331 | quadrants_TextBlockMode, | ||
332 | &(iString){ iBlockLiteral( | ||
333 | name.start + 3, size_Range(&name) - 3, size_Range(&name) - 3) }))); | ||
334 | } | ||
335 | else if (startsWith_Rangecc(name, "ST:")) { /* shaded text */ | ||
336 | repl = range_String(collect_String(renderBlockChars_Text( | ||
337 | &fontSymbola_Embedded, | ||
338 | 20, | ||
339 | shading_TextBlockMode, | ||
340 | &(iString){ iBlockLiteral( | ||
341 | name.start + 3, size_Range(&name) - 3, size_Range(&name) - 3) }))); | ||
342 | } | ||
343 | else if (equal_Rangecc(name, "ALT")) { | ||
344 | #if defined (iPlatformApple) | ||
345 | repl = range_CStr("\u2325"); | ||
346 | #else | ||
347 | repl = range_CStr("Alt"); | ||
348 | #endif | ||
349 | } | ||
350 | else if (equal_Rangecc(name, "ALT+")) { | ||
351 | #if defined (iPlatformApple) | ||
352 | repl = range_CStr("\u2325"); | ||
353 | #else | ||
354 | repl = range_CStr("Alt+"); | ||
355 | #endif | ||
356 | } | ||
357 | else if (equal_Rangecc(name, "CTRL")) { | ||
358 | #if defined (iPlatformApple) | ||
359 | repl = range_CStr("\u2318"); | ||
360 | #else | ||
361 | repl = range_CStr("Ctrl"); | ||
362 | #endif | ||
292 | } | 363 | } |
364 | else if (equal_Rangecc(name, "CTRL+")) { | ||
365 | #if defined (iPlatformApple) | ||
366 | repl = range_CStr("\u2318"); | ||
367 | #else | ||
368 | repl = range_CStr("Ctrl+"); | ||
369 | #endif | ||
370 | } | ||
371 | else if (equal_Rangecc(name, "SHIFT")) { | ||
372 | #if defined (iPlatformApple) | ||
373 | repl = range_CStr("\u21e7"); | ||
374 | #else | ||
375 | repl = range_CStr("Shift"); | ||
376 | #endif | ||
377 | } | ||
378 | else if (equal_Rangecc(name, "SHIFT+")) { | ||
379 | #if defined (iPlatformApple) | ||
380 | repl = range_CStr("\u21e7"); | ||
381 | #else | ||
382 | repl = range_CStr("Shift+"); | ||
383 | #endif | ||
384 | } | ||
385 | remove_Block(replaced, span.start, size_Range(&span)); | ||
386 | insertData_Block(replaced, span.start, repl.start, size_Range(&repl)); | ||
387 | iZap(m); | ||
293 | } while (matchRange_RegExp(var, range_Block(replaced), &m)); | 388 | } while (matchRange_RegExp(var, range_Block(replaced), &m)); |
294 | block = replaced; | 389 | block = replaced; |
295 | } | 390 | } |
@@ -305,9 +400,12 @@ void submit_GmRequest(iGmRequest *d) { | |||
305 | clear_GmResponse(&d->resp); | 400 | clear_GmResponse(&d->resp); |
306 | iUrl url; | 401 | iUrl url; |
307 | init_Url(&url, &d->url); | 402 | init_Url(&url, &d->url); |
308 | /* Check for special protocols. */ | 403 | /* Check for special schemes. */ |
309 | /* TODO: If this were a library, these could be handled via callbacks. */ | 404 | /* TODO: If this were a library, these could be handled via callbacks. */ |
310 | if (equalCase_Rangecc(&url.protocol, "about")) { | 405 | /* TODO: Handle app's configured proxies and these via the same mechanism. */ |
406 | const iString *host = collect_String(newRange_String(url.host)); | ||
407 | uint16_t port = toInt_String(collect_String(newRange_String(url.port))); | ||
408 | if (equalCase_Rangecc(url.scheme, "about")) { | ||
311 | const iBlock *src = aboutPageSource_(url.path); | 409 | const iBlock *src = aboutPageSource_(url.path); |
312 | if (src) { | 410 | if (src) { |
313 | d->resp.statusCode = success_GmStatusCode; | 411 | d->resp.statusCode = success_GmStatusCode; |
@@ -323,7 +421,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
323 | iNotifyAudience(d, finished, GmRequestFinished); | 421 | iNotifyAudience(d, finished, GmRequestFinished); |
324 | return; | 422 | return; |
325 | } | 423 | } |
326 | else if (equalCase_Rangecc(&url.protocol, "file")) { | 424 | else if (equalCase_Rangecc(url.scheme, "file")) { |
327 | iString *path = collect_String(urlDecode_String(collect_String(newRange_String(url.path)))); | 425 | iString *path = collect_String(urlDecode_String(collect_String(newRange_String(url.path)))); |
328 | iFile * f = new_File(path); | 426 | iFile * f = new_File(path); |
329 | if (open_File(f, readOnly_FileMode)) { | 427 | if (open_File(f, readOnly_FileMode)) { |
@@ -361,9 +459,9 @@ void submit_GmRequest(iGmRequest *d) { | |||
361 | iNotifyAudience(d, finished, GmRequestFinished); | 459 | iNotifyAudience(d, finished, GmRequestFinished); |
362 | return; | 460 | return; |
363 | } | 461 | } |
364 | else if (equalCase_Rangecc(&url.protocol, "data")) { | 462 | else if (equalCase_Rangecc(url.scheme, "data")) { |
365 | d->resp.statusCode = success_GmStatusCode; | 463 | d->resp.statusCode = success_GmStatusCode; |
366 | iString *src = collectNewCStr_String(url.protocol.start + 5); | 464 | iString *src = collectNewCStr_String(url.scheme.start + 5); |
367 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; | 465 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; |
368 | while (header.end < constEnd_String(src) && *header.end != ',') { | 466 | while (header.end < constEnd_String(src) && *header.end != ',') { |
369 | header.end++; | 467 | header.end++; |
@@ -372,8 +470,8 @@ void submit_GmRequest(iGmRequest *d) { | |||
372 | setRange_String(&d->resp.meta, header); | 470 | setRange_String(&d->resp.meta, header); |
373 | /* Check what's in the header. */ { | 471 | /* Check what's in the header. */ { |
374 | iRangecc entry = iNullRange; | 472 | iRangecc entry = iNullRange; |
375 | while (nextSplit_Rangecc(&header, ";", &entry)) { | 473 | while (nextSplit_Rangecc(header, ";", &entry)) { |
376 | if (equal_Rangecc(&entry, "base64")) { | 474 | if (equal_Rangecc(entry, "base64")) { |
377 | isBase64 = iTrue; | 475 | isBase64 = iTrue; |
378 | } | 476 | } |
379 | } | 477 | } |
@@ -392,15 +490,31 @@ void submit_GmRequest(iGmRequest *d) { | |||
392 | iNotifyAudience(d, finished, GmRequestFinished); | 490 | iNotifyAudience(d, finished, GmRequestFinished); |
393 | return; | 491 | return; |
394 | } | 492 | } |
493 | else if (schemeProxy_App(url.scheme)) { | ||
494 | /* User has configured a proxy server for this scheme. */ | ||
495 | const iString *proxy = schemeProxy_App(url.scheme); | ||
496 | if (contains_String(proxy, ':')) { | ||
497 | const size_t cpos = indexOf_String(proxy, ':'); | ||
498 | port = atoi(cstr_String(proxy) + cpos + 1); | ||
499 | host = collect_String(newCStrN_String(cstr_String(proxy), cpos)); | ||
500 | } | ||
501 | else { | ||
502 | host = proxy; | ||
503 | port = 0; | ||
504 | } | ||
505 | } | ||
395 | d->state = receivingHeader_GmRequestState; | 506 | d->state = receivingHeader_GmRequestState; |
396 | d->req = new_TlsRequest(); | 507 | d->req = new_TlsRequest(); |
508 | const iGmIdentity *identity = identityForUrl_GmCerts(d->certs, &d->url); | ||
509 | if (identity) { | ||
510 | setCertificate_TlsRequest(d->req, identity->cert); | ||
511 | } | ||
397 | iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_); | 512 | iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_); |
398 | iConnect(TlsRequest, d->req, finished, d, requestFinished_GmRequest_); | 513 | iConnect(TlsRequest, d->req, finished, d, requestFinished_GmRequest_); |
399 | uint16_t port = toInt_String(collect_String(newRange_String(url.port))); | ||
400 | if (port == 0) { | 514 | if (port == 0) { |
401 | port = 1965; /* default Gemini port */ | 515 | port = 1965; /* default Gemini port */ |
402 | } | 516 | } |
403 | setUrl_TlsRequest(d->req, collect_String(newRange_String(url.host)), port); | 517 | setUrl_TlsRequest(d->req, host, port); |
404 | setContent_TlsRequest(d->req, | 518 | setContent_TlsRequest(d->req, |
405 | utf8_String(collectNewFormat_String("%s\r\n", cstr_String(&d->url)))); | 519 | utf8_String(collectNewFormat_String("%s\r\n", cstr_String(&d->url)))); |
406 | submit_TlsRequest(d->req); | 520 | submit_TlsRequest(d->req); |