summaryrefslogtreecommitdiff
path: root/src/gmrequest.c
diff options
context:
space:
mode:
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)