summaryrefslogtreecommitdiff
path: root/src/gmrequest.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r--src/gmrequest.c134
1 files changed, 126 insertions, 8 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 5baf1620..8a9226a1 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -123,6 +123,8 @@ struct Impl_GmRequest {
123 iTlsRequest * req; 123 iTlsRequest * req;
124 iSocket * gopher; /* socket for Gopher connections */ 124 iSocket * gopher; /* socket for Gopher connections */
125 char gopherType; 125 char gopherType;
126 iBlock gopherBody;
127 iBool gopherPre;
126 iGmResponse resp; 128 iGmResponse resp;
127 iAudience * updated; 129 iAudience * updated;
128 iAudience * finished; 130 iAudience * finished;
@@ -339,13 +341,127 @@ static const iBlock *replaceVariables_(const iBlock *block) {
339 return block; 341 return block;
340} 342}
341 343
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
342static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { 445static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) {
343 iBool notifyUpdate = iFalse; 446 iBool notifyUpdate = iFalse;
344 lock_Mutex(&d->mutex); 447 lock_Mutex(&d->mutex);
448 d->resp.statusCode = success_GmStatusCode;
345 iBlock *data = readAll_Socket(socket); 449 iBlock *data = readAll_Socket(socket);
346 if (!isEmpty_Block(data)) { 450 if (!isEmpty_Block(data)) {
347 append_Block(&d->resp.body, data); 451 if (d->gopherType == '1') {
348 notifyUpdate = iTrue; 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 }
349 } 465 }
350 delete_Block(data); 466 delete_Block(data);
351 unlock_Mutex(&d->mutex); 467 unlock_Mutex(&d->mutex);
@@ -357,14 +473,10 @@ static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) {
357static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { 473static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) {
358 iUnused(socket); 474 iUnused(socket);
359 iBool notify = iFalse; 475 iBool notify = iFalse;
360// gopherRead_GmRequest_(d, socket);
361 lock_Mutex(&d->mutex); 476 lock_Mutex(&d->mutex);
362 if (d->state != failure_GmRequestState) { 477 if (d->state != failure_GmRequestState) {
363 d->state = finished_GmRequestState; 478 d->state = finished_GmRequestState;
364 notify = iTrue; 479 notify = iTrue;
365 /* TODO: Convert the received text into text/gemini. */
366 setCStr_String(&d->resp.meta, "text/plain");
367 d->resp.statusCode = success_GmStatusCode;
368 } 480 }
369 unlock_Mutex(&d->mutex); 481 unlock_Mutex(&d->mutex);
370 if (notify) { 482 if (notify) {
@@ -378,6 +490,7 @@ static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, co
378 d->state = failure_GmRequestState; 490 d->state = failure_GmRequestState;
379 d->resp.statusCode = tlsFailure_GmStatusCode; 491 d->resp.statusCode = tlsFailure_GmStatusCode;
380 format_String(&d->resp.meta, "(%d) %s", error, msg); 492 format_String(&d->resp.meta, "(%d) %s", error, msg);
493 clear_Block(&d->resp.body);
381 unlock_Mutex(&d->mutex); 494 unlock_Mutex(&d->mutex);
382 iNotifyAudience(d, finished, GmRequestFinished); 495 iNotifyAudience(d, finished, GmRequestFinished);
383} 496}
@@ -386,6 +499,7 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
386 if (port == 0) { 499 if (port == 0) {
387 port = 70; /* default port */ 500 port = 70; /* default port */
388 } 501 }
502 clear_Block(&d->gopherBody);
389 d->state = receivingBody_GmRequestState; 503 d->state = receivingBody_GmRequestState;
390 d->gopher = new_Socket(cstr_String(host), port); 504 d->gopher = new_Socket(cstr_String(host), port);
391 iConnect(Socket, d->gopher, readyRead, d, gopherRead_GmRequest_); 505 iConnect(Socket, d->gopher, readyRead, d, gopherRead_GmRequest_);
@@ -394,15 +508,17 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
394 open_Socket(d->gopher); 508 open_Socket(d->gopher);
395 iUrl parts; 509 iUrl parts;
396 init_Url(&parts, &d->url); 510 init_Url(&parts, &d->url);
511 d->gopherType = '1';
397 if (!isEmpty_Range(&parts.path)) { 512 if (!isEmpty_Range(&parts.path)) {
398 if (*parts.path.start == '/') { 513 if (*parts.path.start == '/') {
399 parts.path.start++; 514 parts.path.start++;
400 } 515 }
401 d->gopherType = *parts.path.start; 516 if (parts.path.start < parts.path.end) {
402 while (*parts.path.start != '/' && parts.path.start < parts.path.end) { 517 d->gopherType = *parts.path.start;
403 parts.path.start++; 518 parts.path.start++;
404 } 519 }
405 } 520 }
521 d->gopherPre = iFalse;
406 writeData_Socket(d->gopher, parts.path.start, size_Range(&parts.path)); 522 writeData_Socket(d->gopher, parts.path.start, size_Range(&parts.path));
407 writeData_Socket(d->gopher, "\r\n", 2); 523 writeData_Socket(d->gopher, "\r\n", 2);
408} 524}
@@ -413,6 +529,7 @@ void init_GmRequest(iGmRequest *d, iGmCerts *certs) {
413 init_Mutex(&d->mutex); 529 init_Mutex(&d->mutex);
414 init_GmResponse(&d->resp); 530 init_GmResponse(&d->resp);
415 init_String(&d->url); 531 init_String(&d->url);
532 init_Block(&d->gopherBody, 0);
416 d->certs = certs; 533 d->certs = certs;
417 d->req = NULL; 534 d->req = NULL;
418 d->gopher = NULL; 535 d->gopher = NULL;
@@ -438,6 +555,7 @@ void deinit_GmRequest(iGmRequest *d) {
438 } 555 }
439 iReleasePtr(&d->req); 556 iReleasePtr(&d->req);
440 iReleasePtr(&d->gopher); 557 iReleasePtr(&d->gopher);
558 deinit_Block(&d->gopherBody);
441 delete_Audience(d->finished); 559 delete_Audience(d->finished);
442 delete_Audience(d->updated); 560 delete_Audience(d->updated);
443 deinit_GmResponse(&d->resp); 561 deinit_GmResponse(&d->resp);