summaryrefslogtreecommitdiff
path: root/src/gmrequest.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-24 13:19:38 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-24 13:19:38 +0300
commit0f0b4250ca460d58edb61cc0dd509ba1980c3272 (patch)
treee8e9be30b2ec7942c4796cf93cbbd42e0c272e6a /src/gmrequest.c
parentdd73346aad190239ed133d0c4835dbaa05d1b0cd (diff)
Added GmRequest for handling the request
This feels a little bit too complex, with GmRequest observing TlsRequest and then notifying its own audience. There are still some issues with cancelling requests as well.
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r--src/gmrequest.c240
1 files changed, 240 insertions, 0 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c
new file mode 100644
index 00000000..2bda65e9
--- /dev/null
+++ b/src/gmrequest.c
@@ -0,0 +1,240 @@
1#include "gmrequest.h"
2#include "gmutil.h"
3
4#include <the_Foundation/file.h>
5#include <the_Foundation/tlsrequest.h>
6#include <the_Foundation/mutex.h>
7
8#include <SDL_timer.h>
9
10static const int BODY_TIMEOUT = 1500; /* ms */
11
12enum iGmRequestState {
13 initialized_GmRequestState,
14 receivingHeader_GmRequestState,
15 receivingBody_GmRequestState,
16 finished_GmRequestState
17};
18
19struct Impl_GmRequest {
20 iObject object;
21 iMutex mutex;
22 enum iGmRequestState state;
23 iString url;
24 iTlsRequest *req;
25 enum iGmStatusCode code;
26 iString header;
27 iBlock body; /* rest of the received data */
28 uint32_t timeoutId; /* in case server doesn't close the connection */
29 iAudience *updated;
30 iAudience *finished;
31};
32
33iDefineObjectConstruction(GmRequest)
34iDefineAudienceGetter(GmRequest, updated)
35iDefineAudienceGetter(GmRequest, finished)
36
37void init_GmRequest(iGmRequest *d) {
38 init_Mutex(&d->mutex);
39 d->state = initialized_GmRequestState;
40 init_String(&d->url);
41 d->req = NULL;
42 d->code = none_GmStatusCode;
43 init_String(&d->header);
44 init_Block(&d->body, 0);
45 d->timeoutId = 0;
46 d->updated = NULL;
47 d->finished = NULL;
48}
49
50void deinit_GmRequest(iGmRequest *d) {
51 lock_Mutex(&d->mutex);
52 if (d->timeoutId) {
53 SDL_RemoveTimer(d->timeoutId);
54 }
55 if (d->req) {
56 if (!isFinished_GmRequest(d)) {
57 iDisconnectObject(TlsRequest, d->req, readyRead, d);
58 iDisconnectObject(TlsRequest, d->req, finished, d);
59 cancel_TlsRequest(d->req);
60 d->state = finished_GmRequestState;
61 iRelease(d->req);
62 d->req = NULL;
63 }
64 }
65 unlock_Mutex(&d->mutex);
66 delete_Audience(d->finished);
67 delete_Audience(d->updated);
68 deinit_Block(&d->body);
69 deinit_String(&d->header);
70 deinit_String(&d->url);
71 deinit_Mutex(&d->mutex);
72}
73
74void setUrl_GmRequest(iGmRequest *d, const iString *url) {
75 set_String(&d->url, url);
76}
77
78static uint32_t timedOutWhileReceivingBody_GmRequest_(uint32_t interval, void *obj) {
79 iGmRequest *d = obj;
80 iGuardMutex(&d->mutex, cancel_TlsRequest(d->req));
81 iUnused(interval);
82 return 0;
83}
84
85static void restartTimeout_GmRequest_(iGmRequest *d) {
86 /* Note: `d` is currently locked. */
87 if (d->timeoutId) {
88 SDL_RemoveTimer(d->timeoutId);
89 }
90 d->timeoutId = SDL_AddTimer(BODY_TIMEOUT, timedOutWhileReceivingBody_GmRequest_, d);
91}
92
93static void readIncoming_GmRequest_(iAnyObject *obj) {
94 iGmRequest *d = (iGmRequest *) obj;
95 iBool notifyUpdate = iFalse;
96 iBool notifyDone = iFalse;
97 lock_Mutex(&d->mutex);
98 iAssert(d->state != finished_GmRequestState); /* notifications out of order? */
99 iBlock *data = readAll_TlsRequest(d->req);
100 fflush(stdout);
101 if (d->state == receivingHeader_GmRequestState) {
102 appendCStrN_String(&d->header, constData_Block(data), size_Block(data));
103 /* Check if the header line is complete. */
104 size_t endPos = indexOfCStr_String(&d->header, "\r\n");
105 if (endPos != iInvalidPos) {
106 /* Move remainder to the body. */
107 setData_Block(&d->body,
108 constBegin_String(&d->header) + endPos + 2,
109 size_String(&d->header) - endPos - 2);
110 remove_Block(&d->header.chars, endPos, iInvalidSize);
111 /* parse and remove the code */
112 if (size_String(&d->header) < 3) {
113 clear_String(&d->header);
114 d->code = invalidHeader_GmStatusCode;
115 d->state = finished_GmRequestState;
116 notifyDone = iTrue;
117 }
118 const int code = toInt_String(&d->header);
119 if (code == 0 || cstr_String(&d->header)[2] != ' ') {
120 clear_String(&d->header);
121 d->code = invalidHeader_GmStatusCode;
122 d->state = finished_GmRequestState;
123 notifyDone = iTrue;
124 }
125 remove_Block(&d->header.chars, 0, 3); /* just the meta */
126 d->code = code;
127 d->state = receivingBody_GmRequestState;
128 notifyUpdate = iTrue;
129 /* Start a timeout for the remainder of the response, in case the connection
130 remains open. */
131 restartTimeout_GmRequest_(d);
132 }
133 }
134 else if (d->state == receivingBody_GmRequestState) {
135 append_Block(&d->body, data);
136 restartTimeout_GmRequest_(d);
137 notifyUpdate = iTrue;
138 }
139 delete_Block(data);
140 unlock_Mutex(&d->mutex);
141 if (notifyUpdate) {
142 iNotifyAudience(d, updated, GmRequestUpdated);
143 }
144 if (notifyDone) {
145 iNotifyAudience(d, finished, GmRequestFinished);
146 }
147}
148
149static void requestFinished_GmRequest_(iAnyObject *obj) {
150 iGmRequest *d = (iGmRequest *) obj;
151 lock_Mutex(&d->mutex);
152 /* There shouldn't be anything left to read. */ {
153 iBlock *data = readAll_TlsRequest(d->req);
154 iAssert(isEmpty_Block(data));
155 delete_Block(data);
156 }
157 SDL_RemoveTimer(d->timeoutId);
158 d->timeoutId = 0;
159 iReleaseLater(d->req);
160 d->req = NULL;
161 d->state = finished_GmRequestState;
162 unlock_Mutex(&d->mutex);
163 iNotifyAudience(d, finished, GmRequestFinished);
164}
165
166void submit_GmRequest(iGmRequest *d) {
167 iAssert(d->state == initialized_GmRequestState);
168 if (d->state != initialized_GmRequestState) {
169 return;
170 }
171 d->code = none_GmStatusCode;
172 clear_String(&d->header);
173 clear_Block(&d->body);
174 iUrl url;
175 init_Url(&url, &d->url);
176 if (!cmpCStrSc_Rangecc(&url.protocol, "file", &iCaseInsensitive)) {
177 iFile *f = new_File(collect_String(newRange_String(url.path)));
178 if (open_File(f, readOnly_FileMode)) {
179 /* TODO: Check supported file types: images, audio */
180 d->code = success_GmStatusCode;
181 setCStr_String(&d->header, "text/gemini; charset=utf-8");
182 set_Block(&d->body, collect_Block(readAll_File(f)));
183 iNotifyAudience(d, updated, GmRequestUpdated);
184 }
185 else {
186 d->code = failedToOpenFile_GmStatusCode;
187 }
188 iRelease(f);
189 d->state = finished_GmRequestState;
190 iNotifyAudience(d, finished, GmRequestFinished);
191 return;
192 }
193 d->state = receivingHeader_GmRequestState;
194 d->req = new_TlsRequest();
195 iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_);
196 iConnect(TlsRequest, d->req, finished, d, requestFinished_GmRequest_);
197 uint16_t port = toInt_String(collect_String(newRange_String(url.port)));
198 if (port == 0) {
199 port = 1965; /* default Gemini port */
200 }
201 setUrl_TlsRequest(d->req, collect_String(newRange_String(url.host)), port);
202 setContent_TlsRequest(d->req,
203 utf8_String(collectNewFormat_String("%s\r\n", cstr_String(&d->url))));
204 submit_TlsRequest(d->req);
205}
206
207iBool isFinished_GmRequest(const iGmRequest *d) {
208 iBool done;
209 iGuardMutex(&d->mutex, done = (d->state == finished_GmRequestState));
210 return done;
211}
212
213const char *error_GmRequest(const iGmRequest *d) {
214 if (d->code == failedToOpenFile_GmStatusCode) {
215 return "Failed to open file";
216 }
217 if (d->code == invalidHeader_GmStatusCode) {
218 return "Received invalid header (not Gemini?)";
219 }
220 return NULL; /* TDOO: detailed error string */
221}
222
223enum iGmStatusCode status_GmRequest(const iGmRequest *d) {
224 return d->code;
225}
226
227const iString *meta_GmRequest(const iGmRequest *d) {
228 if (d->state >= receivingBody_GmRequestState) {
229 return &d->header;
230 }
231 return collectNew_String();
232}
233
234const iBlock *body_GmRequest(const iGmRequest *d) {
235 iBlock *body;
236 iGuardMutex(&d->mutex, body = collect_Block(copy_Block(&d->body)));
237 return body;
238}
239
240iDefineClass(GmRequest)