]>
Commit | Line | Data |
---|---|---|
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 | |
40 | typedef 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 | ||
61 | CBDATA_TYPE(clientReplyContext); | |
62 | ||
edce4d98 | 63 | /* Local functions */ |
64 | static int clientGotNotEnough(clientHttpRequest const *); | |
65 | static int clientReplyBodyTooLarge(HttpReply *, ssize_t); | |
66 | static int clientOnlyIfCached(clientHttpRequest * http); | |
67 | static void clientProcessExpired(clientReplyContext *); | |
68 | static void clientProcessMiss(clientReplyContext *); | |
69 | static STCB clientCacheHit; | |
70 | static void clientProcessOnlyIfCachedMiss(clientReplyContext *); | |
71 | static int clientGetsOldEntry(StoreEntry * new, StoreEntry * old, | |
72 | request_t * request); | |
73 | static STCB clientHandleIMSReply; | |
74 | static int modifiedSince(StoreEntry *, request_t *); | |
75 | static log_type clientIdentifyStoreObject(clientHttpRequest * http); | |
76 | static void clientPurgeRequest(clientReplyContext *); | |
77 | static void clientTraceReply(clientStreamNode *, clientReplyContext *); | |
78 | static StoreEntry *clientCreateStoreEntry(clientReplyContext *, method_t, | |
79 | request_flags); | |
80 | static STCB clientSendMoreData; | |
81 | static void clientRemoveStoreReference(clientReplyContext *, store_client **, | |
82 | StoreEntry **); | |
83 | static void clientReplyContextSaveState(clientReplyContext *, | |
84 | clientHttpRequest *); | |
85 | static void clientReplyContextRestoreState(clientReplyContext *, | |
86 | clientHttpRequest *); | |
87 | extern CSS clientReplyStatus; | |
88 | extern ErrorState *clientBuildError(err_type, http_status, char const *, | |
89 | struct in_addr *, request_t *); | |
90 | ||
c8be6d7b | 91 | static void startError(clientReplyContext * context, clientHttpRequest * http, ErrorState * err); |
92 | static void triggerStoreReadWithClientParameters(clientReplyContext * context, clientHttpRequest * http); | |
93 | ||
94 | ||
edce4d98 | 95 | /* The clientReply clean interface */ |
96 | /* privates */ | |
97 | static FREE clientReplyFree; | |
98 | ||
99 | void | |
100 | clientReplyFree(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 | ||
111 | void * | |
112 | clientReplyNewContext(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. */ | |
122 | void | |
123 | clientSetReplyToError(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 | ||
150 | void | |
151 | clientRemoveStoreReference(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 | ||
164 | void | |
165 | clientReplyContextSaveState(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 | ||
180 | void | |
181 | clientReplyContextRestoreState(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 | ||
197 | void | |
198 | startError(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 | 205 | void |
206 | triggerStoreReadWithClientParameters(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 | */ | |
221 | static void | |
222 | clientProcessExpired(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 | ||
274 | int | |
275 | modifiedSince(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 | ||
308 | static int | |
309 | clientGetsOldEntry(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 | ||
346 | void | |
c8be6d7b | 347 | clientHandleIMSReply(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 | ||
469 | CSR clientGetMoreData; | |
470 | CSD 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 | */ | |
479 | void | |
c8be6d7b | 480 | clientCacheHit(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 | */ | |
672 | void | |
673 | clientProcessMiss(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 | */ | |
744 | static void | |
745 | clientProcessOnlyIfCachedMiss(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 | ||
760 | void | |
761 | clientPurgeRequest(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 | ||
858 | void | |
859 | clientTraceReply(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 | |
888 | int | |
889 | clientCheckTransferDone(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 | ||
958 | int | |
959 | clientGotNotEnough(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 | */ | |
977 | int | |
978 | clientHttpRequestStatus(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 | */ | |
1015 | clientStream_status_t | |
1016 | clientReplyStatus(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 | */ | |
1062 | static int | |
1063 | clientAlwaysAllowResponse(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 | */ | |
1084 | static void | |
1085 | clientBuildReplyHeader(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 | ||
1201 | static HttpReply * | |
1202 | clientBuildReply(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 | ||
1219 | static log_type | |
1220 | clientIdentifyStoreObject(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 | */ | |
1297 | void | |
1298 | clientGetMoreData(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. */ | |
1374 | void | |
1375 | clientReplyDetach(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 | */ | |
1389 | void | |
c8be6d7b | 1390 | clientSendMoreData(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 | ||
1566 | int | |
1567 | clientReplyBodyTooLarge(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 | */ | |
1582 | static int | |
1583 | clientOnlyIfCached(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 | */ | |
1593 | StoreEntry * | |
1594 | clientCreateStoreEntry(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 | ||
1630 | ErrorState * | |
1631 | clientBuildError(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 | } |