diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-07 17:38:34 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-07 17:38:34 +0200 |
commit | 1be45b996e3a974f35519c6b9b6d819b77c4a596 (patch) | |
tree | 54183df2b3759baec24f48a24ef928fc8261edb0 /src/gmrequest.c | |
parent | 4da916bcb13db2ec340b6b279c14d5556cc0d913 (diff) |
GmRequest: Mechanism for Gopher requests
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r-- | src/gmrequest.c | 125 |
1 files changed, 100 insertions, 25 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c index 83cbbba7..5baf1620 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | #include <the_Foundation/mutex.h> | 32 | #include <the_Foundation/mutex.h> |
33 | #include <the_Foundation/path.h> | 33 | #include <the_Foundation/path.h> |
34 | #include <the_Foundation/regexp.h> | 34 | #include <the_Foundation/regexp.h> |
35 | #include <the_Foundation/socket.h> | ||
35 | #include <the_Foundation/tlsrequest.h> | 36 | #include <the_Foundation/tlsrequest.h> |
36 | 37 | ||
37 | #include <SDL_timer.h> | 38 | #include <SDL_timer.h> |
@@ -120,6 +121,8 @@ struct Impl_GmRequest { | |||
120 | enum iGmRequestState state; | 121 | enum iGmRequestState state; |
121 | iString url; | 122 | iString url; |
122 | iTlsRequest * req; | 123 | iTlsRequest * req; |
124 | iSocket * gopher; /* socket for Gopher connections */ | ||
125 | char gopherType; | ||
123 | iGmResponse resp; | 126 | iGmResponse resp; |
124 | iAudience * updated; | 127 | iAudience * updated; |
125 | iAudience * finished; | 128 | iAudience * finished; |
@@ -160,13 +163,12 @@ static void checkServerCertificate_GmRequest_(iGmRequest *d) { | |||
160 | } | 163 | } |
161 | } | 164 | } |
162 | 165 | ||
163 | static void readIncoming_GmRequest_(iAnyObject *obj) { | 166 | static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { |
164 | iGmRequest *d = (iGmRequest *) obj; | ||
165 | iBool notifyUpdate = iFalse; | 167 | iBool notifyUpdate = iFalse; |
166 | iBool notifyDone = iFalse; | 168 | iBool notifyDone = iFalse; |
167 | lock_Mutex(&d->mutex); | 169 | lock_Mutex(&d->mutex); |
168 | iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ | 170 | iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ |
169 | iBlock *data = readAll_TlsRequest(d->req); | 171 | iBlock *data = readAll_TlsRequest(req); |
170 | if (d->state == receivingHeader_GmRequestState) { | 172 | if (d->state == receivingHeader_GmRequestState) { |
171 | appendCStrN_String(&d->resp.meta, constData_Block(data), size_Block(data)); | 173 | appendCStrN_String(&d->resp.meta, constData_Block(data), size_Block(data)); |
172 | /* Check if the header line is complete. */ | 174 | /* Check if the header line is complete. */ |
@@ -199,14 +201,10 @@ static void readIncoming_GmRequest_(iAnyObject *obj) { | |||
199 | d->state = receivingBody_GmRequestState; | 201 | d->state = receivingBody_GmRequestState; |
200 | checkServerCertificate_GmRequest_(d); | 202 | checkServerCertificate_GmRequest_(d); |
201 | notifyUpdate = iTrue; | 203 | notifyUpdate = iTrue; |
202 | /* Start a timeout for the remainder of the response, in case the connection | ||
203 | remains open. */ | ||
204 | // restartTimeout_GmRequest_(d); | ||
205 | } | 204 | } |
206 | } | 205 | } |
207 | else if (d->state == receivingBody_GmRequestState) { | 206 | else if (d->state == receivingBody_GmRequestState) { |
208 | append_Block(&d->resp.body, data); | 207 | append_Block(&d->resp.body, data); |
209 | // restartTimeout_GmRequest_(d); | ||
210 | notifyUpdate = iTrue; | 208 | notifyUpdate = iTrue; |
211 | } | 209 | } |
212 | initCurrent_Time(&d->resp.when); | 210 | initCurrent_Time(&d->resp.when); |
@@ -220,22 +218,20 @@ static void readIncoming_GmRequest_(iAnyObject *obj) { | |||
220 | } | 218 | } |
221 | } | 219 | } |
222 | 220 | ||
223 | static void requestFinished_GmRequest_(iAnyObject *obj) { | 221 | static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { |
224 | iGmRequest *d = (iGmRequest *) obj; | 222 | iAssert(req == d->req); |
225 | lock_Mutex(&d->mutex); | 223 | lock_Mutex(&d->mutex); |
226 | /* There shouldn't be anything left to read. */ { | 224 | /* There shouldn't be anything left to read. */ { |
227 | iBlock *data = readAll_TlsRequest(d->req); | 225 | iBlock *data = readAll_TlsRequest(req); |
228 | iAssert(isEmpty_Block(data)); | 226 | iAssert(isEmpty_Block(data)); |
229 | delete_Block(data); | 227 | delete_Block(data); |
230 | initCurrent_Time(&d->resp.when); | 228 | initCurrent_Time(&d->resp.when); |
231 | } | 229 | } |
232 | // SDL_RemoveTimer(d->timeoutId); | 230 | d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState |
233 | // d->timeoutId = 0; | 231 | : finished_GmRequestState); |
234 | d->state = (status_TlsRequest(d->req) == error_TlsRequestStatus ? failure_GmRequestState | ||
235 | : finished_GmRequestState); | ||
236 | if (d->state == failure_GmRequestState) { | 232 | if (d->state == failure_GmRequestState) { |
237 | d->resp.statusCode = tlsFailure_GmStatusCode; | 233 | d->resp.statusCode = tlsFailure_GmStatusCode; |
238 | set_String(&d->resp.meta, errorMessage_TlsRequest(d->req)); | 234 | set_String(&d->resp.meta, errorMessage_TlsRequest(req)); |
239 | } | 235 | } |
240 | checkServerCertificate_GmRequest_(d); | 236 | checkServerCertificate_GmRequest_(d); |
241 | unlock_Mutex(&d->mutex); | 237 | unlock_Mutex(&d->mutex); |
@@ -343,17 +339,87 @@ static const iBlock *replaceVariables_(const iBlock *block) { | |||
343 | return block; | 339 | return block; |
344 | } | 340 | } |
345 | 341 | ||
342 | static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { | ||
343 | iBool notifyUpdate = iFalse; | ||
344 | lock_Mutex(&d->mutex); | ||
345 | iBlock *data = readAll_Socket(socket); | ||
346 | if (!isEmpty_Block(data)) { | ||
347 | append_Block(&d->resp.body, data); | ||
348 | notifyUpdate = iTrue; | ||
349 | } | ||
350 | delete_Block(data); | ||
351 | unlock_Mutex(&d->mutex); | ||
352 | if (notifyUpdate) { | ||
353 | iNotifyAudience(d, updated, GmRequestUpdated); | ||
354 | } | ||
355 | } | ||
356 | |||
357 | static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { | ||
358 | iUnused(socket); | ||
359 | iBool notify = iFalse; | ||
360 | // gopherRead_GmRequest_(d, socket); | ||
361 | lock_Mutex(&d->mutex); | ||
362 | if (d->state != failure_GmRequestState) { | ||
363 | d->state = finished_GmRequestState; | ||
364 | 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 | } | ||
369 | unlock_Mutex(&d->mutex); | ||
370 | if (notify) { | ||
371 | iNotifyAudience(d, finished, GmRequestFinished); | ||
372 | } | ||
373 | } | ||
374 | |||
375 | static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, const char *msg) { | ||
376 | iUnused(socket); | ||
377 | lock_Mutex(&d->mutex); | ||
378 | d->state = failure_GmRequestState; | ||
379 | d->resp.statusCode = tlsFailure_GmStatusCode; | ||
380 | format_String(&d->resp.meta, "(%d) %s", error, msg); | ||
381 | unlock_Mutex(&d->mutex); | ||
382 | iNotifyAudience(d, finished, GmRequestFinished); | ||
383 | } | ||
384 | |||
385 | static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, uint16_t port) { | ||
386 | if (port == 0) { | ||
387 | port = 70; /* default port */ | ||
388 | } | ||
389 | d->state = receivingBody_GmRequestState; | ||
390 | d->gopher = new_Socket(cstr_String(host), port); | ||
391 | iConnect(Socket, d->gopher, readyRead, d, gopherRead_GmRequest_); | ||
392 | iConnect(Socket, d->gopher, disconnected, d, gopherDisconnected_GmRequest_); | ||
393 | iConnect(Socket, d->gopher, error, d, gopherError_GmRequest_); | ||
394 | open_Socket(d->gopher); | ||
395 | iUrl parts; | ||
396 | init_Url(&parts, &d->url); | ||
397 | if (!isEmpty_Range(&parts.path)) { | ||
398 | if (*parts.path.start == '/') { | ||
399 | parts.path.start++; | ||
400 | } | ||
401 | d->gopherType = *parts.path.start; | ||
402 | while (*parts.path.start != '/' && parts.path.start < parts.path.end) { | ||
403 | parts.path.start++; | ||
404 | } | ||
405 | } | ||
406 | writeData_Socket(d->gopher, parts.path.start, size_Range(&parts.path)); | ||
407 | writeData_Socket(d->gopher, "\r\n", 2); | ||
408 | } | ||
409 | |||
346 | /*----------------------------------------------------------------------------------------------*/ | 410 | /*----------------------------------------------------------------------------------------------*/ |
347 | 411 | ||
348 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { | 412 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { |
349 | init_Mutex(&d->mutex); | 413 | init_Mutex(&d->mutex); |
350 | init_GmResponse(&d->resp); | 414 | init_GmResponse(&d->resp); |
351 | init_String(&d->url); | 415 | init_String(&d->url); |
352 | d->certs = certs; | 416 | d->certs = certs; |
353 | d->req = NULL; | 417 | d->req = NULL; |
354 | d->state = initialized_GmRequestState; | 418 | d->gopher = NULL; |
355 | d->updated = NULL; | 419 | d->gopherType = 0; |
356 | d->finished = NULL; | 420 | d->updated = NULL; |
421 | d->finished = NULL; | ||
422 | d->state = initialized_GmRequestState; | ||
357 | } | 423 | } |
358 | 424 | ||
359 | void deinit_GmRequest(iGmRequest *d) { | 425 | void deinit_GmRequest(iGmRequest *d) { |
@@ -364,14 +430,14 @@ void deinit_GmRequest(iGmRequest *d) { | |||
364 | lock_Mutex(&d->mutex); | 430 | lock_Mutex(&d->mutex); |
365 | if (!isFinished_GmRequest(d)) { | 431 | if (!isFinished_GmRequest(d)) { |
366 | unlock_Mutex(&d->mutex); | 432 | unlock_Mutex(&d->mutex); |
367 | cancel_TlsRequest(d->req); | 433 | cancel_GmRequest(d); |
368 | d->state = finished_GmRequestState; | 434 | d->state = finished_GmRequestState; |
369 | } | 435 | } |
370 | else { | 436 | else { |
371 | unlock_Mutex(&d->mutex); | 437 | unlock_Mutex(&d->mutex); |
372 | } | 438 | } |
373 | iRelease(d->req); | 439 | iReleasePtr(&d->req); |
374 | d->req = NULL; | 440 | iReleasePtr(&d->gopher); |
375 | delete_Audience(d->finished); | 441 | delete_Audience(d->finished); |
376 | delete_Audience(d->updated); | 442 | delete_Audience(d->updated); |
377 | deinit_GmResponse(&d->resp); | 443 | deinit_GmResponse(&d->resp); |
@@ -504,6 +570,10 @@ void submit_GmRequest(iGmRequest *d) { | |||
504 | port = 0; | 570 | port = 0; |
505 | } | 571 | } |
506 | } | 572 | } |
573 | else if (equalCase_Rangecc(url.scheme, "gopher")) { | ||
574 | beginGopherConnection_GmRequest_(d, host, port); | ||
575 | return; | ||
576 | } | ||
507 | else if (!equalCase_Rangecc(url.scheme, "gemini")) { | 577 | else if (!equalCase_Rangecc(url.scheme, "gemini")) { |
508 | d->resp.statusCode = unsupportedProtocol_GmStatusCode; | 578 | d->resp.statusCode = unsupportedProtocol_GmStatusCode; |
509 | d->state = finished_GmRequestState; | 579 | d->state = finished_GmRequestState; |
@@ -528,7 +598,12 @@ void submit_GmRequest(iGmRequest *d) { | |||
528 | } | 598 | } |
529 | 599 | ||
530 | void cancel_GmRequest(iGmRequest *d) { | 600 | void cancel_GmRequest(iGmRequest *d) { |
531 | cancel_TlsRequest(d->req); | 601 | if (d->req) { |
602 | cancel_TlsRequest(d->req); | ||
603 | } | ||
604 | if (d->gopher) { | ||
605 | close_Socket(d->gopher); | ||
606 | } | ||
532 | } | 607 | } |
533 | 608 | ||
534 | iBool isFinished_GmRequest(const iGmRequest *d) { | 609 | iBool isFinished_GmRequest(const iGmRequest *d) { |