diff options
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) |