diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-24 22:11:11 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-24 22:11:11 +0200 |
commit | 5268fb9f7e1bca4b0fc496ff115c253aea724e49 (patch) | |
tree | 8023f45f3538f7966c2f75674a4b14fb4778e79c /src/gmrequest.c | |
parent | 9421f64ee86e6aec26e05a45bcfc65edd895a8a8 (diff) |
Fixed threading issues and data races
The most serious problem was that GmRequest's response body was being accessed while the TlsRequest thread was modifying it.
Now the response must always be locked before accessing elsewhere.
There were also inefficient data updates in the media players.
Diffstat (limited to 'src/gmrequest.c')
-rw-r--r-- | src/gmrequest.c | 233 |
1 files changed, 126 insertions, 107 deletions
diff --git a/src/gmrequest.c b/src/gmrequest.c index 15782e01..14f77405 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -123,13 +123,15 @@ enum iGmRequestState { | |||
123 | 123 | ||
124 | struct Impl_GmRequest { | 124 | struct Impl_GmRequest { |
125 | iObject object; | 125 | iObject object; |
126 | iMutex mutex; | 126 | iMutex * mtx; |
127 | iGmCerts * certs; /* not owned */ | 127 | iGmCerts * certs; /* not owned */ |
128 | enum iGmRequestState state; | 128 | enum iGmRequestState state; |
129 | iString url; | 129 | iString url; |
130 | iTlsRequest * req; | 130 | iTlsRequest * req; |
131 | iGopher gopher; | 131 | iGopher gopher; |
132 | iGmResponse resp; | 132 | iGmResponse * resp; |
133 | iBool respLocked; | ||
134 | iAtomicInt allowUpdate; | ||
133 | iAudience * updated; | 135 | iAudience * updated; |
134 | iAudience * finished; | 136 | iAudience * finished; |
135 | }; | 137 | }; |
@@ -140,85 +142,77 @@ iDefineAudienceGetter(GmRequest, finished) | |||
140 | 142 | ||
141 | static void checkServerCertificate_GmRequest_(iGmRequest *d) { | 143 | static void checkServerCertificate_GmRequest_(iGmRequest *d) { |
142 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); | 144 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); |
143 | d->resp.certFlags = 0; | 145 | iGmResponse *resp = d->resp; |
146 | resp->certFlags = 0; | ||
144 | if (cert) { | 147 | if (cert) { |
145 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); | 148 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); |
146 | d->resp.certFlags |= available_GmCertFlag; | 149 | resp->certFlags |= available_GmCertFlag; |
147 | set_Block(&d->resp.certFingerprint, collect_Block(fingerprint_TlsCertificate(cert))); | 150 | set_Block(&resp->certFingerprint, collect_Block(fingerprint_TlsCertificate(cert))); |
148 | d->resp.certFlags |= haveFingerprint_GmCertFlag; | 151 | resp->certFlags |= haveFingerprint_GmCertFlag; |
149 | if (!isExpired_TlsCertificate(cert)) { | 152 | if (!isExpired_TlsCertificate(cert)) { |
150 | d->resp.certFlags |= timeVerified_GmCertFlag; | 153 | resp->certFlags |= timeVerified_GmCertFlag; |
151 | } | 154 | } |
152 | /* TODO: Check for IP too (see below), because it may be specified in the SAN. */ | ||
153 | #if 0 | ||
154 | iString *ip = toStringFlags_Address(address_TlsRequest(d->req), noPort_SocketStringFlag, 0); | ||
155 | if (verifyIp_TlsCertificate(cert, ip)) { | ||
156 | printf("[GmRequest] IP address %s matches!\n", cstr_String(ip)); | ||
157 | } | ||
158 | else { | ||
159 | printf("[GmRequest] IP address %s not matched\n", cstr_String(ip)); | ||
160 | } | ||
161 | delete_String(ip); | ||
162 | #endif | ||
163 | if (verifyDomain_TlsCertificate(cert, domain)) { | 155 | if (verifyDomain_TlsCertificate(cert, domain)) { |
164 | d->resp.certFlags |= domainVerified_GmCertFlag; | 156 | resp->certFlags |= domainVerified_GmCertFlag; |
165 | } | 157 | } |
166 | if (checkTrust_GmCerts(d->certs, domain, cert)) { | 158 | if (checkTrust_GmCerts(d->certs, domain, cert)) { |
167 | d->resp.certFlags |= trusted_GmCertFlag; | 159 | resp->certFlags |= trusted_GmCertFlag; |
168 | } | 160 | } |
169 | validUntil_TlsCertificate(cert, &d->resp.certValidUntil); | 161 | validUntil_TlsCertificate(cert, &resp->certValidUntil); |
170 | set_String(&d->resp.certSubject, collect_String(subject_TlsCertificate(cert))); | 162 | set_String(&resp->certSubject, collect_String(subject_TlsCertificate(cert))); |
171 | } | 163 | } |
172 | } | 164 | } |
173 | 165 | ||
174 | static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { | 166 | static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { |
175 | iBool notifyUpdate = iFalse; | 167 | iBool notifyUpdate = iFalse; |
176 | iBool notifyDone = iFalse; | 168 | iBool notifyDone = iFalse; |
177 | lock_Mutex(&d->mutex); | 169 | lock_Mutex(d->mtx); |
170 | iGmResponse *resp =d->resp; | ||
178 | iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ | 171 | iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ |
179 | iBlock *data = readAll_TlsRequest(req); | 172 | iBlock *data = readAll_TlsRequest(req); |
180 | if (d->state == receivingHeader_GmRequestState) { | 173 | if (d->state == receivingHeader_GmRequestState) { |
181 | appendCStrN_String(&d->resp.meta, constData_Block(data), size_Block(data)); | 174 | appendCStrN_String(&resp->meta, constData_Block(data), size_Block(data)); |
182 | /* Check if the header line is complete. */ | 175 | /* Check if the header line is complete. */ |
183 | size_t endPos = indexOfCStr_String(&d->resp.meta, "\r\n"); | 176 | size_t endPos = indexOfCStr_String(&resp->meta, "\r\n"); |
184 | if (endPos != iInvalidPos) { | 177 | if (endPos != iInvalidPos) { |
185 | /* Move remainder to the body. */ | 178 | /* Move remainder to the body. */ |
186 | setData_Block(&d->resp.body, | 179 | setData_Block(&resp->body, |
187 | constBegin_String(&d->resp.meta) + endPos + 2, | 180 | constBegin_String(&resp->meta) + endPos + 2, |
188 | size_String(&d->resp.meta) - endPos - 2); | 181 | size_String(&resp->meta) - endPos - 2); |
189 | remove_Block(&d->resp.meta.chars, endPos, iInvalidSize); | 182 | remove_Block(&resp->meta.chars, endPos, iInvalidSize); |
190 | /* parse and remove the code */ | 183 | /* parse and remove the code */ |
191 | if (size_String(&d->resp.meta) < 3) { | 184 | if (size_String(&resp->meta) < 3) { |
192 | clear_String(&d->resp.meta); | 185 | clear_String(&resp->meta); |
193 | d->resp.statusCode = invalidHeader_GmStatusCode; | 186 | resp->statusCode = invalidHeader_GmStatusCode; |
194 | d->state = finished_GmRequestState; | 187 | d->state = finished_GmRequestState; |
195 | notifyDone = iTrue; | 188 | notifyDone = iTrue; |
196 | } | 189 | } |
197 | const int code = toInt_String(&d->resp.meta); | 190 | const int code = toInt_String(&resp->meta); |
198 | if (code == 0) { | 191 | if (code == 0) { |
199 | clear_String(&d->resp.meta); | 192 | clear_String(&resp->meta); |
200 | d->resp.statusCode = invalidHeader_GmStatusCode; | 193 | resp->statusCode = invalidHeader_GmStatusCode; |
201 | d->state = finished_GmRequestState; | 194 | d->state = finished_GmRequestState; |
202 | notifyDone = iTrue; | 195 | notifyDone = iTrue; |
203 | } | 196 | } |
204 | remove_Block(&d->resp.meta.chars, 0, 3); /* just the meta */ | 197 | remove_Block(&resp->meta.chars, 0, 3); /* just the meta */ |
205 | if (code == success_GmStatusCode && isEmpty_String(&d->resp.meta)) { | 198 | if (code == success_GmStatusCode && isEmpty_String(&resp->meta)) { |
206 | setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); /* default */ | 199 | setCStr_String(&resp->meta, "text/gemini; charset=utf-8"); /* default */ |
207 | } | 200 | } |
208 | d->resp.statusCode = code; | 201 | resp->statusCode = code; |
209 | d->state = receivingBody_GmRequestState; | 202 | d->state = receivingBody_GmRequestState; |
210 | checkServerCertificate_GmRequest_(d); | 203 | checkServerCertificate_GmRequest_(d); |
211 | notifyUpdate = iTrue; | 204 | notifyUpdate = iTrue; |
212 | } | 205 | } |
213 | } | 206 | } |
214 | else if (d->state == receivingBody_GmRequestState) { | 207 | else if (d->state == receivingBody_GmRequestState) { |
215 | append_Block(&d->resp.body, data); | 208 | append_Block(&resp->body, data); |
216 | notifyUpdate = iTrue; | 209 | notifyUpdate = iTrue; |
217 | } | 210 | } |
218 | initCurrent_Time(&d->resp.when); | 211 | initCurrent_Time(&resp->when); |
219 | delete_Block(data); | 212 | delete_Block(data); |
220 | unlock_Mutex(&d->mutex); | 213 | unlock_Mutex(d->mtx); |
221 | if (notifyUpdate) { | 214 | const iBool allowed = exchange_Atomic(&d->allowUpdate, iFalse); |
215 | if (notifyUpdate && allowed) { | ||
222 | iNotifyAudience(d, updated, GmRequestUpdated); | 216 | iNotifyAudience(d, updated, GmRequestUpdated); |
223 | } | 217 | } |
224 | if (notifyDone) { | 218 | if (notifyDone) { |
@@ -228,21 +222,21 @@ static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { | |||
228 | 222 | ||
229 | static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { | 223 | static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { |
230 | iAssert(req == d->req); | 224 | iAssert(req == d->req); |
231 | lock_Mutex(&d->mutex); | 225 | lock_Mutex(d->mtx); |
232 | /* There shouldn't be anything left to read. */ { | 226 | /* There shouldn't be anything left to read. */ { |
233 | iBlock *data = readAll_TlsRequest(req); | 227 | iBlock *data = readAll_TlsRequest(req); |
234 | iAssert(isEmpty_Block(data)); | 228 | iAssert(isEmpty_Block(data)); |
235 | delete_Block(data); | 229 | delete_Block(data); |
236 | initCurrent_Time(&d->resp.when); | 230 | initCurrent_Time(&d->resp->when); |
237 | } | 231 | } |
238 | d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState | 232 | d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState |
239 | : finished_GmRequestState); | 233 | : finished_GmRequestState); |
240 | if (d->state == failure_GmRequestState) { | 234 | if (d->state == failure_GmRequestState) { |
241 | d->resp.statusCode = tlsFailure_GmStatusCode; | 235 | d->resp->statusCode = tlsFailure_GmStatusCode; |
242 | set_String(&d->resp.meta, errorMessage_TlsRequest(req)); | 236 | set_String(&d->resp->meta, errorMessage_TlsRequest(req)); |
243 | } | 237 | } |
244 | checkServerCertificate_GmRequest_(d); | 238 | checkServerCertificate_GmRequest_(d); |
245 | unlock_Mutex(&d->mutex); | 239 | unlock_Mutex(d->mtx); |
246 | iNotifyAudience(d, finished, GmRequestFinished); | 240 | iNotifyAudience(d, finished, GmRequestFinished); |
247 | } | 241 | } |
248 | 242 | ||
@@ -349,14 +343,14 @@ static const iBlock *replaceVariables_(const iBlock *block) { | |||
349 | 343 | ||
350 | static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { | 344 | static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { |
351 | iBool notifyUpdate = iFalse; | 345 | iBool notifyUpdate = iFalse; |
352 | lock_Mutex(&d->mutex); | 346 | lock_Mutex(d->mtx); |
353 | d->resp.statusCode = success_GmStatusCode; | 347 | d->resp->statusCode = success_GmStatusCode; |
354 | iBlock *data = readAll_Socket(socket); | 348 | iBlock *data = readAll_Socket(socket); |
355 | if (!isEmpty_Block(data)) { | 349 | if (!isEmpty_Block(data)) { |
356 | processResponse_Gopher(&d->gopher, data); | 350 | processResponse_Gopher(&d->gopher, data); |
357 | } | 351 | } |
358 | delete_Block(data); | 352 | delete_Block(data); |
359 | unlock_Mutex(&d->mutex); | 353 | unlock_Mutex(d->mtx); |
360 | if (notifyUpdate) { | 354 | if (notifyUpdate) { |
361 | iNotifyAudience(d, updated, GmRequestUpdated); | 355 | iNotifyAudience(d, updated, GmRequestUpdated); |
362 | } | 356 | } |
@@ -365,12 +359,12 @@ static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { | |||
365 | static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { | 359 | static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { |
366 | iUnused(socket); | 360 | iUnused(socket); |
367 | iBool notify = iFalse; | 361 | iBool notify = iFalse; |
368 | lock_Mutex(&d->mutex); | 362 | lock_Mutex(d->mtx); |
369 | if (d->state != failure_GmRequestState) { | 363 | if (d->state != failure_GmRequestState) { |
370 | d->state = finished_GmRequestState; | 364 | d->state = finished_GmRequestState; |
371 | notify = iTrue; | 365 | notify = iTrue; |
372 | } | 366 | } |
373 | unlock_Mutex(&d->mutex); | 367 | unlock_Mutex(d->mtx); |
374 | if (notify) { | 368 | if (notify) { |
375 | iNotifyAudience(d, finished, GmRequestFinished); | 369 | iNotifyAudience(d, finished, GmRequestFinished); |
376 | } | 370 | } |
@@ -378,12 +372,12 @@ static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { | |||
378 | 372 | ||
379 | static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, const char *msg) { | 373 | static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, const char *msg) { |
380 | iUnused(socket); | 374 | iUnused(socket); |
381 | lock_Mutex(&d->mutex); | 375 | lock_Mutex(d->mtx); |
382 | d->state = failure_GmRequestState; | 376 | d->state = failure_GmRequestState; |
383 | d->resp.statusCode = tlsFailure_GmStatusCode; | 377 | d->resp->statusCode = tlsFailure_GmStatusCode; |
384 | format_String(&d->resp.meta, "%s (errno %d)", msg, error); | 378 | format_String(&d->resp->meta, "%s (errno %d)", msg, error); |
385 | clear_Block(&d->resp.body); | 379 | clear_Block(&d->resp->body); |
386 | unlock_Mutex(&d->mutex); | 380 | unlock_Mutex(d->mtx); |
387 | iNotifyAudience(d, finished, GmRequestFinished); | 381 | iNotifyAudience(d, finished, GmRequestFinished); |
388 | } | 382 | } |
389 | 383 | ||
@@ -392,8 +386,9 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, | |||
392 | port = 70; /* default port */ | 386 | port = 70; /* default port */ |
393 | } | 387 | } |
394 | clear_Block(&d->gopher.source); | 388 | clear_Block(&d->gopher.source); |
395 | d->gopher.meta = &d->resp.meta; | 389 | iGmResponse *resp = d->resp; |
396 | d->gopher.output = &d->resp.body; | 390 | d->gopher.meta = &resp->meta; |
391 | d->gopher.output = &resp->body; | ||
397 | d->state = receivingBody_GmRequestState; | 392 | d->state = receivingBody_GmRequestState; |
398 | d->gopher.socket = new_Socket(cstr_String(host), port); | 393 | d->gopher.socket = new_Socket(cstr_String(host), port); |
399 | iConnect(Socket, d->gopher.socket, readyRead, d, gopherRead_GmRequest_); | 394 | iConnect(Socket, d->gopher.socket, readyRead, d, gopherRead_GmRequest_); |
@@ -401,8 +396,8 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, | |||
401 | iConnect(Socket, d->gopher.socket, error, d, gopherError_GmRequest_); | 396 | iConnect(Socket, d->gopher.socket, error, d, gopherError_GmRequest_); |
402 | open_Gopher(&d->gopher, &d->url); | 397 | open_Gopher(&d->gopher, &d->url); |
403 | if (d->gopher.needQueryArgs) { | 398 | if (d->gopher.needQueryArgs) { |
404 | d->resp.statusCode = input_GmStatusCode; | 399 | resp->statusCode = input_GmStatusCode; |
405 | setCStr_String(&d->resp.meta, "Enter query:"); | 400 | setCStr_String(&resp->meta, "Enter query:"); |
406 | d->state = finished_GmRequestState; | 401 | d->state = finished_GmRequestState; |
407 | iNotifyAudience(d, finished, GmRequestFinished); | 402 | iNotifyAudience(d, finished, GmRequestFinished); |
408 | } | 403 | } |
@@ -411,8 +406,10 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, | |||
411 | /*----------------------------------------------------------------------------------------------*/ | 406 | /*----------------------------------------------------------------------------------------------*/ |
412 | 407 | ||
413 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { | 408 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { |
414 | init_Mutex(&d->mutex); | 409 | d->mtx = new_Mutex(); |
415 | init_GmResponse(&d->resp); | 410 | d->resp = new_GmResponse(); |
411 | d->respLocked = iFalse; | ||
412 | set_Atomic(&d->allowUpdate, iTrue); | ||
416 | init_String(&d->url); | 413 | init_String(&d->url); |
417 | init_Gopher(&d->gopher); | 414 | init_Gopher(&d->gopher); |
418 | d->certs = certs; | 415 | d->certs = certs; |
@@ -427,22 +424,24 @@ void deinit_GmRequest(iGmRequest *d) { | |||
427 | iDisconnectObject(TlsRequest, d->req, readyRead, d); | 424 | iDisconnectObject(TlsRequest, d->req, readyRead, d); |
428 | iDisconnectObject(TlsRequest, d->req, finished, d); | 425 | iDisconnectObject(TlsRequest, d->req, finished, d); |
429 | } | 426 | } |
430 | lock_Mutex(&d->mutex); | 427 | lock_Mutex(d->mtx); |
431 | if (!isFinished_GmRequest(d)) { | 428 | if (!isFinished_GmRequest(d)) { |
432 | unlock_Mutex(&d->mutex); | 429 | unlock_Mutex(d->mtx); |
433 | cancel_GmRequest(d); | 430 | cancel_GmRequest(d); |
434 | d->state = finished_GmRequestState; | 431 | d->state = finished_GmRequestState; |
435 | } | 432 | } |
436 | else { | 433 | else { |
437 | unlock_Mutex(&d->mutex); | 434 | unlock_Mutex(d->mtx); |
438 | } | 435 | } |
439 | iReleasePtr(&d->req); | 436 | iReleasePtr(&d->req); |
440 | deinit_Gopher(&d->gopher); | 437 | deinit_Gopher(&d->gopher); |
441 | delete_Audience(d->finished); | 438 | delete_Audience(d->finished); |
442 | delete_Audience(d->updated); | 439 | delete_Audience(d->updated); |
443 | deinit_GmResponse(&d->resp); | 440 | // delete_GmResponse(d->respPub); |
441 | // deinit_GmResponse(&d->respInt); | ||
442 | delete_GmResponse(d->resp); | ||
444 | deinit_String(&d->url); | 443 | deinit_String(&d->url); |
445 | deinit_Mutex(&d->mutex); | 444 | delete_Mutex(d->mtx); |
446 | } | 445 | } |
447 | 446 | ||
448 | void setUrl_GmRequest(iGmRequest *d, const iString *url) { | 447 | void setUrl_GmRequest(iGmRequest *d, const iString *url) { |
@@ -455,7 +454,9 @@ void submit_GmRequest(iGmRequest *d) { | |||
455 | if (d->state != initialized_GmRequestState) { | 454 | if (d->state != initialized_GmRequestState) { |
456 | return; | 455 | return; |
457 | } | 456 | } |
458 | clear_GmResponse(&d->resp); | 457 | set_Atomic(&d->allowUpdate, iTrue); |
458 | iGmResponse *resp = d->resp; | ||
459 | clear_GmResponse(resp); | ||
459 | iUrl url; | 460 | iUrl url; |
460 | init_Url(&url, &d->url); | 461 | init_Url(&url, &d->url); |
461 | /* Check for special schemes. */ | 462 | /* Check for special schemes. */ |
@@ -466,14 +467,14 @@ void submit_GmRequest(iGmRequest *d) { | |||
466 | if (equalCase_Rangecc(url.scheme, "about")) { | 467 | if (equalCase_Rangecc(url.scheme, "about")) { |
467 | const iBlock *src = aboutPageSource_(url.path); | 468 | const iBlock *src = aboutPageSource_(url.path); |
468 | if (src) { | 469 | if (src) { |
469 | d->resp.statusCode = success_GmStatusCode; | 470 | resp->statusCode = success_GmStatusCode; |
470 | setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); | 471 | setCStr_String(&resp->meta, "text/gemini; charset=utf-8"); |
471 | set_Block(&d->resp.body, replaceVariables_(src)); | 472 | set_Block(&resp->body, replaceVariables_(src)); |
472 | d->state = receivingBody_GmRequestState; | 473 | d->state = receivingBody_GmRequestState; |
473 | iNotifyAudience(d, updated, GmRequestUpdated); | 474 | iNotifyAudience(d, updated, GmRequestUpdated); |
474 | } | 475 | } |
475 | else { | 476 | else { |
476 | d->resp.statusCode = invalidLocalResource_GmStatusCode; | 477 | resp->statusCode = invalidLocalResource_GmStatusCode; |
477 | } | 478 | } |
478 | d->state = finished_GmRequestState; | 479 | d->state = finished_GmRequestState; |
479 | iNotifyAudience(d, finished, GmRequestFinished); | 480 | iNotifyAudience(d, finished, GmRequestFinished); |
@@ -485,41 +486,41 @@ void submit_GmRequest(iGmRequest *d) { | |||
485 | if (open_File(f, readOnly_FileMode)) { | 486 | if (open_File(f, readOnly_FileMode)) { |
486 | /* TODO: Check supported file types: images, audio */ | 487 | /* TODO: Check supported file types: images, audio */ |
487 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ | 488 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ |
488 | d->resp.statusCode = success_GmStatusCode; | 489 | resp->statusCode = success_GmStatusCode; |
489 | if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) { | 490 | if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) { |
490 | setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); | 491 | setCStr_String(&resp->meta, "text/gemini; charset=utf-8"); |
491 | } | 492 | } |
492 | else if (endsWithCase_String(path, ".txt")) { | 493 | else if (endsWithCase_String(path, ".txt")) { |
493 | setCStr_String(&d->resp.meta, "text/plain"); | 494 | setCStr_String(&resp->meta, "text/plain"); |
494 | } | 495 | } |
495 | else if (endsWithCase_String(path, ".png")) { | 496 | else if (endsWithCase_String(path, ".png")) { |
496 | setCStr_String(&d->resp.meta, "image/png"); | 497 | setCStr_String(&resp->meta, "image/png"); |
497 | } | 498 | } |
498 | else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { | 499 | else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { |
499 | setCStr_String(&d->resp.meta, "image/jpeg"); | 500 | setCStr_String(&resp->meta, "image/jpeg"); |
500 | } | 501 | } |
501 | else if (endsWithCase_String(path, ".gif")) { | 502 | else if (endsWithCase_String(path, ".gif")) { |
502 | setCStr_String(&d->resp.meta, "image/gif"); | 503 | setCStr_String(&resp->meta, "image/gif"); |
503 | } | 504 | } |
504 | else if (endsWithCase_String(path, ".wav")) { | 505 | else if (endsWithCase_String(path, ".wav")) { |
505 | setCStr_String(&d->resp.meta, "audio/wave"); | 506 | setCStr_String(&resp->meta, "audio/wave"); |
506 | } | 507 | } |
507 | else if (endsWithCase_String(path, ".ogg")) { | 508 | else if (endsWithCase_String(path, ".ogg")) { |
508 | setCStr_String(&d->resp.meta, "audio/ogg"); | 509 | setCStr_String(&resp->meta, "audio/ogg"); |
509 | } | 510 | } |
510 | else if (endsWithCase_String(path, ".mp3")) { | 511 | else if (endsWithCase_String(path, ".mp3")) { |
511 | setCStr_String(&d->resp.meta, "audio/mpeg"); | 512 | setCStr_String(&resp->meta, "audio/mpeg"); |
512 | } | 513 | } |
513 | else { | 514 | else { |
514 | setCStr_String(&d->resp.meta, "application/octet-stream"); | 515 | setCStr_String(&resp->meta, "application/octet-stream"); |
515 | } | 516 | } |
516 | set_Block(&d->resp.body, collect_Block(readAll_File(f))); | 517 | set_Block(&resp->body, collect_Block(readAll_File(f))); |
517 | d->state = receivingBody_GmRequestState; | 518 | d->state = receivingBody_GmRequestState; |
518 | iNotifyAudience(d, updated, GmRequestUpdated); | 519 | iNotifyAudience(d, updated, GmRequestUpdated); |
519 | } | 520 | } |
520 | else { | 521 | else { |
521 | d->resp.statusCode = failedToOpenFile_GmStatusCode; | 522 | resp->statusCode = failedToOpenFile_GmStatusCode; |
522 | setCStr_String(&d->resp.meta, cstr_String(path)); | 523 | setCStr_String(&resp->meta, cstr_String(path)); |
523 | } | 524 | } |
524 | iRelease(f); | 525 | iRelease(f); |
525 | d->state = finished_GmRequestState; | 526 | d->state = finished_GmRequestState; |
@@ -527,14 +528,14 @@ void submit_GmRequest(iGmRequest *d) { | |||
527 | return; | 528 | return; |
528 | } | 529 | } |
529 | else if (equalCase_Rangecc(url.scheme, "data")) { | 530 | else if (equalCase_Rangecc(url.scheme, "data")) { |
530 | d->resp.statusCode = success_GmStatusCode; | 531 | resp->statusCode = success_GmStatusCode; |
531 | iString *src = collectNewCStr_String(url.scheme.start + 5); | 532 | iString *src = collectNewCStr_String(url.scheme.start + 5); |
532 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; | 533 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; |
533 | while (header.end < constEnd_String(src) && *header.end != ',') { | 534 | while (header.end < constEnd_String(src) && *header.end != ',') { |
534 | header.end++; | 535 | header.end++; |
535 | } | 536 | } |
536 | iBool isBase64 = iFalse; | 537 | iBool isBase64 = iFalse; |
537 | setRange_String(&d->resp.meta, header); | 538 | setRange_String(&resp->meta, header); |
538 | /* Check what's in the header. */ { | 539 | /* Check what's in the header. */ { |
539 | iRangecc entry = iNullRange; | 540 | iRangecc entry = iNullRange; |
540 | while (nextSplit_Rangecc(header, ";", &entry)) { | 541 | while (nextSplit_Rangecc(header, ";", &entry)) { |
@@ -550,7 +551,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
550 | else { | 551 | else { |
551 | set_String(src, collect_String(urlDecode_String(src))); | 552 | set_String(src, collect_String(urlDecode_String(src))); |
552 | } | 553 | } |
553 | set_Block(&d->resp.body, &src->chars); | 554 | set_Block(&resp->body, &src->chars); |
554 | d->state = receivingBody_GmRequestState; | 555 | d->state = receivingBody_GmRequestState; |
555 | iNotifyAudience(d, updated, GmRequestUpdated); | 556 | iNotifyAudience(d, updated, GmRequestUpdated); |
556 | d->state = finished_GmRequestState; | 557 | d->state = finished_GmRequestState; |
@@ -575,7 +576,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
575 | return; | 576 | return; |
576 | } | 577 | } |
577 | else if (!equalCase_Rangecc(url.scheme, "gemini")) { | 578 | else if (!equalCase_Rangecc(url.scheme, "gemini")) { |
578 | d->resp.statusCode = unsupportedProtocol_GmStatusCode; | 579 | resp->statusCode = unsupportedProtocol_GmStatusCode; |
579 | d->state = finished_GmRequestState; | 580 | d->state = finished_GmRequestState; |
580 | iNotifyAudience(d, finished, GmRequestFinished); | 581 | iNotifyAudience(d, finished, GmRequestFinished); |
581 | return; | 582 | return; |
@@ -604,45 +605,63 @@ void cancel_GmRequest(iGmRequest *d) { | |||
604 | cancel_Gopher(&d->gopher); | 605 | cancel_Gopher(&d->gopher); |
605 | } | 606 | } |
606 | 607 | ||
608 | iGmResponse *lockResponse_GmRequest(iGmRequest *d) { | ||
609 | iAssert(!d->respLocked); | ||
610 | lock_Mutex(d->mtx); | ||
611 | d->respLocked = iTrue; | ||
612 | return d->resp; | ||
613 | } | ||
614 | |||
615 | void unlockResponse_GmRequest(iGmRequest *d) { | ||
616 | iAssert(d->respLocked); | ||
617 | d->respLocked = iFalse; | ||
618 | set_Atomic(&d->allowUpdate, iTrue); | ||
619 | unlock_Mutex(d->mtx); | ||
620 | } | ||
621 | |||
607 | iBool isFinished_GmRequest(const iGmRequest *d) { | 622 | iBool isFinished_GmRequest(const iGmRequest *d) { |
608 | iBool done; | 623 | iBool done; |
609 | iGuardMutex(&d->mutex, | 624 | iGuardMutex(d->mtx, |
610 | done = (d->state == finished_GmRequestState || d->state == failure_GmRequestState)); | 625 | done = (d->state == finished_GmRequestState || d->state == failure_GmRequestState)); |
611 | return done; | 626 | return done; |
612 | } | 627 | } |
613 | 628 | ||
614 | enum iGmStatusCode status_GmRequest(const iGmRequest *d) { | 629 | enum iGmStatusCode status_GmRequest(const iGmRequest *d) { |
615 | return d->resp.statusCode; | 630 | enum iGmStatusCode code; |
631 | iGuardMutex(d->mtx, code = d->resp->statusCode); | ||
632 | return code; | ||
616 | } | 633 | } |
617 | 634 | ||
618 | const iString *meta_GmRequest(const iGmRequest *d) { | 635 | const iString *meta_GmRequest(const iGmRequest *d) { |
619 | if (d->state >= receivingBody_GmRequestState) { | 636 | iAssert(isFinished_GmRequest(d)); |
620 | return &d->resp.meta; | 637 | return &d->resp->meta; |
621 | } | ||
622 | return collectNew_String(); | ||
623 | } | 638 | } |
624 | 639 | ||
625 | const iBlock *body_GmRequest(const iGmRequest *d) { | 640 | const iBlock *body_GmRequest(const iGmRequest *d) { |
626 | iBlock *body; | 641 | iAssert(isFinished_GmRequest(d)); |
627 | iGuardMutex(&d->mutex, body = collect_Block(copy_Block(&d->resp.body))); | 642 | return &d->resp->body; |
628 | return body; | ||
629 | } | 643 | } |
630 | 644 | ||
631 | const iString *url_GmRequest(const iGmRequest *d) { | 645 | size_t bodySize_GmRequest(const iGmRequest *d) { |
632 | return &d->url; | 646 | size_t size; |
647 | iGuardMutex(d->mtx, size = size_Block(&d->resp->body)); | ||
648 | return size; | ||
633 | } | 649 | } |
634 | 650 | ||
635 | const iGmResponse *response_GmRequest(const iGmRequest *d) { | 651 | const iString *url_GmRequest(const iGmRequest *d) { |
636 | iAssert(d->state != initialized_GmRequestState); | 652 | return &d->url; |
637 | return &d->resp; | ||
638 | } | 653 | } |
639 | 654 | ||
640 | int certFlags_GmRequest(const iGmRequest *d) { | 655 | int certFlags_GmRequest(const iGmRequest *d) { |
641 | return d->resp.certFlags; | 656 | int flags; |
657 | iGuardMutex(d->mtx, flags = d->resp->certFlags); | ||
658 | return flags; | ||
642 | } | 659 | } |
643 | 660 | ||
644 | iDate certExpirationDate_GmRequest(const iGmRequest *d) { | 661 | iDate certExpirationDate_GmRequest(const iGmRequest *d) { |
645 | return d->resp.certValidUntil; | 662 | iDate expr; |
663 | iGuardMutex(d->mtx, expr = d->resp->certValidUntil); | ||
664 | return expr; | ||
646 | } | 665 | } |
647 | 666 | ||
648 | iDefineClass(GmRequest) | 667 | iDefineClass(GmRequest) |