]> git.ipfire.org Git - thirdparty/squid.git/blame - src/client_side_reply.cc
more API late-catchups
[thirdparty/squid.git] / src / client_side_reply.cc
CommitLineData
edce4d98 1
2/*
7432bf18 3 * $Id: client_side_reply.cc,v 1.8 2002/09/24 12:09:24 robertc Exp $
edce4d98 4 *
5 * DEBUG: section 88 Client-side Reply Routines
6 * AUTHOR: Robert Collins (Originally Duane Wessels in client_side.c)
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 */
35
36#include "squid.h"
c8be6d7b 37#include "StoreClient.h"
38#include "clientStream.h"
edce4d98 39
40typedef struct _clientReplyContext {
41 clientHttpRequest *http;
42 int headers_sz;
43 store_client *sc; /* The store_client we're using */
44 store_client *old_sc; /* ... for entry to be validated */
c8be6d7b 45 StoreIOBuffer tempBuffer; /* For use in validating requests via IMS */
edce4d98 46 int old_reqsize; /* ... again, for the buffer */
47 size_t reqsize;
48 off_t reqofs;
49 char tempbuf[HTTP_REQBUF_SZ]; /* a temporary buffer if we need working storage */
50#if USE_CACHE_DIGESTS
51 const char *lookup_type; /* temporary hack: storeGet() result: HIT/MISS/NONE */
52#endif
53 struct {
54 int storelogiccomplete:1;
55 int complete:1; /* we have read all we can from upstream */
029612cf 56 int headersSent:1;
edce4d98 57 } flags;
58 clientStreamNode *ourNode; /* This will go away if/when this file gets refactored some more */
59} clientReplyContext;
60
61CBDATA_TYPE(clientReplyContext);
62
edce4d98 63/* Local functions */
64static int clientGotNotEnough(clientHttpRequest const *);
65static int clientReplyBodyTooLarge(HttpReply *, ssize_t);
66static int clientOnlyIfCached(clientHttpRequest * http);
67static void clientProcessExpired(clientReplyContext *);
68static void clientProcessMiss(clientReplyContext *);
69static STCB clientCacheHit;
70static void clientProcessOnlyIfCachedMiss(clientReplyContext *);
71static int clientGetsOldEntry(StoreEntry * new, StoreEntry * old,
72 request_t * request);
73static STCB clientHandleIMSReply;
74static int modifiedSince(StoreEntry *, request_t *);
75static log_type clientIdentifyStoreObject(clientHttpRequest * http);
76static void clientPurgeRequest(clientReplyContext *);
77static void clientTraceReply(clientStreamNode *, clientReplyContext *);
78static StoreEntry *clientCreateStoreEntry(clientReplyContext *, method_t,
79 request_flags);
80static STCB clientSendMoreData;
81static void clientRemoveStoreReference(clientReplyContext *, store_client **,
82 StoreEntry **);
83static void clientReplyContextSaveState(clientReplyContext *,
84 clientHttpRequest *);
85static void clientReplyContextRestoreState(clientReplyContext *,
86 clientHttpRequest *);
87extern CSS clientReplyStatus;
88extern ErrorState *clientBuildError(err_type, http_status, char const *,
89 struct in_addr *, request_t *);
90
c8be6d7b 91static void startError(clientReplyContext * context, clientHttpRequest * http, ErrorState * err);
92static void triggerStoreReadWithClientParameters(clientReplyContext * context, clientHttpRequest * http);
93
94
edce4d98 95/* The clientReply clean interface */
96/* privates */
97static FREE clientReplyFree;
98
99void
100clientReplyFree(void *data)
101{
102 clientReplyContext *this = data;
103 clientRemoveStoreReference(this, &this->sc, &this->http->entry);
104 /* old_entry might still be set if we didn't yet get the reply
105 * code in clientHandleIMSReply() */
106 clientRemoveStoreReference(this, &this->old_sc, &this->http->old_entry);
c8be6d7b 107 safe_free(this->tempBuffer.data);
edce4d98 108 cbdataReferenceDone(this->http);
109}
110
111void *
112clientReplyNewContext(clientHttpRequest * clientContext)
113{
114 clientReplyContext *context;
115 CBDATA_INIT_TYPE_FREECB(clientReplyContext, clientReplyFree);
116 context = cbdataAlloc(clientReplyContext);
117 context->http = cbdataReference(clientContext);
118 return context;
119}
120
121/* create an error in the store awaiting the client side to read it. */
122void
123clientSetReplyToError(void *data,
124 err_type err, http_status status, method_t method, char const *uri,
125 struct in_addr *addr, request_t * failedrequest, char *unparsedrequest,
126 auth_user_request_t * auth_user_request)
127{
128 clientReplyContext *context = data;
129 ErrorState *errstate =
130 clientBuildError(err, status, uri, addr, failedrequest);
131 if (unparsedrequest)
132 errstate->request_hdrs = xstrdup(unparsedrequest);
133
134 if (status == HTTP_NOT_IMPLEMENTED && context->http->request)
135 /* prevent confusion over whether we default to persistent or not */
136 context->http->request->flags.proxy_keepalive = 0;
29b8d8d6 137 context->http->al.http.code = errstate->httpStatus;
edce4d98 138
139 context->http->entry =
140 clientCreateStoreEntry(context, method, null_request_flags);
141 if (auth_user_request) {
142 errstate->auth_user_request = auth_user_request;
143 authenticateAuthUserRequestLock(errstate->auth_user_request);
144 }
145 assert(errstate->callback_data == NULL);
146 errorAppendEntry(context->http->entry, errstate);
147 /* Now the caller reads to get this */
148}
149
150void
151clientRemoveStoreReference(clientReplyContext * context, store_client ** scp,
152 StoreEntry ** ep)
153{
154 StoreEntry *e;
155 store_client *sc = *scp;
156 if ((e = *ep) != NULL) {
157 *ep = NULL;
158 storeUnregister(sc, e, context);
159 *scp = NULL;
160 storeUnlockObject(e);
161 }
162}
163
164void
165clientReplyContextSaveState(clientReplyContext * this, clientHttpRequest * http)
166{
167 assert(this->old_sc == NULL);
168 debug(88, 1) ("clientReplyContextSaveState: saving store context\n");
169 http->old_entry = http->entry;
170 this->old_sc = this->sc;
171 this->old_reqsize = this->reqsize;
c8be6d7b 172 this->tempBuffer.offset = this->reqofs;
edce4d98 173 /* Prevent accessing the now saved entries */
174 http->entry = NULL;
175 this->sc = NULL;
176 this->reqsize = 0;
177 this->reqofs = 0;
178}
179
180void
181clientReplyContextRestoreState(clientReplyContext * this,
182 clientHttpRequest * http)
183{
184 assert(this->old_sc != NULL);
185 debug(88, 1) ("clientReplyContextRestoreState: Restoring store context\n");
186 http->entry = http->old_entry;
187 this->sc = this->old_sc;
188 this->reqsize = this->old_reqsize;
c8be6d7b 189 this->reqofs = this->tempBuffer.offset;
edce4d98 190 /* Prevent accessed the old saved entries */
191 http->old_entry = NULL;
192 this->old_sc = NULL;
193 this->old_reqsize = 0;
c8be6d7b 194 this->tempBuffer.offset = 0;
195}
196
197void
198startError(clientReplyContext * context, clientHttpRequest * http, ErrorState * err)
199{
200 http->entry = clientCreateStoreEntry(context, http->request->method, null_request_flags);
201 triggerStoreReadWithClientParameters(context, http);
202 errorAppendEntry(http->entry, err);
edce4d98 203}
204
c8be6d7b 205void
206triggerStoreReadWithClientParameters(clientReplyContext * context, clientHttpRequest * http)
207{
208 clientStreamNode *next = http->client_stream.head->next->data;
209 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
210 /* collapse this to one object if we never tickle the assert */
211 assert(context->http == http);
212 tempBuffer.offset = next->readBuffer.offset;
213 tempBuffer.length = next->readBuffer.length;
214 tempBuffer.data = next->readBuffer.data;
215 storeClientCopy(context->sc, http->entry, tempBuffer, clientSendMoreData, context);
216}
edce4d98 217
218/* there is an expired entry in the store.
219 * setup a temporary buffer area and perform an IMS to the origin
220 */
221static void
222clientProcessExpired(clientReplyContext * context)
223{
224 clientHttpRequest *http = context->http;
225 char *url = http->uri;
226 StoreEntry *entry = NULL;
227 debug(88, 3) ("clientProcessExpired: '%s'\n", http->uri);
228 assert(http->entry->lastmod >= 0);
229 /*
230 * check if we are allowed to contact other servers
231 * @?@: Instead of a 504 (Gateway Timeout) reply, we may want to return
232 * a stale entry *if* it matches client requirements
233 */
234 if (clientOnlyIfCached(http)) {
235 clientProcessOnlyIfCachedMiss(context);
236 return;
237 }
238 http->request->flags.refresh = 1;
239#if STORE_CLIENT_LIST_DEBUG
c8be6d7b 240 /* Prevent a race with the store client memory free routines
edce4d98 241 */
c8be6d7b 242 assert(storeClientIsThisAClient(context->sc, context));
edce4d98 243#endif
244 /* Prepare to make a new temporary request */
245 clientReplyContextSaveState(context, http);
246 entry = storeCreateEntry(url,
247 http->log_uri, http->request->flags, http->request->method);
248 /* NOTE, don't call storeLockObject(), storeCreateEntry() does it */
249 context->sc = storeClientListAdd(entry, context);
250#if DELAY_POOLS
251 /* delay_id is already set on original store client */
252 delaySetStoreClient(context->sc, delayClient(http));
253#endif
254 http->request->lastmod = http->old_entry->lastmod;
255 debug(88, 5) ("clientProcessExpired: lastmod %ld\n",
256 (long int) entry->lastmod);
257 http->entry = entry;
c8be6d7b 258 assert(http->out.offset == 0);
edce4d98 259 fwdStart(http->conn ? http->conn->fd : -1, http->entry, http->request);
260 /* Register with storage manager to receive updates when data comes in. */
261 if (EBIT_TEST(entry->flags, ENTRY_ABORTED))
262 debug(88, 0) ("clientProcessExpired: found ENTRY_ABORTED object\n");
c8be6d7b 263 {
264 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
265 /* start counting the length from 0 */
266 tempBuffer.offset = 0;
267 tempBuffer.length = HTTP_REQBUF_SZ;
268 tempBuffer.data = context->tempbuf;
269 storeClientCopy(context->sc, entry,
270 tempBuffer, clientHandleIMSReply, context);
271 }
edce4d98 272}
273
274int
275modifiedSince(StoreEntry * entry, request_t * request)
276{
277 int object_length;
278 MemObject *mem = entry->mem_obj;
279 time_t mod_time = entry->lastmod;
280 debug(88, 3) ("modifiedSince: '%s'\n", storeUrl(entry));
281 if (mod_time < 0)
282 mod_time = entry->timestamp;
283 debug(88, 3) ("modifiedSince: mod_time = %ld\n", (long int) mod_time);
284 if (mod_time < 0)
285 return 1;
286 /* Find size of the object */
287 object_length = mem->reply->content_length;
288 if (object_length < 0)
289 object_length = contentLen(entry);
290 if (mod_time > request->ims) {
291 debug(88, 3) ("--> YES: entry newer than client\n");
292 return 1;
293 } else if (mod_time < request->ims) {
294 debug(88, 3) ("--> NO: entry older than client\n");
295 return 0;
296 } else if (request->imslen < 0) {
297 debug(88, 3) ("--> NO: same LMT, no client length\n");
298 return 0;
299 } else if (request->imslen == object_length) {
300 debug(88, 3) ("--> NO: same LMT, same length\n");
301 return 0;
302 } else {
303 debug(88, 3) ("--> YES: same LMT, different length\n");
304 return 1;
305 }
306}
307
308static int
309clientGetsOldEntry(StoreEntry * new_entry, StoreEntry * old_entry,
310 request_t * request)
311{
312 const http_status status = new_entry->mem_obj->reply->sline.status;
313 if (0 == status) {
314 debug(88, 5) ("clientGetsOldEntry: YES, broken HTTP reply\n");
315 return 1;
316 }
317 /* If the reply is a failure then send the old object as a last
318 * resort */
319 if (status >= 500 && status < 600) {
320 debug(88, 3) ("clientGetsOldEntry: YES, failure reply=%d\n", status);
321 return 1;
322 }
323 /* If the reply is anything but "Not Modified" then
324 * we must forward it to the client */
325 if (HTTP_NOT_MODIFIED != status) {
326 debug(88, 5) ("clientGetsOldEntry: NO, reply=%d\n", status);
327 return 0;
328 }
329 /* If the client did not send IMS in the request, then it
330 * must get the old object, not this "Not Modified" reply */
331 if (!request->flags.ims) {
332 debug(88, 5) ("clientGetsOldEntry: YES, no client IMS\n");
333 return 1;
334 }
335 /* If the client IMS time is prior to the entry LASTMOD time we
336 * need to send the old object */
337 if (modifiedSince(old_entry, request)) {
338 debug(88, 5) ("clientGetsOldEntry: YES, modified since %ld\n",
339 (long int) request->ims);
340 return 1;
341 }
342 debug(88, 5) ("clientGetsOldEntry: NO, new one is fine\n");
343 return 0;
344}
345
346void
c8be6d7b 347clientHandleIMSReply(void *data, StoreIOBuffer result)
edce4d98 348{
349 clientReplyContext *context = data;
350 clientHttpRequest *http = context->http;
351 StoreEntry *entry = http->entry;
352 MemObject *mem;
353 const char *url = storeUrl(entry);
354 int unlink_request = 0;
355 StoreEntry *oldentry;
356 http_status status;
c8be6d7b 357 debug(88, 3) ("clientHandleIMSReply: %s, %lu bytes\n", url,
358 (long unsigned) result.length);
edce4d98 359 if (entry == NULL) {
360 return;
361 }
c8be6d7b 362 if (result.flags.error && !EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
edce4d98 363 return;
364 }
365 /* update size of the request */
c8be6d7b 366 context->reqsize = result.length + context->reqofs;
edce4d98 367 context->reqofs = context->reqsize;
368 mem = entry->mem_obj;
369 status = mem->reply->sline.status;
370 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
371 debug(88, 3) ("clientHandleIMSReply: ABORTED '%s'\n", url);
372 /* We have an existing entry, but failed to validate it */
373 /* Its okay to send the old one anyway */
29b8d8d6 374 http->logType = LOG_TCP_REFRESH_FAIL_HIT;
edce4d98 375 clientRemoveStoreReference(context, &context->sc, &entry);
376 /* Get the old request back */
377 clientReplyContextRestoreState(context, http);
378 entry = http->entry;
c8be6d7b 379 return;
380 }
381 if (STORE_PENDING == entry->store_status && 0 == status) {
edce4d98 382 /* more headers needed to decide */
383 debug(88, 3) ("clientHandleIMSReply: Incomplete headers for '%s'\n",
384 url);
c8be6d7b 385 if (result.length + context->reqofs >= HTTP_REQBUF_SZ) {
edce4d98 386 /* will not get any bigger than that */
c8be6d7b 387 debug(88, 3)
edce4d98 388 ("clientHandleIMSReply: Reply is too large '%s', using old entry\n",
389 url);
390 /* use old entry, this repeats the code abovez */
29b8d8d6 391 http->logType = LOG_TCP_REFRESH_FAIL_HIT;
edce4d98 392 clientRemoveStoreReference(context, &context->sc, &entry);
393 entry = http->entry = http->old_entry;
394 /* Get the old request back */
395 clientReplyContextRestoreState(context, http);
396 entry = http->entry;
edce4d98 397 } else {
c8be6d7b 398 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
399 tempBuffer.offset = context->reqofs;
400 tempBuffer.length = HTTP_REQBUF_SZ - context->reqofs;
401 tempBuffer.data = context->tempbuf + context->reqofs;
edce4d98 402 storeClientCopy(context->sc, entry,
c8be6d7b 403 tempBuffer,
edce4d98 404 clientHandleIMSReply, context);
edce4d98 405 }
c8be6d7b 406 return;
407 }
408 if (clientGetsOldEntry(entry, http->old_entry, http->request)) {
edce4d98 409 /* We initiated the IMS request, the client is not expecting
410 * 304, so put the good one back. First, make sure the old entry
411 * headers have been loaded from disk. */
412 clientStreamNode *next = context->http->client_stream.head->next->data;
c8be6d7b 413 StoreIOBuffer tempresult = EMPTYIOBUFFER;
edce4d98 414 oldentry = http->old_entry;
29b8d8d6 415 http->logType = LOG_TCP_REFRESH_HIT;
edce4d98 416 if (oldentry->mem_obj->request == NULL) {
417 oldentry->mem_obj->request = requestLink(mem->request);
418 unlink_request = 1;
419 }
420 /* Don't memcpy() the whole reply structure here. For example,
421 * www.thegist.com (Netscape/1.13) returns a content-length for
422 * 304's which seems to be the length of the 304 HEADERS!!! and
423 * not the body they refer to. */
424 httpReplyUpdateOnNotModified(oldentry->mem_obj->reply, mem->reply);
425 storeTimestampsSet(oldentry);
426 clientRemoveStoreReference(context, &context->sc, &entry);
427 oldentry->timestamp = squid_curtime;
428 if (unlink_request) {
429 requestUnlink(oldentry->mem_obj->request);
430 oldentry->mem_obj->request = NULL;
431 }
432 /* Get the old request back */
433 clientReplyContextRestoreState(context, http);
434 entry = http->entry;
435 /* here the data to send is in the next nodes buffers already */
436 assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED));
c8be6d7b 437 tempresult.length = context->reqsize;
438 tempresult.data = next->readBuffer.data;
439 clientSendMoreData(context, tempresult);
440 return;
441 }
442 debug(88, 3) ("clientHandleIMSReply: Sending client the IMS reply for '%s'\n", url);
443 {
edce4d98 444 /* the client can handle this reply, whatever it is */
c8be6d7b 445 StoreIOBuffer tempresult = EMPTYIOBUFFER;
29b8d8d6 446 http->logType = LOG_TCP_REFRESH_MISS;
edce4d98 447 if (HTTP_NOT_MODIFIED == mem->reply->sline.status) {
448 httpReplyUpdateOnNotModified(http->old_entry->mem_obj->reply,
449 mem->reply);
450 storeTimestampsSet(http->old_entry);
29b8d8d6 451 http->logType = LOG_TCP_REFRESH_HIT;
edce4d98 452 }
453 clientRemoveStoreReference(context, &context->old_sc, &http->old_entry);
454 /* here the data to send is the data we just recieved */
c8be6d7b 455 context->tempBuffer.offset = 0;
edce4d98 456 context->old_reqsize = 0;
af29fa86 457 /* clientSendMoreData tracks the offset as well.
458 * Force it back to zero */
459 context->reqofs = 0;
edce4d98 460 assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED));
461 /* TODO: provide SendMoreData with the ready parsed reply */
c8be6d7b 462 tempresult.length = context->reqsize;
463 tempresult.data = context->tempbuf;
464 clientSendMoreData(context, tempresult);
465 return;
edce4d98 466 }
467}
468
469CSR clientGetMoreData;
470CSD clientReplyDetach;
471
472/*
473 * clientCacheHit should only be called until the HTTP reply headers
474 * have been parsed. Normally this should be a single call, but
475 * it might take more than one. As soon as we have the headers,
476 * we hand off to clientSendMoreData, clientProcessExpired, or
477 * clientProcessMiss.
478 */
479void
c8be6d7b 480clientCacheHit(void *data, StoreIOBuffer result)
edce4d98 481{
482 clientReplyContext *context = data;
483 clientHttpRequest *http = context->http;
484 StoreEntry *e = http->entry;
485 MemObject *mem;
486 request_t *r = http->request;
c8be6d7b 487 debug(88, 3) ("clientCacheHit: %s, %ud bytes\n", http->uri, result.length);
edce4d98 488 if (http->entry == NULL) {
489 debug(88, 3) ("clientCacheHit: request aborted\n");
490 return;
c8be6d7b 491 } else if (result.flags.error) {
edce4d98 492 /* swap in failure */
493 debug(88, 3) ("clientCacheHit: swapin failure for %s\n", http->uri);
29b8d8d6 494 http->logType = LOG_TCP_SWAPFAIL_MISS;
edce4d98 495 clientRemoveStoreReference(context, &context->sc, &http->entry);
496 clientProcessMiss(context);
497 return;
498 }
c8be6d7b 499 assert(result.length > 0);
edce4d98 500 mem = e->mem_obj;
501 assert(!EBIT_TEST(e->flags, ENTRY_ABORTED));
502 /* update size of the request */
c8be6d7b 503 context->reqsize = result.length + context->reqofs;
edce4d98 504 if (mem->reply->sline.status == 0) {
505 /*
506 * we don't have full reply headers yet; either wait for more or
507 * punt to clientProcessMiss.
508 */
509 if (e->mem_status == IN_MEMORY || e->store_status == STORE_OK) {
510 clientProcessMiss(context);
c8be6d7b 511 } else if (result.length + context->reqofs >= HTTP_REQBUF_SZ
edce4d98 512 && http->out.offset == 0) {
513 clientProcessMiss(context);
514 } else {
515 clientStreamNode *next;
c8be6d7b 516 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
edce4d98 517 debug(88, 3) ("clientCacheHit: waiting for HTTP reply headers\n");
c8be6d7b 518 context->reqofs += result.length;
edce4d98 519 assert(context->reqofs <= HTTP_REQBUF_SZ);
520 /* get the next users' buffer */
c8be6d7b 521 /* FIXME: HTTP_REQBUF_SZ must be wrong here ??!
522 */
edce4d98 523 next = context->http->client_stream.head->next->data;
c8be6d7b 524 tempBuffer.offset = http->out.offset + context->reqofs;
525 tempBuffer.length = HTTP_REQBUF_SZ;
526 tempBuffer.data = next->readBuffer.data + context->reqofs;
edce4d98 527 storeClientCopy(context->sc, e,
c8be6d7b 528 tempBuffer, clientCacheHit, context);
edce4d98 529 }
530 return;
531 }
532 /*
533 * Got the headers, now grok them
534 */
29b8d8d6 535 assert(http->logType == LOG_TCP_HIT);
edce4d98 536 switch (varyEvaluateMatch(e, r)) {
537 case VARY_NONE:
538 /* No variance detected. Continue as normal */
539 break;
540 case VARY_MATCH:
541 /* This is the correct entity for this request. Continue */
542 debug(88, 2) ("clientProcessHit: Vary MATCH!\n");
543 break;
544 case VARY_OTHER:
545 /* This is not the correct entity for this request. We need
546 * to requery the cache.
547 */
548 clientRemoveStoreReference(context, &context->sc, &http->entry);
549 e = NULL;
550 /* Note: varyEvalyateMatch updates the request with vary information
551 * so we only get here once. (it also takes care of cancelling loops)
552 */
553 debug(88, 2) ("clientProcessHit: Vary detected!\n");
554 clientGetMoreData(context->ourNode, http);
555 return;
556 case VARY_CANCEL:
557 /* varyEvaluateMatch found a object loop. Process as miss */
558 debug(88, 1) ("clientProcessHit: Vary object loop!\n");
559 clientProcessMiss(context);
560 return;
561 }
562 if (r->method == METHOD_PURGE) {
563 clientRemoveStoreReference(context, &context->sc, &http->entry);
564 e = NULL;
565 clientPurgeRequest(context);
566 return;
567 }
568 if (storeCheckNegativeHit(e)) {
29b8d8d6 569 http->logType = LOG_TCP_NEGATIVE_HIT;
c8be6d7b 570 clientSendMoreData(context, result);
edce4d98 571 } else if (r->method == METHOD_HEAD) {
572 /*
573 * RFC 2068 seems to indicate there is no "conditional HEAD"
574 * request. We cannot validate a cached object for a HEAD
575 * request, nor can we return 304.
576 */
577 if (e->mem_status == IN_MEMORY)
29b8d8d6 578 http->logType = LOG_TCP_MEM_HIT;
c8be6d7b 579 clientSendMoreData(context, result);
edce4d98 580 } else if (refreshCheckHTTP(e, r) && !http->flags.internal) {
581 debug(88, 5) ("clientCacheHit: in refreshCheck() block\n");
582 /*
583 * We hold a stale copy; it needs to be validated
584 */
585 /*
586 * The 'need_validation' flag is used to prevent forwarding
587 * loops between siblings. If our copy of the object is stale,
588 * then we should probably only use parents for the validation
589 * request. Otherwise two siblings could generate a loop if
590 * both have a stale version of the object.
591 */
592 r->flags.need_validation = 1;
593 if (e->lastmod < 0) {
594 /*
595 * Previous reply didn't have a Last-Modified header,
596 * we cannot revalidate it.
597 */
29b8d8d6 598 http->logType = LOG_TCP_MISS;
edce4d98 599 clientProcessMiss(context);
600 } else if (r->flags.nocache) {
601 /*
602 * This did not match a refresh pattern that overrides no-cache
603 * we should honour the client no-cache header.
604 */
29b8d8d6 605 http->logType = LOG_TCP_CLIENT_REFRESH_MISS;
edce4d98 606 clientProcessMiss(context);
607 } else if (r->protocol == PROTO_HTTP) {
608 /*
609 * Object needs to be revalidated
610 * XXX This could apply to FTP as well, if Last-Modified is known.
611 */
29b8d8d6 612 http->logType = LOG_TCP_REFRESH_MISS;
edce4d98 613 clientProcessExpired(context);
614 } else {
615 /*
616 * We don't know how to re-validate other protocols. Handle
617 * them as if the object has expired.
618 */
29b8d8d6 619 http->logType = LOG_TCP_MISS;
edce4d98 620 clientProcessMiss(context);
621 }
622 } else if (r->flags.ims) {
623 /*
624 * Handle If-Modified-Since requests from the client
625 */
626 if (mem->reply->sline.status != HTTP_OK) {
627 debug(88, 4) ("clientCacheHit: Reply code %d != 200\n",
628 mem->reply->sline.status);
29b8d8d6 629 http->logType = LOG_TCP_MISS;
edce4d98 630 clientProcessMiss(context);
631 } else if (modifiedSince(e, http->request)) {
29b8d8d6 632 http->logType = LOG_TCP_IMS_HIT;
c8be6d7b 633 clientSendMoreData(context, result);
edce4d98 634 } else {
edce4d98 635 time_t timestamp = e->timestamp;
636 MemBuf mb = httpPacked304Reply(e->mem_obj->reply);
29b8d8d6 637 http->logType = LOG_TCP_IMS_HIT;
edce4d98 638 clientRemoveStoreReference(context, &context->sc, &http->entry);
639 http->entry = e =
640 clientCreateStoreEntry(context, http->request->method,
641 null_request_flags);
642 /*
643 * Copy timestamp from the original entry so the 304
644 * reply has a meaningful Age: header.
645 */
646 e->timestamp = timestamp;
647 httpReplyParse(e->mem_obj->reply, mb.buf, mb.size);
648 storeAppend(e, mb.buf, mb.size);
649 memBufClean(&mb);
650 storeComplete(e);
651 /* TODO: why put this in the store and then serialise it and then parse it again.
652 * Simply mark the request complete in our context and
653 * write the reply struct to the client side
654 */
c8be6d7b 655 triggerStoreReadWithClientParameters(context, http);
edce4d98 656 }
657 } else {
658 /*
659 * plain ol' cache hit
660 */
661 if (e->mem_status == IN_MEMORY)
29b8d8d6 662 http->logType = LOG_TCP_MEM_HIT;
edce4d98 663 else if (Config.onoff.offline)
29b8d8d6 664 http->logType = LOG_TCP_OFFLINE_HIT;
c8be6d7b 665 clientSendMoreData(context, result);
edce4d98 666 }
667}
668
669/*
670 * Prepare to fetch the object as it's a cache miss of some kind.
671 */
672void
673clientProcessMiss(clientReplyContext * context)
674{
675 clientHttpRequest *http = context->http;
676 char *url = http->uri;
677 request_t *r = http->request;
678 ErrorState *err = NULL;
679 debug(88, 4) ("clientProcessMiss: '%s %s'\n",
680 RequestMethodStr[r->method], url);
681 /*
682 * We might have a left-over StoreEntry from a failed cache hit
683 * or IMS request.
684 */
685 if (http->entry) {
686 if (EBIT_TEST(http->entry->flags, ENTRY_SPECIAL)) {
687 debug(88, 0) ("clientProcessMiss: miss on a special object (%s).\n",
688 url);
29b8d8d6 689 debug(88, 0) ("\tlog_type = %s\n", log_tags[http->logType]);
edce4d98 690 storeEntryDump(http->entry, 1);
691 }
692 clientRemoveStoreReference(context, &context->sc, &http->entry);
693 }
694 if (r->method == METHOD_PURGE) {
695 clientPurgeRequest(context);
696 return;
697 }
698 if (clientOnlyIfCached(http)) {
699 clientProcessOnlyIfCachedMiss(context);
700 return;
701 }
702 /*
703 * Deny loops when running in accelerator/transproxy mode.
704 */
705 if (http->flags.accel && r->flags.loopdetect) {
edce4d98 706 http->al.http.code = HTTP_FORBIDDEN;
707 err =
708 clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL,
709 &http->conn->peer.sin_addr, http->request);
710 http->entry =
711 clientCreateStoreEntry(context, r->method, null_request_flags);
712 errorAppendEntry(http->entry, err);
c8be6d7b 713 triggerStoreReadWithClientParameters(context, http);
edce4d98 714 return;
715 } else {
edce4d98 716 assert(http->out.offset == 0);
717 http->entry = clientCreateStoreEntry(context, r->method, r->flags);
c8be6d7b 718 triggerStoreReadWithClientParameters(context, http);
edce4d98 719 if (http->redirect.status) {
720 HttpReply *rep = httpReplyCreate();
721#if LOG_TCP_REDIRECTS
29b8d8d6 722 http->logType = LOG_TCP_REDIRECT;
edce4d98 723#endif
724 storeReleaseRequest(http->entry);
725 httpRedirectReply(rep, http->redirect.status,
726 http->redirect.location);
727 httpReplySwapOut(rep, http->entry);
728 httpReplyDestroy(rep);
729 storeComplete(http->entry);
730 return;
731 }
732 if (http->flags.internal)
733 r->protocol = PROTO_INTERNAL;
734 fwdStart(http->conn ? http->conn->fd : -1, http->entry, r);
735 }
736}
737
738/*
739 * client issued a request with an only-if-cached cache-control directive;
740 * we did not find a cached object that can be returned without
741 * contacting other servers;
742 * respond with a 504 (Gateway Timeout) as suggested in [RFC 2068]
743 */
744static void
745clientProcessOnlyIfCachedMiss(clientReplyContext * context)
746{
747 clientHttpRequest *http = context->http;
748 char *url = http->uri;
749 request_t *r = http->request;
750 ErrorState *err = NULL;
edce4d98 751 debug(88, 4) ("clientProcessOnlyIfCachedMiss: '%s %s'\n",
752 RequestMethodStr[r->method], url);
753 http->al.http.code = HTTP_GATEWAY_TIMEOUT;
754 err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, HTTP_GATEWAY_TIMEOUT, NULL,
755 &http->conn->peer.sin_addr, http->request);
756 clientRemoveStoreReference(context, &context->sc, &http->entry);
c8be6d7b 757 startError(context, http, err);
edce4d98 758}
759
760void
761clientPurgeRequest(clientReplyContext * context)
762{
763 clientHttpRequest *http = context->http;
764 StoreEntry *entry;
765 ErrorState *err = NULL;
766 HttpReply *r;
767 http_status status = HTTP_NOT_FOUND;
768 clientStreamNode *next;
769 http_version_t version;
770 debug(88, 3) ("Config2.onoff.enable_purge = %d\n",
771 Config2.onoff.enable_purge);
772 next = http->client_stream.head->next->data;
773 if (!Config2.onoff.enable_purge) {
29b8d8d6 774 http->logType = LOG_TCP_DENIED;
edce4d98 775 err =
776 clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL,
777 &http->conn->peer.sin_addr, http->request);
c8be6d7b 778 startError(context, http, err);
edce4d98 779 return;
780 }
781 /* Release both IP cache */
782 ipcacheInvalidate(http->request->host);
783
784 /* RC FIXME: This doesn't make sense - we should only ever be here on PURGE
785 * requests!
786 */
787 if (!http->flags.purging) {
788 /* Try to find a base entry */
789 http->flags.purging = 1;
790 entry = storeGetPublicByRequestMethod(http->request, METHOD_GET);
791 if (!entry)
792 entry = storeGetPublicByRequestMethod(http->request, METHOD_HEAD);
793 if (entry) {
c8be6d7b 794 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
edce4d98 795 /* Swap in the metadata */
796 http->entry = entry;
797 storeLockObject(http->entry);
798 storeCreateMemObject(http->entry, http->uri, http->log_uri);
799 http->entry->mem_obj->method = http->request->method;
800 context->sc = storeClientListAdd(http->entry, context);
29b8d8d6 801 http->logType = LOG_TCP_HIT;
edce4d98 802 context->reqofs = 0;
c8be6d7b 803 tempBuffer.offset = http->out.offset;
804 tempBuffer.length = next->readBuffer.length;
805 tempBuffer.data = next->readBuffer.data;
edce4d98 806 storeClientCopy(context->sc, http->entry,
c8be6d7b 807 tempBuffer, clientCacheHit, context);
edce4d98 808 return;
809 }
810 }
29b8d8d6 811 http->logType = LOG_TCP_MISS;
edce4d98 812 /* Release the cached URI */
813 entry = storeGetPublicByRequestMethod(http->request, METHOD_GET);
814 if (entry) {
815 debug(88, 4) ("clientPurgeRequest: GET '%s'\n", storeUrl(entry));
816 storeRelease(entry);
817 status = HTTP_OK;
818 }
819 entry = storeGetPublicByRequestMethod(http->request, METHOD_HEAD);
820 if (entry) {
821 debug(88, 4) ("clientPurgeRequest: HEAD '%s'\n", storeUrl(entry));
822 storeRelease(entry);
823 status = HTTP_OK;
824 }
825 /* And for Vary, release the base URI if none of the headers was included in the request */
826 if (http->request->vary_headers
827 && !strstr(http->request->vary_headers, "=")) {
828 entry = storeGetPublic(urlCanonical(http->request), METHOD_GET);
829 if (entry) {
830 debug(88, 4) ("clientPurgeRequest: Vary GET '%s'\n",
831 storeUrl(entry));
832 storeRelease(entry);
833 status = HTTP_OK;
834 }
835 entry = storeGetPublic(urlCanonical(http->request), METHOD_HEAD);
836 if (entry) {
837 debug(88, 4) ("clientPurgeRequest: Vary HEAD '%s'\n",
838 storeUrl(entry));
839 storeRelease(entry);
840 status = HTTP_OK;
841 }
842 }
843 /*
844 * Make a new entry to hold the reply to be written
845 * to the client.
846 */
847 http->entry =
848 clientCreateStoreEntry(context, http->request->method,
849 null_request_flags);
c8be6d7b 850 triggerStoreReadWithClientParameters(context, http);
edce4d98 851 httpReplyReset(r = http->entry->mem_obj->reply);
852 httpBuildVersion(&version, 1, 0);
853 httpReplySetHeaders(r, version, status, NULL, NULL, 0, 0, -1);
854 httpReplySwapOut(r, http->entry);
855 storeComplete(http->entry);
856}
857
858void
859clientTraceReply(clientStreamNode * node, clientReplyContext * context)
860{
861 HttpReply *rep;
862 http_version_t version;
863 clientStreamNode *next = node->node.next->data;
c8be6d7b 864 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
edce4d98 865 assert(context->http->request->max_forwards == 0);
866 context->http->entry =
867 clientCreateStoreEntry(context, context->http->request->method,
868 null_request_flags);
c8be6d7b 869 tempBuffer.offset = next->readBuffer.offset + context->headers_sz;
870 tempBuffer.length = next->readBuffer.length;
871 tempBuffer.data = next->readBuffer.data;
edce4d98 872 storeClientCopy(context->sc, context->http->entry,
c8be6d7b 873 tempBuffer, clientSendMoreData, context);
edce4d98 874 storeReleaseRequest(context->http->entry);
875 storeBuffer(context->http->entry);
876 rep = httpReplyCreate();
877 httpBuildVersion(&version, 1, 0);
878 httpReplySetHeaders(rep, version, HTTP_OK, NULL, "text/plain",
879 httpRequestPrefixLen(context->http->request), 0, squid_curtime);
880 httpReplySwapOut(rep, context->http->entry);
881 httpReplyDestroy(rep);
882 httpRequestSwapOut(context->http->request, context->http->entry);
883 storeComplete(context->http->entry);
884}
885
886#define SENDING_BODY 0
887#define SENDING_HDRSONLY 1
888int
889clientCheckTransferDone(clientHttpRequest const *http)
890{
891 int sending = SENDING_BODY;
892 StoreEntry *entry = http->entry;
893 MemObject *mem;
894 http_reply *reply;
895 int sendlen;
896 if (entry == NULL)
897 return 0;
898 /*
899 * For now, 'done_copying' is used for special cases like
900 * Range and HEAD requests.
901 */
902 if (http->flags.done_copying)
903 return 1;
904 /*
905 * Handle STORE_OK objects.
906 * objectLen(entry) will be set proprely.
907 * RC: Does objectLen(entry) include the Headers?
908 */
909 if (entry->store_status == STORE_OK) {
910 if (http->out.offset >= objectLen(entry))
911 return 1;
912 else
913 return 0;
914 }
915 /*
916 * Now, handle STORE_PENDING objects
917 */
918 mem = entry->mem_obj;
919 assert(mem != NULL);
920 assert(http->request != NULL);
921 reply = mem->reply;
922 if (reply->hdr_sz == 0)
923 return 0; /* haven't found end of headers yet */
924 else if (reply->sline.status == HTTP_OK)
925 sending = SENDING_BODY;
926 else if (reply->sline.status == HTTP_NO_CONTENT)
927 sending = SENDING_HDRSONLY;
928 else if (reply->sline.status == HTTP_NOT_MODIFIED)
929 sending = SENDING_HDRSONLY;
930 else if (reply->sline.status < HTTP_OK)
931 sending = SENDING_HDRSONLY;
932 else if (http->request->method == METHOD_HEAD)
933 sending = SENDING_HDRSONLY;
934 else
935 sending = SENDING_BODY;
936 /*
937 * Figure out how much data we are supposed to send.
938 * If we are sending a body and we don't have a content-length,
939 * then we must wait for the object to become STORE_OK.
940 */
941 if (sending == SENDING_HDRSONLY)
942 sendlen = reply->hdr_sz;
943 else if (reply->content_length < 0)
944 return 0;
945 else
946 sendlen = reply->content_length + reply->hdr_sz;
947 /*
948 * Now that we have the expected length, did we send it all?
949 */
950 if (http->out.offset < sendlen)
951 return 0;
952 else
953 return 1;
954}
955
956
957
958int
959clientGotNotEnough(clientHttpRequest const *http)
960{
961 int cl =
962 httpReplyBodySize(http->request->method, http->entry->mem_obj->reply);
963 int hs = http->entry->mem_obj->reply->hdr_sz;
964 assert(cl >= 0);
965 if (http->out.offset < cl + hs)
966 return 1;
967 return 0;
968}
969
970
971/* A write has completed, what is the next status based on the
972 * canonical request data?
973 * 1 something is wrong
974 * 0 nothing is wrong.
975 *
976 */
977int
978clientHttpRequestStatus(int fd, clientHttpRequest const *http)
979{
980#if SIZEOF_SIZE_T == 4
981 if (http->out.size > 0x7FFF0000) {
982 debug(88, 1) ("WARNING: closing FD %d to prevent counter overflow\n",
983 fd);
984 debug(88, 1) ("\tclient %s\n",
985 inet_ntoa(http->conn ? http->conn->peer.sin_addr : no_addr));
986 debug(88, 1) ("\treceived %d bytes\n", (int) http->out.size);
987 debug(88, 1) ("\tURI %s\n", http->log_uri);
988 return 1;
989 }
990#endif
991#if SIZEOF_OFF_T == 4
992 if (http->out.offset > 0x7FFF0000) {
993 debug(88, 1) ("WARNING: closing FD %d to prevent counter overflow\n",
994 fd);
995 debug(88, 1) ("\tclient %s\n",
996 inet_ntoa(http->conn ? http->conn->peer.sin_addr : no_addr));
997 debug(88, 1) ("\treceived %d bytes (offset %d)\n", (int) http->out.size,
998 (int) http->out.offset);
999 debug(88, 1) ("\tURI %s\n", http->log_uri);
1000 return 1;
1001 }
1002#endif
1003 return 0;
1004}
1005
1006/* Preconditions:
1007 * *http is a valid structure.
1008 * fd is either -1, or an open fd.
1009 *
1010 * TODO: enumify this
1011 *
1012 * This function is used by any http request sink, to determine the status
1013 * of the object.
1014 */
1015clientStream_status_t
1016clientReplyStatus(clientStreamNode * this, clientHttpRequest * http)
1017{
1018 clientReplyContext *context = this->data;
1019 int done;
1020 /* Here because lower nodes don't need it */
1021 if (http->entry == NULL)
1022 return STREAM_FAILED; /* yuck, but what can we do? */
1023 if (EBIT_TEST(http->entry->flags, ENTRY_ABORTED))
c8be6d7b 1024 /* TODO: Could upstream read errors (result.flags.error) be
edce4d98 1025 * lost, and result in undersize requests being considered
1026 * complete. Should we tcp reset such connections ?
1027 */
1028 return STREAM_FAILED;
1029 if ((done = clientCheckTransferDone(http)) != 0 || context->flags.complete) {
1030 debug(88, 5) ("clientReplyStatus: transfer is DONE\n");
1031 /* Ok we're finished, but how? */
1032 if (httpReplyBodySize(http->request->method,
1033 http->entry->mem_obj->reply) < 0) {
1034 debug(88, 5) ("clientWriteComplete: closing, content_length < 0\n");
1035 return STREAM_FAILED;
1036 } else if (!done) {
1037 debug(88, 5) ("clientWriteComplete: closing, !done, but read 0 bytes\n");
1038 return STREAM_FAILED;
1039 } else if (clientGotNotEnough(http)) {
1040 debug(88, 5) ("clientWriteComplete: client didn't get all it expected\n");
1041 return STREAM_UNPLANNED_COMPLETE;
1042 } else if (http->request->flags.proxy_keepalive) {
1043 return STREAM_COMPLETE;
1044 }
1045 return STREAM_UNPLANNED_COMPLETE;
1046
1047 }
1048 if (clientReplyBodyTooLarge(http->entry->mem_obj->reply, http->out.offset))
1049 return STREAM_FAILED;
1050 return STREAM_NONE;
1051}
1052
1053/* Responses with no body will not have a content-type header,
1054 * which breaks the rep_mime_type acl, which
1055 * coincidentally, is the most common acl for reply access lists.
1056 * A better long term fix for this is to allow acl matchs on the various
1057 * status codes, and then supply a default ruleset that puts these
1058 * codes before any user defines access entries. That way the user
1059 * can choose to block these responses where appropriate, but won't get
1060 * mysterious breakages.
1061 */
1062static int
1063clientAlwaysAllowResponse(http_status sline)
1064{
1065 switch (sline) {
1066 case HTTP_CONTINUE:
1067 case HTTP_SWITCHING_PROTOCOLS:
1068 case HTTP_PROCESSING:
1069 case HTTP_NO_CONTENT:
1070 case HTTP_NOT_MODIFIED:
1071 return 1;
1072 /* unreached */
1073 break;
1074 default:
1075 return 0;
1076 }
1077}
1078
1079/*
1080 * filters out unwanted entries from original reply header
1081 * adds extra entries if we have more info than origin server
1082 * adds Squid specific entries
1083 */
1084static void
1085clientBuildReplyHeader(clientHttpRequest * http, HttpReply * rep)
1086{
1087 HttpHeader *hdr = &rep->header;
c8be6d7b 1088 int is_hit = logTypeIsATcpHit(http->logType);
edce4d98 1089 request_t *request = http->request;
1090#if DONT_FILTER_THESE
1091 /* but you might want to if you run Squid as an HTTP accelerator */
1092 /* httpHeaderDelById(hdr, HDR_ACCEPT_RANGES); */
1093 httpHeaderDelById(hdr, HDR_ETAG);
1094#endif
1095 httpHeaderDelById(hdr, HDR_PROXY_CONNECTION);
1096 /* here: Keep-Alive is a field-name, not a connection directive! */
1097 httpHeaderDelByName(hdr, "Keep-Alive");
1098 /* remove Set-Cookie if a hit */
1099 if (is_hit)
1100 httpHeaderDelById(hdr, HDR_SET_COOKIE);
1101 /* handle Connection header */
1102 if (httpHeaderHas(hdr, HDR_CONNECTION)) {
1103 /* anything that matches Connection list member will be deleted */
1104 String strConnection = httpHeaderGetList(hdr, HDR_CONNECTION);
1105 const HttpHeaderEntry *e;
1106 HttpHeaderPos pos = HttpHeaderInitPos;
1107 /*
1108 * think: on-average-best nesting of the two loops (hdrEntry
1109 * and strListItem) @?@
1110 */
1111 /*
1112 * maybe we should delete standard stuff ("keep-alive","close")
1113 * from strConnection first?
1114 */
1115 while ((e = httpHeaderGetEntry(hdr, &pos))) {
1116 if (strListIsMember(&strConnection, strBuf(e->name), ','))
1117 httpHeaderDelAt(hdr, pos);
1118 }
1119 httpHeaderDelById(hdr, HDR_CONNECTION);
1120 stringClean(&strConnection);
1121 }
1122 /*
1123 * Add a estimated Age header on cache hits.
1124 */
1125 if (is_hit) {
1126 /*
1127 * Remove any existing Age header sent by upstream caches
1128 * (note that the existing header is passed along unmodified
1129 * on cache misses)
1130 */
1131 httpHeaderDelById(hdr, HDR_AGE);
1132 /*
1133 * This adds the calculated object age. Note that the details of the
1134 * age calculation is performed by adjusting the timestamp in
1135 * storeTimestampsSet(), not here.
1136 *
1137 * BROWSER WORKAROUND: IE sometimes hangs when receiving a 0 Age
1138 * header, so don't use it unless there is a age to report. Please
1139 * note that Age is only used to make a conservative estimation of
1140 * the objects age, so a Age: 0 header does not add any useful
1141 * information to the reply in any case.
1142 */
1143 if (NULL == http->entry)
1144 (void) 0;
1145 else if (http->entry->timestamp < 0)
1146 (void) 0;
1147 else if (http->entry->timestamp < squid_curtime)
1148 httpHeaderPutInt(hdr, HDR_AGE,
1149 squid_curtime - http->entry->timestamp);
1150 }
1151 /* Handle authentication headers */
1152 if (request->auth_user_request)
1153 authenticateFixHeader(rep, request->auth_user_request, request,
1154 http->flags.accel, 0);
1155 /* Append X-Cache */
1156 httpHeaderPutStrf(hdr, HDR_X_CACHE, "%s from %s",
1157 is_hit ? "HIT" : "MISS", getMyHostname());
1158#if USE_CACHE_DIGESTS
1159 /* Append X-Cache-Lookup: -- temporary hack, to be removed @?@ @?@ */
1160 httpHeaderPutStrf(hdr, HDR_X_CACHE_LOOKUP, "%s from %s:%d",
1161 context->lookup_type ? context->lookup_type : "NONE",
1162 getMyHostname(), getMyPort());
1163#endif
1164 if (httpReplyBodySize(request->method, rep) < 0) {
1165 debug(88,
1166 3)
1167 ("clientBuildReplyHeader: can't keep-alive, unknown body size\n");
1168 request->flags.proxy_keepalive = 0;
1169 }
4dea3fdd 1170 /* Append VIA */
c8be6d7b 1171 {
4dea3fdd 1172 LOCAL_ARRAY(char, bbuf, MAX_URL + 32);
1173 String strVia = httpHeaderGetList(hdr, HDR_VIA);
1174 snprintf(bbuf, sizeof(bbuf), "%d.%d %s",
c8be6d7b 1175 rep->sline.version.major,
1176 rep->sline.version.minor,
1177 ThisCache);
4dea3fdd 1178 strListAdd(&strVia, bbuf, ',');
1179 httpHeaderDelById(hdr, HDR_VIA);
1180 httpHeaderPutStr(hdr, HDR_VIA, strBuf(strVia));
1181 stringClean(&strVia);
c8be6d7b 1182 }
edce4d98 1183 /* Signal keep-alive if needed */
1184 httpHeaderPutStr(hdr,
1185 http->flags.accel ? HDR_CONNECTION : HDR_PROXY_CONNECTION,
1186 request->flags.proxy_keepalive ? "keep-alive" : "close");
1187#if ADD_X_REQUEST_URI
1188 /*
1189 * Knowing the URI of the request is useful when debugging persistent
1190 * connections in a client; we cannot guarantee the order of http headers,
1191 * but X-Request-URI is likely to be the very last header to ease use from a
1192 * debugger [hdr->entries.count-1].
1193 */
1194 httpHeaderPutStr(hdr, HDR_X_REQUEST_URI,
1195 http->entry->mem_obj->url ? http->entry->mem_obj->url : http->uri);
1196#endif
1197 httpHdrMangleList(hdr, request);
1198}
1199
1200
1201static HttpReply *
1202clientBuildReply(clientHttpRequest * http, const char *buf, size_t size)
1203{
1204 HttpReply *rep = httpReplyCreate();
1205 size_t k = headersEnd(buf, size);
1206 if (k && httpReplyParse(rep, buf, k)) {
1207 /* enforce 1.0 reply version */
1208 httpBuildVersion(&rep->sline.version, 1, 0);
1209 /* do header conversions */
1210 clientBuildReplyHeader(http, rep);
1211 } else {
1212 /* parsing failure, get rid of the invalid reply */
1213 httpReplyDestroy(rep);
1214 rep = NULL;
1215 }
1216 return rep;
1217}
1218
1219static log_type
1220clientIdentifyStoreObject(clientHttpRequest * http)
1221{
1222 request_t *r = http->request;
1223 StoreEntry *e;
1224 if (r->flags.cachable || r->flags.internal)
1225 e = http->entry = storeGetPublicByRequest(r);
1226 else
1227 e = http->entry = NULL;
1228 /* Release negatively cached IP-cache entries on reload */
1229 if (r->flags.nocache)
1230 ipcacheInvalidate(r->host);
1231#if HTTP_VIOLATIONS
1232 else if (r->flags.nocache_hack)
1233 ipcacheInvalidate(r->host);
1234#endif
1235#if USE_CACHE_DIGESTS
1236 context->lookup_type = e ? "HIT" : "MISS";
1237#endif
1238 if (NULL == e) {
1239 /* this object isn't in the cache */
1240 debug(85, 3) ("clientProcessRequest2: storeGet() MISS\n");
1241 return LOG_TCP_MISS;
1242 }
1243 if (Config.onoff.offline) {
1244 debug(85, 3) ("clientProcessRequest2: offline HIT\n");
1245 http->entry = e;
1246 return LOG_TCP_HIT;
1247 }
1248 if (http->redirect.status) {
1249 /* force this to be a miss */
1250 http->entry = NULL;
1251 return LOG_TCP_MISS;
1252 }
1253 if (!storeEntryValidToSend(e)) {
1254 debug(85, 3) ("clientProcessRequest2: !storeEntryValidToSend MISS\n");
1255 http->entry = NULL;
1256 return LOG_TCP_MISS;
1257 }
1258 if (EBIT_TEST(e->flags, ENTRY_SPECIAL)) {
1259 /* Special entries are always hits, no matter what the client says */
1260 debug(85, 3) ("clientProcessRequest2: ENTRY_SPECIAL HIT\n");
1261 http->entry = e;
1262 return LOG_TCP_HIT;
1263 }
1264#if HTTP_VIOLATIONS
1265 if (e->store_status == STORE_PENDING) {
1266 if (r->flags.nocache || r->flags.nocache_hack) {
1267 debug(85, 3) ("Clearing no-cache for STORE_PENDING request\n\t%s\n",
1268 storeUrl(e));
1269 r->flags.nocache = 0;
1270 r->flags.nocache_hack = 0;
1271 }
1272 }
1273#endif
1274 if (r->flags.nocache) {
1275 debug(85, 3) ("clientProcessRequest2: no-cache REFRESH MISS\n");
1276 http->entry = NULL;
1277 return LOG_TCP_CLIENT_REFRESH_MISS;
1278 }
1279 /* We don't cache any range requests (for now!) -- adrian */
1280 if (r->flags.range) {
1281 http->entry = NULL;
1282 return LOG_TCP_MISS;
1283 }
1284 debug(85, 3) ("clientProcessRequest2: default HIT\n");
1285 http->entry = e;
1286 return LOG_TCP_HIT;
1287}
1288
1289/* Request more data from the store for the client Stream
1290 * This is *the* entry point to this module.
1291 *
1292 * Preconditions:
1293 * This is the head of the list.
1294 * There is at least one more node.
1295 * data context is not null
1296 */
1297void
1298clientGetMoreData(clientStreamNode * this, clientHttpRequest * http)
1299{
1300 clientStreamNode *next;
1301 clientReplyContext *context;
1302 /* Test preconditions */
1303 assert(this != NULL);
1304 assert(cbdataReferenceValid(this));
1305 assert(this->data != NULL);
1306 assert(this->node.prev == NULL);
1307 assert(this->node.next != NULL);
1308 context = this->data;
1309 assert(context->http == http);
1310
1311 next = this->node.next->data;
1312 if (!context->ourNode)
1313 context->ourNode = this; /* no cbdatareference, this is only used once, and safely */
1314 if (context->flags.storelogiccomplete) {
c8be6d7b 1315 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
1316 tempBuffer.offset = next->readBuffer.offset + context->headers_sz;
1317 tempBuffer.length = next->readBuffer.length;
1318 tempBuffer.data = next->readBuffer.data;
1319
edce4d98 1320 storeClientCopy(context->sc, http->entry,
c8be6d7b 1321 tempBuffer, clientSendMoreData, context);
edce4d98 1322 return;
1323 }
1324 if (context->http->request->method == METHOD_PURGE) {
1325 clientPurgeRequest(context);
1326 return;
1327 }
1328 if (context->http->request->method == METHOD_TRACE) {
1329 if (context->http->request->max_forwards == 0) {
1330 clientTraceReply(this, context);
1331 return;
1332 }
1333 /* continue forwarding, not finished yet. */
29b8d8d6 1334 http->logType = LOG_TCP_MISS;
edce4d98 1335 } else
29b8d8d6 1336 http->logType = clientIdentifyStoreObject(http);
edce4d98 1337 /* We still have to do store logic processing - vary, cache hit etc */
1338 if (context->http->entry != NULL) {
1339 /* someone found the object in the cache for us */
c8be6d7b 1340 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
edce4d98 1341 storeLockObject(context->http->entry);
1342 if (context->http->entry->mem_obj == NULL) {
1343 /*
1344 * This if-block exists because we don't want to clobber
1345 * a preexiting mem_obj->method value if the mem_obj
1346 * already exists. For example, when a HEAD request
1347 * is a cache hit for a GET response, we want to keep
1348 * the method as GET.
1349 */
1350 storeCreateMemObject(context->http->entry, context->http->uri,
1351 context->http->log_uri);
1352 context->http->entry->mem_obj->method =
1353 context->http->request->method;
1354 }
1355 context->sc = storeClientListAdd(context->http->entry, context);
1356#if DELAY_POOLS
f0faee43 1357 delaySetStoreClient(context->sc, delayClient(context->http));
edce4d98 1358#endif
29b8d8d6 1359 assert(context->http->logType == LOG_TCP_HIT);
edce4d98 1360 context->reqofs = 0;
1361 assert(http->out.offset == http->out.size && http->out.offset == 0);
c8be6d7b 1362 tempBuffer.offset = context->reqofs;
1363 tempBuffer.length = next->readBuffer.length;
1364 tempBuffer.data = next->readBuffer.data;
edce4d98 1365 storeClientCopy(context->sc, http->entry,
c8be6d7b 1366 tempBuffer, clientCacheHit, context);
edce4d98 1367 } else {
29b8d8d6 1368 /* MISS CASE, http->logType is already set! */
edce4d98 1369 clientProcessMiss(context);
1370 }
1371}
1372
1373/* the next node has removed itself from the stream. */
1374void
1375clientReplyDetach(clientStreamNode * node, clientHttpRequest * http)
1376{
1377 /* detach from the stream */
1378 /* NB: This cbdataFrees our context,
1379 * so the clientSendMoreData callback (if any)
1380 * pending in the store will not trigger
1381 */
1382 clientStreamDetach(node, http);
1383}
1384
1385/*
1386 * accepts chunk of a http message in buf, parses prefix, filters headers and
1387 * such, writes processed message to the message recipient
1388 */
1389void
c8be6d7b 1390clientSendMoreData(void *data, StoreIOBuffer result)
edce4d98 1391{
1392 clientReplyContext *context = data;
1393 clientHttpRequest *http = context->http;
1394 clientStreamNode *next = http->client_stream.head->next->data;
1395 StoreEntry *entry = http->entry;
1396 ConnStateData *conn = http->conn;
1397 int fd = conn ? conn->fd : -1;
1398 HttpReply *rep = NULL;
c8be6d7b 1399 char *buf = next->readBuffer.data;
1400 char *body_buf = buf;
1401 ssize_t size = context->reqofs + result.length;
edce4d98 1402 ssize_t body_size = size;
1403
af29fa86 1404 /* This is not valid once we start doing range requests.
1405 * Then it becomes context->reqofs == startoffirstrangeentry
1406 */
c8be6d7b 1407 assert(context->reqofs == 0 || context->flags.storelogiccomplete);
af29fa86 1408
c8be6d7b 1409 if (buf != result.data) {
edce4d98 1410 /* we've got to copy some data */
c8be6d7b 1411 assert(result.length <= next->readBuffer.length);
1412 xmemcpy(buf, result.data, result.length);
edce4d98 1413 body_buf = buf;
1414 }
1415 /* We've got the final data to start pushing... */
1416 context->flags.storelogiccomplete = 1;
1417
c8be6d7b 1418 debug(88, 5) ("clientSendMoreData: %s, %d bytes (%u new bytes)\n",
1419 http->uri, (int) size, result.length);
1420 assert(size <= HTTP_REQBUF_SZ || context->flags.headersSent);
edce4d98 1421 assert(http->request != NULL);
1422 /* ESI TODO: remove this assert once everything is stable */
1423 assert(http->client_stream.head->data
1424 && cbdataReferenceValid(http->client_stream.head->data));
1425 dlinkDelete(&http->active, &ClientActiveRequests);
1426 dlinkAdd(http, &http->active, &ClientActiveRequests);
1427 debug(88, 5) ("clientSendMoreData: FD %d '%s', out.offset=%ld \n",
1428 fd, storeUrl(entry), (long int) http->out.offset);
1429 /* update size of the request */
1430 context->reqsize = size;
1431 if (http->request->flags.reset_tcp) {
1432 /* yuck. FIXME: move to client_side.c */
1433 if (fd != -1)
1434 comm_reset_close(fd);
1435 return;
1436 } else if ( /* aborted request */
1437 (entry && EBIT_TEST(entry->flags, ENTRY_ABORTED)) ||
c8be6d7b 1438 /* Upstream read error */ (result.flags.error) ||
edce4d98 1439 /* Upstream EOF */ (body_size == 0)) {
1440 /* call clientWriteComplete so the client socket gets closed */
1441 /* We call into the stream, because we don't know that there is a
1442 * client socket!
1443 */
c8be6d7b 1444 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
edce4d98 1445 context->flags.complete = 1;
c8be6d7b 1446 tempBuffer.flags.error = result.flags.error;
1447 clientStreamCallback(http->client_stream.head->data, http, NULL,
1448 tempBuffer);
edce4d98 1449 /* clientWriteComplete(fd, NULL, 0, COMM_OK, http); */
1450 return;
1451 }
029612cf 1452 if (context->flags.headersSent != 0) {
c8be6d7b 1453 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
1454 if (result.length == 0)
edce4d98 1455 context->flags.complete = 1;
c8be6d7b 1456 /* REMOVE ME: Only useful for two node streams */
1457 assert(result.offset - context->headers_sz == ((clientStreamNode *) http->client_stream.tail->data)->readBuffer.offset);
7432bf18 1458 tempBuffer.offset = result.offset - context->headers_sz;
c8be6d7b 1459 tempBuffer.length = result.length;
1460 tempBuffer.data = buf;
1461 clientStreamCallback(http->client_stream.head->data, http, NULL,
1462 tempBuffer);
edce4d98 1463 return;
1464 }
1465 /* handle headers */
1466 if (Config.onoff.log_mime_hdrs) {
1467 size_t k;
1468 if ((k = headersEnd(buf, size))) {
1469 safe_free(http->al.headers.reply);
1470 http->al.headers.reply = xcalloc(k + 1, 1);
1471 xstrncpy(http->al.headers.reply, buf, k);
1472 }
1473 }
1474 rep = clientBuildReply(http, buf, size);
1475 if (rep) {
1476 aclCheck_t *ch;
1477 int rv;
1478 httpReplyBodyBuildSize(http->request, rep, &Config.ReplyBodySize);
1479 if (clientReplyBodyTooLarge(rep, rep->content_length)) {
1480 ErrorState *err =
1481 clientBuildError(ERR_TOO_BIG, HTTP_FORBIDDEN, NULL,
1482 http->conn ? &http->conn->peer.sin_addr : &no_addr,
1483 http->request);
edce4d98 1484 clientRemoveStoreReference(context, &context->sc, &http->entry);
c8be6d7b 1485 startError(context, http, err);
edce4d98 1486 httpReplyDestroy(rep);
1487 return;
1488 }
1489 context->headers_sz = rep->hdr_sz;
1490 body_size = size - rep->hdr_sz;
1491 assert(body_size >= 0);
1492 body_buf = buf + rep->hdr_sz;
1493 debug(88,
1494 3)
1495 ("clientSendMoreData: Appending %d bytes after %d bytes of headers\n",
1496 (int) body_size, rep->hdr_sz);
1497 ch = aclChecklistCreate(Config.accessList.reply, http->request, NULL);
1498 ch->reply = rep;
1499 rv = aclCheckFast(Config.accessList.reply, ch);
1500 aclChecklistFree(ch);
1501 ch = NULL;
1502 debug(88, 2) ("The reply for %s %s is %s, because it matched '%s'\n",
1503 RequestMethodStr[http->request->method], http->uri,
1504 rv ? "ALLOWED" : "DENIED",
1505 AclMatchedName ? AclMatchedName : "NO ACL's");
1506 if (!rv && rep->sline.status != HTTP_FORBIDDEN
1507 && !clientAlwaysAllowResponse(rep->sline.status)) {
1508 /* the if above is slightly broken, but there is no way
1509 * to tell if this is a squid generated error page, or one from
1510 * upstream at this point. */
1511 ErrorState *err;
edce4d98 1512 err =
1513 clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL,
1514 http->conn ? &http->conn->peer.sin_addr : &no_addr,
1515 http->request);
1516 clientRemoveStoreReference(context, &context->sc, &http->entry);
c8be6d7b 1517 startError(context, http, err);
edce4d98 1518 httpReplyDestroy(rep);
1519 return;
1520 }
1521 } else if (size < HTTP_REQBUF_SZ && entry->store_status == STORE_PENDING) {
c8be6d7b 1522 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
edce4d98 1523 /* wait for more to arrive */
c8be6d7b 1524 context->reqofs += result.length;
edce4d98 1525 assert(context->reqofs <= HTTP_REQBUF_SZ);
1526 /* TODO: copy into the supplied buffer */
c8be6d7b 1527 tempBuffer.offset = context->reqofs;
1528 tempBuffer.length = next->readBuffer.length - context->reqofs;
1529 tempBuffer.data = next->readBuffer.data + context->reqofs;
edce4d98 1530 storeClientCopy(context->sc, entry,
c8be6d7b 1531 tempBuffer, clientSendMoreData, context);
edce4d98 1532 return;
1533 }
029612cf 1534 if (!context->flags.headersSent)
1535 context->flags.headersSent = 1;
edce4d98 1536 if (http->request->method == METHOD_HEAD) {
1537 if (rep) {
1538 /* do not forward body for HEAD replies */
1539 /* ESI TODO: Can ESI affect headers on the master document */
1540 body_size = 0;
1541 http->flags.done_copying = 1;
1542 context->flags.complete = 1;
1543 } else {
1544 /*
1545 * If we are here, then store_status == STORE_OK and it
1546 * seems we have a HEAD repsponse which is missing the
1547 * empty end-of-headers line (home.mira.net, phttpd/0.99.72
1548 * does this). Because clientBuildReply() fails we just
1549 * call this reply a body, set the done_copying flag and
1550 * continue...
1551 */
1552 http->flags.done_copying = 1;
1553 context->flags.complete = 1;
1554 }
c8be6d7b 1555 } {
1556 StoreIOBuffer tempBuffer = EMPTYIOBUFFER;
1557 assert(rep || (body_buf && body_size));
1558 tempBuffer.length = body_size;
1559 tempBuffer.data = body_buf;
1560 /* TODO: move the data in the buffer back by the request header size */
1561 clientStreamCallback(http->client_stream.head->data, http, rep,
1562 tempBuffer);
edce4d98 1563 }
edce4d98 1564}
1565
1566int
1567clientReplyBodyTooLarge(HttpReply * rep, ssize_t clen)
1568{
1569 if (0 == rep->maxBodySize)
1570 return 0; /* disabled */
1571 if (clen < 0)
1572 return 0; /* unknown */
1573 if (clen > rep->maxBodySize)
1574 return 1; /* too large */
1575 return 0;
1576}
1577
1578/*
1579 * returns true if client specified that the object must come from the cache
1580 * without contacting origin server
1581 */
1582static int
1583clientOnlyIfCached(clientHttpRequest * http)
1584{
1585 const request_t *r = http->request;
1586 assert(r);
1587 return r->cache_control &&
1588 EBIT_TEST(r->cache_control->mask, CC_ONLY_IF_CACHED);
1589}
1590
1591/* Using this breaks the client layering just a little!
1592 */
1593StoreEntry *
1594clientCreateStoreEntry(clientReplyContext * context, method_t m,
1595 request_flags flags)
1596{
1597 clientHttpRequest *h = context->http;
1598 StoreEntry *e;
1599 assert(h != NULL);
1600 /*
1601 * For erroneous requests, we might not have a h->request,
1602 * so make a fake one.
1603 */
1604 if (h->request == NULL)
1605 h->request = requestLink(requestCreate(m, PROTO_NONE, null_string));
1606 e = storeCreateEntry(h->uri, h->log_uri, flags, m);
1607 context->sc = storeClientListAdd(e, context);
1608#if DELAY_POOLS
1609 delaySetStoreClient(context->sc, delayClient(h));
1610#endif
1611 context->reqofs = 0;
1612 context->reqsize = 0;
1613 /* I don't think this is actually needed! -- adrian */
1614 /* h->reqbuf = h->norm_reqbuf; */
1615// assert(h->reqbuf == h->norm_reqbuf);
1616 /* The next line is illegal because we don't know if the client stream
1617 * buffers have been set up
1618 */
1619// storeClientCopy(h->sc, e, 0, HTTP_REQBUF_SZ, h->reqbuf,
1620 // clientSendMoreData, context);
1621 /* So, we mark the store logic as complete */
1622 context->flags.storelogiccomplete = 1;
1623 /* and get the caller to request a read, from whereever they are */
1624 /* NOTE: after ANY data flows down the pipe, even one step,
1625 * this function CAN NOT be used to manage errors
1626 */
1627 return e;
1628}
1629
1630ErrorState *
1631clientBuildError(err_type page_id, http_status status, char const *url,
1632 struct in_addr * src_addr, request_t * request)
1633{
1634 ErrorState *err = errorCon(page_id, status);
1635 err->src_addr = *src_addr;
1636 if (url)
1637 err->url = xstrdup(url);
1638 if (request)
1639 err->request = requestLink(request);
1640 return err;
1641}