]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ICAP/ICAPModXact.cc
Ignore comm_write notifications if the newly added
[thirdparty/squid.git] / src / ICAP / ICAPModXact.cc
CommitLineData
774c051c 1/*
507d0a78 2 * DEBUG: section 93 ICAP (RFC 3507) Client
774c051c 3 */
4
5#include "squid.h"
6#include "comm.h"
5f8252d2 7#include "HttpMsg.h"
774c051c 8#include "HttpRequest.h"
9#include "HttpReply.h"
10#include "ICAPServiceRep.h"
5f8252d2 11#include "ICAPInitiator.h"
c824c43b 12#include "ICAPLauncher.h"
774c051c 13#include "ICAPModXact.h"
14#include "ICAPClient.h"
15#include "ChunkedCodingParser.h"
16#include "TextException.h"
a97e82a8 17#include "AuthUserRequest.h"
12b91c99 18#include "ICAPConfig.h"
985c86bc 19#include "SquidTime.h"
774c051c 20
21// flow and terminology:
22// HTTP| --> receive --> encode --> write --> |network
23// end | <-- send <-- parse <-- read <-- |end
24
774c051c 25// TODO: replace gotEncapsulated() with something faster; we call it often
26
27CBDATA_CLASS_INIT(ICAPModXact);
c824c43b 28CBDATA_CLASS_INIT(ICAPModXactLauncher);
774c051c 29
5f8252d2 30static const size_t TheBackupLimit = BodyPipe::MaxCapacity;
774c051c 31
12b91c99 32extern ICAPConfig TheICAPConfig;
33
774c051c 34
35ICAPModXact::State::State()
36{
37 memset(this, sizeof(*this), 0);
38}
39
5f8252d2 40ICAPModXact::ICAPModXact(ICAPInitiator *anInitiator, HttpMsg *virginHeader,
41 HttpRequest *virginCause, ICAPServiceRep::Pointer &aService):
c824c43b 42 ICAPXaction("ICAPModXact", anInitiator, aService),
5f8252d2 43 icapReply(NULL),
44 virginConsumed(0),
45 bodyParser(NULL)
774c051c 46{
5f8252d2 47 assert(virginHeader);
774c051c 48
5f8252d2 49 virgin.setHeader(virginHeader); // sets virgin.body_pipe if needed
50 virgin.setCause(virginCause); // may be NULL
774c051c 51
5f8252d2 52 // adapted header and body are initialized when we parse them
774c051c 53
54 // writing and reading ends are handled by ICAPXaction
55
56 // encoding
57 // nothing to do because we are using temporary buffers
58
59 // parsing
60 icapReply = new HttpReply;
61 icapReply->protoPrefix = "ICAP/"; // TODO: make an IcapReply class?
62
5f8252d2 63 debugs(93,7, "ICAPModXact initialized." << status());
774c051c 64}
65
5f8252d2 66// initiator wants us to start
67void ICAPModXact::start()
774c051c 68{
5f8252d2 69 ICAPXaction::start();
774c051c 70
71 estimateVirginBody(); // before virgin disappears!
72
73 // it is an ICAP violation to send request to a service w/o known OPTIONS
74
75 if (service().up())
76 startWriting();
77 else
78 waitForService();
79
c824c43b 80 // XXX: If commConnectStart in startWriting fails, we may get here
774c051c 81 //_after_ the object got destroyed. Somebody please fix commConnectStart!
c824c43b 82 // TODO: Does re-entrance protection in callStart() solve the above?
774c051c 83}
84
85static
86void ICAPModXact_noteServiceReady(void *data, ICAPServiceRep::Pointer &)
87{
88 ICAPModXact *x = static_cast<ICAPModXact*>(data);
89 assert(x);
90 x->noteServiceReady();
91}
92
93void ICAPModXact::waitForService()
94{
95 Must(!state.serviceWaiting);
5f8252d2 96 debugs(93, 7, "ICAPModXact will wait for the ICAP service" << status());
774c051c 97 state.serviceWaiting = true;
98 service().callWhenReady(&ICAPModXact_noteServiceReady, this);
99}
100
101void ICAPModXact::noteServiceReady()
102{
103 ICAPXaction_Enter(noteServiceReady);
104
105 Must(state.serviceWaiting);
106 state.serviceWaiting = false;
c99de607 107
c824c43b 108 if (service().up()) {
109 startWriting();
110 } else {
111 disableRetries();
112 mustStop("ICAP service unusable");
113 }
774c051c 114
115 ICAPXaction_Exit();
116}
117
118void ICAPModXact::startWriting()
119{
774c051c 120 state.writing = State::writingConnect;
c824c43b 121
122 decideOnPreview(); // must be decided before we decideOnRetries
123 decideOnRetries();
124
774c051c 125 openConnection();
126 // put nothing here as openConnection calls commConnectStart
127 // and that may call us back without waiting for the next select loop
128}
129
130// connection with the ICAP service established
131void ICAPModXact::handleCommConnected()
132{
133 Must(state.writing == State::writingConnect);
134
135 startReading(); // wait for early errors from the ICAP server
136
137 MemBuf requestBuf;
138 requestBuf.init();
139
140 makeRequestHeaders(requestBuf);
5f8252d2 141 debugs(93, 9, "ICAPModXact ICAP will write" << status() << ":\n" <<
774c051c 142 (requestBuf.terminate(), requestBuf.content()));
143
144 // write headers
145 state.writing = State::writingHeaders;
146 scheduleWrite(requestBuf);
147}
148
b107a5a5 149void ICAPModXact::handleCommWrote(size_t sz)
774c051c 150{
b107a5a5 151 debugs(93, 5, HERE << "Wrote " << sz << " bytes");
152
774c051c 153 if (state.writing == State::writingHeaders)
154 handleCommWroteHeaders();
155 else
156 handleCommWroteBody();
157}
158
159void ICAPModXact::handleCommWroteHeaders()
160{
161 Must(state.writing == State::writingHeaders);
162
5f8252d2 163 // determine next step
164 if (preview.enabled())
165 state.writing = preview.done() ? State::writingPaused : State::writingPreview;
166 else
167 if (virginBody.expected())
168 state.writing = State::writingPrime;
169 else {
c99de607 170 stopWriting(true);
5f8252d2 171 return;
774c051c 172 }
5f8252d2 173
174 writeMore();
774c051c 175}
176
177void ICAPModXact::writeMore()
178{
5f8252d2 179 debugs(93, 5, HERE << "checking whether to write more" << status());
180
774c051c 181 if (writer) // already writing something
182 return;
183
184 switch (state.writing) {
185
186 case State::writingInit: // waiting for service OPTIONS
187 Must(state.serviceWaiting);
188
189 case State::writingConnect: // waiting for the connection to establish
190
191 case State::writingHeaders: // waiting for the headers to be written
192
193 case State::writingPaused: // waiting for the ICAP server response
194
c99de607 195 case State::writingReallyDone: // nothing more to write
196 return;
197
198 case State::writingAlmostDone: // was waiting for the last write
199 stopWriting(false);
774c051c 200 return;
201
202 case State::writingPreview:
5f8252d2 203 writePreviewBody();
774c051c 204 return;
205
206 case State::writingPrime:
207 writePrimeBody();
208 return;
209
210 default:
211 throw TexcHere("ICAPModXact in bad writing state");
212 }
213}
214
5f8252d2 215void ICAPModXact::writePreviewBody()
774c051c 216{
5f8252d2 217 debugs(93, 8, HERE << "will write Preview body from " <<
218 virgin.body_pipe << status());
774c051c 219 Must(state.writing == State::writingPreview);
5f8252d2 220 Must(virgin.body_pipe != NULL);
774c051c 221
5f8252d2 222 const size_t sizeMax = (size_t)virgin.body_pipe->buf().contentSize();
223 const size_t size = XMIN(preview.debt(), sizeMax);
774c051c 224 writeSomeBody("preview body", size);
225
226 // change state once preview is written
227
228 if (preview.done()) {
5f8252d2 229 debugs(93, 7, "ICAPModXact wrote entire Preview body" << status());
774c051c 230
231 if (preview.ieof())
c99de607 232 stopWriting(true);
774c051c 233 else
234 state.writing = State::writingPaused;
235 }
236}
237
238void ICAPModXact::writePrimeBody()
239{
240 Must(state.writing == State::writingPrime);
5f8252d2 241 Must(virginBodyWriting.active());
774c051c 242
5f8252d2 243 const size_t size = (size_t)virgin.body_pipe->buf().contentSize();
774c051c 244 writeSomeBody("prime virgin body", size);
245
5f8252d2 246 if (virginBodyEndReached(virginBodyWriting)) {
247 debugs(93, 5, HERE << "wrote entire body");
c99de607 248 stopWriting(true);
b107a5a5 249 }
774c051c 250}
251
252void ICAPModXact::writeSomeBody(const char *label, size_t size)
253{
c99de607 254 Must(!writer && state.writing < state.writingAlmostDone);
5f8252d2 255 Must(virgin.body_pipe != NULL);
12f4b710 256 debugs(93, 8, HERE << "will write up to " << size << " bytes of " <<
774c051c 257 label);
258
259 MemBuf writeBuf; // TODO: suggest a min size based on size and lastChunk
260
261 writeBuf.init(); // note: we assume that last-chunk will fit
262
5f8252d2 263 const size_t writableSize = virginContentSize(virginBodyWriting);
c99de607 264 const size_t chunkSize = XMIN(writableSize, size);
774c051c 265
266 if (chunkSize) {
12f4b710 267 debugs(93, 7, HERE << "will write " << chunkSize <<
774c051c 268 "-byte chunk of " << label);
5f8252d2 269
270 openChunk(writeBuf, chunkSize, false);
271 writeBuf.append(virginContentData(virginBodyWriting), chunkSize);
272 closeChunk(writeBuf);
273
274 virginBodyWriting.progress(chunkSize);
275 virginConsume();
774c051c 276 } else {
c99de607 277 debugs(93, 7, "ICAPModXact has no writable " << label << " content");
774c051c 278 }
279
5f8252d2 280 const bool wroteEof = virginBodyEndReached(virginBodyWriting);
281 bool lastChunk = wroteEof;
282 if (state.writing == State::writingPreview) {
283 preview.wrote(chunkSize, wroteEof); // even if wrote nothing
284 lastChunk = lastChunk || preview.done();
285 }
774c051c 286
5f8252d2 287 if (lastChunk) {
12f4b710 288 debugs(93, 8, HERE << "will write last-chunk of " << label);
774c051c 289 addLastRequestChunk(writeBuf);
290 }
291
12f4b710 292 debugs(93, 7, HERE << "will write " << writeBuf.contentSize()
774c051c 293 << " raw bytes of " << label);
294
295 if (writeBuf.hasContent()) {
296 scheduleWrite(writeBuf); // comm will free the chunk
297 } else {
298 writeBuf.clean();
299 }
300}
301
774c051c 302void ICAPModXact::addLastRequestChunk(MemBuf &buf)
303{
c99de607 304 const bool ieof = state.writing == State::writingPreview && preview.ieof();
305 openChunk(buf, 0, ieof);
306 closeChunk(buf);
774c051c 307}
308
c99de607 309void ICAPModXact::openChunk(MemBuf &buf, size_t chunkSize, bool ieof)
774c051c 310{
c99de607 311 buf.Printf((ieof ? "%x; ieof\r\n" : "%x\r\n"), (int) chunkSize);
774c051c 312}
313
c99de607 314void ICAPModXact::closeChunk(MemBuf &buf)
774c051c 315{
774c051c 316 buf.append(ICAP::crlf, 2); // chunk-terminating CRLF
317}
318
5f8252d2 319// did the activity reached the end of the virgin body?
320bool ICAPModXact::virginBodyEndReached(const VirginBodyAct &act) const
321{
322 return
323 !act.active() || // did all (assuming it was originally planned)
324 !virgin.body_pipe->expectMoreAfter(act.offset()); // wont have more
325}
326
327// the size of buffered virgin body data available for the specified activity
328// if this size is zero, we may be done or may be waiting for more data
329size_t ICAPModXact::virginContentSize(const VirginBodyAct &act) const
774c051c 330{
5f8252d2 331 Must(act.active());
332 // asbolute start of unprocessed data
333 const size_t start = act.offset();
334 // absolute end of buffered data
335 const size_t end = virginConsumed + virgin.body_pipe->buf().contentSize();
774c051c 336 Must(virginConsumed <= start && start <= end);
337 return end - start;
338}
339
5f8252d2 340// pointer to buffered virgin body data available for the specified activity
341const char *ICAPModXact::virginContentData(const VirginBodyAct &act) const
774c051c 342{
5f8252d2 343 Must(act.active());
344 const size_t start = act.offset();
774c051c 345 Must(virginConsumed <= start);
5f8252d2 346 return virgin.body_pipe->buf().content() + (start-virginConsumed);
774c051c 347}
348
349void ICAPModXact::virginConsume()
350{
5f8252d2 351 if (!virgin.body_pipe)
c824c43b 352 return; // nothing to consume
353
354 if (isRetriable)
355 return; // do not consume if we may have to retry later
5f8252d2 356
357 BodyPipe &bp = *virgin.body_pipe;
358 const size_t have = static_cast<size_t>(bp.buf().contentSize());
774c051c 359 const size_t end = virginConsumed + have;
360 size_t offset = end;
361
5f8252d2 362 if (virginBodyWriting.active())
363 offset = XMIN(virginBodyWriting.offset(), offset);
774c051c 364
5f8252d2 365 if (virginBodySending.active())
366 offset = XMIN(virginBodySending.offset(), offset);
774c051c 367
368 Must(virginConsumed <= offset && offset <= end);
369
370 if (const size_t size = offset - virginConsumed) {
b107a5a5 371 debugs(93, 8, HERE << "consuming " << size << " out of " << have <<
774c051c 372 " virgin body bytes");
5f8252d2 373 bp.consume(size);
774c051c 374 virginConsumed += size;
c824c43b 375 Must(!isRetriable); // or we should not be consuming
774c051c 376 }
377}
378
379void ICAPModXact::handleCommWroteBody()
380{
381 writeMore();
382}
383
c99de607 384// Called when we do not expect to call comm_write anymore.
385// We may have a pending write though.
386// If stopping nicely, we will just wait for that pending write, if any.
387void ICAPModXact::stopWriting(bool nicely)
774c051c 388{
c99de607 389 if (state.writing == State::writingReallyDone)
774c051c 390 return;
391
c99de607 392 if (writer) {
393 if (nicely) {
5f8252d2 394 debugs(93, 7, HERE << "will wait for the last write" << status());
c99de607 395 state.writing = State::writingAlmostDone; // may already be set
5f8252d2 396 checkConsuming();
c99de607 397 return;
398 }
5f8252d2 399 debugs(93, 2, HERE << "will NOT wait for the last write" << status());
774c051c 400
c99de607 401 // Comm does not have an interface to clear the writer callback nicely,
402 // but without clearing the writer we cannot recycle the connection.
403 // We prevent connection reuse and hope that we can handle a callback
5f8252d2 404 // call at any time, usually in the middle of the destruction sequence!
405 // Somebody should add comm_remove_write_handler() to comm API.
c99de607 406 reuseConnection = false;
407 }
408
5f8252d2 409 debugs(93, 7, HERE << "will no longer write" << status());
c99de607 410 state.writing = State::writingReallyDone;
774c051c 411
5f8252d2 412 if (virginBodyWriting.active()) {
413 virginBodyWriting.disable();
414 virginConsume();
415 }
774c051c 416}
417
418void ICAPModXact::stopBackup()
419{
5f8252d2 420 if (!virginBodySending.active())
774c051c 421 return;
422
5f8252d2 423 debugs(93, 7, "ICAPModXact will no longer backup" << status());
424 virginBodySending.disable();
774c051c 425 virginConsume();
426}
427
428bool ICAPModXact::doneAll() const
429{
430 return ICAPXaction::doneAll() && !state.serviceWaiting &&
5f8252d2 431 doneSending() &&
774c051c 432 doneReading() && state.doneWriting();
433}
434
435void ICAPModXact::startReading()
436{
437 Must(connection >= 0);
438 Must(!reader);
5f8252d2 439 Must(!adapted.header);
440 Must(!adapted.body_pipe);
774c051c 441
442 // we use the same buffer for headers and body and then consume headers
443 readMore();
444}
445
446void ICAPModXact::readMore()
447{
3b299123 448 if (reader || doneReading()) {
c99de607 449 debugs(93,3,HERE << "returning from readMore because reader or doneReading()");
774c051c 450 return;
3b299123 451 }
774c051c 452
453 // do not fill readBuf if we have no space to store the result
5f8252d2 454 if (adapted.body_pipe != NULL &&
455 !adapted.body_pipe->buf().hasPotentialSpace()) {
456 debugs(93,3,HERE << "not reading because ICAP reply pipe is full");
774c051c 457 return;
3b299123 458 }
774c051c 459
460 if (readBuf.hasSpace())
461 scheduleRead();
3b299123 462 else
c99de607 463 debugs(93,3,HERE << "nothing to do because !readBuf.hasSpace()");
774c051c 464}
465
466// comm module read a portion of the ICAP response for us
467void ICAPModXact::handleCommRead(size_t)
468{
469 Must(!state.doneParsing());
470 parseMore();
471 readMore();
472}
473
474void ICAPModXact::echoMore()
475{
476 Must(state.sending == State::sendingVirgin);
5f8252d2 477 Must(adapted.body_pipe != NULL);
478 Must(virginBodySending.active());
479
480 const size_t sizeMax = virginContentSize(virginBodySending);
481 debugs(93,5, HERE << "will echo up to " << sizeMax << " bytes from " <<
482 virgin.body_pipe->status());
483 debugs(93,5, HERE << "will echo up to " << sizeMax << " bytes to " <<
484 adapted.body_pipe->status());
485
486 if (sizeMax > 0) {
487 const size_t size = adapted.body_pipe->putMoreData(virginContentData(virginBodySending), sizeMax);
488 debugs(93,5, HERE << "echoed " << size << " out of " << sizeMax <<
774c051c 489 " bytes");
5f8252d2 490 virginBodySending.progress(size);
774c051c 491 virginConsume();
774c051c 492 }
493
5f8252d2 494 if (virginBodyEndReached(virginBodySending)) {
495 debugs(93, 5, "ICAPModXact echoed all" << status());
774c051c 496 stopSending(true);
497 } else {
5f8252d2 498 debugs(93, 5, "ICAPModXact has " <<
499 virgin.body_pipe->buf().contentSize() << " bytes " <<
500 "and expects more to echo" << status());
501 // TODO: timeout if virgin or adapted pipes are broken
774c051c 502 }
503}
504
505bool ICAPModXact::doneSending() const
506{
774c051c 507 return state.sending == State::sendingDone;
508}
509
510void ICAPModXact::stopSending(bool nicely)
511{
512 if (doneSending())
513 return;
514
515 if (state.sending != State::sendingUndecided) {
5f8252d2 516 debugs(93, 7, "ICAPModXact will no longer send" << status());
517 if (adapted.body_pipe != NULL) {
518 virginBodySending.disable();
519 // we may leave debts if we were echoing and the virgin
520 // body_pipe got exhausted before we echoed all planned bytes
521 const bool leftDebts = adapted.body_pipe->needsMoreData();
522 stopProducingFor(adapted.body_pipe, nicely && !leftDebts);
523 }
774c051c 524 } else {
5f8252d2 525 debugs(93, 7, "ICAPModXact will not start sending" << status());
526 Must(!adapted.body_pipe);
774c051c 527 }
528
529 state.sending = State::sendingDone;
5f8252d2 530 checkConsuming();
774c051c 531}
532
5f8252d2 533// should be called after certain state.writing or state.sending changes
534void ICAPModXact::checkConsuming()
774c051c 535{
5f8252d2 536 // quit if we already stopped or are still using the pipe
537 if (!virgin.body_pipe || !state.doneConsumingVirgin())
774c051c 538 return;
539
5f8252d2 540 debugs(93, 7, HERE << "will stop consuming" << status());
541 stopConsumingFrom(virgin.body_pipe);
774c051c 542}
543
544void ICAPModXact::parseMore()
545{
aa761e5f 546 debugs(93, 5, HERE << "have " << readBuf.contentSize() << " bytes to parse" <<
774c051c 547 status());
d5cfacfb 548 debugs(93, 5, HERE << "\n" << readBuf.content());
774c051c 549
550 if (state.parsingHeaders())
551 parseHeaders();
552
553 if (state.parsing == State::psBody)
554 parseBody();
555}
556
557// note that allocation for echoing is done in handle204NoContent()
558void ICAPModXact::maybeAllocateHttpMsg()
559{
5f8252d2 560 if (adapted.header) // already allocated
774c051c 561 return;
562
563 if (gotEncapsulated("res-hdr")) {
5f8252d2 564 adapted.setHeader(new HttpReply);
774c051c 565 } else if (gotEncapsulated("req-hdr")) {
5f8252d2 566 adapted.setHeader(new HttpRequest);
774c051c 567 } else
568 throw TexcHere("Neither res-hdr nor req-hdr in maybeAllocateHttpMsg()");
569}
570
571void ICAPModXact::parseHeaders()
572{
573 Must(state.parsingHeaders());
574
b107a5a5 575 if (state.parsing == State::psIcapHeader) {
576 debugs(93, 5, HERE << "parse ICAP headers");
774c051c 577 parseIcapHead();
b107a5a5 578 }
774c051c 579
b107a5a5 580 if (state.parsing == State::psHttpHeader) {
581 debugs(93, 5, HERE << "parse HTTP headers");
774c051c 582 parseHttpHead();
b107a5a5 583 }
774c051c 584
585 if (state.parsingHeaders()) { // need more data
586 Must(mayReadMore());
587 return;
588 }
589
c824c43b 590 sendAnswer(adapted.header);
774c051c 591
592 if (state.sending == State::sendingVirgin)
593 echoMore();
594}
595
596void ICAPModXact::parseIcapHead()
597{
598 Must(state.sending == State::sendingUndecided);
599
600 if (!parseHead(icapReply))
601 return;
602
fc764d26 603 if (httpHeaderHasConnDir(&icapReply->header, "close")) {
604 debugs(93, 5, HERE << "found connection close");
605 reuseConnection = false;
606 }
607
774c051c 608 switch (icapReply->sline.status) {
609
610 case 100:
611 handle100Continue();
612 break;
613
614 case 200:
b559db5d 615
616 if (!validate200Ok()) {
617 throw TexcHere("Invalid ICAP Response");
618 } else {
619 handle200Ok();
620 }
621
774c051c 622 break;
623
624 case 204:
625 handle204NoContent();
626 break;
627
628 default:
b559db5d 629 debugs(93, 5, HERE << "ICAP status " << icapReply->sline.status);
774c051c 630 handleUnknownScode();
631 break;
632 }
633
634 // handle100Continue() manages state.writing on its own.
635 // Non-100 status means the server needs no postPreview data from us.
636 if (state.writing == State::writingPaused)
c99de607 637 stopWriting(true);
774c051c 638
639 // TODO: Consider applying a Squid 2.5 patch to recognize 201 responses
640}
641
b559db5d 642bool ICAPModXact::validate200Ok()
643{
644 if (ICAP::methodRespmod == service().method) {
645 if (!gotEncapsulated("res-hdr"))
646 return false;
647
648 return true;
649 }
650
651 if (ICAP::methodReqmod == service().method) {
652 if (!gotEncapsulated("res-hdr") && !gotEncapsulated("req-hdr"))
653 return false;
654
655 return true;
656 }
657
658 return false;
659}
660
774c051c 661void ICAPModXact::handle100Continue()
662{
663 Must(state.writing == State::writingPaused);
5f8252d2 664 // server must not respond before the end of preview: we may send ieof
774c051c 665 Must(preview.enabled() && preview.done() && !preview.ieof());
774c051c 666
5f8252d2 667 // 100 "Continue" cancels our preview commitment, not 204s outside preview
668 if (!state.allowedPostview204)
774c051c 669 stopBackup();
670
c99de607 671 state.parsing = State::psIcapHeader; // eventually
672 icapReply->reset();
774c051c 673
674 state.writing = State::writingPrime;
675
676 writeMore();
677}
678
679void ICAPModXact::handle200Ok()
680{
681 state.parsing = State::psHttpHeader;
682 state.sending = State::sendingAdapted;
683 stopBackup();
5f8252d2 684 checkConsuming();
774c051c 685}
686
687void ICAPModXact::handle204NoContent()
688{
689 stopParsing();
774c051c 690
691 // We want to clone the HTTP message, but we do not want
5f8252d2 692 // to copy some non-HTTP state parts that HttpMsg kids carry in them.
774c051c 693 // Thus, we cannot use a smart pointer, copy constructor, or equivalent.
694 // Instead, we simply write the HTTP message and "clone" it by parsing.
695
5f8252d2 696 HttpMsg *oldHead = virgin.header;
774c051c 697 debugs(93, 7, "ICAPModXact cloning virgin message " << oldHead);
698
699 MemBuf httpBuf;
700
701 // write the virgin message into a memory buffer
702 httpBuf.init();
703 packHead(httpBuf, oldHead);
704
c99de607 705 // allocate the adapted message and copy metainfo
5f8252d2 706 Must(!adapted.header);
7514268e 707 HttpMsg *newHead = NULL;
c99de607 708 if (const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(oldHead)) {
709 HttpRequest *newR = new HttpRequest;
5f8252d2 710 inheritVirginProperties(*newR, *oldR);
c99de607 711 newHead = newR;
712 } else
713 if (dynamic_cast<const HttpReply*>(oldHead))
714 newHead = new HttpReply;
774c051c 715 Must(newHead);
716
5f8252d2 717 adapted.setHeader(newHead);
7514268e 718
774c051c 719 // parse the buffer back
720 http_status error = HTTP_STATUS_NONE;
721
722 Must(newHead->parse(&httpBuf, true, &error));
723
724 Must(newHead->hdr_sz == httpBuf.contentSize()); // no leftovers
725
726 httpBuf.clean();
727
5f8252d2 728 debugs(93, 7, "ICAPModXact cloned virgin message " << oldHead << " to " <<
729 newHead);
730
731 // setup adapted body pipe if needed
732 if (oldHead->body_pipe != NULL) {
733 debugs(93, 7, HERE << "will echo virgin body from " <<
734 oldHead->body_pipe);
735 state.sending = State::sendingVirgin;
736 checkConsuming();
737 Must(virginBodySending.active());
738 // TODO: optimize: is it possible to just use the oldHead pipe and
739 // remove ICAP from the loop? This echoing is probably a common case!
740 makeAdaptedBodyPipe("echoed virgin response");
741 if (oldHead->body_pipe->bodySizeKnown())
742 adapted.body_pipe->setBodySize(oldHead->body_pipe->bodySize());
743 debugs(93, 7, HERE << "will echo virgin body to " <<
744 adapted.body_pipe);
745 } else {
746 debugs(93, 7, HERE << "no virgin body to echo");
747 stopSending(true);
748 }
774c051c 749}
750
751void ICAPModXact::handleUnknownScode()
752{
753 stopParsing();
754 stopBackup();
755 // TODO: mark connection as "bad"
756
757 // Terminate the transaction; we do not know how to handle this response.
758 throw TexcHere("Unsupported ICAP status code");
759}
760
761void ICAPModXact::parseHttpHead()
762{
763 if (gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr")) {
764 maybeAllocateHttpMsg();
765
5f8252d2 766 if (!parseHead(adapted.header))
c99de607 767 return; // need more header data
5f8252d2 768
769 if (HttpRequest *newHead = dynamic_cast<HttpRequest*>(adapted.header)) {
770 const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(virgin.header);
771 Must(oldR);
772 // TODO: the adapted request did not really originate from the
773 // client; give proxy admin an option to prevent copying of
774 // sensitive client information here. See the following thread:
775 // http://www.squid-cache.org/mail-archive/squid-dev/200703/0040.html
776 inheritVirginProperties(*newHead, *oldR);
777 }
774c051c 778 }
779
5f8252d2 780 decideOnParsingBody();
774c051c 781}
782
c99de607 783// parses both HTTP and ICAP headers
774c051c 784bool ICAPModXact::parseHead(HttpMsg *head)
785{
c99de607 786 Must(head);
def17b6a 787 debugs(93, 5, HERE << "have " << readBuf.contentSize() << " head bytes to parse" <<
774c051c 788 "; state: " << state.parsing);
789
790 http_status error = HTTP_STATUS_NONE;
791 const bool parsed = head->parse(&readBuf, commEof, &error);
792 Must(parsed || !error); // success or need more data
793
c99de607 794 if (!parsed) { // need more data
b107a5a5 795 debugs(93, 5, HERE << "parse failed, need more data, return false");
774c051c 796 head->reset();
797 return false;
798 }
799
b107a5a5 800 debugs(93, 5, HERE << "parse success, consume " << head->hdr_sz << " bytes, return true");
774c051c 801 readBuf.consume(head->hdr_sz);
802 return true;
803}
804
5f8252d2 805// TODO: Move this method to HttpRequest?
806void ICAPModXact::inheritVirginProperties(HttpRequest &newR, const HttpRequest &oldR) {
774c051c 807
5f8252d2 808 newR.client_addr = oldR.client_addr;
809 newR.client_port = oldR.client_port;
810
811 newR.my_addr = oldR.my_addr;
812 newR.my_port = oldR.my_port;
813
814 // This may be too conservative for the 204 No Content case
815 // may eventually need cloneNullAdaptationImmune() for that.
816 newR.flags = oldR.flags.cloneAdaptationImmune();
774c051c 817
5f8252d2 818 if (oldR.auth_user_request) {
819 newR.auth_user_request = oldR.auth_user_request;
61527519 820 AUTHUSERREQUESTLOCK(newR.auth_user_request, "newR in ICAPModXact");
5f8252d2 821 }
822}
823
824void ICAPModXact::decideOnParsingBody() {
200ac359 825 if (gotEncapsulated("res-body") || gotEncapsulated("req-body")) {
5f8252d2 826 debugs(93, 5, HERE << "expecting a body");
827 state.parsing = State::psBody;
828 bodyParser = new ChunkedCodingParser;
829 makeAdaptedBodyPipe("adapted response from the ICAP server");
830 Must(state.sending == State::sendingAdapted);
774c051c 831 } else {
b559db5d 832 debugs(93, 5, HERE << "not expecting a body");
5f8252d2 833 stopParsing();
834 stopSending(true);
774c051c 835 }
774c051c 836}
837
5f8252d2 838void ICAPModXact::parseBody()
774c051c 839{
5f8252d2 840 Must(state.parsing == State::psBody);
841 Must(bodyParser);
774c051c 842
5f8252d2 843 debugs(93, 5, HERE << "have " << readBuf.contentSize() << " body bytes to parse");
774c051c 844
5f8252d2 845 // the parser will throw on errors
846 BodyPipeCheckout bpc(*adapted.body_pipe);
847 const bool parsed = bodyParser->parse(&readBuf, &bpc.buf);
848 bpc.checkIn();
774c051c 849
aa761e5f 850 debugs(93, 5, HERE << "have " << readBuf.contentSize() << " body bytes after " <<
774c051c 851 "parse; parsed all: " << parsed);
852
5f8252d2 853 if (parsed) {
854 stopParsing();
855 stopSending(true); // the parser succeeds only if all parsed data fits
856 return;
857 }
774c051c 858
c99de607 859 debugs(93,3,HERE << this << " needsMoreData = " << bodyParser->needsMoreData());
3b299123 860
861 if (bodyParser->needsMoreData()) {
c99de607 862 debugs(93,3,HERE << this);
774c051c 863 Must(mayReadMore());
3b299123 864 readMore();
865 }
774c051c 866
867 if (bodyParser->needsMoreSpace()) {
868 Must(!doneSending()); // can hope for more space
5f8252d2 869 Must(adapted.body_pipe->buf().contentSize() > 0); // paranoid
870 // TODO: there should be a timeout in case the sink is broken
871 // or cannot consume partial content (while we need more space)
774c051c 872 }
774c051c 873}
874
875void ICAPModXact::stopParsing()
876{
877 if (state.parsing == State::psDone)
878 return;
879
5f8252d2 880 debugs(93, 7, "ICAPModXact will no longer parse" << status());
774c051c 881
882 delete bodyParser;
883
884 bodyParser = NULL;
885
886 state.parsing = State::psDone;
887}
888
889// HTTP side added virgin body data
5f8252d2 890void ICAPModXact::noteMoreBodyDataAvailable(BodyPipe &)
774c051c 891{
5f8252d2 892 ICAPXaction_Enter(noteMoreBodyDataAvailable);
774c051c 893
774c051c 894 writeMore();
895
896 if (state.sending == State::sendingVirgin)
897 echoMore();
898
899 ICAPXaction_Exit();
900}
901
902// HTTP side sent us all virgin info
5f8252d2 903void ICAPModXact::noteBodyProductionEnded(BodyPipe &)
774c051c 904{
5f8252d2 905 ICAPXaction_Enter(noteBodyProductionEnded);
774c051c 906
5f8252d2 907 Must(virgin.body_pipe->productionEnded());
774c051c 908
909 // push writer and sender in case we were waiting for the last-chunk
910 writeMore();
911
912 if (state.sending == State::sendingVirgin)
913 echoMore();
914
915 ICAPXaction_Exit();
916}
917
5f8252d2 918// body producer aborted
919void ICAPModXact::noteBodyProducerAborted(BodyPipe &)
774c051c 920{
5f8252d2 921 ICAPXaction_Enter(noteBodyProducerAborted);
922
923 mustStop("virgin HTTP body producer aborted");
774c051c 924
5f8252d2 925 ICAPXaction_Exit();
926}
927
5f8252d2 928// adapted body consumer wants more adapted data and
929// possibly freed some buffer space
930void ICAPModXact::noteMoreBodySpaceAvailable(BodyPipe &)
774c051c 931{
5f8252d2 932 ICAPXaction_Enter(noteMoreBodySpaceAvailable);
774c051c 933
934 if (state.sending == State::sendingVirgin)
935 echoMore();
3b299123 936 else if (state.sending == State::sendingAdapted)
937 parseMore();
774c051c 938 else
3b299123 939 Must(state.sending == State::sendingUndecided);
774c051c 940
941 ICAPXaction_Exit();
942}
943
5f8252d2 944// adapted body consumer aborted
945void ICAPModXact::noteBodyConsumerAborted(BodyPipe &)
774c051c 946{
5f8252d2 947 ICAPXaction_Enter(noteBodyConsumerAborted);
774c051c 948
5f8252d2 949 mustStop("adapted body consumer aborted");
774c051c 950
951 ICAPXaction_Exit();
952}
953
954// internal cleanup
5f8252d2 955void ICAPModXact::swanSong()
774c051c 956{
5f8252d2 957 debugs(93, 5, HERE << "swan sings" << status());
958
c99de607 959 stopWriting(false);
c824c43b 960 stopSending(false);
774c051c 961
962 if (icapReply) {
963 delete icapReply;
964 icapReply = NULL;
965 }
966
5f8252d2 967 ICAPXaction::swanSong();
774c051c 968}
969
970void ICAPModXact::makeRequestHeaders(MemBuf &buf)
971{
12b91c99 972 /*
973 * XXX These should use HttpHdr interfaces instead of Printfs
974 */
774c051c 975 const ICAPServiceRep &s = service();
30abd221 976 buf.Printf("%s %s ICAP/1.0\r\n", s.methodStr(), s.uri.buf());
977 buf.Printf("Host: %s:%d\r\n", s.host.buf(), s.port);
12b91c99 978 buf.Printf("Date: %s\r\n", mkrfc1123(squid_curtime));
979
980 if (!TheICAPConfig.reuse_connections)
981 buf.Printf("Connection: close\r\n");
982
774c051c 983 buf.Printf("Encapsulated: ");
984
985 MemBuf httpBuf;
12b91c99 986
774c051c 987 httpBuf.init();
988
989 // build HTTP request header, if any
990 ICAP::Method m = s.method;
991
5f8252d2 992 const HttpRequest *request = virgin.cause ?
993 virgin.cause :
994 dynamic_cast<const HttpRequest*>(virgin.header);
c99de607 995
5f8252d2 996 // to simplify, we could assume that request is always available
c99de607 997
30abd221 998 String urlPath;
c99de607 999 if (request) {
1000 urlPath = request->urlpath;
1001 if (ICAP::methodRespmod == m)
1002 encapsulateHead(buf, "req-hdr", httpBuf, request);
1003 else
1004 if (ICAP::methodReqmod == m)
5f8252d2 1005 encapsulateHead(buf, "req-hdr", httpBuf, virgin.header);
c99de607 1006 }
774c051c 1007
1008 if (ICAP::methodRespmod == m)
5f8252d2 1009 if (const HttpMsg *prime = virgin.header)
774c051c 1010 encapsulateHead(buf, "res-hdr", httpBuf, prime);
1011
1012 if (!virginBody.expected())
1dd6edf2 1013 buf.Printf("null-body=%d", (int) httpBuf.contentSize());
774c051c 1014 else if (ICAP::methodReqmod == m)
1dd6edf2 1015 buf.Printf("req-body=%d", (int) httpBuf.contentSize());
774c051c 1016 else
1dd6edf2 1017 buf.Printf("res-body=%d", (int) httpBuf.contentSize());
774c051c 1018
1019 buf.append(ICAP::crlf, 2); // terminate Encapsulated line
1020
c824c43b 1021 if (preview.enabled()) {
774c051c 1022 buf.Printf("Preview: %d\r\n", (int)preview.ad());
5f8252d2 1023 if (virginBody.expected()) // there is a body to preview
1024 virginBodySending.plan();
1025 else
1026 finishNullOrEmptyBodyPreview(httpBuf);
774c051c 1027 }
1028
1029 if (shouldAllow204()) {
5f8252d2 1030 debugs(93,5, HERE << "will allow 204s outside of preview");
1031 state.allowedPostview204 = true;
774c051c 1032 buf.Printf("Allow: 204\r\n");
5f8252d2 1033 if (virginBody.expected()) // there is a body to echo
1034 virginBodySending.plan();
774c051c 1035 }
1036
c99de607 1037 if (TheICAPConfig.send_client_ip && request)
1038 if (request->client_addr.s_addr != any_addr.s_addr &&
1039 request->client_addr.s_addr != no_addr.s_addr)
12b91c99 1040 buf.Printf("X-Client-IP: %s\r\n", inet_ntoa(request->client_addr));
a97e82a8 1041
c99de607 1042 if (TheICAPConfig.send_client_username && request)
5f8252d2 1043 makeUsernameHeader(request, buf);
a97e82a8 1044
2dfede9e 1045 // fprintf(stderr, "%s\n", buf.content());
a97e82a8 1046
774c051c 1047 buf.append(ICAP::crlf, 2); // terminate ICAP header
1048
1049 // start ICAP request body with encapsulated HTTP headers
1050 buf.append(httpBuf.content(), httpBuf.contentSize());
1051
1052 httpBuf.clean();
1053}
1054
5f8252d2 1055void ICAPModXact::makeUsernameHeader(const HttpRequest *request, MemBuf &buf) {
76f142cd 1056 if (const AuthUserRequest *auth = request->auth_user_request) {
5f8252d2 1057 if (char const *name = auth->username()) {
1058 const char *value = TheICAPConfig.client_username_encode ?
1059 base64_encode(name) : name;
1060 buf.Printf("%s: %s\r\n", TheICAPConfig.client_username_header,
1061 value);
1062 }
1063 }
1064}
1065
774c051c 1066void ICAPModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head)
1067{
1068 // update ICAP header
7cab7e9f 1069 icapBuf.Printf("%s=%d, ", section, (int) httpBuf.contentSize());
774c051c 1070
1071 // pack HTTP head
1072 packHead(httpBuf, head);
1073}
1074
1075void ICAPModXact::packHead(MemBuf &httpBuf, const HttpMsg *head)
1076{
1077 Packer p;
1078 packerToMemInit(&p, &httpBuf);
1079 head->packInto(&p, true);
1080 packerClean(&p);
1081}
1082
1083// decides whether to offer a preview and calculates its size
c824c43b 1084void ICAPModXact::decideOnPreview()
774c051c 1085{
7cdbbd47 1086 if (!TheICAPConfig.preview_enable) {
1087 debugs(93, 5, HERE << "preview disabled by squid.conf");
c824c43b 1088 return;
7cdbbd47 1089 }
1090
c824c43b 1091 const HttpRequest *request = virgin.cause ?
1092 virgin.cause :
1093 dynamic_cast<const HttpRequest*>(virgin.header);
30abd221 1094 const String urlPath = request ? request->urlpath : String();
5f8252d2 1095 size_t wantedSize;
c99de607 1096 if (!service().wantsPreview(urlPath, wantedSize)) {
1097 debugs(93, 5, "ICAPModXact should not offer preview for " << urlPath);
c824c43b 1098 return;
774c051c 1099 }
1100
c824c43b 1101 // we decided to do preview, now compute its size
1102
774c051c 1103 Must(wantedSize >= 0);
1104
1105 // cannot preview more than we can backup
1106 size_t ad = XMIN(wantedSize, TheBackupLimit);
1107
5f8252d2 1108 if (!virginBody.expected())
1109 ad = 0;
774c051c 1110 else
5f8252d2 1111 if (virginBody.knownSize())
1112 ad = XMIN(ad, virginBody.size()); // not more than we have
774c051c 1113
1114 debugs(93, 5, "ICAPModXact should offer " << ad << "-byte preview " <<
1115 "(service wanted " << wantedSize << ")");
1116
1117 preview.enable(ad);
5f8252d2 1118 Must(preview.enabled());
774c051c 1119}
1120
1121// decides whether to allow 204 responses
1122bool ICAPModXact::shouldAllow204()
1123{
1124 if (!service().allows204())
1125 return false;
1126
c824c43b 1127 return canBackupEverything();
1128}
1129
1130// used by shouldAllow204 and decideOnRetries
1131bool ICAPModXact::canBackupEverything() const
1132{
774c051c 1133 if (!virginBody.expected())
c824c43b 1134 return true; // no body means no problems with backup
774c051c 1135
c824c43b 1136 // if there is a body, check whether we can backup it all
774c051c 1137
1138 if (!virginBody.knownSize())
1139 return false;
1140
1141 // or should we have a different backup limit?
1142 // note that '<' allows for 0-termination of the "full" backup buffer
1143 return virginBody.size() < TheBackupLimit;
1144}
1145
c824c43b 1146// Decide whether this transaction can be retried if pconn fails
1147// Must be called after decideOnPreview and before openConnection()
1148void ICAPModXact::decideOnRetries()
1149{
1150 if (!isRetriable)
1151 return; // no, already decided
1152
1153 if (preview.enabled())
1154 return; // yes, because preview provides enough guarantees
1155
1156 if (canBackupEverything())
1157 return; // yes, because we can back everything up
1158
1159 disableRetries(); // no, because we cannot back everything up
1160}
1161
5f8252d2 1162// Normally, the body-writing code handles preview body. It can deal with
1163// bodies of unexpected size, including those that turn out to be empty.
1164// However, that code assumes that the body was expected and body control
1165// structures were initialized. This is not the case when there is no body
1166// or the body is known to be empty, because the virgin message will lack a
1167// body_pipe. So we handle preview of null-body and zero-size bodies here.
1168void ICAPModXact::finishNullOrEmptyBodyPreview(MemBuf &buf)
1169{
1170 Must(!virginBodyWriting.active()); // one reason we handle it here
1171 Must(!virgin.body_pipe); // another reason we handle it here
1172 Must(!preview.ad());
1173
1174 // do not add last-chunk because our Encapsulated header says null-body
1175 // addLastRequestChunk(buf);
1176 preview.wrote(0, true);
1177
1178 Must(preview.done());
1179 Must(preview.ieof());
1180}
1181
774c051c 1182void ICAPModXact::fillPendingStatus(MemBuf &buf) const
1183{
c99de607 1184 ICAPXaction::fillPendingStatus(buf);
1185
774c051c 1186 if (state.serviceWaiting)
1187 buf.append("U", 1);
1188
5f8252d2 1189 if (virgin.body_pipe != NULL)
c99de607 1190 buf.append("R", 1);
1191
5f8252d2 1192 if (connection > 0 && !doneReading())
c99de607 1193 buf.append("r", 1);
1194
774c051c 1195 if (!state.doneWriting() && state.writing != State::writingInit)
1196 buf.Printf("w(%d)", state.writing);
1197
1198 if (preview.enabled()) {
1199 if (!preview.done())
1dd6edf2 1200 buf.Printf("P(%d)", (int) preview.debt());
774c051c 1201 }
1202
5f8252d2 1203 if (virginBodySending.active())
774c051c 1204 buf.append("B", 1);
1205
1206 if (!state.doneParsing() && state.parsing != State::psIcapHeader)
1207 buf.Printf("p(%d)", state.parsing);
1208
1209 if (!doneSending() && state.sending != State::sendingUndecided)
1210 buf.Printf("S(%d)", state.sending);
1211}
1212
1213void ICAPModXact::fillDoneStatus(MemBuf &buf) const
1214{
c99de607 1215 ICAPXaction::fillDoneStatus(buf);
1216
5f8252d2 1217 if (!virgin.body_pipe)
774c051c 1218 buf.append("R", 1);
1219
1220 if (state.doneWriting())
1221 buf.append("w", 1);
1222
1223 if (preview.enabled()) {
1224 if (preview.done())
1225 buf.Printf("P%s", preview.ieof() ? "(ieof)" : "");
1226 }
1227
1228 if (doneReading())
1229 buf.append("r", 1);
1230
1231 if (state.doneParsing())
1232 buf.append("p", 1);
1233
1234 if (doneSending())
1235 buf.append("S", 1);
1236}
1237
1238bool ICAPModXact::gotEncapsulated(const char *section) const
1239{
a9925b40 1240 return icapReply->header.getByNameListMember("Encapsulated",
1241 section, ',').size() > 0;
774c051c 1242}
1243
1244// calculate whether there is a virgin HTTP body and
1245// whether its expected size is known
5f8252d2 1246// TODO: rename because we do not just estimate
774c051c 1247void ICAPModXact::estimateVirginBody()
1248{
5f8252d2 1249 // note: lack of size info may disable previews and 204s
774c051c 1250
5f8252d2 1251 HttpMsg *msg = virgin.header;
1252 Must(msg);
774c051c 1253
1254 method_t method;
1255
5f8252d2 1256 if (virgin.cause)
1257 method = virgin.cause->method;
774c051c 1258 else
5f8252d2 1259 if (HttpRequest *req = dynamic_cast<HttpRequest*>(msg))
1260 method = req->method;
1261 else
1262 method = METHOD_NONE;
774c051c 1263
1264 ssize_t size;
5f8252d2 1265 // expectingBody returns true for zero-sized bodies, but we will not
1266 // get a pipe for that body, so we treat the message as bodyless
1267 if (method != METHOD_NONE && msg->expectingBody(method, size) && size) {
1268 debugs(93, 6, "ICAPModXact expects virgin body from " <<
1269 virgin.body_pipe << "; size: " << size);
1270
1271 virginBody.expect(size);
1272 virginBodyWriting.plan();
1273
1274 // sign up as a body consumer
1275 Must(msg->body_pipe != NULL);
1276 Must(msg->body_pipe == virgin.body_pipe);
1277 Must(virgin.body_pipe->setConsumerIfNotLate(this));
1278
1279 // make sure TheBackupLimit is in-sync with the buffer size
1280 Must(TheBackupLimit <= static_cast<size_t>(msg->body_pipe->buf().max_capacity));
774c051c 1281 } else {
1282 debugs(93, 6, "ICAPModXact does not expect virgin body");
5f8252d2 1283 Must(msg->body_pipe == NULL);
1284 checkConsuming();
774c051c 1285 }
1286}
1287
5f8252d2 1288void ICAPModXact::makeAdaptedBodyPipe(const char *what) {
1289 Must(!adapted.body_pipe);
1290 Must(!adapted.header->body_pipe);
1291 adapted.header->body_pipe = new BodyPipe(this);
1292 adapted.body_pipe = adapted.header->body_pipe;
1293 debugs(93, 7, HERE << "will supply " << what << " via " <<
1294 adapted.body_pipe << " pipe");
1295}
1296
774c051c 1297
1298// TODO: Move SizedEstimate, MemBufBackup, and ICAPPreview elsewhere
1299
1300SizedEstimate::SizedEstimate()
1301 : theData(dtUnexpected)
1302{}
1303
1304void SizedEstimate::expect(ssize_t aSize)
1305{
1306 theData = (aSize >= 0) ? aSize : (ssize_t)dtUnknown;
1307}
1308
1309bool SizedEstimate::expected() const
1310{
1311 return theData != dtUnexpected;
1312}
1313
1314bool SizedEstimate::knownSize() const
1315{
1316 Must(expected());
1317 return theData != dtUnknown;
1318}
1319
1320size_t SizedEstimate::size() const
1321{
1322 Must(knownSize());
1323 return static_cast<size_t>(theData);
1324}
1325
1326
1327
5f8252d2 1328VirginBodyAct::VirginBodyAct(): theStart(-1)
774c051c 1329{}
1330
5f8252d2 1331void VirginBodyAct::plan()
774c051c 1332{
1333 if (theStart < 0)
1334 theStart = 0;
774c051c 1335}
1336
5f8252d2 1337void VirginBodyAct::disable()
774c051c 1338{
5f8252d2 1339 theStart = -2;
774c051c 1340}
1341
5f8252d2 1342void VirginBodyAct::progress(size_t size)
774c051c 1343{
1344 Must(active());
1345 Must(size >= 0);
1346 theStart += static_cast<ssize_t>(size);
774c051c 1347}
1348
5f8252d2 1349size_t VirginBodyAct::offset() const
774c051c 1350{
1351 Must(active());
1352 return static_cast<size_t>(theStart);
1353}
1354
774c051c 1355
1356ICAPPreview::ICAPPreview(): theWritten(0), theAd(0), theState(stDisabled)
1357{}
1358
1359void ICAPPreview::enable(size_t anAd)
1360{
1361 // TODO: check for anAd not exceeding preview size limit
1362 Must(anAd >= 0);
1363 Must(!enabled());
1364 theAd = anAd;
1365 theState = stWriting;
1366}
1367
1368bool ICAPPreview::enabled() const
1369{
1370 return theState != stDisabled;
1371}
1372
1373size_t ICAPPreview::ad() const
1374{
1375 Must(enabled());
1376 return theAd;
1377}
1378
1379bool ICAPPreview::done() const
1380{
1381 Must(enabled());
1382 return theState >= stIeof;
1383}
1384
1385bool ICAPPreview::ieof() const
1386{
1387 Must(enabled());
1388 return theState == stIeof;
1389}
1390
1391size_t ICAPPreview::debt() const
1392{
1393 Must(enabled());
1394 return done() ? 0 : (theAd - theWritten);
1395}
1396
c99de607 1397void ICAPPreview::wrote(size_t size, bool wroteEof)
774c051c 1398{
1399 Must(enabled());
5f8252d2 1400
774c051c 1401 theWritten += size;
1402
5f8252d2 1403 Must(theWritten <= theAd);
1404
1405 if (wroteEof)
1406 theState = stIeof; // written size is irrelevant
1407 else
774c051c 1408 if (theWritten >= theAd)
5f8252d2 1409 theState = stDone;
774c051c 1410}
1411
3cfc19b3 1412bool ICAPModXact::fillVirginHttpHeader(MemBuf &mb) const
1413{
5f8252d2 1414 if (virgin.header == NULL)
3cfc19b3 1415 return false;
1416
5f8252d2 1417 virgin.header->firstLineBuf(mb);
3cfc19b3 1418
1419 return true;
1420}
c824c43b 1421
1422
1423/* ICAPModXactLauncher */
1424
1425ICAPModXactLauncher::ICAPModXactLauncher(ICAPInitiator *anInitiator, HttpMsg *virginHeader, HttpRequest *virginCause, ICAPServiceRep::Pointer &aService):
1426 ICAPLauncher("ICAPModXactLauncher", anInitiator, aService)
1427{
1428 virgin.setHeader(virginHeader);
1429 virgin.setCause(virginCause);
1430}
1431
1432ICAPXaction *ICAPModXactLauncher::createXaction()
1433{
1434 return new ICAPModXact(this, virgin.header, virgin.cause, theService);
1435}