summaryrefslogtreecommitdiff
path: root/src/gmrequest.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-11-24 22:11:11 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-11-24 22:11:11 +0200
commit5268fb9f7e1bca4b0fc496ff115c253aea724e49 (patch)
tree8023f45f3538f7966c2f75674a4b14fb4778e79c /src/gmrequest.c
parent9421f64ee86e6aec26e05a45bcfc65edd895a8a8 (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.c233
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
124struct Impl_GmRequest { 124struct 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
141static void checkServerCertificate_GmRequest_(iGmRequest *d) { 143static 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
174static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { 166static 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
229static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { 223static 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
350static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { 344static 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) {
365static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { 359static 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
379static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, const char *msg) { 373static 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
413void init_GmRequest(iGmRequest *d, iGmCerts *certs) { 408void 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
448void setUrl_GmRequest(iGmRequest *d, const iString *url) { 447void 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
608iGmResponse *lockResponse_GmRequest(iGmRequest *d) {
609 iAssert(!d->respLocked);
610 lock_Mutex(d->mtx);
611 d->respLocked = iTrue;
612 return d->resp;
613}
614
615void 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
607iBool isFinished_GmRequest(const iGmRequest *d) { 622iBool 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
614enum iGmStatusCode status_GmRequest(const iGmRequest *d) { 629enum 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
618const iString *meta_GmRequest(const iGmRequest *d) { 635const 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
625const iBlock *body_GmRequest(const iGmRequest *d) { 640const 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
631const iString *url_GmRequest(const iGmRequest *d) { 645size_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
635const iGmResponse *response_GmRequest(const iGmRequest *d) { 651const iString *url_GmRequest(const iGmRequest *d) {
636 iAssert(d->state != initialized_GmRequestState); 652 return &d->url;
637 return &d->resp;
638} 653}
639 654
640int certFlags_GmRequest(const iGmRequest *d) { 655int 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
644iDate certExpirationDate_GmRequest(const iGmRequest *d) { 661iDate 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
648iDefineClass(GmRequest) 667iDefineClass(GmRequest)