]>
Commit | Line | Data |
---|---|---|
edce4d98 | 1 | |
2 | /* | |
61fb2d9a | 3 | * $Id: client_side_reply.cc,v 1.40 2003/02/19 21:23:51 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" |
e6ccf245 | 38 | #include "Store.h" |
528b2c61 | 39 | #include "HttpReply.h" |
40 | #include "HttpRequest.h" | |
e6ccf245 | 41 | |
c8be6d7b | 42 | #include "clientStream.h" |
e6ccf245 | 43 | #include "authenticate.h" |
528b2c61 | 44 | #include "MemObject.h" |
45 | #include "client_side_request.h" | |
4fb35c3c | 46 | #include "ACLChecklist.h" |
b67e2c8c | 47 | #if DELAY_POOLS |
48 | #include "DelayPools.h" | |
49 | #endif | |
e6ccf245 | 50 | |
528b2c61 | 51 | static STCB clientHandleIMSReply; |
52 | static STCB clientSendMoreData; | |
e6ccf245 | 53 | class clientReplyContext : public StoreClient { |
54 | public: | |
a695955b | 55 | void *operator new (size_t byteCount); |
e6ccf245 | 56 | void operator delete (void *address); |
57 | ||
58 | void saveState(clientHttpRequest *); | |
59 | void restoreState(clientHttpRequest *); | |
60 | void purgeRequest (); | |
61 | void purgeRequestFindObjectToPurge(); | |
62 | void purgeDoMissPurge(); | |
3b13a8fd | 63 | void purgeFoundGet(StoreEntry *newEntry); |
64 | void purgeFoundHead(StoreEntry *newEntry); | |
65 | void purgeFoundObject(StoreEntry *entry); | |
528b2c61 | 66 | void sendClientUpstreamResponse(); |
3b13a8fd | 67 | void purgeDoPurgeGet(StoreEntry *entry); |
68 | void purgeDoPurgeHead(StoreEntry *entry); | |
e6ccf245 | 69 | void doGetMoreData(); |
70 | void identifyStoreObject(); | |
3b13a8fd | 71 | void identifyFoundObject(StoreEntry *entry); |
e39b9382 | 72 | int storeOKTransferDone() const; |
73 | int storeNotOKTransferDone() const; | |
e6ccf245 | 74 | |
75 | http_status purgeStatus; | |
edce4d98 | 76 | |
e6ccf245 | 77 | /* state variable - replace with class to handle storeentries at some point */ |
78 | int lookingforstore; | |
3b13a8fd | 79 | virtual void created (StoreEntry *newEntry); |
e6ccf245 | 80 | |
edce4d98 | 81 | clientHttpRequest *http; |
82 | int headers_sz; | |
83 | store_client *sc; /* The store_client we're using */ | |
84 | store_client *old_sc; /* ... for entry to be validated */ | |
c8be6d7b | 85 | StoreIOBuffer tempBuffer; /* For use in validating requests via IMS */ |
edce4d98 | 86 | int old_reqsize; /* ... again, for the buffer */ |
87 | size_t reqsize; | |
88 | off_t reqofs; | |
89 | char tempbuf[HTTP_REQBUF_SZ]; /* a temporary buffer if we need working storage */ | |
90 | #if USE_CACHE_DIGESTS | |
91 | const char *lookup_type; /* temporary hack: storeGet() result: HIT/MISS/NONE */ | |
92 | #endif | |
93 | struct { | |
94 | int storelogiccomplete:1; | |
95 | int complete:1; /* we have read all we can from upstream */ | |
528b2c61 | 96 | bool headersSent; |
edce4d98 | 97 | } flags; |
98 | clientStreamNode *ourNode; /* This will go away if/when this file gets refactored some more */ | |
e6ccf245 | 99 | private: |
528b2c61 | 100 | friend void clientSendMoreData(void *data, StoreIOBuffer result); |
101 | friend void clientHandleIMSReply(void *data, StoreIOBuffer result); | |
102 | static STCB clientSendMoreData; | |
103 | clientStreamNode *getNextNode() const; | |
104 | void sendMoreData (StoreIOBuffer result); | |
105 | void makeThisHead(); | |
106 | bool errorInStream(StoreIOBuffer const &result, size_t const &sizeToProcess)const ; | |
107 | void sendStreamError(StoreIOBuffer const &result); | |
108 | void pushStreamData(StoreIOBuffer const &result, char *source); | |
109 | void waitForMoreData (StoreIOBuffer const &result); | |
110 | clientStreamNode * next() const; | |
111 | void startSendProcess(); | |
4993f571 | 112 | StoreIOBuffer holdingBuffer; |
113 | HttpReply *holdingReply; | |
114 | void processReplyAccess(); | |
115 | static PF ProcessReply; | |
116 | void processReply(bool accessAllowed); | |
e6ccf245 | 117 | }; |
edce4d98 | 118 | |
119 | CBDATA_TYPE(clientReplyContext); | |
120 | ||
edce4d98 | 121 | /* Local functions */ |
122 | static int clientGotNotEnough(clientHttpRequest const *); | |
528b2c61 | 123 | static int clientReplyBodyTooLarge(HttpReply const *, ssize_t); |
edce4d98 | 124 | static int clientOnlyIfCached(clientHttpRequest * http); |
125 | static void clientProcessExpired(clientReplyContext *); | |
126 | static void clientProcessMiss(clientReplyContext *); | |
127 | static STCB clientCacheHit; | |
128 | static void clientProcessOnlyIfCachedMiss(clientReplyContext *); | |
e6ccf245 | 129 | static int clientGetsOldEntry(StoreEntry *, StoreEntry * old, |
edce4d98 | 130 | request_t * request); |
528b2c61 | 131 | |
edce4d98 | 132 | static int modifiedSince(StoreEntry *, request_t *); |
edce4d98 | 133 | static void clientTraceReply(clientStreamNode *, clientReplyContext *); |
134 | static StoreEntry *clientCreateStoreEntry(clientReplyContext *, method_t, | |
135 | request_flags); | |
528b2c61 | 136 | |
edce4d98 | 137 | static void clientRemoveStoreReference(clientReplyContext *, store_client **, |
138 | StoreEntry **); | |
e6ccf245 | 139 | extern "C" CSS clientReplyStatus; |
edce4d98 | 140 | extern ErrorState *clientBuildError(err_type, http_status, char const *, |
141 | struct in_addr *, request_t *); | |
142 | ||
c8be6d7b | 143 | static void startError(clientReplyContext * context, clientHttpRequest * http, ErrorState * err); |
528b2c61 | 144 | static void triggerInitialStoreReadWithClientParameters(clientReplyContext * context, clientHttpRequest * http); |
0e3be1ea | 145 | static int clientCheckTransferDone(clientReplyContext * context); |
528b2c61 | 146 | static void clientObeyConnectionHeader(clientHttpRequest * http, HttpReply * rep); |
c8be6d7b | 147 | |
edce4d98 | 148 | /* The clientReply clean interface */ |
149 | /* privates */ | |
150 | static FREE clientReplyFree; | |
151 | ||
152 | void | |
153 | clientReplyFree(void *data) | |
154 | { | |
e6ccf245 | 155 | clientReplyContext *thisClient = (clientReplyContext *)data; |
156 | clientRemoveStoreReference(thisClient, &thisClient->sc, &thisClient->http->entry); | |
edce4d98 | 157 | /* old_entry might still be set if we didn't yet get the reply |
158 | * code in clientHandleIMSReply() */ | |
e6ccf245 | 159 | clientRemoveStoreReference(thisClient, &thisClient->old_sc, &thisClient->http->old_entry); |
160 | safe_free(thisClient->tempBuffer.data); | |
161 | cbdataReferenceDone(thisClient->http); | |
edce4d98 | 162 | } |
163 | ||
164 | void * | |
165 | clientReplyNewContext(clientHttpRequest * clientContext) | |
166 | { | |
e6ccf245 | 167 | clientReplyContext *context = new clientReplyContext; |
edce4d98 | 168 | context->http = cbdataReference(clientContext); |
169 | return context; | |
170 | } | |
171 | ||
172 | /* create an error in the store awaiting the client side to read it. */ | |
173 | void | |
174 | clientSetReplyToError(void *data, | |
175 | err_type err, http_status status, method_t method, char const *uri, | |
176 | struct in_addr *addr, request_t * failedrequest, char *unparsedrequest, | |
177 | auth_user_request_t * auth_user_request) | |
178 | { | |
e6ccf245 | 179 | clientReplyContext *context = (clientReplyContext *)data; |
edce4d98 | 180 | ErrorState *errstate = |
181 | clientBuildError(err, status, uri, addr, failedrequest); | |
182 | if (unparsedrequest) | |
183 | errstate->request_hdrs = xstrdup(unparsedrequest); | |
184 | ||
185 | if (status == HTTP_NOT_IMPLEMENTED && context->http->request) | |
186 | /* prevent confusion over whether we default to persistent or not */ | |
187 | context->http->request->flags.proxy_keepalive = 0; | |
29b8d8d6 | 188 | context->http->al.http.code = errstate->httpStatus; |
edce4d98 | 189 | |
190 | context->http->entry = | |
e429f975 | 191 | clientCreateStoreEntry(context, method, request_flags()); |
edce4d98 | 192 | if (auth_user_request) { |
193 | errstate->auth_user_request = auth_user_request; | |
194 | authenticateAuthUserRequestLock(errstate->auth_user_request); | |
195 | } | |
196 | assert(errstate->callback_data == NULL); | |
197 | errorAppendEntry(context->http->entry, errstate); | |
198 | /* Now the caller reads to get this */ | |
199 | } | |
200 | ||
201 | void | |
202 | clientRemoveStoreReference(clientReplyContext * context, store_client ** scp, | |
203 | StoreEntry ** ep) | |
204 | { | |
205 | StoreEntry *e; | |
206 | store_client *sc = *scp; | |
207 | if ((e = *ep) != NULL) { | |
208 | *ep = NULL; | |
209 | storeUnregister(sc, e, context); | |
210 | *scp = NULL; | |
211 | storeUnlockObject(e); | |
212 | } | |
213 | } | |
214 | ||
e6ccf245 | 215 | void * |
a695955b | 216 | clientReplyContext::operator new (size_t byteCount) |
e6ccf245 | 217 | { |
218 | /* derived classes with different sizes must implement their own new */ | |
219 | assert (byteCount == sizeof (clientReplyContext)); | |
220 | CBDATA_INIT_TYPE_FREECB(clientReplyContext, clientReplyFree); | |
221 | return cbdataAlloc(clientReplyContext); | |
222 | } | |
223 | ||
224 | void | |
225 | clientReplyContext::operator delete (void *address) | |
226 | { | |
227 | cbdataFree ((clientReplyContext *)address); | |
228 | } | |
229 | ||
230 | ||
edce4d98 | 231 | void |
e6ccf245 | 232 | clientReplyContext::saveState(clientHttpRequest * http) |
edce4d98 | 233 | { |
e6ccf245 | 234 | assert(old_sc == NULL); |
61fb2d9a | 235 | debug(88, 3)("clientReplyContext::saveState: saving store context\n"); |
edce4d98 | 236 | http->old_entry = http->entry; |
e6ccf245 | 237 | old_sc = sc; |
238 | old_reqsize = reqsize; | |
239 | tempBuffer.offset = reqofs; | |
edce4d98 | 240 | /* Prevent accessing the now saved entries */ |
241 | http->entry = NULL; | |
e6ccf245 | 242 | sc = NULL; |
243 | reqsize = 0; | |
244 | reqofs = 0; | |
edce4d98 | 245 | } |
246 | ||
247 | void | |
e6ccf245 | 248 | clientReplyContext::restoreState(clientHttpRequest * http) |
edce4d98 | 249 | { |
e6ccf245 | 250 | assert(old_sc != NULL); |
61fb2d9a | 251 | debug(88, 3)("clientReplyContext::restoreState: Restoring store context\n"); |
edce4d98 | 252 | http->entry = http->old_entry; |
e6ccf245 | 253 | sc = old_sc; |
254 | reqsize = old_reqsize; | |
255 | reqofs = tempBuffer.offset; | |
edce4d98 | 256 | /* Prevent accessed the old saved entries */ |
257 | http->old_entry = NULL; | |
e6ccf245 | 258 | old_sc = NULL; |
259 | old_reqsize = 0; | |
260 | tempBuffer.offset = 0; | |
c8be6d7b | 261 | } |
262 | ||
263 | void | |
264 | startError(clientReplyContext * context, clientHttpRequest * http, ErrorState * err) | |
265 | { | |
e429f975 | 266 | http->entry = clientCreateStoreEntry(context, http->request->method, request_flags()); |
528b2c61 | 267 | triggerInitialStoreReadWithClientParameters(context, http); |
c8be6d7b | 268 | errorAppendEntry(http->entry, err); |
edce4d98 | 269 | } |
270 | ||
e6ccf245 | 271 | clientStreamNode * |
528b2c61 | 272 | clientReplyContext::getNextNode() const |
e6ccf245 | 273 | { |
274 | return (clientStreamNode *)ourNode->node.next->data; | |
275 | } | |
276 | ||
528b2c61 | 277 | /* This function is wrong - the client parameters don't include the |
278 | * header offset | |
279 | */ | |
c8be6d7b | 280 | void |
528b2c61 | 281 | triggerInitialStoreReadWithClientParameters(clientReplyContext * context, clientHttpRequest * http) |
c8be6d7b | 282 | { |
e6ccf245 | 283 | clientStreamNode *next = (clientStreamNode *)http->client_stream.head->next->data; |
528b2c61 | 284 | StoreIOBuffer tempBuffer; |
c8be6d7b | 285 | /* collapse this to one object if we never tickle the assert */ |
286 | assert(context->http == http); | |
528b2c61 | 287 | /* when confident, 0 becomes context->reqofs, and then this factors into |
288 | * startSendProcess | |
289 | */ | |
290 | assert(context->reqofs == 0); | |
291 | tempBuffer.offset = 0; | |
c8be6d7b | 292 | tempBuffer.length = next->readBuffer.length; |
293 | tempBuffer.data = next->readBuffer.data; | |
294 | storeClientCopy(context->sc, http->entry, tempBuffer, clientSendMoreData, context); | |
295 | } | |
edce4d98 | 296 | |
297 | /* there is an expired entry in the store. | |
298 | * setup a temporary buffer area and perform an IMS to the origin | |
299 | */ | |
300 | static void | |
301 | clientProcessExpired(clientReplyContext * context) | |
302 | { | |
303 | clientHttpRequest *http = context->http; | |
304 | char *url = http->uri; | |
305 | StoreEntry *entry = NULL; | |
528b2c61 | 306 | debug(88, 3)("clientProcessExpired: '%s'", http->uri); |
edce4d98 | 307 | assert(http->entry->lastmod >= 0); |
308 | /* | |
309 | * check if we are allowed to contact other servers | |
310 | * @?@: Instead of a 504 (Gateway Timeout) reply, we may want to return | |
311 | * a stale entry *if* it matches client requirements | |
312 | */ | |
313 | if (clientOnlyIfCached(http)) { | |
314 | clientProcessOnlyIfCachedMiss(context); | |
315 | return; | |
316 | } | |
317 | http->request->flags.refresh = 1; | |
318 | #if STORE_CLIENT_LIST_DEBUG | |
c8be6d7b | 319 | /* Prevent a race with the store client memory free routines |
edce4d98 | 320 | */ |
c8be6d7b | 321 | assert(storeClientIsThisAClient(context->sc, context)); |
edce4d98 | 322 | #endif |
323 | /* Prepare to make a new temporary request */ | |
e6ccf245 | 324 | context->saveState(http); |
edce4d98 | 325 | entry = storeCreateEntry(url, |
326 | http->log_uri, http->request->flags, http->request->method); | |
327 | /* NOTE, don't call storeLockObject(), storeCreateEntry() does it */ | |
328 | context->sc = storeClientListAdd(entry, context); | |
329 | #if DELAY_POOLS | |
330 | /* delay_id is already set on original store client */ | |
b67e2c8c | 331 | context->sc->setDelayId(DelayId::DelayClient(http)); |
edce4d98 | 332 | #endif |
333 | http->request->lastmod = http->old_entry->lastmod; | |
528b2c61 | 334 | debug(88, 5)("clientProcessExpired : lastmod %ld", |
edce4d98 | 335 | (long int) entry->lastmod); |
336 | http->entry = entry; | |
c8be6d7b | 337 | assert(http->out.offset == 0); |
edce4d98 | 338 | fwdStart(http->conn ? http->conn->fd : -1, http->entry, http->request); |
339 | /* Register with storage manager to receive updates when data comes in. */ | |
340 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) | |
528b2c61 | 341 | debug(88, 0) ("clientProcessExpired: Found ENTRY_ABORTED object"); |
c8be6d7b | 342 | { |
528b2c61 | 343 | StoreIOBuffer tempBuffer; |
c8be6d7b | 344 | /* start counting the length from 0 */ |
345 | tempBuffer.offset = 0; | |
346 | tempBuffer.length = HTTP_REQBUF_SZ; | |
347 | tempBuffer.data = context->tempbuf; | |
348 | storeClientCopy(context->sc, entry, | |
349 | tempBuffer, clientHandleIMSReply, context); | |
350 | } | |
edce4d98 | 351 | } |
352 | ||
353 | int | |
354 | modifiedSince(StoreEntry * entry, request_t * request) | |
355 | { | |
356 | int object_length; | |
edce4d98 | 357 | time_t mod_time = entry->lastmod; |
358 | debug(88, 3) ("modifiedSince: '%s'\n", storeUrl(entry)); | |
359 | if (mod_time < 0) | |
360 | mod_time = entry->timestamp; | |
361 | debug(88, 3) ("modifiedSince: mod_time = %ld\n", (long int) mod_time); | |
362 | if (mod_time < 0) | |
363 | return 1; | |
364 | /* Find size of the object */ | |
528b2c61 | 365 | object_length = entry->getReply()->content_length; |
edce4d98 | 366 | if (object_length < 0) |
367 | object_length = contentLen(entry); | |
368 | if (mod_time > request->ims) { | |
369 | debug(88, 3) ("--> YES: entry newer than client\n"); | |
370 | return 1; | |
371 | } else if (mod_time < request->ims) { | |
372 | debug(88, 3) ("--> NO: entry older than client\n"); | |
373 | return 0; | |
374 | } else if (request->imslen < 0) { | |
375 | debug(88, 3) ("--> NO: same LMT, no client length\n"); | |
376 | return 0; | |
377 | } else if (request->imslen == object_length) { | |
378 | debug(88, 3) ("--> NO: same LMT, same length\n"); | |
379 | return 0; | |
380 | } else { | |
381 | debug(88, 3) ("--> YES: same LMT, different length\n"); | |
382 | return 1; | |
383 | } | |
384 | } | |
385 | ||
386 | static int | |
387 | clientGetsOldEntry(StoreEntry * new_entry, StoreEntry * old_entry, | |
388 | request_t * request) | |
389 | { | |
528b2c61 | 390 | const http_status status = new_entry->getReply()->sline.status; |
edce4d98 | 391 | if (0 == status) { |
392 | debug(88, 5) ("clientGetsOldEntry: YES, broken HTTP reply\n"); | |
393 | return 1; | |
394 | } | |
395 | /* If the reply is a failure then send the old object as a last | |
396 | * resort */ | |
397 | if (status >= 500 && status < 600) { | |
398 | debug(88, 3) ("clientGetsOldEntry: YES, failure reply=%d\n", status); | |
399 | return 1; | |
400 | } | |
401 | /* If the reply is anything but "Not Modified" then | |
402 | * we must forward it to the client */ | |
403 | if (HTTP_NOT_MODIFIED != status) { | |
404 | debug(88, 5) ("clientGetsOldEntry: NO, reply=%d\n", status); | |
405 | return 0; | |
406 | } | |
1ef68325 | 407 | /* If the client did not send IMS in the request, then it |
408 | * must get the old object, not this "Not Modified" reply | |
409 | * REGARDLESS of validation */ | |
410 | if (!request->flags.ims) { | |
411 | debug(88, 5) ("clientGetsOldEntry: YES, no client IMS\n"); | |
412 | return 1; | |
413 | } | |
528b2c61 | 414 | /* If key metadata in the reply are not consistent with the |
415 | * old entry, we must use the new reply. | |
416 | * Note: this means that the server is sending garbage replies | |
417 | * in that it has sent an IMS that is incompatible with our request!? | |
418 | */ | |
419 | /* This is a duplicate call through the HandleIMS code path. | |
420 | * Can we guarantee we don't need it elsewhere? | |
421 | */ | |
422 | if (!httpReplyValidatorsMatch(new_entry->getReply(), | |
423 | old_entry->getReply())) { | |
1ef68325 | 424 | debug(88, 5) ("clientGetsOldEntry: NO, Old object has been invalidated" |
528b2c61 | 425 | "by the new one\n"); |
426 | return 0; | |
427 | } | |
edce4d98 | 428 | /* If the client IMS time is prior to the entry LASTMOD time we |
429 | * need to send the old object */ | |
430 | if (modifiedSince(old_entry, request)) { | |
431 | debug(88, 5) ("clientGetsOldEntry: YES, modified since %ld\n", | |
432 | (long int) request->ims); | |
433 | return 1; | |
434 | } | |
435 | debug(88, 5) ("clientGetsOldEntry: NO, new one is fine\n"); | |
436 | return 0; | |
437 | } | |
438 | ||
528b2c61 | 439 | void |
440 | clientReplyContext::sendClientUpstreamResponse() | |
441 | { | |
442 | StoreIOBuffer tempresult; | |
443 | http->logType = LOG_TCP_REFRESH_MISS; | |
444 | clientRemoveStoreReference(this, &old_sc, &http->old_entry); | |
445 | /* here the data to send is the data we just recieved */ | |
446 | tempBuffer.offset = 0; | |
447 | old_reqsize = 0; | |
448 | /* clientSendMoreData tracks the offset as well. | |
449 | * Force it back to zero */ | |
450 | reqofs = 0; | |
451 | assert(!EBIT_TEST(http->entry->flags, ENTRY_ABORTED)); | |
452 | /* TODO: provide SendMoreData with the ready parsed reply */ | |
453 | tempresult.length = reqsize; | |
454 | tempresult.data = tempbuf; | |
455 | clientSendMoreData(this, tempresult); | |
456 | } | |
457 | ||
edce4d98 | 458 | void |
c8be6d7b | 459 | clientHandleIMSReply(void *data, StoreIOBuffer result) |
edce4d98 | 460 | { |
e6ccf245 | 461 | clientReplyContext *context = (clientReplyContext *)data; |
edce4d98 | 462 | clientHttpRequest *http = context->http; |
463 | StoreEntry *entry = http->entry; | |
edce4d98 | 464 | const char *url = storeUrl(entry); |
465 | int unlink_request = 0; | |
466 | StoreEntry *oldentry; | |
467 | http_status status; | |
c8be6d7b | 468 | debug(88, 3) ("clientHandleIMSReply: %s, %lu bytes\n", url, |
469 | (long unsigned) result.length); | |
edce4d98 | 470 | if (entry == NULL) { |
471 | return; | |
472 | } | |
c8be6d7b | 473 | if (result.flags.error && !EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
edce4d98 | 474 | return; |
475 | } | |
476 | /* update size of the request */ | |
c8be6d7b | 477 | context->reqsize = result.length + context->reqofs; |
528b2c61 | 478 | status = entry->getReply()->sline.status; |
edce4d98 | 479 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
480 | debug(88, 3) ("clientHandleIMSReply: ABORTED '%s'\n", url); | |
481 | /* We have an existing entry, but failed to validate it */ | |
482 | /* Its okay to send the old one anyway */ | |
29b8d8d6 | 483 | http->logType = LOG_TCP_REFRESH_FAIL_HIT; |
edce4d98 | 484 | clientRemoveStoreReference(context, &context->sc, &entry); |
485 | /* Get the old request back */ | |
e6ccf245 | 486 | context->restoreState(http); |
edce4d98 | 487 | entry = http->entry; |
c8be6d7b | 488 | return; |
489 | } | |
490 | if (STORE_PENDING == entry->store_status && 0 == status) { | |
edce4d98 | 491 | /* more headers needed to decide */ |
492 | debug(88, 3) ("clientHandleIMSReply: Incomplete headers for '%s'\n", | |
493 | url); | |
528b2c61 | 494 | if (result.length + context->reqsize >= HTTP_REQBUF_SZ) { |
edce4d98 | 495 | /* will not get any bigger than that */ |
c8be6d7b | 496 | debug(88, 3) |
edce4d98 | 497 | ("clientHandleIMSReply: Reply is too large '%s', using old entry\n", |
498 | url); | |
499 | /* use old entry, this repeats the code abovez */ | |
29b8d8d6 | 500 | http->logType = LOG_TCP_REFRESH_FAIL_HIT; |
edce4d98 | 501 | clientRemoveStoreReference(context, &context->sc, &entry); |
502 | entry = http->entry = http->old_entry; | |
503 | /* Get the old request back */ | |
e6ccf245 | 504 | context->restoreState(http); |
edce4d98 | 505 | entry = http->entry; |
edce4d98 | 506 | } else { |
4993f571 | 507 | context->reqofs += result.length; |
528b2c61 | 508 | context->waitForMoreData(result); |
edce4d98 | 509 | } |
c8be6d7b | 510 | return; |
511 | } | |
512 | if (clientGetsOldEntry(entry, http->old_entry, http->request)) { | |
528b2c61 | 513 | /* We initiated the IMS request and the IMS is compatible with |
514 | * our object. As the client is not expecting | |
edce4d98 | 515 | * 304, so put the good one back. First, make sure the old entry |
516 | * headers have been loaded from disk. */ | |
e6ccf245 | 517 | clientStreamNode *next = (clientStreamNode *)context->http->client_stream.head->next->data; |
528b2c61 | 518 | StoreIOBuffer tempresult; |
edce4d98 | 519 | oldentry = http->old_entry; |
29b8d8d6 | 520 | http->logType = LOG_TCP_REFRESH_HIT; |
1ef68325 | 521 | if (httpReplyValidatorsMatch(entry->getReply(), |
522 | http->old_entry->getReply())) { | |
523 | if (oldentry->mem_obj->request == NULL) { | |
524 | oldentry->mem_obj->request = requestLink(entry->mem_obj->request); | |
525 | unlink_request = 1; | |
526 | } | |
527 | /* Don't memcpy() the whole reply structure here. For example, | |
528 | * www.thegist.com (Netscape/1.13) returns a content-length for | |
529 | * 304's which seems to be the length of the 304 HEADERS!!! and | |
530 | * not the body they refer to. */ | |
531 | httpReplyUpdateOnNotModified((HttpReply *)oldentry->getReply(), entry->getReply()); | |
532 | storeTimestampsSet(oldentry); | |
533 | clientRemoveStoreReference(context, &context->sc, &entry); | |
534 | oldentry->timestamp = squid_curtime; | |
535 | if (unlink_request) { | |
536 | requestUnlink(oldentry->mem_obj->request); | |
537 | oldentry->mem_obj->request = NULL; | |
538 | } | |
edce4d98 | 539 | } |
540 | /* Get the old request back */ | |
e6ccf245 | 541 | context->restoreState(http); |
edce4d98 | 542 | entry = http->entry; |
543 | /* here the data to send is in the next nodes buffers already */ | |
544 | assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED)); | |
c8be6d7b | 545 | tempresult.length = context->reqsize; |
546 | tempresult.data = next->readBuffer.data; | |
547 | clientSendMoreData(context, tempresult); | |
548 | return; | |
528b2c61 | 549 | } else { |
550 | /* The client gets the new entry, | |
551 | * either as a 304 (they initiated the IMS) or | |
552 | * as a full request from the upstream | |
553 | * The new entry is *not* a 304 reply, or | |
554 | * is a 304 that is incompatible with our cached entities. | |
555 | */ | |
556 | if (http->request->flags.ims) { | |
557 | /* The client asked for a IMS, and can deal | |
558 | * with any reply | |
559 | * XXX TODO: invalidate our object if it's not valid any more. | |
560 | * Send the IMS reply to the client. | |
561 | */ | |
562 | context->sendClientUpstreamResponse(); | |
563 | } else if (httpReplyValidatorsMatch (entry->getReply(), | |
564 | http->old_entry->getReply())) { | |
565 | /* Our object is usable once updated */ | |
566 | /* the client did not ask for IMS, send the whole object | |
567 | */ | |
568 | /* the client needs to get this reply */ | |
569 | StoreIOBuffer tempresult; | |
29b8d8d6 | 570 | http->logType = LOG_TCP_REFRESH_MISS; |
528b2c61 | 571 | if (HTTP_NOT_MODIFIED == entry->getReply()->sline.status) { |
572 | httpReplyUpdateOnNotModified((HttpReply *)http->old_entry->getReply(), | |
573 | entry->getReply()); | |
edce4d98 | 574 | storeTimestampsSet(http->old_entry); |
29b8d8d6 | 575 | http->logType = LOG_TCP_REFRESH_HIT; |
edce4d98 | 576 | } |
577 | clientRemoveStoreReference(context, &context->old_sc, &http->old_entry); | |
578 | /* here the data to send is the data we just recieved */ | |
c8be6d7b | 579 | context->tempBuffer.offset = 0; |
edce4d98 | 580 | context->old_reqsize = 0; |
af29fa86 | 581 | /* clientSendMoreData tracks the offset as well. |
582 | * Force it back to zero */ | |
583 | context->reqofs = 0; | |
edce4d98 | 584 | assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED)); |
585 | /* TODO: provide SendMoreData with the ready parsed reply */ | |
c8be6d7b | 586 | tempresult.length = context->reqsize; |
587 | tempresult.data = context->tempbuf; | |
588 | clientSendMoreData(context, tempresult); | |
528b2c61 | 589 | } else { |
590 | /* the client asked for the whole object, and | |
591 | * 1) our object was stale | |
592 | * 2) our internally generated IMS failed to validate | |
593 | * 3) the server sent incompatible headers in it's reply | |
594 | */ | |
595 | http->logType = LOG_TCP_REFRESH_MISS; | |
596 | clientProcessMiss(context); | |
597 | /* We start over for everything except IMS because: | |
598 | * 1) HEAD requests will go straight through now | |
599 | * 2) GET requests will go straight through now | |
600 | * 3) IMS requests are a corner case. If the server | |
601 | * decided to give us different data, we should give | |
602 | * that to the client, which means returning our IMS request. | |
603 | */ | |
604 | } | |
edce4d98 | 605 | } |
606 | } | |
607 | ||
e6ccf245 | 608 | extern "C" CSR clientGetMoreData; |
609 | extern "C" CSD clientReplyDetach; | |
edce4d98 | 610 | |
611 | /* | |
612 | * clientCacheHit should only be called until the HTTP reply headers | |
613 | * have been parsed. Normally this should be a single call, but | |
614 | * it might take more than one. As soon as we have the headers, | |
615 | * we hand off to clientSendMoreData, clientProcessExpired, or | |
616 | * clientProcessMiss. | |
617 | */ | |
618 | void | |
c8be6d7b | 619 | clientCacheHit(void *data, StoreIOBuffer result) |
edce4d98 | 620 | { |
e6ccf245 | 621 | clientReplyContext *context = (clientReplyContext *)data; |
edce4d98 | 622 | clientHttpRequest *http = context->http; |
623 | StoreEntry *e = http->entry; | |
edce4d98 | 624 | request_t *r = http->request; |
c4b7a5a9 | 625 | debug(88, 3) ("clientCacheHit: %s, %ud bytes\n", http->uri, (unsigned int)result.length); |
edce4d98 | 626 | if (http->entry == NULL) { |
627 | debug(88, 3) ("clientCacheHit: request aborted\n"); | |
628 | return; | |
c8be6d7b | 629 | } else if (result.flags.error) { |
edce4d98 | 630 | /* swap in failure */ |
631 | debug(88, 3) ("clientCacheHit: swapin failure for %s\n", http->uri); | |
29b8d8d6 | 632 | http->logType = LOG_TCP_SWAPFAIL_MISS; |
edce4d98 | 633 | clientRemoveStoreReference(context, &context->sc, &http->entry); |
634 | clientProcessMiss(context); | |
635 | return; | |
636 | } | |
d3b3ab85 | 637 | if (result.length == 0) { |
638 | /* the store couldn't get enough data from the file for us to id the | |
639 | * object | |
640 | */ | |
641 | /* treat as a miss */ | |
642 | http->logType = LOG_TCP_MISS; | |
643 | clientProcessMiss(context); | |
644 | return; | |
645 | } | |
edce4d98 | 646 | assert(!EBIT_TEST(e->flags, ENTRY_ABORTED)); |
647 | /* update size of the request */ | |
c8be6d7b | 648 | context->reqsize = result.length + context->reqofs; |
528b2c61 | 649 | if (e->getReply()->sline.status == 0) { |
edce4d98 | 650 | /* |
651 | * we don't have full reply headers yet; either wait for more or | |
652 | * punt to clientProcessMiss. | |
653 | */ | |
654 | if (e->mem_status == IN_MEMORY || e->store_status == STORE_OK) { | |
655 | clientProcessMiss(context); | |
c8be6d7b | 656 | } else if (result.length + context->reqofs >= HTTP_REQBUF_SZ |
edce4d98 | 657 | && http->out.offset == 0) { |
658 | clientProcessMiss(context); | |
659 | } else { | |
660 | clientStreamNode *next; | |
528b2c61 | 661 | StoreIOBuffer tempBuffer; |
edce4d98 | 662 | debug(88, 3) ("clientCacheHit: waiting for HTTP reply headers\n"); |
c8be6d7b | 663 | context->reqofs += result.length; |
edce4d98 | 664 | assert(context->reqofs <= HTTP_REQBUF_SZ); |
665 | /* get the next users' buffer */ | |
c8be6d7b | 666 | /* FIXME: HTTP_REQBUF_SZ must be wrong here ??! |
667 | */ | |
e6ccf245 | 668 | next = (clientStreamNode *)context->http->client_stream.head->next->data; |
c8be6d7b | 669 | tempBuffer.offset = http->out.offset + context->reqofs; |
670 | tempBuffer.length = HTTP_REQBUF_SZ; | |
671 | tempBuffer.data = next->readBuffer.data + context->reqofs; | |
edce4d98 | 672 | storeClientCopy(context->sc, e, |
c8be6d7b | 673 | tempBuffer, clientCacheHit, context); |
edce4d98 | 674 | } |
675 | return; | |
676 | } | |
677 | /* | |
678 | * Got the headers, now grok them | |
679 | */ | |
29b8d8d6 | 680 | assert(http->logType == LOG_TCP_HIT); |
edce4d98 | 681 | switch (varyEvaluateMatch(e, r)) { |
682 | case VARY_NONE: | |
683 | /* No variance detected. Continue as normal */ | |
684 | break; | |
685 | case VARY_MATCH: | |
686 | /* This is the correct entity for this request. Continue */ | |
687 | debug(88, 2) ("clientProcessHit: Vary MATCH!\n"); | |
688 | break; | |
689 | case VARY_OTHER: | |
690 | /* This is not the correct entity for this request. We need | |
691 | * to requery the cache. | |
692 | */ | |
693 | clientRemoveStoreReference(context, &context->sc, &http->entry); | |
694 | e = NULL; | |
695 | /* Note: varyEvalyateMatch updates the request with vary information | |
696 | * so we only get here once. (it also takes care of cancelling loops) | |
697 | */ | |
698 | debug(88, 2) ("clientProcessHit: Vary detected!\n"); | |
699 | clientGetMoreData(context->ourNode, http); | |
700 | return; | |
701 | case VARY_CANCEL: | |
702 | /* varyEvaluateMatch found a object loop. Process as miss */ | |
703 | debug(88, 1) ("clientProcessHit: Vary object loop!\n"); | |
704 | clientProcessMiss(context); | |
705 | return; | |
706 | } | |
707 | if (r->method == METHOD_PURGE) { | |
708 | clientRemoveStoreReference(context, &context->sc, &http->entry); | |
709 | e = NULL; | |
e6ccf245 | 710 | context->purgeRequest(); |
edce4d98 | 711 | return; |
712 | } | |
713 | if (storeCheckNegativeHit(e)) { | |
29b8d8d6 | 714 | http->logType = LOG_TCP_NEGATIVE_HIT; |
c8be6d7b | 715 | clientSendMoreData(context, result); |
f7cf9b8c | 716 | } else if (!Config.onoff.offline && refreshCheckHTTP(e, r) && !http->flags.internal) { |
edce4d98 | 717 | debug(88, 5) ("clientCacheHit: in refreshCheck() block\n"); |
718 | /* | |
719 | * We hold a stale copy; it needs to be validated | |
720 | */ | |
721 | /* | |
722 | * The 'need_validation' flag is used to prevent forwarding | |
723 | * loops between siblings. If our copy of the object is stale, | |
724 | * then we should probably only use parents for the validation | |
725 | * request. Otherwise two siblings could generate a loop if | |
726 | * both have a stale version of the object. | |
727 | */ | |
728 | r->flags.need_validation = 1; | |
729 | if (e->lastmod < 0) { | |
730 | /* | |
731 | * Previous reply didn't have a Last-Modified header, | |
732 | * we cannot revalidate it. | |
733 | */ | |
29b8d8d6 | 734 | http->logType = LOG_TCP_MISS; |
edce4d98 | 735 | clientProcessMiss(context); |
736 | } else if (r->flags.nocache) { | |
737 | /* | |
738 | * This did not match a refresh pattern that overrides no-cache | |
739 | * we should honour the client no-cache header. | |
740 | */ | |
29b8d8d6 | 741 | http->logType = LOG_TCP_CLIENT_REFRESH_MISS; |
edce4d98 | 742 | clientProcessMiss(context); |
743 | } else if (r->protocol == PROTO_HTTP) { | |
744 | /* | |
745 | * Object needs to be revalidated | |
746 | * XXX This could apply to FTP as well, if Last-Modified is known. | |
747 | */ | |
29b8d8d6 | 748 | http->logType = LOG_TCP_REFRESH_MISS; |
edce4d98 | 749 | clientProcessExpired(context); |
750 | } else { | |
751 | /* | |
752 | * We don't know how to re-validate other protocols. Handle | |
753 | * them as if the object has expired. | |
754 | */ | |
29b8d8d6 | 755 | http->logType = LOG_TCP_MISS; |
edce4d98 | 756 | clientProcessMiss(context); |
757 | } | |
758 | } else if (r->flags.ims) { | |
759 | /* | |
760 | * Handle If-Modified-Since requests from the client | |
761 | */ | |
528b2c61 | 762 | if (e->getReply()->sline.status != HTTP_OK) { |
edce4d98 | 763 | debug(88, 4) ("clientCacheHit: Reply code %d != 200\n", |
528b2c61 | 764 | e->getReply()->sline.status); |
29b8d8d6 | 765 | http->logType = LOG_TCP_MISS; |
edce4d98 | 766 | clientProcessMiss(context); |
767 | } else if (modifiedSince(e, http->request)) { | |
29b8d8d6 | 768 | http->logType = LOG_TCP_IMS_HIT; |
c8be6d7b | 769 | clientSendMoreData(context, result); |
edce4d98 | 770 | } else { |
edce4d98 | 771 | time_t timestamp = e->timestamp; |
528b2c61 | 772 | HttpReply *temprep = httpReplyMake304 (e->getReply()); |
29b8d8d6 | 773 | http->logType = LOG_TCP_IMS_HIT; |
edce4d98 | 774 | clientRemoveStoreReference(context, &context->sc, &http->entry); |
775 | http->entry = e = | |
776 | clientCreateStoreEntry(context, http->request->method, | |
e429f975 | 777 | request_flags()); |
edce4d98 | 778 | /* |
779 | * Copy timestamp from the original entry so the 304 | |
780 | * reply has a meaningful Age: header. | |
781 | */ | |
782 | e->timestamp = timestamp; | |
528b2c61 | 783 | httpReplySwapOut (temprep, e); |
784 | e->complete(); | |
edce4d98 | 785 | /* TODO: why put this in the store and then serialise it and then parse it again. |
786 | * Simply mark the request complete in our context and | |
787 | * write the reply struct to the client side | |
788 | */ | |
528b2c61 | 789 | triggerInitialStoreReadWithClientParameters(context, http); |
edce4d98 | 790 | } |
791 | } else { | |
792 | /* | |
793 | * plain ol' cache hit | |
794 | */ | |
795 | if (e->mem_status == IN_MEMORY) | |
29b8d8d6 | 796 | http->logType = LOG_TCP_MEM_HIT; |
edce4d98 | 797 | else if (Config.onoff.offline) |
29b8d8d6 | 798 | http->logType = LOG_TCP_OFFLINE_HIT; |
c8be6d7b | 799 | clientSendMoreData(context, result); |
edce4d98 | 800 | } |
801 | } | |
802 | ||
803 | /* | |
804 | * Prepare to fetch the object as it's a cache miss of some kind. | |
805 | */ | |
806 | void | |
807 | clientProcessMiss(clientReplyContext * context) | |
808 | { | |
809 | clientHttpRequest *http = context->http; | |
810 | char *url = http->uri; | |
811 | request_t *r = http->request; | |
812 | ErrorState *err = NULL; | |
813 | debug(88, 4) ("clientProcessMiss: '%s %s'\n", | |
814 | RequestMethodStr[r->method], url); | |
815 | /* | |
816 | * We might have a left-over StoreEntry from a failed cache hit | |
817 | * or IMS request. | |
818 | */ | |
819 | if (http->entry) { | |
820 | if (EBIT_TEST(http->entry->flags, ENTRY_SPECIAL)) { | |
821 | debug(88, 0) ("clientProcessMiss: miss on a special object (%s).\n", | |
822 | url); | |
29b8d8d6 | 823 | debug(88, 0) ("\tlog_type = %s\n", log_tags[http->logType]); |
edce4d98 | 824 | storeEntryDump(http->entry, 1); |
825 | } | |
826 | clientRemoveStoreReference(context, &context->sc, &http->entry); | |
827 | } | |
828 | if (r->method == METHOD_PURGE) { | |
e6ccf245 | 829 | context->purgeRequest(); |
edce4d98 | 830 | return; |
831 | } | |
832 | if (clientOnlyIfCached(http)) { | |
833 | clientProcessOnlyIfCachedMiss(context); | |
834 | return; | |
835 | } | |
836 | /* | |
837 | * Deny loops when running in accelerator/transproxy mode. | |
838 | */ | |
839 | if (http->flags.accel && r->flags.loopdetect) { | |
edce4d98 | 840 | http->al.http.code = HTTP_FORBIDDEN; |
841 | err = | |
842 | clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, | |
843 | &http->conn->peer.sin_addr, http->request); | |
844 | http->entry = | |
e429f975 | 845 | clientCreateStoreEntry(context, r->method, request_flags()); |
edce4d98 | 846 | errorAppendEntry(http->entry, err); |
528b2c61 | 847 | triggerInitialStoreReadWithClientParameters(context, http); |
edce4d98 | 848 | return; |
849 | } else { | |
edce4d98 | 850 | assert(http->out.offset == 0); |
851 | http->entry = clientCreateStoreEntry(context, r->method, r->flags); | |
528b2c61 | 852 | triggerInitialStoreReadWithClientParameters(context, http); |
edce4d98 | 853 | if (http->redirect.status) { |
854 | HttpReply *rep = httpReplyCreate(); | |
855 | #if LOG_TCP_REDIRECTS | |
29b8d8d6 | 856 | http->logType = LOG_TCP_REDIRECT; |
edce4d98 | 857 | #endif |
858 | storeReleaseRequest(http->entry); | |
859 | httpRedirectReply(rep, http->redirect.status, | |
860 | http->redirect.location); | |
861 | httpReplySwapOut(rep, http->entry); | |
528b2c61 | 862 | http->entry->complete(); |
edce4d98 | 863 | return; |
864 | } | |
865 | if (http->flags.internal) | |
866 | r->protocol = PROTO_INTERNAL; | |
867 | fwdStart(http->conn ? http->conn->fd : -1, http->entry, r); | |
868 | } | |
869 | } | |
870 | ||
871 | /* | |
872 | * client issued a request with an only-if-cached cache-control directive; | |
873 | * we did not find a cached object that can be returned without | |
874 | * contacting other servers; | |
875 | * respond with a 504 (Gateway Timeout) as suggested in [RFC 2068] | |
876 | */ | |
877 | static void | |
878 | clientProcessOnlyIfCachedMiss(clientReplyContext * context) | |
879 | { | |
880 | clientHttpRequest *http = context->http; | |
881 | char *url = http->uri; | |
882 | request_t *r = http->request; | |
883 | ErrorState *err = NULL; | |
edce4d98 | 884 | debug(88, 4) ("clientProcessOnlyIfCachedMiss: '%s %s'\n", |
885 | RequestMethodStr[r->method], url); | |
886 | http->al.http.code = HTTP_GATEWAY_TIMEOUT; | |
887 | err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, HTTP_GATEWAY_TIMEOUT, NULL, | |
888 | &http->conn->peer.sin_addr, http->request); | |
889 | clientRemoveStoreReference(context, &context->sc, &http->entry); | |
c8be6d7b | 890 | startError(context, http, err); |
edce4d98 | 891 | } |
892 | ||
893 | void | |
e6ccf245 | 894 | clientReplyContext::purgeRequestFindObjectToPurge() |
895 | { | |
896 | /* Try to find a base entry */ | |
897 | http->flags.purging = 1; | |
898 | lookingforstore = 1; | |
3b13a8fd | 899 | StoreEntry::getPublicByRequestMethod(this, http->request, METHOD_GET); |
e6ccf245 | 900 | } |
901 | ||
902 | void | |
3b13a8fd | 903 | clientReplyContext::created(StoreEntry *newEntry) |
e6ccf245 | 904 | { |
905 | if (lookingforstore == 1) | |
906 | purgeFoundGet(newEntry); | |
907 | else if (lookingforstore == 2) | |
908 | purgeFoundHead(newEntry); | |
909 | else if (lookingforstore == 3) | |
910 | purgeDoPurgeGet(newEntry); | |
911 | else if (lookingforstore == 4) | |
912 | purgeDoPurgeHead(newEntry); | |
913 | else if (lookingforstore == 5) | |
914 | identifyFoundObject(newEntry); | |
915 | } | |
916 | ||
917 | void | |
3b13a8fd | 918 | clientReplyContext::purgeFoundGet(StoreEntry *newEntry) |
e6ccf245 | 919 | { |
920 | if (newEntry->isNull()) { | |
921 | lookingforstore = 2; | |
3b13a8fd | 922 | StoreEntry::getPublicByRequestMethod(this, http->request, METHOD_HEAD); |
e6ccf245 | 923 | } else |
924 | purgeFoundObject (newEntry); | |
925 | } | |
926 | ||
927 | void | |
3b13a8fd | 928 | clientReplyContext::purgeFoundHead(StoreEntry *newEntry) |
e6ccf245 | 929 | { |
930 | if (newEntry->isNull()) | |
931 | purgeDoMissPurge(); | |
932 | else | |
933 | purgeFoundObject (newEntry); | |
934 | } | |
935 | ||
936 | void | |
3b13a8fd | 937 | clientReplyContext::purgeFoundObject(StoreEntry *entry) |
e6ccf245 | 938 | { |
939 | assert (entry && !entry->isNull()); | |
528b2c61 | 940 | StoreIOBuffer tempBuffer; |
e6ccf245 | 941 | /* Swap in the metadata */ |
942 | http->entry = entry; | |
943 | storeLockObject(http->entry); | |
944 | storeCreateMemObject(http->entry, http->uri, http->log_uri); | |
945 | http->entry->mem_obj->method = http->request->method; | |
946 | sc = storeClientListAdd(http->entry, this); | |
947 | http->logType = LOG_TCP_HIT; | |
948 | reqofs = 0; | |
949 | tempBuffer.offset = http->out.offset; | |
950 | clientStreamNode *next = (clientStreamNode *)http->client_stream.head->next->data; | |
951 | tempBuffer.length = next->readBuffer.length; | |
952 | tempBuffer.data = next->readBuffer.data; | |
953 | storeClientCopy(sc, http->entry, | |
954 | tempBuffer, clientCacheHit, this); | |
955 | } | |
956 | ||
957 | void | |
958 | clientReplyContext::purgeRequest() | |
edce4d98 | 959 | { |
edce4d98 | 960 | debug(88, 3) ("Config2.onoff.enable_purge = %d\n", |
961 | Config2.onoff.enable_purge); | |
edce4d98 | 962 | if (!Config2.onoff.enable_purge) { |
29b8d8d6 | 963 | http->logType = LOG_TCP_DENIED; |
e6ccf245 | 964 | ErrorState *err = |
edce4d98 | 965 | clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, |
966 | &http->conn->peer.sin_addr, http->request); | |
e6ccf245 | 967 | startError(this, http, err); |
edce4d98 | 968 | return; |
969 | } | |
970 | /* Release both IP cache */ | |
971 | ipcacheInvalidate(http->request->host); | |
972 | ||
e6ccf245 | 973 | if (!http->flags.purging) |
974 | purgeRequestFindObjectToPurge(); | |
975 | else | |
976 | purgeDoMissPurge(); | |
977 | } | |
978 | ||
979 | void | |
980 | clientReplyContext::purgeDoMissPurge() | |
981 | { | |
29b8d8d6 | 982 | http->logType = LOG_TCP_MISS; |
e6ccf245 | 983 | lookingforstore = 3; |
3b13a8fd | 984 | StoreEntry::getPublicByRequestMethod(this,http->request, METHOD_GET); |
e6ccf245 | 985 | } |
986 | ||
987 | void | |
3b13a8fd | 988 | clientReplyContext::purgeDoPurgeGet(StoreEntry *newEntry) |
e6ccf245 | 989 | { |
990 | assert (newEntry); | |
991 | /* Move to new() when that is created */ | |
992 | purgeStatus = HTTP_NOT_FOUND; | |
993 | ||
994 | if (!newEntry->isNull()) { | |
995 | /* Release the cached URI */ | |
996 | debug(88, 4) ("clientPurgeRequest: GET '%s'\n", storeUrl(newEntry)); | |
997 | storeRelease(newEntry); | |
998 | purgeStatus = HTTP_OK; | |
edce4d98 | 999 | } |
e6ccf245 | 1000 | lookingforstore = 4; |
3b13a8fd | 1001 | StoreEntry::getPublicByRequestMethod(this, http->request, METHOD_HEAD); |
e6ccf245 | 1002 | } |
1003 | ||
1004 | void | |
3b13a8fd | 1005 | clientReplyContext::purgeDoPurgeHead(StoreEntry *newEntry) |
e6ccf245 | 1006 | { |
1007 | if (newEntry) { | |
1008 | debug(88, 4) ("clientPurgeRequest: HEAD '%s'\n", storeUrl(newEntry)); | |
1009 | storeRelease(newEntry); | |
1010 | purgeStatus = HTTP_OK; | |
edce4d98 | 1011 | } |
e6ccf245 | 1012 | HttpReply *r; |
1013 | http_version_t version; | |
1014 | ||
edce4d98 | 1015 | /* And for Vary, release the base URI if none of the headers was included in the request */ |
1016 | if (http->request->vary_headers | |
1017 | && !strstr(http->request->vary_headers, "=")) { | |
e6ccf245 | 1018 | StoreEntry *entry = storeGetPublic(urlCanonical(http->request), METHOD_GET); |
edce4d98 | 1019 | if (entry) { |
1020 | debug(88, 4) ("clientPurgeRequest: Vary GET '%s'\n", | |
1021 | storeUrl(entry)); | |
1022 | storeRelease(entry); | |
e6ccf245 | 1023 | purgeStatus = HTTP_OK; |
edce4d98 | 1024 | } |
1025 | entry = storeGetPublic(urlCanonical(http->request), METHOD_HEAD); | |
1026 | if (entry) { | |
1027 | debug(88, 4) ("clientPurgeRequest: Vary HEAD '%s'\n", | |
1028 | storeUrl(entry)); | |
1029 | storeRelease(entry); | |
e6ccf245 | 1030 | purgeStatus = HTTP_OK; |
edce4d98 | 1031 | } |
1032 | } | |
1033 | /* | |
1034 | * Make a new entry to hold the reply to be written | |
1035 | * to the client. | |
1036 | */ | |
528b2c61 | 1037 | /* FIXME: This doesn't need to go through the store. Simply |
1038 | * push down the client chain | |
1039 | */ | |
edce4d98 | 1040 | http->entry = |
e6ccf245 | 1041 | clientCreateStoreEntry(this, http->request->method, |
e429f975 | 1042 | request_flags()); |
528b2c61 | 1043 | triggerInitialStoreReadWithClientParameters(this, http); |
1044 | r = httpReplyCreate(); | |
edce4d98 | 1045 | httpBuildVersion(&version, 1, 0); |
e6ccf245 | 1046 | httpReplySetHeaders(r, version, purgeStatus, NULL, NULL, 0, 0, -1); |
edce4d98 | 1047 | httpReplySwapOut(r, http->entry); |
528b2c61 | 1048 | http->entry->complete(); |
edce4d98 | 1049 | } |
1050 | ||
1051 | void | |
1052 | clientTraceReply(clientStreamNode * node, clientReplyContext * context) | |
1053 | { | |
1054 | HttpReply *rep; | |
1055 | http_version_t version; | |
e6ccf245 | 1056 | clientStreamNode *next = (clientStreamNode *)node->node.next->data; |
528b2c61 | 1057 | StoreIOBuffer tempBuffer; |
edce4d98 | 1058 | assert(context->http->request->max_forwards == 0); |
1059 | context->http->entry = | |
1060 | clientCreateStoreEntry(context, context->http->request->method, | |
e429f975 | 1061 | request_flags()); |
c8be6d7b | 1062 | tempBuffer.offset = next->readBuffer.offset + context->headers_sz; |
1063 | tempBuffer.length = next->readBuffer.length; | |
1064 | tempBuffer.data = next->readBuffer.data; | |
edce4d98 | 1065 | storeClientCopy(context->sc, context->http->entry, |
c8be6d7b | 1066 | tempBuffer, clientSendMoreData, context); |
edce4d98 | 1067 | storeReleaseRequest(context->http->entry); |
1068 | storeBuffer(context->http->entry); | |
1069 | rep = httpReplyCreate(); | |
1070 | httpBuildVersion(&version, 1, 0); | |
1071 | httpReplySetHeaders(rep, version, HTTP_OK, NULL, "text/plain", | |
1072 | httpRequestPrefixLen(context->http->request), 0, squid_curtime); | |
1073 | httpReplySwapOut(rep, context->http->entry); | |
edce4d98 | 1074 | httpRequestSwapOut(context->http->request, context->http->entry); |
528b2c61 | 1075 | context->http->entry->complete(); |
edce4d98 | 1076 | } |
1077 | ||
1078 | #define SENDING_BODY 0 | |
1079 | #define SENDING_HDRSONLY 1 | |
1080 | int | |
0e3be1ea | 1081 | clientCheckTransferDone(clientReplyContext * context) |
edce4d98 | 1082 | { |
e39b9382 | 1083 | StoreEntry *entry = context->http->entry; |
edce4d98 | 1084 | if (entry == NULL) |
1085 | return 0; | |
1086 | /* | |
1087 | * For now, 'done_copying' is used for special cases like | |
1088 | * Range and HEAD requests. | |
1089 | */ | |
e39b9382 | 1090 | if (context->http->flags.done_copying) |
edce4d98 | 1091 | return 1; |
1092 | /* | |
1093 | * Handle STORE_OK objects. | |
1094 | * objectLen(entry) will be set proprely. | |
1095 | * RC: Does objectLen(entry) include the Headers? | |
0e3be1ea | 1096 | * RC: Yes. |
edce4d98 | 1097 | */ |
1098 | if (entry->store_status == STORE_OK) { | |
e39b9382 | 1099 | return context->storeOKTransferDone(); |
1100 | } else { | |
1101 | return context->storeNotOKTransferDone(); | |
edce4d98 | 1102 | } |
e39b9382 | 1103 | } |
1104 | ||
1105 | int | |
1106 | clientReplyContext::storeOKTransferDone() const | |
1107 | { | |
1108 | if (http->out.offset >= objectLen(http->entry) - headers_sz) | |
1109 | return 1; | |
1110 | return 0; | |
1111 | } | |
1112 | ||
1113 | int | |
1114 | clientReplyContext::storeNotOKTransferDone() const | |
1115 | { | |
edce4d98 | 1116 | /* |
1117 | * Now, handle STORE_PENDING objects | |
1118 | */ | |
e39b9382 | 1119 | MemObject *mem = http->entry->mem_obj; |
edce4d98 | 1120 | assert(mem != NULL); |
1121 | assert(http->request != NULL); | |
0e3be1ea | 1122 | /* mem->reply was wrong because it uses the UPSTREAM header length!!! */ |
528b2c61 | 1123 | http_reply const *reply = mem->getReply(); |
e39b9382 | 1124 | if (headers_sz == 0) |
1d53363a | 1125 | /* haven't found end of headers yet */ |
1126 | return 0; | |
e39b9382 | 1127 | |
1128 | int sending = SENDING_BODY; | |
1129 | if (reply->sline.status == HTTP_NO_CONTENT || | |
1d53363a | 1130 | reply->sline.status == HTTP_NOT_MODIFIED || |
1131 | reply->sline.status < HTTP_OK || | |
1132 | http->request->method == METHOD_HEAD) | |
edce4d98 | 1133 | sending = SENDING_HDRSONLY; |
edce4d98 | 1134 | /* |
1135 | * Figure out how much data we are supposed to send. | |
1136 | * If we are sending a body and we don't have a content-length, | |
1137 | * then we must wait for the object to become STORE_OK. | |
1138 | */ | |
0e3be1ea | 1139 | if (reply->content_length < 0) |
edce4d98 | 1140 | return 0; |
e39b9382 | 1141 | |
1142 | size_t expectedLength = http->out.headers_sz + reply->content_length; | |
1143 | if (http->out.size < expectedLength) | |
edce4d98 | 1144 | return 0; |
1145 | else | |
1146 | return 1; | |
1147 | } | |
1148 | ||
1149 | ||
1150 | ||
1151 | int | |
1152 | clientGotNotEnough(clientHttpRequest const *http) | |
1153 | { | |
1154 | int cl = | |
528b2c61 | 1155 | httpReplyBodySize(http->request->method, http->entry->mem_obj->getReply()); |
edce4d98 | 1156 | assert(cl >= 0); |
8d7e46c7 | 1157 | if (http->out.offset < cl) |
edce4d98 | 1158 | return 1; |
1159 | return 0; | |
1160 | } | |
1161 | ||
1162 | ||
1163 | /* A write has completed, what is the next status based on the | |
1164 | * canonical request data? | |
1165 | * 1 something is wrong | |
1166 | * 0 nothing is wrong. | |
1167 | * | |
1168 | */ | |
1169 | int | |
1170 | clientHttpRequestStatus(int fd, clientHttpRequest const *http) | |
1171 | { | |
1172 | #if SIZEOF_SIZE_T == 4 | |
1173 | if (http->out.size > 0x7FFF0000) { | |
1174 | debug(88, 1) ("WARNING: closing FD %d to prevent counter overflow\n", | |
1175 | fd); | |
1176 | debug(88, 1) ("\tclient %s\n", | |
1177 | inet_ntoa(http->conn ? http->conn->peer.sin_addr : no_addr)); | |
1178 | debug(88, 1) ("\treceived %d bytes\n", (int) http->out.size); | |
1179 | debug(88, 1) ("\tURI %s\n", http->log_uri); | |
1180 | return 1; | |
1181 | } | |
1182 | #endif | |
1183 | #if SIZEOF_OFF_T == 4 | |
1184 | if (http->out.offset > 0x7FFF0000) { | |
1185 | debug(88, 1) ("WARNING: closing FD %d to prevent counter overflow\n", | |
1186 | fd); | |
1187 | debug(88, 1) ("\tclient %s\n", | |
1188 | inet_ntoa(http->conn ? http->conn->peer.sin_addr : no_addr)); | |
1189 | debug(88, 1) ("\treceived %d bytes (offset %d)\n", (int) http->out.size, | |
1190 | (int) http->out.offset); | |
1191 | debug(88, 1) ("\tURI %s\n", http->log_uri); | |
1192 | return 1; | |
1193 | } | |
1194 | #endif | |
1195 | return 0; | |
1196 | } | |
1197 | ||
1198 | /* Preconditions: | |
1199 | * *http is a valid structure. | |
1200 | * fd is either -1, or an open fd. | |
1201 | * | |
1202 | * TODO: enumify this | |
1203 | * | |
1204 | * This function is used by any http request sink, to determine the status | |
1205 | * of the object. | |
1206 | */ | |
1207 | clientStream_status_t | |
e6ccf245 | 1208 | clientReplyStatus(clientStreamNode * aNode, clientHttpRequest * http) |
edce4d98 | 1209 | { |
e6ccf245 | 1210 | clientReplyContext *context = (clientReplyContext *)aNode->data; |
edce4d98 | 1211 | int done; |
1212 | /* Here because lower nodes don't need it */ | |
1213 | if (http->entry == NULL) | |
1214 | return STREAM_FAILED; /* yuck, but what can we do? */ | |
1215 | if (EBIT_TEST(http->entry->flags, ENTRY_ABORTED)) | |
c8be6d7b | 1216 | /* TODO: Could upstream read errors (result.flags.error) be |
edce4d98 | 1217 | * lost, and result in undersize requests being considered |
1218 | * complete. Should we tcp reset such connections ? | |
1219 | */ | |
1220 | return STREAM_FAILED; | |
0e3be1ea | 1221 | if ((done = clientCheckTransferDone(context)) != 0 || context->flags.complete) { |
edce4d98 | 1222 | debug(88, 5) ("clientReplyStatus: transfer is DONE\n"); |
1223 | /* Ok we're finished, but how? */ | |
1224 | if (httpReplyBodySize(http->request->method, | |
528b2c61 | 1225 | http->entry->getReply()) < 0) { |
0e3be1ea | 1226 | debug(88, 5) ("clientReplyStatus: closing, content_length < 0\n"); |
edce4d98 | 1227 | return STREAM_FAILED; |
0e3be1ea | 1228 | } |
1229 | if (!done) { | |
1230 | debug(88, 5) ("clientReplyStatus: closing, !done, but read 0 bytes\n"); | |
edce4d98 | 1231 | return STREAM_FAILED; |
0e3be1ea | 1232 | } |
1233 | if (clientGotNotEnough(http)) { | |
1234 | debug(88, 5) ("clientReplyStatus: client didn't get all it expected\n"); | |
edce4d98 | 1235 | return STREAM_UNPLANNED_COMPLETE; |
0e3be1ea | 1236 | } |
1237 | if (http->request->flags.proxy_keepalive) { | |
1238 | debug(88, 5) ("clientReplyStatus: stream complete and can keepalive\n"); | |
edce4d98 | 1239 | return STREAM_COMPLETE; |
1240 | } | |
0e3be1ea | 1241 | debug(88, 5) ("clientReplyStatus: stream was not expected to complete!\n"); |
edce4d98 | 1242 | return STREAM_UNPLANNED_COMPLETE; |
edce4d98 | 1243 | } |
528b2c61 | 1244 | if (clientReplyBodyTooLarge(http->entry->getReply(), http->out.offset)) { |
0e3be1ea | 1245 | debug(88, 5) ("clientReplyStatus: client reply body is too large\n"); |
edce4d98 | 1246 | return STREAM_FAILED; |
0e3be1ea | 1247 | } |
edce4d98 | 1248 | return STREAM_NONE; |
1249 | } | |
1250 | ||
1251 | /* Responses with no body will not have a content-type header, | |
1252 | * which breaks the rep_mime_type acl, which | |
1253 | * coincidentally, is the most common acl for reply access lists. | |
1254 | * A better long term fix for this is to allow acl matchs on the various | |
1255 | * status codes, and then supply a default ruleset that puts these | |
1256 | * codes before any user defines access entries. That way the user | |
1257 | * can choose to block these responses where appropriate, but won't get | |
1258 | * mysterious breakages. | |
1259 | */ | |
1260 | static int | |
1261 | clientAlwaysAllowResponse(http_status sline) | |
1262 | { | |
1263 | switch (sline) { | |
1264 | case HTTP_CONTINUE: | |
1265 | case HTTP_SWITCHING_PROTOCOLS: | |
1266 | case HTTP_PROCESSING: | |
1267 | case HTTP_NO_CONTENT: | |
1268 | case HTTP_NOT_MODIFIED: | |
1269 | return 1; | |
1270 | /* unreached */ | |
1271 | break; | |
1272 | default: | |
1273 | return 0; | |
1274 | } | |
1275 | } | |
1276 | ||
528b2c61 | 1277 | void |
1278 | clientObeyConnectionHeader(clientHttpRequest * http, HttpReply * rep) | |
1279 | { | |
1280 | HttpHeader *hdr = &rep->header; | |
1281 | if (httpHeaderHas(hdr, HDR_CONNECTION)) { | |
1282 | /* anything that matches Connection list member will be deleted */ | |
1283 | String strConnection = httpHeaderGetList(hdr, HDR_CONNECTION); | |
1284 | const HttpHeaderEntry *e; | |
1285 | HttpHeaderPos pos = HttpHeaderInitPos; | |
1286 | /* | |
1287 | * think: on-average-best nesting of the two loops (hdrEntry | |
1288 | * and strListItem) @?@ | |
1289 | */ | |
1290 | /* | |
1291 | * maybe we should delete standard stuff ("keep-alive","close") | |
1292 | * from strConnection first? | |
1293 | */ | |
1294 | while ((e = httpHeaderGetEntry(hdr, &pos))) { | |
1295 | if (strListIsMember(&strConnection, e->name.buf(), ',')) | |
1296 | httpHeaderDelAt(hdr, pos); | |
1297 | } | |
1298 | httpHeaderDelById(hdr, HDR_CONNECTION); | |
1299 | strConnection.clean(); | |
1300 | } | |
1301 | } | |
1302 | ||
edce4d98 | 1303 | /* |
1304 | * filters out unwanted entries from original reply header | |
1305 | * adds extra entries if we have more info than origin server | |
1306 | * adds Squid specific entries | |
1307 | */ | |
1308 | static void | |
e6ccf245 | 1309 | clientBuildReplyHeader(clientReplyContext *context, HttpReply * rep) |
edce4d98 | 1310 | { |
e6ccf245 | 1311 | clientHttpRequest * http = context->http; |
edce4d98 | 1312 | HttpHeader *hdr = &rep->header; |
c8be6d7b | 1313 | int is_hit = logTypeIsATcpHit(http->logType); |
edce4d98 | 1314 | request_t *request = http->request; |
1315 | #if DONT_FILTER_THESE | |
1316 | /* but you might want to if you run Squid as an HTTP accelerator */ | |
1317 | /* httpHeaderDelById(hdr, HDR_ACCEPT_RANGES); */ | |
1318 | httpHeaderDelById(hdr, HDR_ETAG); | |
1319 | #endif | |
1320 | httpHeaderDelById(hdr, HDR_PROXY_CONNECTION); | |
1321 | /* here: Keep-Alive is a field-name, not a connection directive! */ | |
1322 | httpHeaderDelByName(hdr, "Keep-Alive"); | |
1323 | /* remove Set-Cookie if a hit */ | |
1324 | if (is_hit) | |
1325 | httpHeaderDelById(hdr, HDR_SET_COOKIE); | |
528b2c61 | 1326 | clientObeyConnectionHeader(http, rep); |
1327 | // if (request->range) | |
1328 | // clientBuildRangeHeader(http, rep); | |
edce4d98 | 1329 | /* |
1330 | * Add a estimated Age header on cache hits. | |
1331 | */ | |
1332 | if (is_hit) { | |
1333 | /* | |
1334 | * Remove any existing Age header sent by upstream caches | |
1335 | * (note that the existing header is passed along unmodified | |
1336 | * on cache misses) | |
1337 | */ | |
1338 | httpHeaderDelById(hdr, HDR_AGE); | |
1339 | /* | |
1340 | * This adds the calculated object age. Note that the details of the | |
1341 | * age calculation is performed by adjusting the timestamp in | |
1342 | * storeTimestampsSet(), not here. | |
1343 | * | |
1344 | * BROWSER WORKAROUND: IE sometimes hangs when receiving a 0 Age | |
1345 | * header, so don't use it unless there is a age to report. Please | |
1346 | * note that Age is only used to make a conservative estimation of | |
1347 | * the objects age, so a Age: 0 header does not add any useful | |
1348 | * information to the reply in any case. | |
1349 | */ | |
1350 | if (NULL == http->entry) | |
1351 | (void) 0; | |
1352 | else if (http->entry->timestamp < 0) | |
1353 | (void) 0; | |
528b2c61 | 1354 | else if (http->entry->timestamp < squid_curtime) { |
edce4d98 | 1355 | httpHeaderPutInt(hdr, HDR_AGE, |
1356 | squid_curtime - http->entry->timestamp); | |
528b2c61 | 1357 | /* Signal old objects. NB: rfc 2616 is not clear, |
1358 | * by implication, on whether we should do this to all | |
1359 | * responses, or only cache hits. | |
1360 | * 14.46 states it ONLY applys for heuristically caclulated | |
1361 | * freshness values, 13.2.4 doesn't specify the same limitation. | |
1362 | * We interpret RFC 2616 under the combination. | |
1363 | */ | |
1364 | /* TODO: if maxage or s-maxage is present, don't do this */ | |
1365 | if (squid_curtime - http->entry->timestamp >= 86400) { | |
1366 | char tempbuf[512]; | |
1367 | snprintf (tempbuf, sizeof(tempbuf), "%s %s %s", | |
1368 | "113", ThisCache, | |
1369 | "This cache hit is still fresh and more than 1 day old"); | |
1370 | httpHeaderPutStr(hdr, HDR_WARNING, tempbuf); | |
1371 | } | |
1372 | } | |
1373 | ||
edce4d98 | 1374 | } |
0bd9aa82 | 1375 | /* Filter unproxyable authentication types */ |
ddd31ed3 | 1376 | if (http->logType != LOG_TCP_DENIED && |
0bd9aa82 | 1377 | (httpHeaderHas(hdr, HDR_WWW_AUTHENTICATE) || httpHeaderHas(hdr, HDR_PROXY_AUTHENTICATE))) { |
1378 | HttpHeaderPos pos = HttpHeaderInitPos; | |
1379 | HttpHeaderEntry *e; | |
1380 | while ((e = httpHeaderGetEntry(hdr, &pos))) { | |
1381 | if (e->id == HDR_WWW_AUTHENTICATE || e->id == HDR_PROXY_AUTHENTICATE) { | |
528b2c61 | 1382 | const char *value = e->value.buf(); |
0bd9aa82 | 1383 | if ((strncasecmp(value, "NTLM", 4) == 0 && |
1384 | (value[4] == '\0' || value[4] == ' ')) | |
1385 | || | |
1386 | (strncasecmp(value, "Negotiate", 9) == 0 && | |
1387 | (value[9] == '\0' || value[9] == ' '))) | |
1388 | httpHeaderDelAt(hdr, pos); | |
1389 | } | |
1390 | } | |
1391 | } | |
edce4d98 | 1392 | /* Handle authentication headers */ |
1393 | if (request->auth_user_request) | |
1394 | authenticateFixHeader(rep, request->auth_user_request, request, | |
1395 | http->flags.accel, 0); | |
1396 | /* Append X-Cache */ | |
1397 | httpHeaderPutStrf(hdr, HDR_X_CACHE, "%s from %s", | |
1398 | is_hit ? "HIT" : "MISS", getMyHostname()); | |
1399 | #if USE_CACHE_DIGESTS | |
1400 | /* Append X-Cache-Lookup: -- temporary hack, to be removed @?@ @?@ */ | |
1401 | httpHeaderPutStrf(hdr, HDR_X_CACHE_LOOKUP, "%s from %s:%d", | |
cbdfbf35 | 1402 | context->lookup_type ? context->lookup_type : "NONE", |
edce4d98 | 1403 | getMyHostname(), getMyPort()); |
1404 | #endif | |
1405 | if (httpReplyBodySize(request->method, rep) < 0) { | |
1406 | debug(88, | |
1407 | 3) | |
1408 | ("clientBuildReplyHeader: can't keep-alive, unknown body size\n"); | |
1409 | request->flags.proxy_keepalive = 0; | |
1410 | } | |
4dea3fdd | 1411 | /* Append VIA */ |
c8be6d7b | 1412 | { |
4dea3fdd | 1413 | LOCAL_ARRAY(char, bbuf, MAX_URL + 32); |
1414 | String strVia = httpHeaderGetList(hdr, HDR_VIA); | |
1415 | snprintf(bbuf, sizeof(bbuf), "%d.%d %s", | |
c8be6d7b | 1416 | rep->sline.version.major, |
1417 | rep->sline.version.minor, | |
1418 | ThisCache); | |
4dea3fdd | 1419 | strListAdd(&strVia, bbuf, ','); |
1420 | httpHeaderDelById(hdr, HDR_VIA); | |
528b2c61 | 1421 | httpHeaderPutStr(hdr, HDR_VIA, strVia.buf()); |
1422 | strVia.clean(); | |
c8be6d7b | 1423 | } |
edce4d98 | 1424 | /* Signal keep-alive if needed */ |
1425 | httpHeaderPutStr(hdr, | |
1426 | http->flags.accel ? HDR_CONNECTION : HDR_PROXY_CONNECTION, | |
1427 | request->flags.proxy_keepalive ? "keep-alive" : "close"); | |
1428 | #if ADD_X_REQUEST_URI | |
1429 | /* | |
1430 | * Knowing the URI of the request is useful when debugging persistent | |
1431 | * connections in a client; we cannot guarantee the order of http headers, | |
1432 | * but X-Request-URI is likely to be the very last header to ease use from a | |
1433 | * debugger [hdr->entries.count-1]. | |
1434 | */ | |
1435 | httpHeaderPutStr(hdr, HDR_X_REQUEST_URI, | |
1436 | http->entry->mem_obj->url ? http->entry->mem_obj->url : http->uri); | |
1437 | #endif | |
1438 | httpHdrMangleList(hdr, request); | |
1439 | } | |
1440 | ||
1441 | ||
1442 | static HttpReply * | |
e6ccf245 | 1443 | clientBuildReply(clientReplyContext *context, const char *buf, size_t size) |
edce4d98 | 1444 | { |
1445 | HttpReply *rep = httpReplyCreate(); | |
1446 | size_t k = headersEnd(buf, size); | |
1447 | if (k && httpReplyParse(rep, buf, k)) { | |
1448 | /* enforce 1.0 reply version */ | |
1449 | httpBuildVersion(&rep->sline.version, 1, 0); | |
1450 | /* do header conversions */ | |
cbdfbf35 | 1451 | clientBuildReplyHeader(context, rep); |
edce4d98 | 1452 | } else { |
1453 | /* parsing failure, get rid of the invalid reply */ | |
1454 | httpReplyDestroy(rep); | |
1455 | rep = NULL; | |
528b2c61 | 1456 | /* This is wrong. httpReplyDestroy should to the rep |
1457 | * for us, and we can destroy our own range info | |
1458 | */ | |
1459 | if (context->http->request->range) { | |
1460 | /* this will fail and destroy request->range */ | |
1461 | // clientBuildRangeHeader(context->http, rep); | |
1462 | } | |
edce4d98 | 1463 | } |
1464 | return rep; | |
1465 | } | |
1466 | ||
e6ccf245 | 1467 | void |
1468 | clientReplyContext::identifyStoreObject() | |
edce4d98 | 1469 | { |
1470 | request_t *r = http->request; | |
e6ccf245 | 1471 | if (r->flags.cachable || r->flags.internal) { |
1472 | lookingforstore = 5; | |
3b13a8fd | 1473 | StoreEntry::getPublicByRequest (this, r); |
e6ccf245 | 1474 | } else |
1475 | identifyFoundObject (NullStoreEntry::getInstance()); | |
1476 | } | |
1477 | ||
1478 | void | |
1479 | clientReplyContext::identifyFoundObject(StoreEntry *newEntry) | |
1480 | { | |
1481 | StoreEntry *e = newEntry; | |
1482 | request_t *r = http->request; | |
1483 | if (e->isNull()) { | |
1484 | http->entry = NULL; | |
1485 | } else { | |
1486 | http->entry = e; | |
1487 | } | |
528b2c61 | 1488 | e = http->entry; |
edce4d98 | 1489 | /* Release negatively cached IP-cache entries on reload */ |
1490 | if (r->flags.nocache) | |
1491 | ipcacheInvalidate(r->host); | |
1492 | #if HTTP_VIOLATIONS | |
1493 | else if (r->flags.nocache_hack) | |
1494 | ipcacheInvalidate(r->host); | |
1495 | #endif | |
1496 | #if USE_CACHE_DIGESTS | |
0df8d05e | 1497 | lookup_type = http->entry ? "HIT" : "MISS"; |
edce4d98 | 1498 | #endif |
0df8d05e | 1499 | if (NULL == http->entry) { |
edce4d98 | 1500 | /* this object isn't in the cache */ |
1501 | debug(85, 3) ("clientProcessRequest2: storeGet() MISS\n"); | |
e6ccf245 | 1502 | http->logType = LOG_TCP_MISS; |
1503 | doGetMoreData(); | |
1504 | return; | |
edce4d98 | 1505 | } |
1506 | if (Config.onoff.offline) { | |
1507 | debug(85, 3) ("clientProcessRequest2: offline HIT\n"); | |
e6ccf245 | 1508 | http->logType = LOG_TCP_HIT; |
1509 | doGetMoreData(); | |
1510 | return; | |
edce4d98 | 1511 | } |
1512 | if (http->redirect.status) { | |
1513 | /* force this to be a miss */ | |
1514 | http->entry = NULL; | |
e6ccf245 | 1515 | http->logType = LOG_TCP_MISS; |
1516 | doGetMoreData(); | |
1517 | return; | |
edce4d98 | 1518 | } |
1519 | if (!storeEntryValidToSend(e)) { | |
1520 | debug(85, 3) ("clientProcessRequest2: !storeEntryValidToSend MISS\n"); | |
1521 | http->entry = NULL; | |
e6ccf245 | 1522 | http->logType = LOG_TCP_MISS; |
1523 | doGetMoreData(); | |
1524 | return; | |
edce4d98 | 1525 | } |
1526 | if (EBIT_TEST(e->flags, ENTRY_SPECIAL)) { | |
1527 | /* Special entries are always hits, no matter what the client says */ | |
1528 | debug(85, 3) ("clientProcessRequest2: ENTRY_SPECIAL HIT\n"); | |
e6ccf245 | 1529 | http->logType = LOG_TCP_HIT; |
1530 | doGetMoreData(); | |
1531 | return; | |
edce4d98 | 1532 | } |
1533 | #if HTTP_VIOLATIONS | |
0df8d05e | 1534 | if (http->entry->store_status == STORE_PENDING) { |
edce4d98 | 1535 | if (r->flags.nocache || r->flags.nocache_hack) { |
1536 | debug(85, 3) ("Clearing no-cache for STORE_PENDING request\n\t%s\n", | |
0df8d05e | 1537 | storeUrl(http->entry)); |
edce4d98 | 1538 | r->flags.nocache = 0; |
1539 | r->flags.nocache_hack = 0; | |
1540 | } | |
1541 | } | |
1542 | #endif | |
1543 | if (r->flags.nocache) { | |
1544 | debug(85, 3) ("clientProcessRequest2: no-cache REFRESH MISS\n"); | |
1545 | http->entry = NULL; | |
e6ccf245 | 1546 | http->logType = LOG_TCP_CLIENT_REFRESH_MISS; |
1547 | doGetMoreData(); | |
1548 | return; | |
edce4d98 | 1549 | } |
1550 | /* We don't cache any range requests (for now!) -- adrian */ | |
528b2c61 | 1551 | /* RBC - and we won't until the store supports sparse objects. |
1552 | * I suspec this test is incorrect though, as we can extract ranges from | |
1553 | * a fully cached object | |
1554 | */ | |
edce4d98 | 1555 | if (r->flags.range) { |
528b2c61 | 1556 | /* XXX: test to see if we can satisfy the range with the cached object */ |
1557 | debug(85, 3) ("clientProcessRequest2: force MISS due to range presence\n"); | |
edce4d98 | 1558 | http->entry = NULL; |
e6ccf245 | 1559 | http->logType = LOG_TCP_MISS; |
1560 | doGetMoreData(); | |
1561 | return; | |
edce4d98 | 1562 | } |
1563 | debug(85, 3) ("clientProcessRequest2: default HIT\n"); | |
e6ccf245 | 1564 | http->logType = LOG_TCP_HIT; |
1565 | doGetMoreData(); | |
edce4d98 | 1566 | } |
1567 | ||
1568 | /* Request more data from the store for the client Stream | |
1569 | * This is *the* entry point to this module. | |
1570 | * | |
1571 | * Preconditions: | |
1572 | * This is the head of the list. | |
1573 | * There is at least one more node. | |
1574 | * data context is not null | |
1575 | */ | |
1576 | void | |
e6ccf245 | 1577 | clientGetMoreData(clientStreamNode * aNode, clientHttpRequest * http) |
edce4d98 | 1578 | { |
1579 | clientStreamNode *next; | |
1580 | clientReplyContext *context; | |
1581 | /* Test preconditions */ | |
e6ccf245 | 1582 | assert(aNode != NULL); |
1583 | assert(cbdataReferenceValid(aNode)); | |
1584 | assert(aNode->data != NULL); | |
1585 | assert(aNode->node.prev == NULL); | |
1586 | assert(aNode->node.next != NULL); | |
1587 | context = (clientReplyContext *)aNode->data; | |
edce4d98 | 1588 | assert(context->http == http); |
1589 | ||
e6ccf245 | 1590 | next = ( clientStreamNode *)aNode->node.next->data; |
edce4d98 | 1591 | if (!context->ourNode) |
e6ccf245 | 1592 | context->ourNode = aNode; |
1593 | /* no cbdatareference, this is only used once, and safely */ | |
edce4d98 | 1594 | if (context->flags.storelogiccomplete) { |
528b2c61 | 1595 | StoreIOBuffer tempBuffer; |
c8be6d7b | 1596 | tempBuffer.offset = next->readBuffer.offset + context->headers_sz; |
1597 | tempBuffer.length = next->readBuffer.length; | |
1598 | tempBuffer.data = next->readBuffer.data; | |
1599 | ||
edce4d98 | 1600 | storeClientCopy(context->sc, http->entry, |
c8be6d7b | 1601 | tempBuffer, clientSendMoreData, context); |
edce4d98 | 1602 | return; |
1603 | } | |
1604 | if (context->http->request->method == METHOD_PURGE) { | |
e6ccf245 | 1605 | context->purgeRequest(); |
edce4d98 | 1606 | return; |
1607 | } | |
1608 | if (context->http->request->method == METHOD_TRACE) { | |
1609 | if (context->http->request->max_forwards == 0) { | |
e6ccf245 | 1610 | clientTraceReply(aNode, context); |
edce4d98 | 1611 | return; |
1612 | } | |
1613 | /* continue forwarding, not finished yet. */ | |
29b8d8d6 | 1614 | http->logType = LOG_TCP_MISS; |
e6ccf245 | 1615 | context->doGetMoreData(); |
edce4d98 | 1616 | } else |
e6ccf245 | 1617 | context->identifyStoreObject(); |
1618 | } | |
1619 | ||
1620 | void | |
1621 | clientReplyContext::doGetMoreData() | |
1622 | { | |
edce4d98 | 1623 | /* We still have to do store logic processing - vary, cache hit etc */ |
e6ccf245 | 1624 | if (http->entry != NULL) { |
edce4d98 | 1625 | /* someone found the object in the cache for us */ |
528b2c61 | 1626 | StoreIOBuffer tempBuffer; |
e6ccf245 | 1627 | storeLockObject(http->entry); |
1628 | if (http->entry->mem_obj == NULL) { | |
edce4d98 | 1629 | /* |
1630 | * This if-block exists because we don't want to clobber | |
1631 | * a preexiting mem_obj->method value if the mem_obj | |
1632 | * already exists. For example, when a HEAD request | |
1633 | * is a cache hit for a GET response, we want to keep | |
1634 | * the method as GET. | |
1635 | */ | |
e6ccf245 | 1636 | storeCreateMemObject(http->entry, http->uri, |
1637 | http->log_uri); | |
1638 | http->entry->mem_obj->method = | |
1639 | http->request->method; | |
edce4d98 | 1640 | } |
e6ccf245 | 1641 | sc = storeClientListAdd(http->entry, this); |
edce4d98 | 1642 | #if DELAY_POOLS |
b67e2c8c | 1643 | sc->setDelayId(DelayId::DelayClient(http)); |
edce4d98 | 1644 | #endif |
e6ccf245 | 1645 | assert(http->logType == LOG_TCP_HIT); |
1646 | reqofs = 0; | |
1647 | /* guarantee nothing has been sent yet! */ | |
1648 | assert(http->out.size == 0); | |
1649 | assert(http->out.offset == 0); | |
1650 | tempBuffer.offset = reqofs; | |
1651 | tempBuffer.length = getNextNode()->readBuffer.length; | |
1652 | tempBuffer.data = getNextNode()->readBuffer.data; | |
1653 | storeClientCopy(sc, http->entry, | |
1654 | tempBuffer, clientCacheHit, this); | |
edce4d98 | 1655 | } else { |
29b8d8d6 | 1656 | /* MISS CASE, http->logType is already set! */ |
e6ccf245 | 1657 | clientProcessMiss(this); |
edce4d98 | 1658 | } |
1659 | } | |
1660 | ||
1661 | /* the next node has removed itself from the stream. */ | |
1662 | void | |
1663 | clientReplyDetach(clientStreamNode * node, clientHttpRequest * http) | |
1664 | { | |
1665 | /* detach from the stream */ | |
1666 | /* NB: This cbdataFrees our context, | |
1667 | * so the clientSendMoreData callback (if any) | |
1668 | * pending in the store will not trigger | |
1669 | */ | |
1670 | clientStreamDetach(node, http); | |
1671 | } | |
1672 | ||
1673 | /* | |
1674 | * accepts chunk of a http message in buf, parses prefix, filters headers and | |
1675 | * such, writes processed message to the message recipient | |
1676 | */ | |
1677 | void | |
c8be6d7b | 1678 | clientSendMoreData(void *data, StoreIOBuffer result) |
528b2c61 | 1679 | { |
1680 | clientReplyContext::clientSendMoreData (data, result); | |
1681 | } | |
1682 | ||
1683 | void | |
1684 | clientReplyContext::clientSendMoreData (void *data, StoreIOBuffer result) | |
edce4d98 | 1685 | { |
e6ccf245 | 1686 | clientReplyContext *context = static_cast<clientReplyContext *>(data); |
528b2c61 | 1687 | context->sendMoreData (result); |
1688 | } | |
1689 | ||
1690 | void | |
1691 | clientReplyContext::makeThisHead() | |
1692 | { | |
1693 | /* At least, I think thats what this does */ | |
1694 | dlinkDelete(&http->active, &ClientActiveRequests); | |
1695 | dlinkAdd(http, &http->active, &ClientActiveRequests); | |
1696 | } | |
1697 | ||
1698 | bool | |
1699 | clientReplyContext::errorInStream(StoreIOBuffer const &result, size_t const &sizeToProcess)const | |
1700 | { | |
1701 | return /* aborted request */ | |
1702 | (http->entry && EBIT_TEST(http->entry->flags, ENTRY_ABORTED)) || | |
1703 | /* Upstream read error */ (result.flags.error) || | |
1704 | /* Upstream EOF */ (sizeToProcess == 0); | |
1705 | } | |
1706 | ||
1707 | void | |
1708 | clientReplyContext::sendStreamError(StoreIOBuffer const &result) | |
1709 | { | |
1710 | /* call clientWriteComplete so the client socket gets closed */ | |
1711 | /* We call into the stream, because we don't know that there is a | |
1712 | * client socket! | |
1713 | */ | |
1714 | debug(88,5)("clientReplyContext::sendStreamError: A stream error has occured, marking as complete and sending no data.\n"); | |
1715 | StoreIOBuffer tempBuffer; | |
1716 | flags.complete = 1; | |
1717 | tempBuffer.flags.error = result.flags.error; | |
1718 | clientStreamCallback((clientStreamNode*)http->client_stream.head->data, http, NULL, | |
1719 | tempBuffer); | |
1720 | } | |
1721 | ||
1722 | void | |
1723 | clientReplyContext::pushStreamData(StoreIOBuffer const &result, char *source) | |
1724 | { | |
1725 | StoreIOBuffer tempBuffer; | |
1726 | if (result.length == 0) { | |
1727 | debug (88,5)("clientReplyContext::pushStreamData: marking request as complete due to 0 length store result\n"); | |
1728 | flags.complete = 1; | |
1729 | } | |
1730 | /* REMOVE ME: Only useful for two node streams */ | |
1731 | assert(result.offset - headers_sz == ((clientStreamNode *) http->client_stream.tail->data)->readBuffer.offset); | |
1732 | tempBuffer.offset = result.offset - headers_sz; | |
1733 | tempBuffer.length = result.length; | |
1734 | tempBuffer.data = source; | |
1735 | clientStreamCallback((clientStreamNode*)http->client_stream.head->data, http, NULL, | |
1736 | tempBuffer); | |
1737 | } | |
1738 | ||
1739 | clientStreamNode * | |
1740 | clientReplyContext::next() const | |
1741 | { | |
1742 | assert ( (clientStreamNode*)http->client_stream.head->next->data == getNextNode()); | |
1743 | return getNextNode(); | |
1744 | } | |
1745 | ||
1746 | void | |
1747 | clientReplyContext::waitForMoreData (StoreIOBuffer const &result) | |
1748 | { | |
1749 | debug(88,5)("clientReplyContext::waitForMoreData: Waiting for more data to parse reply headers in client side.\n"); | |
1750 | /* We don't have enough to parse the metadata yet */ | |
1751 | /* TODO: the store should give us out of band metadata and | |
1752 | * obsolete this routine | |
1753 | */ | |
1754 | /* wait for more to arrive */ | |
528b2c61 | 1755 | startSendProcess(); |
1756 | } | |
1757 | ||
1758 | void | |
1759 | clientReplyContext::startSendProcess() | |
1760 | { | |
1761 | debug(88,5)("clientReplyContext::startSendProcess: triggering store read to clientSendMoreData\n"); | |
1762 | assert(reqofs <= HTTP_REQBUF_SZ); | |
1763 | /* TODO: copy into the supplied buffer */ | |
1764 | StoreIOBuffer tempBuffer; | |
1765 | tempBuffer.offset = reqofs; | |
1766 | tempBuffer.length = next()->readBuffer.length - reqofs; | |
1767 | tempBuffer.data = next()->readBuffer.data + reqofs; | |
1768 | storeClientCopy(sc, http->entry, | |
1769 | tempBuffer, clientSendMoreData, this); | |
1770 | } | |
1771 | ||
4993f571 | 1772 | void |
1773 | clientReplyContext::processReplyAccess () | |
1774 | { | |
1775 | HttpReply *rep = holdingReply; | |
1776 | holdingReply = NULL; | |
1777 | httpReplyBodyBuildSize(http->request, rep, &Config.ReplyBodySize); | |
1778 | if (clientReplyBodyTooLarge(rep, rep->content_length)) { | |
1779 | ErrorState *err = | |
1780 | clientBuildError(ERR_TOO_BIG, HTTP_FORBIDDEN, NULL, | |
1781 | http->conn ? &http->conn->peer.sin_addr : &no_addr, | |
1782 | http->request); | |
1783 | clientRemoveStoreReference(this, &sc, &http->entry); | |
1784 | startError(this, http, err); | |
1785 | httpReplyDestroy(rep); | |
1786 | return; | |
1787 | } | |
1788 | headers_sz = rep->hdr_sz; | |
1789 | ACLChecklist *replyChecklist; | |
1790 | replyChecklist = clientAclChecklistCreate(Config.accessList.reply, http); | |
1791 | replyChecklist->reply = rep; | |
1792 | holdingReply = rep; | |
225b7b10 | 1793 | replyChecklist->nonBlockingCheck(ProcessReply, this); |
4993f571 | 1794 | } |
1795 | ||
1796 | void | |
1797 | clientReplyContext::ProcessReply (int rv, void *voidMe) | |
1798 | { | |
1799 | clientReplyContext *me = static_cast<clientReplyContext *>(voidMe); | |
1800 | me->processReply(rv); | |
1801 | } | |
1802 | ||
1803 | void | |
1804 | clientReplyContext::processReply(bool accessAllowed) | |
1805 | { | |
1806 | debug(88, 2) ("The reply for %s %s is %s, because it matched '%s'\n", | |
1807 | RequestMethodStr[http->request->method], http->uri, | |
1808 | accessAllowed ? "ALLOWED" : "DENIED", | |
1809 | AclMatchedName ? AclMatchedName : "NO ACL's"); | |
1810 | HttpReply *rep = holdingReply; | |
1811 | holdingReply = NULL; | |
1812 | if (!accessAllowed && rep->sline.status != HTTP_FORBIDDEN | |
1813 | && !clientAlwaysAllowResponse(rep->sline.status)) { | |
1814 | /* the if above is slightly broken, but there is no way | |
1815 | * to tell if this is a squid generated error page, or one from | |
1816 | * upstream at this point. */ | |
1817 | ErrorState *err; | |
1818 | err = | |
1819 | clientBuildError(ERR_ACCESS_DENIED, HTTP_FORBIDDEN, NULL, | |
1820 | http->conn ? &http->conn->peer.sin_addr : &no_addr, | |
1821 | http->request); | |
1822 | clientRemoveStoreReference(this, &sc, &http->entry); | |
1823 | startError(this, http, err); | |
1824 | httpReplyDestroy(rep); | |
1825 | return; | |
1826 | } | |
1827 | ssize_t body_size = reqofs - rep->hdr_sz; | |
1828 | assert(body_size >= 0); | |
1829 | debug(88,3) | |
1830 | ("clientSendMoreData: Appending %d bytes after %d bytes of headers\n", | |
1831 | (int) body_size, rep->hdr_sz); | |
1832 | if (http->request->method == METHOD_HEAD) { | |
1833 | /* do not forward body for HEAD replies */ | |
1834 | body_size = 0; | |
1835 | http->flags.done_copying = 1; | |
1836 | flags.complete = 1; | |
1837 | } | |
1838 | assert (!flags.headersSent); | |
1839 | flags.headersSent = true; | |
1840 | ||
1841 | StoreIOBuffer tempBuffer; | |
1842 | char *buf = next()->readBuffer.data; | |
1843 | char *body_buf = buf + rep->hdr_sz; | |
1844 | if (next()->readBuffer.offset != 0) { | |
1845 | if (next()->readBuffer.offset > body_size) { | |
1846 | /* Can't use any of the body we recieved. send nothing */ | |
1847 | tempBuffer.length = 0; | |
1848 | tempBuffer.data = NULL; | |
1849 | } else { | |
1850 | tempBuffer.length = body_size - next()->readBuffer.offset; | |
1851 | tempBuffer.data = body_buf + next()->readBuffer.offset; | |
1852 | } | |
1853 | } else { | |
1854 | tempBuffer.length = body_size; | |
1855 | tempBuffer.data = body_buf; | |
1856 | } | |
1857 | /* TODO: move the data in the buffer back by the request header size */ | |
1858 | clientStreamCallback((clientStreamNode *)http->client_stream.head->data, | |
1859 | http, rep, tempBuffer); | |
1860 | return; | |
1861 | } | |
1862 | ||
528b2c61 | 1863 | void |
1864 | clientReplyContext::sendMoreData (StoreIOBuffer result) | |
1865 | { | |
edce4d98 | 1866 | StoreEntry *entry = http->entry; |
1867 | ConnStateData *conn = http->conn; | |
1868 | int fd = conn ? conn->fd : -1; | |
1869 | HttpReply *rep = NULL; | |
528b2c61 | 1870 | char *buf = next()->readBuffer.data; |
c8be6d7b | 1871 | char *body_buf = buf; |
edce4d98 | 1872 | |
528b2c61 | 1873 | /* This is always valid until we get the headers as metadata from |
1874 | * storeClientCopy. | |
1875 | * Then it becomes reqofs == next->readBuffer.offset() | |
af29fa86 | 1876 | */ |
528b2c61 | 1877 | assert(reqofs == 0 || flags.storelogiccomplete); |
af29fa86 | 1878 | |
528b2c61 | 1879 | if (flags.headersSent && buf != result.data) { |
edce4d98 | 1880 | /* we've got to copy some data */ |
528b2c61 | 1881 | assert(result.length <= next()->readBuffer.length); |
c8be6d7b | 1882 | xmemcpy(buf, result.data, result.length); |
edce4d98 | 1883 | body_buf = buf; |
528b2c61 | 1884 | } else if (!flags.headersSent && |
1885 | buf + reqofs !=result.data) { | |
cc7de9f3 | 1886 | /* we've got to copy some data */ |
528b2c61 | 1887 | assert(result.length + reqofs <= next()->readBuffer.length); |
1888 | xmemcpy(buf + reqofs, result.data, result.length); | |
cc7de9f3 | 1889 | body_buf = buf; |
edce4d98 | 1890 | } |
1891 | /* We've got the final data to start pushing... */ | |
528b2c61 | 1892 | flags.storelogiccomplete = 1; |
edce4d98 | 1893 | |
4993f571 | 1894 | reqofs += result.length; |
1895 | ||
1896 | assert(reqofs <= HTTP_REQBUF_SZ || flags.headersSent); | |
edce4d98 | 1897 | assert(http->request != NULL); |
1898 | /* ESI TODO: remove this assert once everything is stable */ | |
1899 | assert(http->client_stream.head->data | |
1900 | && cbdataReferenceValid(http->client_stream.head->data)); | |
528b2c61 | 1901 | |
1902 | makeThisHead(); | |
1903 | debug(88, 5) ("clientSendMoreData: %s, %d bytes (%u new bytes)\n", | |
4993f571 | 1904 | http->uri, (int) reqofs, (unsigned int)result.length); |
edce4d98 | 1905 | debug(88, 5) ("clientSendMoreData: FD %d '%s', out.offset=%ld \n", |
1906 | fd, storeUrl(entry), (long int) http->out.offset); | |
528b2c61 | 1907 | |
edce4d98 | 1908 | /* update size of the request */ |
4993f571 | 1909 | reqsize = reqofs; |
528b2c61 | 1910 | |
e429f975 | 1911 | if (http->request->flags.resetTCP()) { |
edce4d98 | 1912 | /* yuck. FIXME: move to client_side.c */ |
1913 | if (fd != -1) | |
1914 | comm_reset_close(fd); | |
1915 | return; | |
528b2c61 | 1916 | } |
1917 | ||
4993f571 | 1918 | if (errorInStream(result, reqofs)) { |
528b2c61 | 1919 | sendStreamError(result); |
edce4d98 | 1920 | return; |
1921 | } | |
528b2c61 | 1922 | |
1923 | if (flags.headersSent) { | |
1924 | pushStreamData (result, buf); | |
edce4d98 | 1925 | return; |
1926 | } | |
1927 | /* handle headers */ | |
1928 | if (Config.onoff.log_mime_hdrs) { | |
1929 | size_t k; | |
4993f571 | 1930 | if ((k = headersEnd(buf, reqofs))) { |
edce4d98 | 1931 | safe_free(http->al.headers.reply); |
e6ccf245 | 1932 | http->al.headers.reply = (char *)xcalloc(k + 1, 1); |
edce4d98 | 1933 | xstrncpy(http->al.headers.reply, buf, k); |
1934 | } | |
1935 | } | |
4993f571 | 1936 | rep = clientBuildReply(this, buf, reqofs); |
1937 | ssize_t body_size = reqofs; | |
edce4d98 | 1938 | if (rep) { |
4993f571 | 1939 | holdingReply = rep; |
1940 | holdingBuffer = result; | |
1941 | processReplyAccess (); | |
528b2c61 | 1942 | return; |
4993f571 | 1943 | } else if (reqofs < HTTP_REQBUF_SZ && entry->store_status == STORE_PENDING) { |
528b2c61 | 1944 | waitForMoreData(result); |
1945 | return; | |
1946 | } else if (http->request->method == METHOD_HEAD) { | |
edce4d98 | 1947 | /* |
1948 | * If we are here, then store_status == STORE_OK and it | |
1949 | * seems we have a HEAD repsponse which is missing the | |
1950 | * empty end-of-headers line (home.mira.net, phttpd/0.99.72 | |
1951 | * does this). Because clientBuildReply() fails we just | |
1952 | * call this reply a body, set the done_copying flag and | |
1953 | * continue... | |
1954 | */ | |
528b2c61 | 1955 | /* RBC: Note that this is seriously broken, as we *need* the |
1956 | * metadata to allow further client modules to work. As such | |
1957 | * webservers are seriously broken, this is probably not | |
1958 | * going to get fixed.. perhapos we should remove it? | |
1959 | */ | |
1960 | debug (88,0)("Broken head response - probably phttpd/0.99.72\n"); | |
edce4d98 | 1961 | http->flags.done_copying = 1; |
528b2c61 | 1962 | flags.complete = 1; |
decfdad0 | 1963 | /* |
1964 | * And as this is a malformed HTTP reply we cannot keep | |
1965 | * the connection persistent | |
1966 | */ | |
ae3ec961 | 1967 | http->request->flags.proxy_keepalive = 0; |
528b2c61 | 1968 | |
1969 | StoreIOBuffer tempBuffer; | |
1970 | assert(body_buf && body_size); | |
1971 | tempBuffer.length = body_size; | |
1972 | tempBuffer.data = body_buf; | |
1973 | clientStreamCallback((clientStreamNode *)http->client_stream.head->data, | |
1974 | http, NULL, tempBuffer); | |
1975 | } else { | |
1976 | debug (88,0)("clientReplyContext::sendMoreData: Unable to parse reply headers within a single HTTP_REQBUF_SZ length buffer\n"); | |
1977 | StoreIOBuffer tempBuffer; | |
1978 | tempBuffer.flags.error = 1; | |
1979 | sendStreamError(tempBuffer); | |
4993f571 | 1980 | return; |
edce4d98 | 1981 | } |
528b2c61 | 1982 | fatal ("clientReplyContext::sendMoreData: Unreachable code reached \n"); |
edce4d98 | 1983 | } |
1984 | ||
1985 | int | |
528b2c61 | 1986 | clientReplyBodyTooLarge(HttpReply const * rep, ssize_t clen) |
edce4d98 | 1987 | { |
1988 | if (0 == rep->maxBodySize) | |
1989 | return 0; /* disabled */ | |
1990 | if (clen < 0) | |
1991 | return 0; /* unknown */ | |
e6ccf245 | 1992 | if ((unsigned int)clen > rep->maxBodySize) |
edce4d98 | 1993 | return 1; /* too large */ |
1994 | return 0; | |
1995 | } | |
1996 | ||
1997 | /* | |
1998 | * returns true if client specified that the object must come from the cache | |
1999 | * without contacting origin server | |
2000 | */ | |
2001 | static int | |
2002 | clientOnlyIfCached(clientHttpRequest * http) | |
2003 | { | |
2004 | const request_t *r = http->request; | |
2005 | assert(r); | |
2006 | return r->cache_control && | |
2007 | EBIT_TEST(r->cache_control->mask, CC_ONLY_IF_CACHED); | |
2008 | } | |
2009 | ||
2010 | /* Using this breaks the client layering just a little! | |
2011 | */ | |
2012 | StoreEntry * | |
2013 | clientCreateStoreEntry(clientReplyContext * context, method_t m, | |
2014 | request_flags flags) | |
2015 | { | |
2016 | clientHttpRequest *h = context->http; | |
2017 | StoreEntry *e; | |
2018 | assert(h != NULL); | |
2019 | /* | |
2020 | * For erroneous requests, we might not have a h->request, | |
2021 | * so make a fake one. | |
2022 | */ | |
2023 | if (h->request == NULL) | |
2024 | h->request = requestLink(requestCreate(m, PROTO_NONE, null_string)); | |
2025 | e = storeCreateEntry(h->uri, h->log_uri, flags, m); | |
2026 | context->sc = storeClientListAdd(e, context); | |
2027 | #if DELAY_POOLS | |
b67e2c8c | 2028 | context->sc->setDelayId(DelayId::DelayClient(h)); |
edce4d98 | 2029 | #endif |
2030 | context->reqofs = 0; | |
2031 | context->reqsize = 0; | |
2032 | /* I don't think this is actually needed! -- adrian */ | |
2033 | /* h->reqbuf = h->norm_reqbuf; */ | |
2034 | // assert(h->reqbuf == h->norm_reqbuf); | |
2035 | /* The next line is illegal because we don't know if the client stream | |
2036 | * buffers have been set up | |
2037 | */ | |
2038 | // storeClientCopy(h->sc, e, 0, HTTP_REQBUF_SZ, h->reqbuf, | |
2039 | // clientSendMoreData, context); | |
2040 | /* So, we mark the store logic as complete */ | |
2041 | context->flags.storelogiccomplete = 1; | |
2042 | /* and get the caller to request a read, from whereever they are */ | |
2043 | /* NOTE: after ANY data flows down the pipe, even one step, | |
2044 | * this function CAN NOT be used to manage errors | |
2045 | */ | |
2046 | return e; | |
2047 | } | |
2048 | ||
2049 | ErrorState * | |
2050 | clientBuildError(err_type page_id, http_status status, char const *url, | |
2051 | struct in_addr * src_addr, request_t * request) | |
2052 | { | |
2053 | ErrorState *err = errorCon(page_id, status); | |
2054 | err->src_addr = *src_addr; | |
2055 | if (url) | |
2056 | err->url = xstrdup(url); | |
2057 | if (request) | |
2058 | err->request = requestLink(request); | |
2059 | return err; | |
2060 | } |