diff options
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r-- | src/gmrequest.c | 187 |
1 files changed, 95 insertions, 92 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c index 7b110c03..692267a5 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -9,7 +9,44 @@ | |||
9 | 9 | ||
10 | #include <SDL_timer.h> | 10 | #include <SDL_timer.h> |
11 | 11 | ||
12 | static const int BODY_TIMEOUT = 3000; /* ms */ | 12 | void init_GmResponse(iGmResponse *d) { |
13 | d->statusCode = none_GmStatusCode; | ||
14 | init_String(&d->meta); | ||
15 | init_Block(&d->body, 0); | ||
16 | d->certFlags = 0; | ||
17 | iZap(d->certValidUntil); | ||
18 | } | ||
19 | |||
20 | void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) { | ||
21 | d->statusCode = other->statusCode; | ||
22 | initCopy_String(&d->meta, &other->meta); | ||
23 | initCopy_Block(&d->body, &other->body); | ||
24 | d->certFlags = other->certFlags; | ||
25 | d->certValidUntil = other->certValidUntil; | ||
26 | } | ||
27 | |||
28 | void deinit_GmResponse(iGmResponse *d) { | ||
29 | deinit_Block(&d->body); | ||
30 | deinit_String(&d->meta); | ||
31 | } | ||
32 | |||
33 | void clear_GmResponse(iGmResponse *d) { | ||
34 | d->statusCode = none_GmStatusCode; | ||
35 | clear_String(&d->meta); | ||
36 | clear_Block(&d->body); | ||
37 | d->certFlags = 0; | ||
38 | iZap(d->certValidUntil); | ||
39 | } | ||
40 | |||
41 | iGmResponse *copy_GmResponse(const iGmResponse *d) { | ||
42 | iGmResponse *copied = iMalloc(GmResponse); | ||
43 | initCopy_GmResponse(copied, d); | ||
44 | return copied; | ||
45 | } | ||
46 | |||
47 | /*----------------------------------------------------------------------------------------------*/ | ||
48 | |||
49 | static const int bodyTimeout_GmRequest_ = 3000; /* ms */ | ||
13 | 50 | ||
14 | enum iGmRequestState { | 51 | enum iGmRequestState { |
15 | initialized_GmRequestState, | 52 | initialized_GmRequestState, |
@@ -22,12 +59,9 @@ struct Impl_GmRequest { | |||
22 | iObject object; | 59 | iObject object; |
23 | iMutex mutex; | 60 | iMutex mutex; |
24 | enum iGmRequestState state; | 61 | enum iGmRequestState state; |
25 | int certFlags; | ||
26 | iString url; | 62 | iString url; |
27 | iTlsRequest *req; | 63 | iTlsRequest *req; |
28 | enum iGmStatusCode code; | 64 | iGmResponse resp; |
29 | iString header; | ||
30 | iBlock body; /* rest of the received data */ | ||
31 | uint32_t timeoutId; /* in case server doesn't close the connection */ | 65 | uint32_t timeoutId; /* in case server doesn't close the connection */ |
32 | iAudience *updated; | 66 | iAudience *updated; |
33 | iAudience *finished; | 67 | iAudience *finished; |
@@ -40,12 +74,9 @@ iDefineAudienceGetter(GmRequest, finished) | |||
40 | void init_GmRequest(iGmRequest *d) { | 74 | void init_GmRequest(iGmRequest *d) { |
41 | init_Mutex(&d->mutex); | 75 | init_Mutex(&d->mutex); |
42 | d->state = initialized_GmRequestState; | 76 | d->state = initialized_GmRequestState; |
43 | d->certFlags = 0; | 77 | init_GmResponse(&d->resp); |
44 | init_String(&d->url); | 78 | init_String(&d->url); |
45 | d->req = NULL; | 79 | d->req = NULL; |
46 | d->code = none_GmStatusCode; | ||
47 | init_String(&d->header); | ||
48 | init_Block(&d->body, 0); | ||
49 | d->timeoutId = 0; | 80 | d->timeoutId = 0; |
50 | d->updated = NULL; | 81 | d->updated = NULL; |
51 | d->finished = NULL; | 82 | d->finished = NULL; |
@@ -72,8 +103,7 @@ void deinit_GmRequest(iGmRequest *d) { | |||
72 | d->req = NULL; | 103 | d->req = NULL; |
73 | delete_Audience(d->finished); | 104 | delete_Audience(d->finished); |
74 | delete_Audience(d->updated); | 105 | delete_Audience(d->updated); |
75 | deinit_Block(&d->body); | 106 | deinit_GmResponse(&d->resp); |
76 | deinit_String(&d->header); | ||
77 | deinit_String(&d->url); | 107 | deinit_String(&d->url); |
78 | deinit_Mutex(&d->mutex); | 108 | deinit_Mutex(&d->mutex); |
79 | } | 109 | } |
@@ -95,25 +125,26 @@ static void restartTimeout_GmRequest_(iGmRequest *d) { | |||
95 | if (d->timeoutId) { | 125 | if (d->timeoutId) { |
96 | SDL_RemoveTimer(d->timeoutId); | 126 | SDL_RemoveTimer(d->timeoutId); |
97 | } | 127 | } |
98 | d->timeoutId = SDL_AddTimer(BODY_TIMEOUT, timedOutWhileReceivingBody_GmRequest_, d); | 128 | d->timeoutId = SDL_AddTimer(bodyTimeout_GmRequest_, timedOutWhileReceivingBody_GmRequest_, d); |
99 | } | 129 | } |
100 | 130 | ||
101 | static void checkServerCertificate_GmRequest_(iGmRequest *d) { | 131 | static void checkServerCertificate_GmRequest_(iGmRequest *d) { |
102 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); | 132 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); |
103 | d->certFlags = 0; | 133 | d->resp.certFlags = 0; |
104 | if (cert) { | 134 | if (cert) { |
105 | iGmCerts * certDb = certs_App(); | 135 | iGmCerts * certDb = certs_App(); |
106 | const iRangecc domain = urlHost_String(&d->url); | 136 | const iRangecc domain = urlHost_String(&d->url); |
107 | d->certFlags |= available_GmRequestCertFlag; | 137 | d->resp.certFlags |= available_GmCertFlag; |
108 | if (!isExpired_TlsCertificate(cert)) { | 138 | if (!isExpired_TlsCertificate(cert)) { |
109 | d->certFlags |= timeVerified_GmRequestCertFlag; | 139 | d->resp.certFlags |= timeVerified_GmCertFlag; |
110 | } | 140 | } |
111 | if (verifyDomain_TlsCertificate(cert, domain)) { | 141 | if (verifyDomain_TlsCertificate(cert, domain)) { |
112 | d->certFlags |= domainVerified_GmRequestCertFlag; | 142 | d->resp.certFlags |= domainVerified_GmCertFlag; |
113 | } | 143 | } |
114 | if (checkTrust_GmCerts(certDb, domain, cert)) { | 144 | if (checkTrust_GmCerts(certDb, domain, cert)) { |
115 | d->certFlags |= trusted_GmRequestCertFlag; | 145 | d->resp.certFlags |= trusted_GmCertFlag; |
116 | } | 146 | } |
147 | validUntil_TlsCertificate(serverCertificate_TlsRequest(d->req), &d->resp.certValidUntil); | ||
117 | } | 148 | } |
118 | } | 149 | } |
119 | 150 | ||
@@ -126,35 +157,35 @@ static void readIncoming_GmRequest_(iAnyObject *obj) { | |||
126 | iBlock *data = readAll_TlsRequest(d->req); | 157 | iBlock *data = readAll_TlsRequest(d->req); |
127 | fflush(stdout); | 158 | fflush(stdout); |
128 | if (d->state == receivingHeader_GmRequestState) { | 159 | if (d->state == receivingHeader_GmRequestState) { |
129 | appendCStrN_String(&d->header, constData_Block(data), size_Block(data)); | 160 | appendCStrN_String(&d->resp.meta, constData_Block(data), size_Block(data)); |
130 | /* Check if the header line is complete. */ | 161 | /* Check if the header line is complete. */ |
131 | size_t endPos = indexOfCStr_String(&d->header, "\r\n"); | 162 | size_t endPos = indexOfCStr_String(&d->resp.meta, "\r\n"); |
132 | if (endPos != iInvalidPos) { | 163 | if (endPos != iInvalidPos) { |
133 | /* Move remainder to the body. */ | 164 | /* Move remainder to the body. */ |
134 | setData_Block(&d->body, | 165 | setData_Block(&d->resp.body, |
135 | constBegin_String(&d->header) + endPos + 2, | 166 | constBegin_String(&d->resp.meta) + endPos + 2, |
136 | size_String(&d->header) - endPos - 2); | 167 | size_String(&d->resp.meta) - endPos - 2); |
137 | remove_Block(&d->header.chars, endPos, iInvalidSize); | 168 | remove_Block(&d->resp.meta.chars, endPos, iInvalidSize); |
138 | /* parse and remove the code */ | 169 | /* parse and remove the code */ |
139 | if (size_String(&d->header) < 3) { | 170 | if (size_String(&d->resp.meta) < 3) { |
140 | clear_String(&d->header); | 171 | clear_String(&d->resp.meta); |
141 | d->code = invalidHeader_GmStatusCode; | 172 | d->resp.statusCode = invalidHeader_GmStatusCode; |
142 | d->state = finished_GmRequestState; | 173 | d->state = finished_GmRequestState; |
143 | notifyDone = iTrue; | 174 | notifyDone = iTrue; |
144 | } | 175 | } |
145 | const int code = toInt_String(&d->header); | 176 | const int code = toInt_String(&d->resp.meta); |
146 | if (code == 0 || cstr_String(&d->header)[2] != ' ') { | 177 | if (code == 0 || cstr_String(&d->resp.meta)[2] != ' ') { |
147 | clear_String(&d->header); | 178 | clear_String(&d->resp.meta); |
148 | d->code = invalidHeader_GmStatusCode; | 179 | d->resp.statusCode = invalidHeader_GmStatusCode; |
149 | d->state = finished_GmRequestState; | 180 | d->state = finished_GmRequestState; |
150 | notifyDone = iTrue; | 181 | notifyDone = iTrue; |
151 | } | 182 | } |
152 | remove_Block(&d->header.chars, 0, 3); /* just the meta */ | 183 | remove_Block(&d->resp.meta.chars, 0, 3); /* just the meta */ |
153 | if (code == success_GmStatusCode && isEmpty_String(&d->header)) { | 184 | if (code == success_GmStatusCode && isEmpty_String(&d->resp.meta)) { |
154 | setCStr_String(&d->header, "text/gemini; charset=utf-8"); /* default */ | 185 | setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); /* default */ |
155 | } | 186 | } |
156 | d->code = code; | 187 | d->resp.statusCode = code; |
157 | d->state = receivingBody_GmRequestState; | 188 | d->state = receivingBody_GmRequestState; |
158 | checkServerCertificate_GmRequest_(d); | 189 | checkServerCertificate_GmRequest_(d); |
159 | notifyUpdate = iTrue; | 190 | notifyUpdate = iTrue; |
160 | /* Start a timeout for the remainder of the response, in case the connection | 191 | /* Start a timeout for the remainder of the response, in case the connection |
@@ -163,7 +194,7 @@ static void readIncoming_GmRequest_(iAnyObject *obj) { | |||
163 | } | 194 | } |
164 | } | 195 | } |
165 | else if (d->state == receivingBody_GmRequestState) { | 196 | else if (d->state == receivingBody_GmRequestState) { |
166 | append_Block(&d->body, data); | 197 | append_Block(&d->resp.body, data); |
167 | restartTimeout_GmRequest_(d); | 198 | restartTimeout_GmRequest_(d); |
168 | notifyUpdate = iTrue; | 199 | notifyUpdate = iTrue; |
169 | } | 200 | } |
@@ -189,26 +220,6 @@ static void requestFinished_GmRequest_(iAnyObject *obj) { | |||
189 | d->timeoutId = 0; | 220 | d->timeoutId = 0; |
190 | d->state = finished_GmRequestState; | 221 | d->state = finished_GmRequestState; |
191 | checkServerCertificate_GmRequest_(d); | 222 | checkServerCertificate_GmRequest_(d); |
192 | #if 0 | ||
193 | printf("Server certificate:\n%s\n", cstrLocal_String(pem_TlsCertificate(cert))); | ||
194 | iBlock *sha = fingerprint_TlsCertificate(cert); | ||
195 | printf("Fingerprint: %s\n", | ||
196 | cstr_String(collect_String( | ||
197 | hexEncode_Block(collect_Block(fingerprint_TlsCertificate(cert)))))); | ||
198 | delete_Block(sha); | ||
199 | iDate expiry; | ||
200 | validUntil_TlsCertificate(cert, &expiry); | ||
201 | printf("Valid until %04d-%02d-%02d\n", expiry.year, expiry.month, expiry.day); | ||
202 | printf("Has expired: %s\n", isExpired_TlsCertificate(cert) ? "yes" : "no"); | ||
203 | //printf("Subject: %s\n", cstrLocal_String(subject_TlsCertificate(serverCertificate_TlsRequest(d->req)))); | ||
204 | /* Verify. */ { | ||
205 | iUrl parts; | ||
206 | init_Url(&parts, &d->url); | ||
207 | printf("Domain name is %s\n", | ||
208 | verifyDomain_TlsCertificate(cert, parts.host) ? "valid" : "not valid"); | ||
209 | } | ||
210 | fflush(stdout); | ||
211 | #endif | ||
212 | unlock_Mutex(&d->mutex); | 223 | unlock_Mutex(&d->mutex); |
213 | iNotifyAudience(d, finished, GmRequestFinished); | 224 | iNotifyAudience(d, finished, GmRequestFinished); |
214 | } | 225 | } |
@@ -218,10 +229,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
218 | if (d->state != initialized_GmRequestState) { | 229 | if (d->state != initialized_GmRequestState) { |
219 | return; | 230 | return; |
220 | } | 231 | } |
221 | d->code = none_GmStatusCode; | 232 | clear_GmResponse(&d->resp); |
222 | clear_String(&d->header); | ||
223 | clear_Block(&d->body); | ||
224 | d->certFlags = 0; | ||
225 | iUrl url; | 233 | iUrl url; |
226 | init_Url(&url, &d->url); | 234 | init_Url(&url, &d->url); |
227 | if (equalCase_Rangecc(&url.protocol, "file")) { | 235 | if (equalCase_Rangecc(&url.protocol, "file")) { |
@@ -230,50 +238,47 @@ void submit_GmRequest(iGmRequest *d) { | |||
230 | if (open_File(f, readOnly_FileMode)) { | 238 | if (open_File(f, readOnly_FileMode)) { |
231 | /* TODO: Check supported file types: images, audio */ | 239 | /* TODO: Check supported file types: images, audio */ |
232 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ | 240 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ |
233 | d->code = success_GmStatusCode; | 241 | d->resp.statusCode = success_GmStatusCode; |
234 | d->certFlags = 0; | ||
235 | if (endsWithCase_String(path, ".gmi")) { | 242 | if (endsWithCase_String(path, ".gmi")) { |
236 | setCStr_String(&d->header, "text/gemini; charset=utf-8"); | 243 | setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); |
237 | } | 244 | } |
238 | else if (endsWithCase_String(path, ".txt")) { | 245 | else if (endsWithCase_String(path, ".txt")) { |
239 | setCStr_String(&d->header, "text/plain"); | 246 | setCStr_String(&d->resp.meta, "text/plain"); |
240 | } | 247 | } |
241 | else if (endsWithCase_String(path, ".png")) { | 248 | else if (endsWithCase_String(path, ".png")) { |
242 | setCStr_String(&d->header, "image/png"); | 249 | setCStr_String(&d->resp.meta, "image/png"); |
243 | } | 250 | } |
244 | else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { | 251 | else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { |
245 | setCStr_String(&d->header, "image/jpeg"); | 252 | setCStr_String(&d->resp.meta, "image/jpeg"); |
246 | } | 253 | } |
247 | else if (endsWithCase_String(path, ".gif")) { | 254 | else if (endsWithCase_String(path, ".gif")) { |
248 | setCStr_String(&d->header, "image/gif"); | 255 | setCStr_String(&d->resp.meta, "image/gif"); |
249 | } | 256 | } |
250 | else { | 257 | else { |
251 | setCStr_String(&d->header, "application/octet-stream"); | 258 | setCStr_String(&d->resp.meta, "application/octet-stream"); |
252 | } | 259 | } |
253 | set_Block(&d->body, collect_Block(readAll_File(f))); | 260 | set_Block(&d->resp.body, collect_Block(readAll_File(f))); |
254 | d->state = receivingBody_GmRequestState; | 261 | d->state = receivingBody_GmRequestState; |
255 | iNotifyAudience(d, updated, GmRequestUpdated); | 262 | iNotifyAudience(d, updated, GmRequestUpdated); |
256 | } | 263 | } |
257 | else { | 264 | else { |
258 | d->code = failedToOpenFile_GmStatusCode; | 265 | d->resp.statusCode = failedToOpenFile_GmStatusCode; |
259 | setCStr_String(&d->header, cstr_String(path)); | 266 | setCStr_String(&d->resp.meta, cstr_String(path)); |
260 | } | 267 | } |
261 | iRelease(f); | 268 | iRelease(f); |
262 | d->certFlags = 0; | ||
263 | d->state = finished_GmRequestState; | 269 | d->state = finished_GmRequestState; |
264 | iNotifyAudience(d, finished, GmRequestFinished); | 270 | iNotifyAudience(d, finished, GmRequestFinished); |
265 | return; | 271 | return; |
266 | } | 272 | } |
267 | else if (equalCase_Rangecc(&url.protocol, "data")) { | 273 | else if (equalCase_Rangecc(&url.protocol, "data")) { |
268 | d->code = success_GmStatusCode; | 274 | d->resp.statusCode = success_GmStatusCode; |
269 | d->certFlags = 0; | ||
270 | iString *src = collectNewCStr_String(url.protocol.start + 5); | 275 | iString *src = collectNewCStr_String(url.protocol.start + 5); |
271 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; | 276 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; |
272 | while (header.end < constEnd_String(src) && *header.end != ',') { | 277 | while (header.end < constEnd_String(src) && *header.end != ',') { |
273 | header.end++; | 278 | header.end++; |
274 | } | 279 | } |
275 | iBool isBase64 = iFalse; | 280 | iBool isBase64 = iFalse; |
276 | setRange_String(&d->header, header); | 281 | setRange_String(&d->resp.meta, header); |
277 | /* Check what's in the header. */ { | 282 | /* Check what's in the header. */ { |
278 | iRangecc entry = iNullRange; | 283 | iRangecc entry = iNullRange; |
279 | while (nextSplit_Rangecc(&header, ";", &entry)) { | 284 | while (nextSplit_Rangecc(&header, ";", &entry)) { |
@@ -289,7 +294,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
289 | else { | 294 | else { |
290 | set_String(src, collect_String(urlDecode_String(src))); | 295 | set_String(src, collect_String(urlDecode_String(src))); |
291 | } | 296 | } |
292 | set_Block(&d->body, &src->chars); | 297 | set_Block(&d->resp.body, &src->chars); |
293 | d->state = receivingBody_GmRequestState; | 298 | d->state = receivingBody_GmRequestState; |
294 | iNotifyAudience(d, updated, GmRequestUpdated); | 299 | iNotifyAudience(d, updated, GmRequestUpdated); |
295 | d->state = finished_GmRequestState; | 300 | d->state = finished_GmRequestState; |
@@ -317,19 +322,19 @@ iBool isFinished_GmRequest(const iGmRequest *d) { | |||
317 | } | 322 | } |
318 | 323 | ||
319 | enum iGmStatusCode status_GmRequest(const iGmRequest *d) { | 324 | enum iGmStatusCode status_GmRequest(const iGmRequest *d) { |
320 | return d->code; | 325 | return d->resp.statusCode; |
321 | } | 326 | } |
322 | 327 | ||
323 | const iString *meta_GmRequest(const iGmRequest *d) { | 328 | const iString *meta_GmRequest(const iGmRequest *d) { |
324 | if (d->state >= receivingBody_GmRequestState) { | 329 | if (d->state >= receivingBody_GmRequestState) { |
325 | return &d->header; | 330 | return &d->resp.meta; |
326 | } | 331 | } |
327 | return collectNew_String(); | 332 | return collectNew_String(); |
328 | } | 333 | } |
329 | 334 | ||
330 | const iBlock *body_GmRequest(const iGmRequest *d) { | 335 | const iBlock *body_GmRequest(const iGmRequest *d) { |
331 | iBlock *body; | 336 | iBlock *body; |
332 | iGuardMutex(&d->mutex, body = collect_Block(copy_Block(&d->body))); | 337 | iGuardMutex(&d->mutex, body = collect_Block(copy_Block(&d->resp.body))); |
333 | return body; | 338 | return body; |
334 | } | 339 | } |
335 | 340 | ||
@@ -337,19 +342,17 @@ const iString *url_GmRequest(const iGmRequest *d) { | |||
337 | return &d->url; | 342 | return &d->url; |
338 | } | 343 | } |
339 | 344 | ||
345 | const iGmResponse *response_GmRequest(const iGmRequest *d) { | ||
346 | iAssert(d->state == finished_GmRequestState); | ||
347 | return &d->resp; | ||
348 | } | ||
349 | |||
340 | int certFlags_GmRequest(const iGmRequest *d) { | 350 | int certFlags_GmRequest(const iGmRequest *d) { |
341 | return d->certFlags; | 351 | return d->resp.certFlags; |
342 | } | 352 | } |
343 | 353 | ||
344 | iDate certExpirationDate_GmRequest(const iGmRequest *d) { | 354 | iDate certExpirationDate_GmRequest(const iGmRequest *d) { |
345 | iDate expiry; | 355 | return d->resp.certValidUntil; |
346 | if (d->req) { | ||
347 | validUntil_TlsCertificate(serverCertificate_TlsRequest(d->req), &expiry); | ||
348 | } | ||
349 | else { | ||
350 | iZap(expiry); | ||
351 | } | ||
352 | return expiry; | ||
353 | } | 356 | } |
354 | 357 | ||
355 | iDefineClass(GmRequest) | 358 | iDefineClass(GmRequest) |