]> git.ipfire.org Git - thirdparty/squid.git/blob - src/client_side_reply.cc
Merged from trunk 13199.
[thirdparty/squid.git] / src / client_side_reply.cc
1 /*
2 * DEBUG: section 88 Client-side Reply Routines
3 * AUTHOR: Robert Collins (Originally Duane Wessels in client_side.c)
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 */
32 #include "squid.h"
33 #include "acl/FilledChecklist.h"
34 #include "acl/Gadgets.h"
35 #include "anyp/PortCfg.h"
36 #include "client_side_reply.h"
37 #include "errorpage.h"
38 #include "ETag.h"
39 #include "fd.h"
40 #include "fde.h"
41 #include "format/Token.h"
42 #include "FwdState.h"
43 #include "globals.h"
44 #include "globals.h"
45 #include "HttpHeaderTools.h"
46 #include "HttpReply.h"
47 #include "HttpRequest.h"
48 #include "ip/QosConfig.h"
49 #include "ipcache.h"
50 #include "log/access_log.h"
51 #include "MemObject.h"
52 #include "mime_header.h"
53 #include "neighbors.h"
54 #include "refresh.h"
55 #include "RequestFlags.h"
56 #include "SquidConfig.h"
57 #include "SquidTime.h"
58 #include "Store.h"
59 #include "StrList.h"
60 #include "tools.h"
61 #include "URL.h"
62 #if USE_AUTH
63 #include "auth/UserRequest.h"
64 #endif
65 #if USE_DELAY_POOLS
66 #include "DelayPools.h"
67 #endif
68 #if USE_SQUID_ESI
69 #include "esi/Esi.h"
70 #endif
71
72 #include <memory>
73
74 CBDATA_CLASS_INIT(clientReplyContext);
75
76 /* Local functions */
77 extern "C" CSS clientReplyStatus;
78 ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
79
80 /* privates */
81
82 clientReplyContext::~clientReplyContext()
83 {
84 deleting = true;
85 /* This may trigger a callback back into SendMoreData as the cbdata
86 * is still valid
87 */
88 removeClientStoreReference(&sc, http);
89 /* old_entry might still be set if we didn't yet get the reply
90 * code in HandleIMSReply() */
91 removeStoreReference(&old_sc, &old_entry);
92 safe_free(tempBuffer.data);
93 cbdataReferenceDone(http);
94 HTTPMSGUNLOCK(reply);
95 }
96
97 clientReplyContext::clientReplyContext(ClientHttpRequest *clientContext) : http (cbdataReference(clientContext)), old_entry (NULL), old_sc(NULL), deleting(false)
98 {}
99
100 /** Create an error in the store awaiting the client side to read it.
101 *
102 * This may be better placed in the clientStream logic, but it has not been
103 * relocated there yet
104 */
105 void
106 clientReplyContext::setReplyToError(
107 err_type err, Http::StatusCode status, const HttpRequestMethod& method, char const *uri,
108 Ip::Address &addr, HttpRequest * failedrequest, const char *unparsedrequest,
109 #if USE_AUTH
110 Auth::UserRequest::Pointer auth_user_request
111 #else
112 void*
113 #endif
114 )
115 {
116 ErrorState *errstate = clientBuildError(err, status, uri, addr, failedrequest);
117
118 if (unparsedrequest)
119 errstate->request_hdrs = xstrdup(unparsedrequest);
120
121 #if USE_AUTH
122 errstate->auth_user_request = auth_user_request;
123 #endif
124 setReplyToError(method, errstate);
125 }
126
127 void clientReplyContext::setReplyToError(const HttpRequestMethod& method, ErrorState *errstate)
128 {
129 if (errstate->httpStatus == Http::scNotImplemented && http->request)
130 /* prevent confusion over whether we default to persistent or not */
131 http->request->flags.proxyKeepalive = false;
132
133 http->al->http.code = errstate->httpStatus;
134
135 createStoreEntry(method, RequestFlags());
136 assert(errstate->callback_data == NULL);
137 errorAppendEntry(http->storeEntry(), errstate);
138 /* Now the caller reads to get this */
139 }
140
141 void clientReplyContext::setReplyToStoreEntry(StoreEntry *entry)
142 {
143 entry->lock("clientReplyContext::setReplyToStoreEntry"); // removeClientStoreReference() unlocks
144 sc = storeClientListAdd(entry, this);
145 #if USE_DELAY_POOLS
146 sc->setDelayId(DelayId::DelayClient(http));
147 #endif
148 reqofs = 0;
149 reqsize = 0;
150 flags.storelogiccomplete = 1;
151 http->storeEntry(entry);
152 }
153
154 void
155 clientReplyContext::removeStoreReference(store_client ** scp,
156 StoreEntry ** ep)
157 {
158 StoreEntry *e;
159 store_client *sc_tmp = *scp;
160
161 if ((e = *ep) != NULL) {
162 *ep = NULL;
163 storeUnregister(sc_tmp, e, this);
164 *scp = NULL;
165 e->unlock("clientReplyContext::removeStoreReference");
166 }
167 }
168
169 void
170 clientReplyContext::removeClientStoreReference(store_client **scp, ClientHttpRequest *aHttpRequest)
171 {
172 StoreEntry *reference = aHttpRequest->storeEntry();
173 removeStoreReference(scp, &reference);
174 aHttpRequest->storeEntry(reference);
175 }
176
177 void
178 clientReplyContext::saveState()
179 {
180 assert(old_sc == NULL);
181 debugs(88, 3, "clientReplyContext::saveState: saving store context");
182 old_entry = http->storeEntry();
183 old_sc = sc;
184 old_reqsize = reqsize;
185 tempBuffer.offset = reqofs;
186 /* Prevent accessing the now saved entries */
187 http->storeEntry(NULL);
188 sc = NULL;
189 reqsize = 0;
190 reqofs = 0;
191 }
192
193 void
194 clientReplyContext::restoreState()
195 {
196 assert(old_sc != NULL);
197 debugs(88, 3, "clientReplyContext::restoreState: Restoring store context");
198 removeClientStoreReference(&sc, http);
199 http->storeEntry(old_entry);
200 sc = old_sc;
201 reqsize = old_reqsize;
202 reqofs = tempBuffer.offset;
203 /* Prevent accessed the old saved entries */
204 old_entry = NULL;
205 old_sc = NULL;
206 old_reqsize = 0;
207 tempBuffer.offset = 0;
208 }
209
210 void
211 clientReplyContext::startError(ErrorState * err)
212 {
213 createStoreEntry(http->request->method, RequestFlags());
214 triggerInitialStoreRead();
215 errorAppendEntry(http->storeEntry(), err);
216 }
217
218 clientStreamNode *
219 clientReplyContext::getNextNode() const
220 {
221 return (clientStreamNode *)ourNode->node.next->data;
222 }
223
224 /* This function is wrong - the client parameters don't include the
225 * header offset
226 */
227 void
228 clientReplyContext::triggerInitialStoreRead()
229 {
230 /* when confident, 0 becomes reqofs, and then this factors into
231 * startSendProcess
232 */
233 assert(reqofs == 0);
234 StoreIOBuffer localTempBuffer (next()->readBuffer.length, 0, next()->readBuffer.data);
235 storeClientCopy(sc, http->storeEntry(), localTempBuffer, SendMoreData, this);
236 }
237
238 /* there is an expired entry in the store.
239 * setup a temporary buffer area and perform an IMS to the origin
240 */
241 void
242 clientReplyContext::processExpired()
243 {
244 const char *url = storeId();
245 StoreEntry *entry = NULL;
246 debugs(88, 3, "clientReplyContext::processExpired: '" << http->uri << "'");
247 assert(http->storeEntry()->lastmod >= 0);
248 /*
249 * check if we are allowed to contact other servers
250 * @?@: Instead of a 504 (Gateway Timeout) reply, we may want to return
251 * a stale entry *if* it matches client requirements
252 */
253
254 if (http->onlyIfCached()) {
255 processOnlyIfCachedMiss();
256 return;
257 }
258
259 http->request->flags.refresh = true;
260 #if STORE_CLIENT_LIST_DEBUG
261 /* Prevent a race with the store client memory free routines
262 */
263 assert(storeClientIsThisAClient(sc, this));
264 #endif
265 /* Prepare to make a new temporary request */
266 saveState();
267 entry = storeCreateEntry(url,
268 http->log_uri, http->request->flags, http->request->method);
269 /* NOTE, don't call StoreEntry->lock(), storeCreateEntry() does it */
270 sc = storeClientListAdd(entry, this);
271 #if USE_DELAY_POOLS
272 /* delay_id is already set on original store client */
273 sc->setDelayId(DelayId::DelayClient(http));
274 #endif
275
276 http->request->lastmod = old_entry->lastmod;
277
278 if (!http->request->header.has(HDR_IF_NONE_MATCH)) {
279 ETag etag = {NULL, -1}; // TODO: make that a default ETag constructor
280 if (old_entry->hasEtag(etag) && !etag.weak)
281 http->request->etag = etag.str;
282 }
283
284 debugs(88, 5, "clientReplyContext::processExpired : lastmod " << entry->lastmod );
285 http->storeEntry(entry);
286 assert(http->out.offset == 0);
287 assert(http->request->clientConnectionManager == http->getConn());
288
289 /*
290 * A refcounted pointer so that FwdState stays around as long as
291 * this clientReplyContext does
292 */
293 Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL;
294 FwdState::Start(conn, http->storeEntry(), http->request, http->al);
295
296 /* Register with storage manager to receive updates when data comes in. */
297
298 if (EBIT_TEST(entry->flags, ENTRY_ABORTED))
299 debugs(88, DBG_CRITICAL, "clientReplyContext::processExpired: Found ENTRY_ABORTED object");
300
301 {
302 /* start counting the length from 0 */
303 StoreIOBuffer localTempBuffer(HTTP_REQBUF_SZ, 0, tempbuf);
304 storeClientCopy(sc, entry, localTempBuffer, HandleIMSReply, this);
305 }
306 }
307
308 void
309 clientReplyContext::sendClientUpstreamResponse()
310 {
311 StoreIOBuffer tempresult;
312 removeStoreReference(&old_sc, &old_entry);
313 /* here the data to send is the data we just received */
314 tempBuffer.offset = 0;
315 old_reqsize = 0;
316 /* sendMoreData tracks the offset as well.
317 * Force it back to zero */
318 reqofs = 0;
319 assert(!EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED));
320 /* TODO: provide sendMoreData with the ready parsed reply */
321 tempresult.length = reqsize;
322 tempresult.data = tempbuf;
323 sendMoreData(tempresult);
324 }
325
326 void
327 clientReplyContext::HandleIMSReply(void *data, StoreIOBuffer result)
328 {
329 clientReplyContext *context = (clientReplyContext *)data;
330 context->handleIMSReply(result);
331 }
332
333 void
334 clientReplyContext::sendClientOldEntry()
335 {
336 /* Get the old request back */
337 restoreState();
338 /* here the data to send is in the next nodes buffers already */
339 assert(!EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED));
340 /* sendMoreData tracks the offset as well.
341 * Force it back to zero */
342 reqofs = 0;
343 StoreIOBuffer tempresult (reqsize, reqofs, next()->readBuffer.data);
344 sendMoreData(tempresult);
345 }
346
347 /* This is the workhorse of the HandleIMSReply callback.
348 *
349 * It is called when we've got data back from the origin following our
350 * IMS request to revalidate a stale entry.
351 */
352 void
353 clientReplyContext::handleIMSReply(StoreIOBuffer result)
354 {
355 if (deleting)
356 return;
357
358 debugs(88, 3, "handleIMSReply: " << http->storeEntry()->url() << ", " << (long unsigned) result.length << " bytes" );
359
360 if (http->storeEntry() == NULL)
361 return;
362
363 if (result.flags.error && !EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED))
364 return;
365
366 /* update size of the request */
367 reqsize = result.length + reqofs;
368
369 const Http::StatusCode status = http->storeEntry()->getReply()->sline.status();
370
371 // request to origin was aborted
372 if (EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED)) {
373 debugs(88, 3, "handleIMSReply: request to origin aborted '" << http->storeEntry()->url() << "', sending old entry to client" );
374 http->logType = LOG_TCP_REFRESH_FAIL_OLD;
375 sendClientOldEntry();
376 }
377
378 HttpReply *old_rep = (HttpReply *) old_entry->getReply();
379
380 // origin replied 304
381 if (status == Http::scNotModified) {
382 http->logType = LOG_TCP_REFRESH_UNMODIFIED;
383 http->request->flags.staleIfHit = false; // old_entry is no longer stale
384
385 // update headers on existing entry
386 old_rep->updateOnNotModified(http->storeEntry()->getReply());
387 old_entry->timestampsSet();
388
389 // if client sent IMS
390
391 if (http->request->flags.ims && !old_entry->modifiedSince(http->request)) {
392 // forward the 304 from origin
393 debugs(88, 3, "handleIMSReply: origin replied 304, revalidating existing entry and forwarding 304 to client");
394 sendClientUpstreamResponse();
395 } else {
396 // send existing entry, it's still valid
397 debugs(88, 3, "handleIMSReply: origin replied 304, revalidating existing entry and sending " <<
398 old_rep->sline.status() << " to client");
399 sendClientOldEntry();
400 }
401 }
402
403 // origin replied with a non-error code
404 else if (status > Http::scNone && status < Http::scInternalServerError) {
405 // forward response from origin
406 http->logType = LOG_TCP_REFRESH_MODIFIED;
407 debugs(88, 3, "handleIMSReply: origin replied " << status << ", replacing existing entry and forwarding to client");
408 sendClientUpstreamResponse();
409 }
410
411 // origin replied with an error
412 else if (http->request->flags.failOnValidationError) {
413 http->logType = LOG_TCP_REFRESH_FAIL_ERR;
414 debugs(88, 3, "handleIMSReply: origin replied with error " << status <<
415 ", forwarding to client due to fail_on_validation_err");
416 sendClientUpstreamResponse();
417 } else {
418 // ignore and let client have old entry
419 http->logType = LOG_TCP_REFRESH_FAIL_OLD;
420 debugs(88, 3, "handleIMSReply: origin replied with error " <<
421 status << ", sending old entry (" << old_rep->sline.status() << ") to client");
422 sendClientOldEntry();
423 }
424 }
425
426 SQUIDCEXTERN CSR clientGetMoreData;
427 SQUIDCEXTERN CSD clientReplyDetach;
428
429 /**
430 * clientReplyContext::cacheHit Should only be called until the HTTP reply headers
431 * have been parsed. Normally this should be a single call, but
432 * it might take more than one. As soon as we have the headers,
433 * we hand off to clientSendMoreData, processExpired, or
434 * processMiss.
435 */
436 void
437 clientReplyContext::CacheHit(void *data, StoreIOBuffer result)
438 {
439 clientReplyContext *context = (clientReplyContext *)data;
440 context->cacheHit(result);
441 }
442
443 /**
444 * Process a possible cache HIT.
445 */
446 void
447 clientReplyContext::cacheHit(StoreIOBuffer result)
448 {
449 /** Ignore if the HIT object is being deleted. */
450 if (deleting)
451 return;
452
453 StoreEntry *e = http->storeEntry();
454
455 HttpRequest *r = http->request;
456
457 debugs(88, 3, "clientCacheHit: " << http->uri << ", " << result.length << " bytes");
458
459 if (http->storeEntry() == NULL) {
460 debugs(88, 3, "clientCacheHit: request aborted");
461 return;
462 } else if (result.flags.error) {
463 /* swap in failure */
464 debugs(88, 3, "clientCacheHit: swapin failure for " << http->uri);
465 http->logType = LOG_TCP_SWAPFAIL_MISS;
466 removeClientStoreReference(&sc, http);
467 processMiss();
468 return;
469 }
470
471 if (result.length == 0) {
472 /* the store couldn't get enough data from the file for us to id the
473 * object
474 */
475 /* treat as a miss */
476 http->logType = LOG_TCP_MISS;
477 processMiss();
478 return;
479 }
480
481 assert(!EBIT_TEST(e->flags, ENTRY_ABORTED));
482 /* update size of the request */
483 reqsize = result.length + reqofs;
484
485 /*
486 * Got the headers, now grok them
487 */
488 assert(http->logType == LOG_TCP_HIT);
489
490 if (strcmp(e->mem_obj->storeId(), http->request->storeId()) != 0) {
491 debugs(33, DBG_IMPORTANT, "clientProcessHit: URL mismatch, '" << e->mem_obj->storeId() << "' != '" << http->request->storeId() << "'");
492 http->logType = LOG_TCP_MISS; // we lack a more precise LOG_*_MISS code
493 processMiss();
494 return;
495 }
496
497 switch (varyEvaluateMatch(e, r)) {
498
499 case VARY_NONE:
500 /* No variance detected. Continue as normal */
501 break;
502
503 case VARY_MATCH:
504 /* This is the correct entity for this request. Continue */
505 debugs(88, 2, "clientProcessHit: Vary MATCH!");
506 break;
507
508 case VARY_OTHER:
509 /* This is not the correct entity for this request. We need
510 * to requery the cache.
511 */
512 removeClientStoreReference(&sc, http);
513 e = NULL;
514 /* Note: varyEvalyateMatch updates the request with vary information
515 * so we only get here once. (it also takes care of cancelling loops)
516 */
517 debugs(88, 2, "clientProcessHit: Vary detected!");
518 clientGetMoreData(ourNode, http);
519 return;
520
521 case VARY_CANCEL:
522 /* varyEvaluateMatch found a object loop. Process as miss */
523 debugs(88, DBG_IMPORTANT, "clientProcessHit: Vary object loop!");
524 http->logType = LOG_TCP_MISS; // we lack a more precise LOG_*_MISS code
525 processMiss();
526 return;
527 }
528
529 if (r->method == Http::METHOD_PURGE) {
530 removeClientStoreReference(&sc, http);
531 e = NULL;
532 purgeRequest();
533 return;
534 }
535
536 if (e->checkNegativeHit()
537 && !r->flags.noCacheHack()
538 ) {
539 http->logType = LOG_TCP_NEGATIVE_HIT;
540 sendMoreData(result);
541 } else if (blockedHit()) {
542 debugs(88, 5, "send_hit forces a MISS");
543 http->logType = LOG_TCP_MISS;
544 processMiss();
545 return;
546 } else if (!http->flags.internal && refreshCheckHTTP(e, r)) {
547 debugs(88, 5, "clientCacheHit: in refreshCheck() block");
548 /*
549 * We hold a stale copy; it needs to be validated
550 */
551 /*
552 * The 'needValidation' flag is used to prevent forwarding
553 * loops between siblings. If our copy of the object is stale,
554 * then we should probably only use parents for the validation
555 * request. Otherwise two siblings could generate a loop if
556 * both have a stale version of the object.
557 */
558 r->flags.needValidation = true;
559
560 if (e->lastmod < 0) {
561 debugs(88, 3, "validate HIT object? NO. Missing Last-Modified header. Do MISS.");
562 /*
563 * Previous reply didn't have a Last-Modified header,
564 * we cannot revalidate it.
565 */
566 http->logType = LOG_TCP_MISS;
567 processMiss();
568 } else if (r->flags.noCache) {
569 debugs(88, 3, "validate HIT object? NO. Client sent CC:no-cache. Do CLIENT_REFRESH_MISS");
570 /*
571 * This did not match a refresh pattern that overrides no-cache
572 * we should honour the client no-cache header.
573 */
574 http->logType = LOG_TCP_CLIENT_REFRESH_MISS;
575 processMiss();
576 } else if (r->protocol == AnyP::PROTO_HTTP) {
577 debugs(88, 3, "validate HIT object? YES.");
578 /*
579 * Object needs to be revalidated
580 * XXX This could apply to FTP as well, if Last-Modified is known.
581 */
582 processExpired();
583 } else {
584 debugs(88, 3, "validate HIT object? NO. Client protocol non-HTTP. Do MISS.");
585 /*
586 * We don't know how to re-validate other protocols. Handle
587 * them as if the object has expired.
588 */
589 http->logType = LOG_TCP_MISS;
590 processMiss();
591 }
592 } else if (r->conditional())
593 processConditional(result);
594 else {
595 /*
596 * plain ol' cache hit
597 */
598
599 #if USE_DELAY_POOLS
600 if (e->store_status != STORE_OK)
601 http->logType = LOG_TCP_MISS;
602 else
603 #endif
604 if (e->mem_status == IN_MEMORY)
605 http->logType = LOG_TCP_MEM_HIT;
606 else if (Config.onoff.offline)
607 http->logType = LOG_TCP_OFFLINE_HIT;
608
609 sendMoreData(result);
610 }
611 }
612
613 /**
614 * Prepare to fetch the object as it's a cache miss of some kind.
615 */
616 void
617 clientReplyContext::processMiss()
618 {
619 char *url = http->uri;
620 HttpRequest *r = http->request;
621 ErrorState *err = NULL;
622 debugs(88, 4, "clientProcessMiss: '" << RequestMethodStr(r->method) << " " << url << "'");
623
624 /**
625 * We might have a left-over StoreEntry from a failed cache hit
626 * or IMS request.
627 */
628 if (http->storeEntry()) {
629 if (EBIT_TEST(http->storeEntry()->flags, ENTRY_SPECIAL)) {
630 debugs(88, DBG_CRITICAL, "clientProcessMiss: miss on a special object (" << url << ").");
631 debugs(88, DBG_CRITICAL, "\tlog_type = " << LogTags_str[http->logType]);
632 http->storeEntry()->dump(1);
633 }
634
635 removeClientStoreReference(&sc, http);
636 }
637
638 /** Check if its a PURGE request to be actioned. */
639 if (r->method == Http::METHOD_PURGE) {
640 purgeRequest();
641 return;
642 }
643
644 /** Check if its an 'OTHER' request. Purge all cached entries if so and continue. */
645 if (r->method == Http::METHOD_OTHER) {
646 purgeAllCached();
647 }
648
649 /** Check if 'only-if-cached' flag is set. Action if so. */
650 if (http->onlyIfCached()) {
651 processOnlyIfCachedMiss();
652 return;
653 }
654
655 /// Deny loops
656 if (r->flags.loopDetected) {
657 http->al->http.code = Http::scForbidden;
658 err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->clientConnection->remote, http->request);
659 createStoreEntry(r->method, RequestFlags());
660 errorAppendEntry(http->storeEntry(), err);
661 triggerInitialStoreRead();
662 return;
663 } else {
664 assert(http->out.offset == 0);
665 createStoreEntry(r->method, r->flags);
666 triggerInitialStoreRead();
667
668 if (http->redirect.status) {
669 HttpReply *rep = new HttpReply;
670 http->logType = LOG_TCP_REDIRECT;
671 http->storeEntry()->releaseRequest();
672 rep->redirect(http->redirect.status, http->redirect.location);
673 http->storeEntry()->replaceHttpReply(rep);
674 http->storeEntry()->complete();
675 return;
676 }
677
678 /** Check for internal requests. Update Protocol info if so. */
679 if (http->flags.internal)
680 r->protocol = AnyP::PROTO_INTERNAL;
681
682 assert(r->clientConnectionManager == http->getConn());
683
684 /** Start forwarding to get the new object from network */
685 Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL;
686 FwdState::Start(conn, http->storeEntry(), r, http->al);
687 }
688 }
689
690 /**
691 * client issued a request with an only-if-cached cache-control directive;
692 * we did not find a cached object that can be returned without
693 * contacting other servers;
694 * respond with a 504 (Gateway Timeout) as suggested in [RFC 2068]
695 */
696 void
697 clientReplyContext::processOnlyIfCachedMiss()
698 {
699 debugs(88, 4, "clientProcessOnlyIfCachedMiss: '" <<
700 RequestMethodStr(http->request->method) << " " << http->uri << "'");
701 http->al->http.code = Http::scGateway_Timeout;
702 ErrorState *err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, Http::scGateway_Timeout, NULL,
703 http->getConn()->clientConnection->remote, http->request);
704 removeClientStoreReference(&sc, http);
705 startError(err);
706 }
707
708 /// process conditional request from client
709 void
710 clientReplyContext::processConditional(StoreIOBuffer &result)
711 {
712 StoreEntry *const e = http->storeEntry();
713
714 if (e->getReply()->sline.status() != Http::scOkay) {
715 debugs(88, 4, "clientReplyContext::processConditional: Reply code " <<
716 e->getReply()->sline.status() << " != 200");
717 http->logType = LOG_TCP_MISS;
718 processMiss();
719 return;
720 }
721
722 HttpRequest &r = *http->request;
723
724 if (r.header.has(HDR_IF_MATCH) && !e->hasIfMatchEtag(r)) {
725 // RFC 2616: reply with 412 Precondition Failed if If-Match did not match
726 sendPreconditionFailedError();
727 return;
728 }
729
730 bool matchedIfNoneMatch = false;
731 if (r.header.has(HDR_IF_NONE_MATCH)) {
732 if (!e->hasIfNoneMatchEtag(r)) {
733 // RFC 2616: ignore IMS if If-None-Match did not match
734 r.flags.ims = false;
735 r.ims = -1;
736 r.imslen = 0;
737 r.header.delById(HDR_IF_MODIFIED_SINCE);
738 http->logType = LOG_TCP_MISS;
739 sendMoreData(result);
740 return;
741 }
742
743 if (!r.flags.ims) {
744 // RFC 2616: if If-None-Match matched and there is no IMS,
745 // reply with 304 Not Modified or 412 Precondition Failed
746 sendNotModifiedOrPreconditionFailedError();
747 return;
748 }
749
750 // otherwise check IMS below to decide if we reply with 304 or 412
751 matchedIfNoneMatch = true;
752 }
753
754 if (r.flags.ims) {
755 // handle If-Modified-Since requests from the client
756 if (e->modifiedSince(&r)) {
757 http->logType = LOG_TCP_IMS_HIT;
758 sendMoreData(result);
759 return;
760 }
761
762 if (matchedIfNoneMatch) {
763 // If-None-Match matched, reply with 304 Not Modified or
764 // 412 Precondition Failed
765 sendNotModifiedOrPreconditionFailedError();
766 return;
767 }
768
769 // otherwise reply with 304 Not Modified
770 sendNotModified();
771 }
772 }
773
774 /// whether squid.conf send_hit prevents us from serving this hit
775 bool
776 clientReplyContext::blockedHit() const
777 {
778 if (!Config.accessList.sendHit)
779 return false; // hits are not blocked by default
780
781 if (http->flags.internal)
782 return false; // internal content "hits" cannot be blocked
783
784 if (const HttpReply *rep = http->storeEntry()->getReply()) {
785 std::auto_ptr<ACLFilledChecklist> chl(clientAclChecklistCreate(Config.accessList.sendHit, http));
786 chl->reply = const_cast<HttpReply*>(rep); // ACLChecklist API bug
787 HTTPMSGLOCK(chl->reply);
788 return chl->fastCheck() != ACCESS_ALLOWED; // when in doubt, block
789 }
790
791 // This does not happen, I hope, because we are called from CacheHit, which
792 // is called via a storeClientCopy() callback, and store should initialize
793 // the reply before calling that callback.
794 debugs(88, 3, "Missing reply!");
795 return false;
796 }
797
798 void
799 clientReplyContext::purgeRequestFindObjectToPurge()
800 {
801 /* Try to find a base entry */
802 http->flags.purging = true;
803 lookingforstore = 1;
804
805 // TODO: can we use purgeAllCached() here instead of doing the
806 // getPublicByRequestMethod() dance?
807 StoreEntry::getPublicByRequestMethod(this, http->request, Http::METHOD_GET);
808 }
809
810 // Purges all entries with a given url
811 // TODO: move to SideAgent parent, when we have one
812 /*
813 * We probably cannot purge Vary-affected responses because their MD5
814 * keys depend on vary headers.
815 */
816 void
817 purgeEntriesByUrl(HttpRequest * req, const char *url)
818 {
819 #if USE_HTCP
820 bool get_or_head_sent = false;
821 #endif
822
823 for (HttpRequestMethod m(Http::METHOD_NONE); m != Http::METHOD_ENUM_END; ++m) {
824 if (m.respMaybeCacheable()) {
825 if (StoreEntry *entry = storeGetPublic(url, m)) {
826 debugs(88, 5, "purging " << *entry << ' ' << RequestMethodStr(m) << ' ' << url);
827 #if USE_HTCP
828 neighborsHtcpClear(entry, url, req, m, HTCP_CLR_INVALIDATION);
829 if (m == Http::METHOD_GET || m == Http::METHOD_HEAD) {
830 get_or_head_sent = true;
831 }
832 #endif
833 entry->release();
834 }
835 }
836 }
837
838 #if USE_HTCP
839 if (!get_or_head_sent) {
840 neighborsHtcpClear(NULL, url, req, HttpRequestMethod(Http::METHOD_GET), HTCP_CLR_INVALIDATION);
841 }
842 #endif
843 }
844
845 void
846 clientReplyContext::purgeAllCached()
847 {
848 const char *url = urlCanonical(http->request);
849 purgeEntriesByUrl(http->request, url);
850 }
851
852 void
853 clientReplyContext::created(StoreEntry *newEntry)
854 {
855 if (lookingforstore == 1)
856 purgeFoundGet(newEntry);
857 else if (lookingforstore == 2)
858 purgeFoundHead(newEntry);
859 else if (lookingforstore == 3)
860 purgeDoPurgeGet(newEntry);
861 else if (lookingforstore == 4)
862 purgeDoPurgeHead(newEntry);
863 else if (lookingforstore == 5)
864 identifyFoundObject(newEntry);
865 }
866
867 void
868 clientReplyContext::purgeFoundGet(StoreEntry *newEntry)
869 {
870 if (newEntry->isNull()) {
871 lookingforstore = 2;
872 StoreEntry::getPublicByRequestMethod(this, http->request, Http::METHOD_HEAD);
873 } else
874 purgeFoundObject (newEntry);
875 }
876
877 void
878 clientReplyContext::purgeFoundHead(StoreEntry *newEntry)
879 {
880 if (newEntry->isNull())
881 purgeDoMissPurge();
882 else
883 purgeFoundObject (newEntry);
884 }
885
886 void
887 clientReplyContext::purgeFoundObject(StoreEntry *entry)
888 {
889 assert (entry && !entry->isNull());
890
891 if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) {
892 http->logType = LOG_TCP_DENIED;
893 ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL,
894 http->getConn()->clientConnection->remote, http->request);
895 startError(err);
896 return; // XXX: leaking unused entry if some store does not keep it
897 }
898
899 StoreIOBuffer localTempBuffer;
900 /* Swap in the metadata */
901 http->storeEntry(entry);
902
903 http->storeEntry()->lock("clientReplyContext::purgeFoundObject");
904 http->storeEntry()->createMemObject(storeId(), http->log_uri,
905 http->request->method);
906
907 sc = storeClientListAdd(http->storeEntry(), this);
908
909 http->logType = LOG_TCP_HIT;
910
911 reqofs = 0;
912
913 localTempBuffer.offset = http->out.offset;
914
915 localTempBuffer.length = next()->readBuffer.length;
916
917 localTempBuffer.data = next()->readBuffer.data;
918
919 storeClientCopy(sc, http->storeEntry(),
920 localTempBuffer, CacheHit, this);
921 }
922
923 void
924 clientReplyContext::purgeRequest()
925 {
926 debugs(88, 3, "Config2.onoff.enable_purge = " <<
927 Config2.onoff.enable_purge);
928
929 if (!Config2.onoff.enable_purge) {
930 http->logType = LOG_TCP_DENIED;
931 ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->clientConnection->remote, http->request);
932 startError(err);
933 return;
934 }
935
936 /* Release both IP cache */
937 ipcacheInvalidate(http->request->GetHost());
938
939 if (!http->flags.purging)
940 purgeRequestFindObjectToPurge();
941 else
942 purgeDoMissPurge();
943 }
944
945 void
946 clientReplyContext::purgeDoMissPurge()
947 {
948 http->logType = LOG_TCP_MISS;
949 lookingforstore = 3;
950 StoreEntry::getPublicByRequestMethod(this,http->request, Http::METHOD_GET);
951 }
952
953 void
954 clientReplyContext::purgeDoPurgeGet(StoreEntry *newEntry)
955 {
956 assert (newEntry);
957 /* Move to new() when that is created */
958 purgeStatus = Http::scNotFound;
959
960 if (!newEntry->isNull()) {
961 /* Release the cached URI */
962 debugs(88, 4, "clientPurgeRequest: GET '" << newEntry->url() << "'" );
963 #if USE_HTCP
964 neighborsHtcpClear(newEntry, NULL, http->request, HttpRequestMethod(Http::METHOD_GET), HTCP_CLR_PURGE);
965 #endif
966 newEntry->release();
967 purgeStatus = Http::scOkay;
968 }
969
970 lookingforstore = 4;
971 StoreEntry::getPublicByRequestMethod(this, http->request, Http::METHOD_HEAD);
972 }
973
974 void
975 clientReplyContext::purgeDoPurgeHead(StoreEntry *newEntry)
976 {
977 if (newEntry && !newEntry->isNull()) {
978 debugs(88, 4, "clientPurgeRequest: HEAD '" << newEntry->url() << "'" );
979 #if USE_HTCP
980 neighborsHtcpClear(newEntry, NULL, http->request, HttpRequestMethod(Http::METHOD_HEAD), HTCP_CLR_PURGE);
981 #endif
982 newEntry->release();
983 purgeStatus = Http::scOkay;
984 }
985
986 /* And for Vary, release the base URI if none of the headers was included in the request */
987
988 if (http->request->vary_headers
989 && !strstr(http->request->vary_headers, "=")) {
990 StoreEntry *entry = storeGetPublic(urlCanonical(http->request), Http::METHOD_GET);
991
992 if (entry) {
993 debugs(88, 4, "clientPurgeRequest: Vary GET '" << entry->url() << "'" );
994 #if USE_HTCP
995 neighborsHtcpClear(entry, NULL, http->request, HttpRequestMethod(Http::METHOD_GET), HTCP_CLR_PURGE);
996 #endif
997 entry->release();
998 purgeStatus = Http::scOkay;
999 }
1000
1001 entry = storeGetPublic(urlCanonical(http->request), Http::METHOD_HEAD);
1002
1003 if (entry) {
1004 debugs(88, 4, "clientPurgeRequest: Vary HEAD '" << entry->url() << "'" );
1005 #if USE_HTCP
1006 neighborsHtcpClear(entry, NULL, http->request, HttpRequestMethod(Http::METHOD_HEAD), HTCP_CLR_PURGE);
1007 #endif
1008 entry->release();
1009 purgeStatus = Http::scOkay;
1010 }
1011 }
1012
1013 /*
1014 * Make a new entry to hold the reply to be written
1015 * to the client.
1016 */
1017 /* FIXME: This doesn't need to go through the store. Simply
1018 * push down the client chain
1019 */
1020 createStoreEntry(http->request->method, RequestFlags());
1021
1022 triggerInitialStoreRead();
1023
1024 HttpReply *rep = new HttpReply;
1025 rep->setHeaders(purgeStatus, NULL, NULL, 0, 0, -1);
1026 http->storeEntry()->replaceHttpReply(rep);
1027 http->storeEntry()->complete();
1028 }
1029
1030 void
1031 clientReplyContext::traceReply(clientStreamNode * node)
1032 {
1033 clientStreamNode *nextNode = (clientStreamNode *)node->node.next->data;
1034 StoreIOBuffer localTempBuffer;
1035 createStoreEntry(http->request->method, RequestFlags());
1036 localTempBuffer.offset = nextNode->readBuffer.offset + headers_sz;
1037 localTempBuffer.length = nextNode->readBuffer.length;
1038 localTempBuffer.data = nextNode->readBuffer.data;
1039 storeClientCopy(sc, http->storeEntry(),
1040 localTempBuffer, SendMoreData, this);
1041 http->storeEntry()->releaseRequest();
1042 http->storeEntry()->buffer();
1043 HttpReply *rep = new HttpReply;
1044 rep->setHeaders(Http::scOkay, NULL, "text/plain", http->request->prefixLen(), 0, squid_curtime);
1045 http->storeEntry()->replaceHttpReply(rep);
1046 http->request->swapOut(http->storeEntry());
1047 http->storeEntry()->complete();
1048 }
1049
1050 #define SENDING_BODY 0
1051 #define SENDING_HDRSONLY 1
1052 int
1053 clientReplyContext::checkTransferDone()
1054 {
1055 StoreEntry *entry = http->storeEntry();
1056
1057 if (entry == NULL)
1058 return 0;
1059
1060 /*
1061 * For now, 'done_copying' is used for special cases like
1062 * Range and HEAD requests.
1063 */
1064 if (http->flags.done_copying)
1065 return 1;
1066
1067 if (http->request->flags.chunkedReply && !flags.complete) {
1068 // last-chunk was not sent
1069 return 0;
1070 }
1071
1072 /*
1073 * Handle STORE_OK objects.
1074 * objectLen(entry) will be set proprely.
1075 * RC: Does objectLen(entry) include the Headers?
1076 * RC: Yes.
1077 */
1078 if (entry->store_status == STORE_OK) {
1079 return storeOKTransferDone();
1080 } else {
1081 return storeNotOKTransferDone();
1082 }
1083 }
1084
1085 int
1086 clientReplyContext::storeOKTransferDone() const
1087 {
1088 assert(http->storeEntry()->objectLen() >= 0);
1089 assert(http->storeEntry()->objectLen() >= headers_sz);
1090 if (http->out.offset >= http->storeEntry()->objectLen() - headers_sz) {
1091 debugs(88,3,HERE << "storeOKTransferDone " <<
1092 " out.offset=" << http->out.offset <<
1093 " objectLen()=" << http->storeEntry()->objectLen() <<
1094 " headers_sz=" << headers_sz);
1095 return 1;
1096 }
1097
1098 return 0;
1099 }
1100
1101 int
1102 clientReplyContext::storeNotOKTransferDone() const
1103 {
1104 /*
1105 * Now, handle STORE_PENDING objects
1106 */
1107 MemObject *mem = http->storeEntry()->mem_obj;
1108 assert(mem != NULL);
1109 assert(http->request != NULL);
1110 /* mem->reply was wrong because it uses the UPSTREAM header length!!! */
1111 HttpReply const *curReply = mem->getReply();
1112
1113 if (headers_sz == 0)
1114 /* haven't found end of headers yet */
1115 return 0;
1116
1117 /*
1118 * Figure out how much data we are supposed to send.
1119 * If we are sending a body and we don't have a content-length,
1120 * then we must wait for the object to become STORE_OK.
1121 */
1122 if (curReply->content_length < 0)
1123 return 0;
1124
1125 int64_t expectedLength = curReply->content_length + http->out.headers_sz;
1126
1127 if (http->out.size < expectedLength)
1128 return 0;
1129 else {
1130 debugs(88,3,HERE << "storeNotOKTransferDone " <<
1131 " out.size=" << http->out.size <<
1132 " expectedLength=" << expectedLength);
1133 return 1;
1134 }
1135 }
1136
1137 /* A write has completed, what is the next status based on the
1138 * canonical request data?
1139 * 1 something is wrong
1140 * 0 nothing is wrong.
1141 *
1142 */
1143 int
1144 clientHttpRequestStatus(int fd, ClientHttpRequest const *http)
1145 {
1146 #if SIZEOF_INT64_T == 4
1147 if (http->out.size > 0x7FFF0000) {
1148 debugs(88, DBG_IMPORTANT, "WARNING: closing FD " << fd << " to prevent out.size counter overflow");
1149 debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
1150 debugs(88, DBG_IMPORTANT, "\treceived " << http->out.size << " bytes");
1151 debugs(88, DBG_IMPORTANT, "\tURI " << http->log_uri);
1152 return 1;
1153 }
1154
1155 if (http->out.offset > 0x7FFF0000) {
1156 debugs(88, DBG_IMPORTANT, "WARNING: closing FD " << fd < " to prevent out.offset counter overflow");
1157 debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
1158 debugs(88, DBG_IMPORTANT, "\treceived " << http->out.size << " bytes, offset " << http->out.offset);
1159 debugs(88, DBG_IMPORTANT, "\tURI " << http->log_uri);
1160 return 1;
1161 }
1162
1163 #endif
1164 return 0;
1165 }
1166
1167 /* Preconditions:
1168 * *http is a valid structure.
1169 * fd is either -1, or an open fd.
1170 *
1171 * TODO: enumify this
1172 *
1173 * This function is used by any http request sink, to determine the status
1174 * of the object.
1175 */
1176 clientStream_status_t
1177 clientReplyStatus(clientStreamNode * aNode, ClientHttpRequest * http)
1178 {
1179 clientReplyContext *context = dynamic_cast<clientReplyContext *>(aNode->data.getRaw());
1180 assert (context);
1181 assert (context->http == http);
1182 return context->replyStatus();
1183 }
1184
1185 clientStream_status_t
1186 clientReplyContext::replyStatus()
1187 {
1188 int done;
1189 /* Here because lower nodes don't need it */
1190
1191 if (http->storeEntry() == NULL) {
1192 debugs(88, 5, "clientReplyStatus: no storeEntry");
1193 return STREAM_FAILED; /* yuck, but what can we do? */
1194 }
1195
1196 if (EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED)) {
1197 /* TODO: Could upstream read errors (result.flags.error) be
1198 * lost, and result in undersize requests being considered
1199 * complete. Should we tcp reset such connections ?
1200 */
1201 debugs(88, 5, "clientReplyStatus: aborted storeEntry");
1202 return STREAM_FAILED;
1203 }
1204
1205 if ((done = checkTransferDone()) != 0 || flags.complete) {
1206 debugs(88, 5, "clientReplyStatus: transfer is DONE: " << done << flags.complete);
1207 /* Ok we're finished, but how? */
1208
1209 const int64_t expectedBodySize =
1210 http->storeEntry()->getReply()->bodySize(http->request->method);
1211 if (!http->request->flags.proxyKeepalive && expectedBodySize < 0) {
1212 debugs(88, 5, "clientReplyStatus: closing, content_length < 0");
1213 return STREAM_FAILED;
1214 }
1215
1216 if (!done) {
1217 debugs(88, 5, "clientReplyStatus: closing, !done, but read 0 bytes");
1218 return STREAM_FAILED;
1219 }
1220
1221 if (expectedBodySize >= 0 && !http->gotEnough()) {
1222 debugs(88, 5, "clientReplyStatus: client didn't get all it expected");
1223 return STREAM_UNPLANNED_COMPLETE;
1224 }
1225
1226 debugs(88, 5, "clientReplyStatus: stream complete; keepalive=" <<
1227 http->request->flags.proxyKeepalive);
1228 return STREAM_COMPLETE;
1229 }
1230
1231 // XXX: Should this be checked earlier? We could return above w/o checking.
1232 if (reply->receivedBodyTooLarge(*http->request, http->out.offset - 4096)) {
1233 /* 4096 is a margin for the HTTP headers included in out.offset */
1234 debugs(88, 5, "clientReplyStatus: client reply body is too large");
1235 return STREAM_FAILED;
1236 }
1237
1238 return STREAM_NONE;
1239 }
1240
1241 /* Responses with no body will not have a content-type header,
1242 * which breaks the rep_mime_type acl, which
1243 * coincidentally, is the most common acl for reply access lists.
1244 * A better long term fix for this is to allow acl matchs on the various
1245 * status codes, and then supply a default ruleset that puts these
1246 * codes before any user defines access entries. That way the user
1247 * can choose to block these responses where appropriate, but won't get
1248 * mysterious breakages.
1249 */
1250 bool
1251 clientReplyContext::alwaysAllowResponse(Http::StatusCode sline) const
1252 {
1253 bool result;
1254
1255 switch (sline) {
1256
1257 case Http::scContinue:
1258
1259 case Http::scSwitchingProtocols:
1260
1261 case Http::scProcessing:
1262
1263 case Http::scNoContent:
1264
1265 case Http::scNotModified:
1266 result = true;
1267 break;
1268
1269 default:
1270 result = false;
1271 }
1272
1273 return result;
1274 }
1275
1276 /**
1277 * Generate the reply headers sent to client.
1278 *
1279 * Filters out unwanted entries and hop-by-hop from original reply header
1280 * then adds extra entries if we have more info than origin server
1281 * then adds Squid specific entries
1282 */
1283 void
1284 clientReplyContext::buildReplyHeader()
1285 {
1286 HttpHeader *hdr = &reply->header;
1287 int is_hit = logTypeIsATcpHit(http->logType);
1288 HttpRequest *request = http->request;
1289 #if DONT_FILTER_THESE
1290 /* but you might want to if you run Squid as an HTTP accelerator */
1291 /* hdr->delById(HDR_ACCEPT_RANGES); */
1292 hdr->delById(HDR_ETAG);
1293 #endif
1294
1295 if (is_hit)
1296 hdr->delById(HDR_SET_COOKIE);
1297 // TODO: RFC 2965 : Must honour Cache-Control: no-cache="set-cookie2" and remove header.
1298
1299 // if there is not configured a peer proxy with login=PASS or login=PASSTHRU option enabled
1300 // remove the Proxy-Authenticate header
1301 if ( !request->peer_login || (strcmp(request->peer_login,"PASS") != 0 && strcmp(request->peer_login,"PASSTHRU") != 0))
1302 reply->header.delById(HDR_PROXY_AUTHENTICATE);
1303
1304 reply->header.removeHopByHopEntries();
1305
1306 // if (request->range)
1307 // clientBuildRangeHeader(http, reply);
1308
1309 /*
1310 * Add a estimated Age header on cache hits.
1311 */
1312 if (is_hit) {
1313 /*
1314 * Remove any existing Age header sent by upstream caches
1315 * (note that the existing header is passed along unmodified
1316 * on cache misses)
1317 */
1318 hdr->delById(HDR_AGE);
1319 /*
1320 * This adds the calculated object age. Note that the details of the
1321 * age calculation is performed by adjusting the timestamp in
1322 * StoreEntry::timestampsSet(), not here.
1323 */
1324 if (EBIT_TEST(http->storeEntry()->flags, ENTRY_SPECIAL)) {
1325 hdr->delById(HDR_DATE);
1326 hdr->insertTime(HDR_DATE, squid_curtime);
1327 } else if (http->getConn() && http->getConn()->port->actAsOrigin) {
1328 // Swap the Date: header to current time if we are simulating an origin
1329 HttpHeaderEntry *h = hdr->findEntry(HDR_DATE);
1330 if (h)
1331 hdr->putExt("X-Origin-Date", h->value.termedBuf());
1332 hdr->delById(HDR_DATE);
1333 hdr->insertTime(HDR_DATE, squid_curtime);
1334 h = hdr->findEntry(HDR_EXPIRES);
1335 if (h && http->storeEntry()->expires >= 0) {
1336 hdr->putExt("X-Origin-Expires", h->value.termedBuf());
1337 hdr->delById(HDR_EXPIRES);
1338 hdr->insertTime(HDR_EXPIRES, squid_curtime + http->storeEntry()->expires - http->storeEntry()->timestamp);
1339 }
1340 if (http->storeEntry()->timestamp <= squid_curtime) {
1341 // put X-Cache-Age: instead of Age:
1342 char age[64];
1343 snprintf(age, sizeof(age), "%ld", (long int) squid_curtime - http->storeEntry()->timestamp);
1344 hdr->putExt("X-Cache-Age", age);
1345 }
1346 } else if (http->storeEntry()->timestamp <= squid_curtime) {
1347 hdr->putInt(HDR_AGE,
1348 squid_curtime - http->storeEntry()->timestamp);
1349 /* Signal old objects. NB: rfc 2616 is not clear,
1350 * by implication, on whether we should do this to all
1351 * responses, or only cache hits.
1352 * 14.46 states it ONLY applys for heuristically caclulated
1353 * freshness values, 13.2.4 doesn't specify the same limitation.
1354 * We interpret RFC 2616 under the combination.
1355 */
1356 /* TODO: if maxage or s-maxage is present, don't do this */
1357
1358 if (squid_curtime - http->storeEntry()->timestamp >= 86400) {
1359 char tbuf[512];
1360 snprintf (tbuf, sizeof(tbuf), "%s %s %s",
1361 "113", ThisCache,
1362 "This cache hit is still fresh and more than 1 day old");
1363 hdr->putStr(HDR_WARNING, tbuf);
1364 }
1365 }
1366 }
1367
1368 /* RFC 2616: Section 14.18
1369 *
1370 * Add a Date: header if missing.
1371 * We have access to a clock therefore are required to amend any shortcoming in servers.
1372 *
1373 * NP: done after Age: to prevent ENTRY_SPECIAL double-handling this header.
1374 */
1375 if ( !hdr->has(HDR_DATE) ) {
1376 if (!http->storeEntry())
1377 hdr->insertTime(HDR_DATE, squid_curtime);
1378 else if (http->storeEntry()->timestamp > 0)
1379 hdr->insertTime(HDR_DATE, http->storeEntry()->timestamp);
1380 else {
1381 debugs(88,DBG_IMPORTANT,"BUG 3279: HTTP reply without Date:");
1382 /* dump something useful about the problem */
1383 http->storeEntry()->dump(DBG_IMPORTANT);
1384 }
1385 }
1386
1387 // add Warnings required by RFC 2616 if serving a stale hit
1388 if (http->request->flags.staleIfHit && logTypeIsATcpHit(http->logType)) {
1389 hdr->putWarning(110, "Response is stale");
1390 if (http->request->flags.needValidation)
1391 hdr->putWarning(111, "Revalidation failed");
1392 }
1393
1394 /* Filter unproxyable authentication types */
1395 if (http->logType != LOG_TCP_DENIED &&
1396 hdr->has(HDR_WWW_AUTHENTICATE)) {
1397 HttpHeaderPos pos = HttpHeaderInitPos;
1398 HttpHeaderEntry *e;
1399
1400 int connection_auth_blocked = 0;
1401 while ((e = hdr->getEntry(&pos))) {
1402 if (e->id == HDR_WWW_AUTHENTICATE) {
1403 const char *value = e->value.rawBuf();
1404
1405 if ((strncasecmp(value, "NTLM", 4) == 0 &&
1406 (value[4] == '\0' || value[4] == ' '))
1407 ||
1408 (strncasecmp(value, "Negotiate", 9) == 0 &&
1409 (value[9] == '\0' || value[9] == ' '))
1410 ||
1411 (strncasecmp(value, "Kerberos", 8) == 0 &&
1412 (value[8] == '\0' || value[8] == ' '))) {
1413 if (request->flags.connectionAuthDisabled) {
1414 hdr->delAt(pos, connection_auth_blocked);
1415 continue;
1416 }
1417 request->flags.mustKeepalive = true;
1418 if (!request->flags.accelerated && !request->flags.intercepted) {
1419 httpHeaderPutStrf(hdr, HDR_PROXY_SUPPORT, "Session-Based-Authentication");
1420 /*
1421 We send "[Proxy-]Connection: Proxy-Support" header to mark
1422 Proxy-Support as a hop-by-hop header for intermediaries that do not
1423 understand the semantics of this header. The RFC should have included
1424 this recommendation.
1425 */
1426 httpHeaderPutStrf(hdr, HDR_CONNECTION, "Proxy-support");
1427 }
1428 break;
1429 }
1430 }
1431 }
1432
1433 if (connection_auth_blocked)
1434 hdr->refreshMask();
1435 }
1436
1437 #if USE_AUTH
1438 /* Handle authentication headers */
1439 if (http->logType == LOG_TCP_DENIED &&
1440 ( reply->sline.status() == Http::scProxyAuthenticationRequired ||
1441 reply->sline.status() == Http::scUnauthorized)
1442 ) {
1443 /* Add authentication header */
1444 /*! \todo alter errorstate to be accel on|off aware. The 0 on the next line
1445 * depends on authenticate behaviour: all schemes to date send no extra
1446 * data on 407/401 responses, and do not check the accel state on 401/407
1447 * responses
1448 */
1449 authenticateFixHeader(reply, request->auth_user_request, request, 0, 1);
1450 } else if (request->auth_user_request != NULL)
1451 authenticateFixHeader(reply, request->auth_user_request, request, http->flags.accel, 0);
1452 #endif
1453
1454 /* Append X-Cache */
1455 httpHeaderPutStrf(hdr, HDR_X_CACHE, "%s from %s",
1456 is_hit ? "HIT" : "MISS", getMyHostname());
1457
1458 #if USE_CACHE_DIGESTS
1459 /* Append X-Cache-Lookup: -- temporary hack, to be removed @?@ @?@ */
1460 httpHeaderPutStrf(hdr, HDR_X_CACHE_LOOKUP, "%s from %s:%d",
1461 lookup_type ? lookup_type : "NONE",
1462 getMyHostname(), getMyPort());
1463
1464 #endif
1465
1466 const bool maySendChunkedReply = !request->multipartRangeRequest() &&
1467 reply->sline.protocol == AnyP::PROTO_HTTP && // response is HTTP
1468 (request->http_ver >= Http::ProtocolVersion(1, 1));
1469
1470 /* Check whether we should send keep-alive */
1471 if (!Config.onoff.error_pconns && reply->sline.status() >= 400 && !request->flags.mustKeepalive) {
1472 debugs(33, 3, "clientBuildReplyHeader: Error, don't keep-alive");
1473 request->flags.proxyKeepalive = false;
1474 } else if (!Config.onoff.client_pconns && !request->flags.mustKeepalive) {
1475 debugs(33, 2, "clientBuildReplyHeader: Connection Keep-Alive not requested by admin or client");
1476 request->flags.proxyKeepalive = false;
1477 } else if (request->flags.proxyKeepalive && shutting_down) {
1478 debugs(88, 3, "clientBuildReplyHeader: Shutting down, don't keep-alive.");
1479 request->flags.proxyKeepalive = false;
1480 } else if (request->flags.connectionAuth && !reply->keep_alive) {
1481 debugs(33, 2, "clientBuildReplyHeader: Connection oriented auth but server side non-persistent");
1482 request->flags.proxyKeepalive = false;
1483 } else if (reply->bodySize(request->method) < 0 && !maySendChunkedReply) {
1484 debugs(88, 3, "clientBuildReplyHeader: can't keep-alive, unknown body size" );
1485 request->flags.proxyKeepalive = false;
1486 } else if (fdUsageHigh()&& !request->flags.mustKeepalive) {
1487 debugs(88, 3, "clientBuildReplyHeader: Not many unused FDs, can't keep-alive");
1488 request->flags.proxyKeepalive = false;
1489 } else if (request->flags.sslBumped && !reply->persistent()) {
1490 // We do not really have to close, but we pretend we are a tunnel.
1491 debugs(88, 3, "clientBuildReplyHeader: bumped reply forces close");
1492 request->flags.proxyKeepalive = false;
1493 } else if (request->pinnedConnection() && !reply->persistent()) {
1494 // The peer wants to close the pinned connection
1495 debugs(88, 3, "pinned reply forces close");
1496 request->flags.proxyKeepalive = false;
1497 }
1498
1499 // Decide if we send chunked reply
1500 if (maySendChunkedReply &&
1501 request->flags.proxyKeepalive &&
1502 reply->bodySize(request->method) < 0) {
1503 debugs(88, 3, "clientBuildReplyHeader: chunked reply");
1504 request->flags.chunkedReply = true;
1505 hdr->putStr(HDR_TRANSFER_ENCODING, "chunked");
1506 }
1507
1508 /* Append VIA */
1509 if (Config.onoff.via) {
1510 LOCAL_ARRAY(char, bbuf, MAX_URL + 32);
1511 String strVia;
1512 hdr->getList(HDR_VIA, &strVia);
1513 snprintf(bbuf, MAX_URL + 32, "%d.%d %s",
1514 reply->sline.version.major,
1515 reply->sline.version.minor,
1516 ThisCache);
1517 strListAdd(&strVia, bbuf, ',');
1518 hdr->delById(HDR_VIA);
1519 hdr->putStr(HDR_VIA, strVia.termedBuf());
1520 }
1521 /* Signal keep-alive or close explicitly */
1522 hdr->putStr(HDR_CONNECTION, request->flags.proxyKeepalive ? "keep-alive" : "close");
1523
1524 #if ADD_X_REQUEST_URI
1525 /*
1526 * Knowing the URI of the request is useful when debugging persistent
1527 * connections in a client; we cannot guarantee the order of http headers,
1528 * but X-Request-URI is likely to be the very last header to ease use from a
1529 * debugger [hdr->entries.count-1].
1530 */
1531 hdr->putStr(HDR_X_REQUEST_URI,
1532 http->memOjbect()->url ? http->memObject()->url : http->uri);
1533
1534 #endif
1535
1536 /* Surrogate-Control requires Surrogate-Capability from upstream to pass on */
1537 if ( hdr->has(HDR_SURROGATE_CONTROL) ) {
1538 if (!request->header.has(HDR_SURROGATE_CAPABILITY)) {
1539 hdr->delById(HDR_SURROGATE_CONTROL);
1540 }
1541 /* TODO: else case: drop any controls intended specifically for our surrogate ID */
1542 }
1543
1544 httpHdrMangleList(hdr, request, ROR_REPLY);
1545 }
1546
1547 void
1548 clientReplyContext::cloneReply()
1549 {
1550 assert(reply == NULL);
1551
1552 reply = http->storeEntry()->getReply()->clone();
1553 HTTPMSGLOCK(reply);
1554
1555 if (reply->sline.protocol == AnyP::PROTO_HTTP) {
1556 /* RFC 2616 requires us to advertise our 1.1 version (but only on real HTTP traffic) */
1557 reply->sline.version = Http::ProtocolVersion(1,1);
1558 }
1559
1560 /* do header conversions */
1561 buildReplyHeader();
1562 }
1563
1564 /// Safely disposes of an entry pointing to a cache hit that we do not want.
1565 /// We cannot just ignore the entry because it may be locking or otherwise
1566 /// holding an associated cache resource of some sort.
1567 void
1568 clientReplyContext::forgetHit()
1569 {
1570 StoreEntry *e = http->storeEntry();
1571 assert(e); // or we are not dealing with a hit
1572 // We probably have not locked the entry earlier, unfortunately. We lock it
1573 // now so that we can unlock two lines later (and trigger cleanup).
1574 // Ideally, ClientHttpRequest::storeEntry() should lock/unlock, but it is
1575 // used so inconsistently that simply adding locking there leads to bugs.
1576 e->lock("clientReplyContext::forgetHit");
1577 http->storeEntry(NULL);
1578 e->unlock("clientReplyContext::forgetHit"); // may delete e
1579 }
1580
1581 void
1582 clientReplyContext::identifyStoreObject()
1583 {
1584 HttpRequest *r = http->request;
1585
1586 if (r->flags.cachable || r->flags.internal) {
1587 lookingforstore = 5;
1588 StoreEntry::getPublicByRequest (this, r);
1589 } else {
1590 identifyFoundObject (NullStoreEntry::getInstance());
1591 }
1592 }
1593
1594 /**
1595 * Check state of the current StoreEntry object.
1596 * to see if we can determine the final status of the request.
1597 */
1598 void
1599 clientReplyContext::identifyFoundObject(StoreEntry *newEntry)
1600 {
1601 StoreEntry *e = newEntry;
1602 HttpRequest *r = http->request;
1603
1604 /** \li If the entry received isNull() then we ignore it. */
1605 if (e->isNull()) {
1606 http->storeEntry(NULL);
1607 } else {
1608 http->storeEntry(e);
1609 }
1610
1611 e = http->storeEntry();
1612
1613 /* Release IP-cache entries on reload */
1614 /** \li If the request has no-cache flag set or some no_cache HACK in operation we
1615 * 'invalidate' the cached IP entries for this request ???
1616 */
1617 if (r->flags.noCache || r->flags.noCacheHack())
1618 ipcacheInvalidateNegative(r->GetHost());
1619
1620 #if USE_CACHE_DIGESTS
1621 lookup_type = http->storeEntry() ? "HIT" : "MISS";
1622 #endif
1623
1624 if (NULL == http->storeEntry()) {
1625 /** \li If no StoreEntry object is current assume this object isn't in the cache set MISS*/
1626 debugs(85, 3, "StoreEntry is NULL - MISS");
1627 http->logType = LOG_TCP_MISS;
1628 doGetMoreData();
1629 return;
1630 }
1631
1632 if (Config.onoff.offline) {
1633 /** \li If we are running in offline mode set to HIT */
1634 debugs(85, 3, "offline HIT " << *e);
1635 http->logType = LOG_TCP_HIT;
1636 doGetMoreData();
1637 return;
1638 }
1639
1640 if (http->redirect.status) {
1641 /** \li If redirection status is True force this to be a MISS */
1642 debugs(85, 3, "REDIRECT status forced StoreEntry to NULL (no body on 3XX responses) " << *e);
1643 forgetHit();
1644 http->logType = LOG_TCP_REDIRECT;
1645 doGetMoreData();
1646 return;
1647 }
1648
1649 if (!e->validToSend()) {
1650 debugs(85, 3, "!storeEntryValidToSend MISS " << *e);
1651 forgetHit();
1652 http->logType = LOG_TCP_MISS;
1653 doGetMoreData();
1654 return;
1655 }
1656
1657 if (EBIT_TEST(e->flags, ENTRY_SPECIAL)) {
1658 /* \li Special entries are always hits, no matter what the client says */
1659 debugs(85, 3, "ENTRY_SPECIAL HIT " << *e);
1660 http->logType = LOG_TCP_HIT;
1661 doGetMoreData();
1662 return;
1663 }
1664
1665 if (r->flags.noCache) {
1666 debugs(85, 3, "no-cache REFRESH MISS " << *e);
1667 forgetHit();
1668 http->logType = LOG_TCP_CLIENT_REFRESH_MISS;
1669 doGetMoreData();
1670 return;
1671 }
1672
1673 debugs(85, 3, "default HIT " << *e);
1674 http->logType = LOG_TCP_HIT;
1675 doGetMoreData();
1676 }
1677
1678 /**
1679 * Request more data from the store for the client Stream
1680 * This is *the* entry point to this module.
1681 *
1682 * Preconditions:
1683 * - This is the head of the list.
1684 * - There is at least one more node.
1685 * - Data context is not null
1686 */
1687 void
1688 clientGetMoreData(clientStreamNode * aNode, ClientHttpRequest * http)
1689 {
1690 /* Test preconditions */
1691 assert(aNode != NULL);
1692 assert(cbdataReferenceValid(aNode));
1693 assert(aNode->node.prev == NULL);
1694 assert(aNode->node.next != NULL);
1695 clientReplyContext *context = dynamic_cast<clientReplyContext *>(aNode->data.getRaw());
1696 assert (context);
1697 assert(context->http == http);
1698
1699 clientStreamNode *next = ( clientStreamNode *)aNode->node.next->data;
1700
1701 if (!context->ourNode)
1702 context->ourNode = aNode;
1703
1704 /* no cbdatareference, this is only used once, and safely */
1705 if (context->flags.storelogiccomplete) {
1706 StoreIOBuffer tempBuffer;
1707 tempBuffer.offset = next->readBuffer.offset + context->headers_sz;
1708 tempBuffer.length = next->readBuffer.length;
1709 tempBuffer.data = next->readBuffer.data;
1710
1711 storeClientCopy(context->sc, http->storeEntry(),
1712 tempBuffer, clientReplyContext::SendMoreData, context);
1713 return;
1714 }
1715
1716 if (context->http->request->method == Http::METHOD_PURGE) {
1717 context->purgeRequest();
1718 return;
1719 }
1720
1721 // OPTIONS with Max-Forwards:0 handled in clientProcessRequest()
1722
1723 if (context->http->request->method == Http::METHOD_TRACE) {
1724 if (context->http->request->header.getInt64(HDR_MAX_FORWARDS) == 0) {
1725 context->traceReply(aNode);
1726 return;
1727 }
1728
1729 /* continue forwarding, not finished yet. */
1730 http->logType = LOG_TCP_MISS;
1731
1732 context->doGetMoreData();
1733 } else
1734 context->identifyStoreObject();
1735 }
1736
1737 void
1738 clientReplyContext::doGetMoreData()
1739 {
1740 /* We still have to do store logic processing - vary, cache hit etc */
1741 if (http->storeEntry() != NULL) {
1742 /* someone found the object in the cache for us */
1743 StoreIOBuffer localTempBuffer;
1744
1745 http->storeEntry()->lock("clientReplyContext::doGetMoreData");
1746
1747 MemObject *mem_obj = http->storeEntry()->makeMemObject();
1748 if (!mem_obj->hasUris()) {
1749 /*
1750 * This if-block exists because we don't want to clobber
1751 * a preexiting mem_obj->method value if the mem_obj
1752 * already exists. For example, when a HEAD request
1753 * is a cache hit for a GET response, we want to keep
1754 * the method as GET.
1755 */
1756 mem_obj->setUris(storeId(), http->log_uri, http->request->method);
1757 /**
1758 * Here we can see if the object was
1759 * created using URL or alternative StoreID from helper.
1760 */
1761 debugs(88, 3, "storeId: " << http->storeEntry()->mem_obj->storeId());
1762 }
1763
1764 sc = storeClientListAdd(http->storeEntry(), this);
1765 #if USE_DELAY_POOLS
1766 sc->setDelayId(DelayId::DelayClient(http));
1767 #endif
1768
1769 assert(http->logType == LOG_TCP_HIT);
1770 reqofs = 0;
1771 /* guarantee nothing has been sent yet! */
1772 assert(http->out.size == 0);
1773 assert(http->out.offset == 0);
1774
1775 if (Ip::Qos::TheConfig.isHitTosActive()) {
1776 Ip::Qos::doTosLocalHit(http->getConn()->clientConnection);
1777 }
1778
1779 if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
1780 Ip::Qos::doNfmarkLocalHit(http->getConn()->clientConnection);
1781 }
1782
1783 localTempBuffer.offset = reqofs;
1784 localTempBuffer.length = getNextNode()->readBuffer.length;
1785 localTempBuffer.data = getNextNode()->readBuffer.data;
1786 storeClientCopy(sc, http->storeEntry(), localTempBuffer, CacheHit, this);
1787 } else {
1788 /* MISS CASE, http->logType is already set! */
1789 processMiss();
1790 }
1791 }
1792
1793 /** The next node has removed itself from the stream. */
1794 void
1795 clientReplyDetach(clientStreamNode * node, ClientHttpRequest * http)
1796 {
1797 /** detach from the stream */
1798 clientStreamDetach(node, http);
1799 }
1800
1801 /**
1802 * Accepts chunk of a http message in buf, parses prefix, filters headers and
1803 * such, writes processed message to the message recipient
1804 */
1805 void
1806 clientReplyContext::SendMoreData(void *data, StoreIOBuffer result)
1807 {
1808 clientReplyContext *context = static_cast<clientReplyContext *>(data);
1809 context->sendMoreData (result);
1810 }
1811
1812 void
1813 clientReplyContext::makeThisHead()
1814 {
1815 /* At least, I think thats what this does */
1816 dlinkDelete(&http->active, &ClientActiveRequests);
1817 dlinkAdd(http, &http->active, &ClientActiveRequests);
1818 }
1819
1820 bool
1821 clientReplyContext::errorInStream(StoreIOBuffer const &result, size_t const &sizeToProcess)const
1822 {
1823 return /* aborted request */
1824 (http->storeEntry() && EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED)) ||
1825 /* Upstream read error */ (result.flags.error) ||
1826 /* Upstream EOF */ (sizeToProcess == 0);
1827 }
1828
1829 void
1830 clientReplyContext::sendStreamError(StoreIOBuffer const &result)
1831 {
1832 /** call clientWriteComplete so the client socket gets closed
1833 *
1834 * We call into the stream, because we don't know that there is a
1835 * client socket!
1836 */
1837 debugs(88, 5, "clientReplyContext::sendStreamError: A stream error has occured, marking as complete and sending no data.");
1838 StoreIOBuffer localTempBuffer;
1839 flags.complete = 1;
1840 http->request->flags.streamError = true;
1841 localTempBuffer.flags.error = result.flags.error;
1842 clientStreamCallback((clientStreamNode*)http->client_stream.head->data, http, NULL,
1843 localTempBuffer);
1844 }
1845
1846 void
1847 clientReplyContext::pushStreamData(StoreIOBuffer const &result, char *source)
1848 {
1849 StoreIOBuffer localTempBuffer;
1850
1851 if (result.length == 0) {
1852 debugs(88, 5, "clientReplyContext::pushStreamData: marking request as complete due to 0 length store result");
1853 flags.complete = 1;
1854 }
1855
1856 assert(result.offset - headers_sz == next()->readBuffer.offset);
1857 localTempBuffer.offset = result.offset - headers_sz;
1858 localTempBuffer.length = result.length;
1859
1860 if (localTempBuffer.length)
1861 localTempBuffer.data = source;
1862
1863 clientStreamCallback((clientStreamNode*)http->client_stream.head->data, http, NULL,
1864 localTempBuffer);
1865 }
1866
1867 clientStreamNode *
1868 clientReplyContext::next() const
1869 {
1870 assert ( (clientStreamNode*)http->client_stream.head->next->data == getNextNode());
1871 return getNextNode();
1872 }
1873
1874 void
1875 clientReplyContext::sendBodyTooLargeError()
1876 {
1877 Ip::Address tmp_noaddr;
1878 tmp_noaddr.setNoAddr(); // TODO: make a global const
1879 http->logType = LOG_TCP_DENIED_REPLY;
1880 ErrorState *err = clientBuildError(ERR_TOO_BIG, Http::scForbidden, NULL,
1881 http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
1882 http->request);
1883 removeClientStoreReference(&(sc), http);
1884 HTTPMSGUNLOCK(reply);
1885 startError(err);
1886
1887 }
1888
1889 /// send 412 (Precondition Failed) to client
1890 void
1891 clientReplyContext::sendPreconditionFailedError()
1892 {
1893 http->logType = LOG_TCP_HIT;
1894 ErrorState *const err =
1895 clientBuildError(ERR_PRECONDITION_FAILED, Http::scPreconditionFailed,
1896 NULL, http->getConn()->clientConnection->remote, http->request);
1897 removeClientStoreReference(&sc, http);
1898 HTTPMSGUNLOCK(reply);
1899 startError(err);
1900 }
1901
1902 /// send 304 (Not Modified) to client
1903 void
1904 clientReplyContext::sendNotModified()
1905 {
1906 StoreEntry *e = http->storeEntry();
1907 const time_t timestamp = e->timestamp;
1908 HttpReply *const temprep = e->getReply()->make304();
1909 http->logType = LOG_TCP_IMS_HIT;
1910 removeClientStoreReference(&sc, http);
1911 createStoreEntry(http->request->method, RequestFlags());
1912 e = http->storeEntry();
1913 // Copy timestamp from the original entry so the 304
1914 // reply has a meaningful Age: header.
1915 e->timestampsSet();
1916 e->timestamp = timestamp;
1917 e->replaceHttpReply(temprep);
1918 e->complete();
1919 /*
1920 * TODO: why put this in the store and then serialise it and
1921 * then parse it again. Simply mark the request complete in
1922 * our context and write the reply struct to the client side.
1923 */
1924 triggerInitialStoreRead();
1925 }
1926
1927 /// send 304 (Not Modified) or 412 (Precondition Failed) to client
1928 /// depending on request method
1929 void
1930 clientReplyContext::sendNotModifiedOrPreconditionFailedError()
1931 {
1932 if (http->request->method == Http::METHOD_GET ||
1933 http->request->method == Http::METHOD_HEAD)
1934 sendNotModified();
1935 else
1936 sendPreconditionFailedError();
1937 }
1938
1939 void
1940 clientReplyContext::processReplyAccess ()
1941 {
1942 /* NP: this should probably soft-fail to a zero-sized-reply error ?? */
1943 assert(reply);
1944
1945 /** Don't block our own responses or HTTP status messages */
1946 if (http->logType == LOG_TCP_DENIED ||
1947 http->logType == LOG_TCP_DENIED_REPLY ||
1948 alwaysAllowResponse(reply->sline.status())) {
1949 headers_sz = reply->hdr_sz;
1950 processReplyAccessResult(ACCESS_ALLOWED);
1951 return;
1952 }
1953
1954 /** Check for reply to big error */
1955 if (reply->expectedBodyTooLarge(*http->request)) {
1956 sendBodyTooLargeError();
1957 return;
1958 }
1959
1960 headers_sz = reply->hdr_sz;
1961
1962 /** check for absent access controls (permit by default) */
1963 if (!Config.accessList.reply) {
1964 processReplyAccessResult(ACCESS_ALLOWED);
1965 return;
1966 }
1967
1968 /** Process http_reply_access lists */
1969 ACLFilledChecklist *replyChecklist =
1970 clientAclChecklistCreate(Config.accessList.reply, http);
1971 replyChecklist->reply = reply;
1972 HTTPMSGLOCK(replyChecklist->reply);
1973 replyChecklist->nonBlockingCheck(ProcessReplyAccessResult, this);
1974 }
1975
1976 void
1977 clientReplyContext::ProcessReplyAccessResult(allow_t rv, void *voidMe)
1978 {
1979 clientReplyContext *me = static_cast<clientReplyContext *>(voidMe);
1980 me->processReplyAccessResult(rv);
1981 }
1982
1983 void
1984 clientReplyContext::processReplyAccessResult(const allow_t &accessAllowed)
1985 {
1986 debugs(88, 2, "The reply for " << RequestMethodStr(http->request->method)
1987 << " " << http->uri << " is " << accessAllowed << ", because it matched '"
1988 << (AclMatchedName ? AclMatchedName : "NO ACL's") << "'" );
1989
1990 if (accessAllowed != ACCESS_ALLOWED) {
1991 ErrorState *err;
1992 err_type page_id;
1993 page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName, 1);
1994
1995 http->logType = LOG_TCP_DENIED_REPLY;
1996
1997 if (page_id == ERR_NONE)
1998 page_id = ERR_ACCESS_DENIED;
1999
2000 Ip::Address tmp_noaddr;
2001 tmp_noaddr.setNoAddr();
2002 err = clientBuildError(page_id, Http::scForbidden, NULL,
2003 http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
2004 http->request);
2005
2006 removeClientStoreReference(&sc, http);
2007
2008 HTTPMSGUNLOCK(reply);
2009
2010 startError(err);
2011
2012 return;
2013 }
2014
2015 /* Ok, the reply is allowed, */
2016 http->loggingEntry(http->storeEntry());
2017
2018 ssize_t body_size = reqofs - reply->hdr_sz;
2019 if (body_size < 0) {
2020 reqofs = reply->hdr_sz;
2021 body_size = 0;
2022 }
2023
2024 debugs(88, 3, "clientReplyContext::sendMoreData: Appending " <<
2025 (int) body_size << " bytes after " << reply->hdr_sz <<
2026 " bytes of headers");
2027
2028 #if USE_SQUID_ESI
2029
2030 if (http->flags.accel && reply->sline.status() != Http::scForbidden &&
2031 !alwaysAllowResponse(reply->sline.status()) &&
2032 esiEnableProcessing(reply)) {
2033 debugs(88, 2, "Enabling ESI processing for " << http->uri);
2034 clientStreamInsertHead(&http->client_stream, esiStreamRead,
2035 esiProcessStream, esiStreamDetach, esiStreamStatus, NULL);
2036 }
2037
2038 #endif
2039
2040 if (http->request->method == Http::METHOD_HEAD) {
2041 /* do not forward body for HEAD replies */
2042 body_size = 0;
2043 http->flags.done_copying = true;
2044 flags.complete = 1;
2045 }
2046
2047 assert (!flags.headersSent);
2048 flags.headersSent = true;
2049
2050 StoreIOBuffer localTempBuffer;
2051 char *buf = next()->readBuffer.data;
2052 char *body_buf = buf + reply->hdr_sz;
2053
2054 //Server side may disable ranges under some circumstances.
2055
2056 if ((!http->request->range))
2057 next()->readBuffer.offset = 0;
2058
2059 body_buf -= next()->readBuffer.offset;
2060
2061 if (next()->readBuffer.offset != 0) {
2062 if (next()->readBuffer.offset > body_size) {
2063 /* Can't use any of the body we received. send nothing */
2064 localTempBuffer.length = 0;
2065 localTempBuffer.data = NULL;
2066 } else {
2067 localTempBuffer.length = body_size - next()->readBuffer.offset;
2068 localTempBuffer.data = body_buf + next()->readBuffer.offset;
2069 }
2070 } else {
2071 localTempBuffer.length = body_size;
2072 localTempBuffer.data = body_buf;
2073 }
2074
2075 /* TODO??: move the data in the buffer back by the request header size */
2076 clientStreamCallback((clientStreamNode *)http->client_stream.head->data,
2077 http, reply, localTempBuffer);
2078
2079 return;
2080 }
2081
2082 void
2083 clientReplyContext::sendMoreData (StoreIOBuffer result)
2084 {
2085 if (deleting)
2086 return;
2087
2088 StoreEntry *entry = http->storeEntry();
2089
2090 ConnStateData * conn = http->getConn();
2091
2092 // too late, our conn is closing
2093 // TODO: should we also quit?
2094 if (conn == NULL) {
2095 debugs(33,3, "not sending more data to a closed connection" );
2096 return;
2097 }
2098 if (!conn->isOpen()) {
2099 debugs(33,3, "not sending more data to closing connection " << conn->clientConnection);
2100 return;
2101 }
2102 if (conn->pinning.zeroReply) {
2103 debugs(33,3, "not sending more data after a pinned zero reply " << conn->clientConnection);
2104 return;
2105 }
2106
2107 char *buf = next()->readBuffer.data;
2108
2109 if (buf != result.data) {
2110 /* we've got to copy some data */
2111 assert(result.length <= next()->readBuffer.length);
2112 memcpy(buf, result.data, result.length);
2113 }
2114
2115 if (reqofs==0 && !logTypeIsATcpHit(http->logType) && Comm::IsConnOpen(conn->clientConnection)) {
2116 if (Ip::Qos::TheConfig.isHitTosActive()) {
2117 Ip::Qos::doTosLocalMiss(conn->clientConnection, http->request->hier.code);
2118 }
2119 if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
2120 Ip::Qos::doNfmarkLocalMiss(conn->clientConnection, http->request->hier.code);
2121 }
2122 }
2123
2124 /* We've got the final data to start pushing... */
2125 flags.storelogiccomplete = 1;
2126
2127 reqofs += result.length;
2128
2129 assert(reqofs <= HTTP_REQBUF_SZ || flags.headersSent);
2130
2131 assert(http->request != NULL);
2132
2133 /* ESI TODO: remove this assert once everything is stable */
2134 assert(http->client_stream.head->data
2135 && cbdataReferenceValid(http->client_stream.head->data));
2136
2137 makeThisHead();
2138
2139 debugs(88, 5, "clientReplyContext::sendMoreData: " << http->uri << ", " <<
2140 reqofs << " bytes (" << result.length <<
2141 " new bytes)");
2142 debugs(88, 5, "clientReplyContext::sendMoreData:"
2143 << conn->clientConnection <<
2144 " '" << entry->url() << "'" <<
2145 " out.offset=" << http->out.offset);
2146
2147 /* update size of the request */
2148 reqsize = reqofs;
2149
2150 if (errorInStream(result, reqofs)) {
2151 sendStreamError(result);
2152 return;
2153 }
2154
2155 if (flags.headersSent) {
2156 pushStreamData (result, buf);
2157 return;
2158 }
2159
2160 cloneReply();
2161
2162 /* handle headers */
2163
2164 if (Config.onoff.log_mime_hdrs) {
2165 size_t k;
2166
2167 if ((k = headersEnd(buf, reqofs))) {
2168 safe_free(http->al->headers.reply);
2169 http->al->headers.reply = (char *)xcalloc(k + 1, 1);
2170 xstrncpy(http->al->headers.reply, buf, k);
2171 }
2172 }
2173
2174 holdingBuffer = result;
2175 processReplyAccess();
2176 return;
2177 }
2178
2179 /* Using this breaks the client layering just a little!
2180 */
2181 void
2182 clientReplyContext::createStoreEntry(const HttpRequestMethod& m, RequestFlags reqFlags)
2183 {
2184 assert(http != NULL);
2185 /*
2186 * For erroneous requests, we might not have a h->request,
2187 * so make a fake one.
2188 */
2189
2190 if (http->request == NULL) {
2191 http->request = new HttpRequest(m, AnyP::PROTO_NONE, null_string);
2192 HTTPMSGLOCK(http->request);
2193 }
2194
2195 StoreEntry *e = storeCreateEntry(storeId(), http->log_uri, reqFlags, m);
2196
2197 // Make entry collapsable ASAP, to increase collapsing chances for others,
2198 // TODO: every must-revalidate and similar request MUST reach the origin,
2199 // but do we have to prohibit others from collapsing on that request?
2200 if (Config.onoff.collapsed_forwarding && reqFlags.cachable &&
2201 !reqFlags.needValidation &&
2202 (m == Http::METHOD_GET || m == Http::METHOD_HEAD)) {
2203 // make the entry available for future requests now
2204 Store::Root().allowCollapsing(e, reqFlags, m);
2205 }
2206
2207 sc = storeClientListAdd(e, this);
2208
2209 #if USE_DELAY_POOLS
2210 sc->setDelayId(DelayId::DelayClient(http));
2211 #endif
2212
2213 reqofs = 0;
2214
2215 reqsize = 0;
2216
2217 /* I don't think this is actually needed! -- adrian */
2218 /* http->reqbuf = http->norm_reqbuf; */
2219 // assert(http->reqbuf == http->norm_reqbuf);
2220 /* The next line is illegal because we don't know if the client stream
2221 * buffers have been set up
2222 */
2223 // storeClientCopy(http->sc, e, 0, HTTP_REQBUF_SZ, http->reqbuf,
2224 // SendMoreData, this);
2225 /* So, we mark the store logic as complete */
2226 flags.storelogiccomplete = 1;
2227
2228 /* and get the caller to request a read, from whereever they are */
2229 /* NOTE: after ANY data flows down the pipe, even one step,
2230 * this function CAN NOT be used to manage errors
2231 */
2232 http->storeEntry(e);
2233 }
2234
2235 ErrorState *
2236 clientBuildError(err_type page_id, Http::StatusCode status, char const *url,
2237 Ip::Address &src_addr, HttpRequest * request)
2238 {
2239 ErrorState *err = new ErrorState(page_id, status, request);
2240 err->src_addr = src_addr;
2241
2242 if (url)
2243 err->url = xstrdup(url);
2244
2245 return err;
2246 }