summaryrefslogtreecommitdiff
path: root/src/gmrequest.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-11-07 22:41:00 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-11-07 22:41:00 +0200
commit0e03a97ba89bb9e8fccf6978250addc14799c5c1 (patch)
treee840a7465101216d1d2e9b11aaed2b3a883b7466 /src/gmrequest.c
parentbf293d976fffcc80c4e4f26d553cda33746b95bc (diff)
Added support for Gopher
Needs more testing. Queries are not supported yet.
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r--src/gmrequest.c164
1 files changed, 15 insertions, 149 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 8a9226a1..d143e8da 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#include "gmrequest.h" 23#include "gmrequest.h"
24#include "gmutil.h" 24#include "gmutil.h"
25#include "gmcerts.h" 25#include "gmcerts.h"
26#include "gopher.h"
26#include "app.h" /* dataDir_App() */ 27#include "app.h" /* dataDir_App() */
27#include "embedded.h" 28#include "embedded.h"
28#include "ui/text.h" 29#include "ui/text.h"
@@ -121,10 +122,7 @@ struct Impl_GmRequest {
121 enum iGmRequestState state; 122 enum iGmRequestState state;
122 iString url; 123 iString url;
123 iTlsRequest * req; 124 iTlsRequest * req;
124 iSocket * gopher; /* socket for Gopher connections */ 125 iGopher gopher;
125 char gopherType;
126 iBlock gopherBody;
127 iBool gopherPre;
128 iGmResponse resp; 126 iGmResponse resp;
129 iAudience * updated; 127 iAudience * updated;
130 iAudience * finished; 128 iAudience * finished;
@@ -341,127 +339,13 @@ static const iBlock *replaceVariables_(const iBlock *block) {
341 return block; 339 return block;
342} 340}
343 341
344iLocalDef iBool isLineTerminator_(const char *str) {
345 return str[0] == '\r' && str[1] == '\n';
346}
347
348iLocalDef iBool isPreformatted_(iRangecc text) {
349 int numPunct = 0;
350 iBool isSpace = iFalse;
351 for (const char *ch = text.start; ch != text.end; ch++) {
352 if (ispunct(*ch)) {
353 if (++numPunct == 4)
354 return iTrue;
355 }
356 else {
357 numPunct = 0;
358 }
359 if (*ch == ' ' || *ch == '\n') {
360 if (isSpace) return iTrue;
361 isSpace = iTrue;
362 }
363 else {
364 isSpace = iFalse;
365 }
366 }
367 return iFalse;
368}
369
370static void setGopherPre_GmRequest_(iGmRequest *d, iBool pre) {
371 if (pre && !d->gopherPre) {
372 appendCStr_Block(&d->resp.body, "```\n");
373 }
374 else if (!pre && d->gopherPre) {
375 appendCStr_Block(&d->resp.body, "```\n");
376 }
377 d->gopherPre = pre;
378}
379
380static iBool convertGopherLines_GmRequest_(iGmRequest *d) {
381 iBool converted = iFalse;
382 iRangecc body = range_Block(&d->gopherBody);
383 iRegExp *pattern = new_RegExp("(.)([^\t]*)\t([^\t]*)\t([^\t]*)\t([0-9]+)", caseInsensitive_RegExpOption);
384 for (;;) {
385 /* Find the end of the line. */
386 iRangecc line = { body.start, body.start };
387 while (line.end < body.end - 1 && !isLineTerminator_(line.end)) {
388 line.end++;
389 }
390 if (line.end >= body.end - 1 || !isLineTerminator_(line.end)) {
391 /* Not a complete line. */
392 break;
393 }
394 body.start = line.end + 2;
395 iRegExpMatch m;
396 init_RegExpMatch(&m);
397 if (matchRange_RegExp(pattern, line, &m)) {
398 const char lineType = *capturedRange_RegExpMatch(&m, 1).start;
399 const iRangecc text = capturedRange_RegExpMatch(&m, 2);
400 const iRangecc path = capturedRange_RegExpMatch(&m, 3);
401 const iRangecc domain = capturedRange_RegExpMatch(&m, 4);
402 const iRangecc port = capturedRange_RegExpMatch(&m, 5);
403 iString *out = new_String();
404 switch (lineType) {
405 case 'i':
406 case '3': {
407 setGopherPre_GmRequest_(d, isPreformatted_(text));
408 appendData_Block(&d->resp.body, text.start, size_Range(&text));
409 appendCStr_Block(&d->resp.body, "\n");
410 break;
411 }
412 case '0':
413 case '1':
414 case '7':
415 case '4':
416 case '5':
417 case '9':
418 case 'g':
419 case 'I':
420 case 's': {
421 iBeginCollect();
422 setGopherPre_GmRequest_(d, iFalse);
423 format_String(out,
424 "=> gopher://%s:%s/%c%s %s\n",
425 cstr_Rangecc(domain),
426 cstr_Rangecc(port),
427 lineType,
428 cstr_Rangecc(path),
429 cstr_Rangecc(text));
430 appendData_Block(&d->resp.body, constBegin_String(out), size_String(out));
431 iEndCollect();
432 break;
433 }
434 default:
435 break; /* Ignore unknown types. */
436 }
437 delete_String(out);
438 }
439 }
440 iRelease(pattern);
441 remove_Block(&d->gopherBody, 0, body.start - constBegin_Block(&d->gopherBody));
442 return converted;
443}
444
445static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { 342static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) {
446 iBool notifyUpdate = iFalse; 343 iBool notifyUpdate = iFalse;
447 lock_Mutex(&d->mutex); 344 lock_Mutex(&d->mutex);
448 d->resp.statusCode = success_GmStatusCode; 345 d->resp.statusCode = success_GmStatusCode;
449 iBlock *data = readAll_Socket(socket); 346 iBlock *data = readAll_Socket(socket);
450 if (!isEmpty_Block(data)) { 347 if (!isEmpty_Block(data)) {
451 if (d->gopherType == '1') { 348 processResponse_Gopher(&d->gopher, data);
452 setCStr_String(&d->resp.meta, "text/gemini");
453 append_Block(&d->gopherBody, data);
454 if (convertGopherLines_GmRequest_(d)) {
455 notifyUpdate = iTrue;
456 }
457 }
458 else {
459 if (d->gopherType == '0') {
460 setCStr_String(&d->resp.meta, "text/plain");
461 }
462 append_Block(&d->resp.body, data);
463 notifyUpdate = iTrue;
464 }
465 } 349 }
466 delete_Block(data); 350 delete_Block(data);
467 unlock_Mutex(&d->mutex); 351 unlock_Mutex(&d->mutex);
@@ -499,28 +383,15 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
499 if (port == 0) { 383 if (port == 0) {
500 port = 70; /* default port */ 384 port = 70; /* default port */
501 } 385 }
502 clear_Block(&d->gopherBody); 386 clear_Block(&d->gopher.source);
503 d->state = receivingBody_GmRequestState; 387 d->gopher.meta = &d->resp.meta;
504 d->gopher = new_Socket(cstr_String(host), port); 388 d->gopher.output = &d->resp.body;
505 iConnect(Socket, d->gopher, readyRead, d, gopherRead_GmRequest_); 389 d->state = receivingBody_GmRequestState;
506 iConnect(Socket, d->gopher, disconnected, d, gopherDisconnected_GmRequest_); 390 d->gopher.socket = new_Socket(cstr_String(host), port);
507 iConnect(Socket, d->gopher, error, d, gopherError_GmRequest_); 391 iConnect(Socket, d->gopher.socket, readyRead, d, gopherRead_GmRequest_);
508 open_Socket(d->gopher); 392 iConnect(Socket, d->gopher.socket, disconnected, d, gopherDisconnected_GmRequest_);
509 iUrl parts; 393 iConnect(Socket, d->gopher.socket, error, d, gopherError_GmRequest_);
510 init_Url(&parts, &d->url); 394 open_Gopher(&d->gopher, &d->url);
511 d->gopherType = '1';
512 if (!isEmpty_Range(&parts.path)) {
513 if (*parts.path.start == '/') {
514 parts.path.start++;
515 }
516 if (parts.path.start < parts.path.end) {
517 d->gopherType = *parts.path.start;
518 parts.path.start++;
519 }
520 }
521 d->gopherPre = iFalse;
522 writeData_Socket(d->gopher, parts.path.start, size_Range(&parts.path));
523 writeData_Socket(d->gopher, "\r\n", 2);
524} 395}
525 396
526/*----------------------------------------------------------------------------------------------*/ 397/*----------------------------------------------------------------------------------------------*/
@@ -529,11 +400,9 @@ void init_GmRequest(iGmRequest *d, iGmCerts *certs) {
529 init_Mutex(&d->mutex); 400 init_Mutex(&d->mutex);
530 init_GmResponse(&d->resp); 401 init_GmResponse(&d->resp);
531 init_String(&d->url); 402 init_String(&d->url);
532 init_Block(&d->gopherBody, 0); 403 init_Gopher(&d->gopher);
533 d->certs = certs; 404 d->certs = certs;
534 d->req = NULL; 405 d->req = NULL;
535 d->gopher = NULL;
536 d->gopherType = 0;
537 d->updated = NULL; 406 d->updated = NULL;
538 d->finished = NULL; 407 d->finished = NULL;
539 d->state = initialized_GmRequestState; 408 d->state = initialized_GmRequestState;
@@ -554,8 +423,7 @@ void deinit_GmRequest(iGmRequest *d) {
554 unlock_Mutex(&d->mutex); 423 unlock_Mutex(&d->mutex);
555 } 424 }
556 iReleasePtr(&d->req); 425 iReleasePtr(&d->req);
557 iReleasePtr(&d->gopher); 426 deinit_Gopher(&d->gopher);
558 deinit_Block(&d->gopherBody);
559 delete_Audience(d->finished); 427 delete_Audience(d->finished);
560 delete_Audience(d->updated); 428 delete_Audience(d->updated);
561 deinit_GmResponse(&d->resp); 429 deinit_GmResponse(&d->resp);
@@ -719,9 +587,7 @@ void cancel_GmRequest(iGmRequest *d) {
719 if (d->req) { 587 if (d->req) {
720 cancel_TlsRequest(d->req); 588 cancel_TlsRequest(d->req);
721 } 589 }
722 if (d->gopher) { 590 cancel_Gopher(&d->gopher);
723 close_Socket(d->gopher);
724 }
725} 591}
726 592
727iBool isFinished_GmRequest(const iGmRequest *d) { 593iBool isFinished_GmRequest(const iGmRequest *d) {