]> git.ipfire.org Git - thirdparty/squid.git/blame - src/http/Stream.cc
Rename allow_t to Acl::Answer (#425)
[thirdparty/squid.git] / src / http / Stream.cc
CommitLineData
d6fdeb41 1/*
f6e9a3ee 2 * Copyright (C) 1996-2019 The Squid Software Foundation and contributors
d6fdeb41
AJ
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9#include "squid.h"
10#include "client_side_request.h"
d3dddfb5 11#include "http/Stream.h"
d6fdeb41
AJ
12#include "HttpHdrContRange.h"
13#include "HttpHeaderTools.h"
bc8628c7 14#include "Store.h"
d6fdeb41 15#include "TimeOrTag.h"
b27668ec
EB
16#if USE_DELAY_POOLS
17#include "acl/FilledChecklist.h"
18#include "ClientInfo.h"
19#include "fde.h"
20#include "MessageDelayPools.h"
21#endif
d6fdeb41 22
d3dddfb5 23Http::Stream::Stream(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) :
d6fdeb41
AJ
24 clientConnection(aConn),
25 http(aReq),
26 reply(nullptr),
27 writtenToSocket(0),
28 mayUseConnection_(false),
942b1c39 29 connRegistered_(false)
d6fdeb41
AJ
30{
31 assert(http != nullptr);
942b1c39 32 memset(reqbuf, '\0', sizeof (reqbuf));
d6fdeb41
AJ
33 flags.deferred = 0;
34 flags.parsed_ok = 0;
35 deferredparams.node = nullptr;
36 deferredparams.rep = nullptr;
37}
38
d3dddfb5 39Http::Stream::~Stream()
d6fdeb41
AJ
40{
41 if (auto node = getTail()) {
d3dddfb5 42 if (auto ctx = dynamic_cast<Http::Stream *>(node->data.getRaw())) {
d6fdeb41
AJ
43 /* We are *always* the tail - prevent recursive free */
44 assert(this == ctx);
45 node->data = nullptr;
46 }
47 }
48 httpRequestFree(http);
49}
50
51void
d3dddfb5 52Http::Stream::registerWithConn()
d6fdeb41
AJ
53{
54 assert(!connRegistered_);
55 assert(getConn());
56 connRegistered_ = true;
d3dddfb5 57 getConn()->pipeline.add(Http::StreamPointer(this));
d6fdeb41
AJ
58}
59
60bool
d3dddfb5 61Http::Stream::startOfOutput() const
d6fdeb41
AJ
62{
63 return http->out.size == 0;
64}
65
66void
d3dddfb5 67Http::Stream::writeComplete(size_t size)
d6fdeb41
AJ
68{
69 const StoreEntry *entry = http->storeEntry();
70 debugs(33, 5, clientConnection << ", sz " << size <<
71 ", off " << (http->out.size + size) << ", len " <<
72 (entry ? entry->objectLen() : 0));
73
74 http->out.size += size;
75
76 if (clientHttpRequestStatus(clientConnection->fd, http)) {
77 initiateClose("failure or true request status");
78 /* Do we leak here ? */
79 return;
80 }
81
82 switch (socketState()) {
83
84 case STREAM_NONE:
85 pullData();
86 break;
87
88 case STREAM_COMPLETE: {
89 debugs(33, 5, clientConnection << " Stream complete, keepalive is " <<
abc18020 90 http->request->flags.proxyKeepalive);
d6fdeb41
AJ
91 ConnStateData *c = getConn();
92 if (!http->request->flags.proxyKeepalive)
93 clientConnection->close();
94 finished();
95 c->kick();
96 }
97 return;
98
99 case STREAM_UNPLANNED_COMPLETE:
100 initiateClose("STREAM_UNPLANNED_COMPLETE");
101 return;
102
103 case STREAM_FAILED:
104 initiateClose("STREAM_FAILED");
105 return;
106
107 default:
d3dddfb5 108 fatal("Hit unreachable code in Http::Stream::writeComplete\n");
d6fdeb41
AJ
109 }
110}
111
112void
d3dddfb5 113Http::Stream::pullData()
d6fdeb41
AJ
114{
115 debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection);
116
117 /* More data will be coming from the stream. */
942b1c39 118 StoreIOBuffer readBuffer;
d6fdeb41
AJ
119 /* XXX: Next requested byte in the range sequence */
120 /* XXX: length = getmaximumrangelenfgth */
121 readBuffer.offset = getNextRangeOffset();
942b1c39
AJ
122 readBuffer.length = HTTP_REQBUF_SZ;
123 readBuffer.data = reqbuf;
d6fdeb41
AJ
124 /* we may note we have reached the end of the wanted ranges */
125 clientStreamRead(getTail(), http, readBuffer);
126}
127
128bool
d3dddfb5 129Http::Stream::multipartRangeRequest() const
d6fdeb41
AJ
130{
131 return http->multipartRangeRequest();
132}
133
134int64_t
d3dddfb5 135Http::Stream::getNextRangeOffset() const
d6fdeb41
AJ
136{
137 debugs (33, 5, "range: " << http->request->range <<
138 "; http offset " << http->out.offset <<
139 "; reply " << reply);
140
141 // XXX: This method is called from many places, including pullData() which
142 // may be called before prepareReply() [on some Squid-generated errors].
143 // Hence, we may not even know yet whether we should honor/do ranges.
144
145 if (http->request->range) {
146 /* offset in range specs does not count the prefix of an http msg */
147 /* check: reply was parsed and range iterator was initialized */
148 assert(http->range_iter.valid);
149 /* filter out data according to range specs */
150 assert(canPackMoreRanges());
151 {
152 assert(http->range_iter.currentSpec());
153 /* offset of still missing data */
154 int64_t start = http->range_iter.currentSpec()->offset +
155 http->range_iter.currentSpec()->length -
156 http->range_iter.debt();
157 debugs(33, 3, "clientPackMoreRanges: in: offset: " << http->out.offset);
158 debugs(33, 3, "clientPackMoreRanges: out:"
159 " start: " << start <<
160 " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" <<
161 " [" << http->range_iter.currentSpec()->offset <<
162 ", " << http->range_iter.currentSpec()->offset +
163 http->range_iter.currentSpec()->length << "),"
164 " len: " << http->range_iter.currentSpec()->length <<
165 " debt: " << http->range_iter.debt());
166 if (http->range_iter.currentSpec()->length != -1)
167 assert(http->out.offset <= start); /* we did not miss it */
168
169 return start;
170 }
171
8341f96d 172 } else if (reply && reply->contentRange()) {
d6fdeb41
AJ
173 /* request does not have ranges, but reply does */
174 /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
175 * becomes HttpHdrRange rather than HttpHdrRangeSpec.
176 */
8341f96d 177 return http->out.offset + reply->contentRange()->spec.offset;
d6fdeb41
AJ
178 }
179
180 return http->out.offset;
181}
182
183/**
184 * increments iterator "i"
185 * used by clientPackMoreRanges
186 *
187 * \retval true there is still data available to pack more ranges
188 * \retval false
189 */
190bool
d3dddfb5 191Http::Stream::canPackMoreRanges() const
d6fdeb41
AJ
192{
193 /** first update iterator "i" if needed */
194 if (!http->range_iter.debt()) {
195 debugs(33, 5, "At end of current range spec for " << clientConnection);
196
197 if (http->range_iter.pos != http->range_iter.end)
198 ++http->range_iter.pos;
199
200 http->range_iter.updateSpec();
201 }
202
203 assert(!http->range_iter.debt() == !http->range_iter.currentSpec());
204
205 /* paranoid sync condition */
206 /* continue condition: need_more_data */
207 debugs(33, 5, "returning " << (http->range_iter.currentSpec() ? true : false));
208 return http->range_iter.currentSpec() ? true : false;
209}
210
211/// Adapt stream status to account for Range cases
212clientStream_status_t
d3dddfb5 213Http::Stream::socketState()
d6fdeb41
AJ
214{
215 switch (clientStreamStatus(getTail(), http)) {
216
217 case STREAM_NONE:
218 /* check for range support ending */
219 if (http->request->range) {
220 /* check: reply was parsed and range iterator was initialized */
221 assert(http->range_iter.valid);
222 /* filter out data according to range specs */
223
224 if (!canPackMoreRanges()) {
225 debugs(33, 5, "Range request at end of returnable " <<
226 "range sequence on " << clientConnection);
227 // we got everything we wanted from the store
228 return STREAM_COMPLETE;
229 }
8341f96d 230 } else if (reply && reply->contentRange()) {
d6fdeb41
AJ
231 /* reply has content-range, but Squid is not managing ranges */
232 const int64_t &bytesSent = http->out.offset;
8341f96d 233 const int64_t &bytesExpected = reply->contentRange()->spec.length;
d6fdeb41
AJ
234
235 debugs(33, 7, "body bytes sent vs. expected: " <<
236 bytesSent << " ? " << bytesExpected << " (+" <<
8341f96d 237 reply->contentRange()->spec.offset << ")");
d6fdeb41
AJ
238
239 // did we get at least what we expected, based on range specs?
240
241 if (bytesSent == bytesExpected) // got everything
242 return STREAM_COMPLETE;
243
244 if (bytesSent > bytesExpected) // Error: Sent more than expected
245 return STREAM_UNPLANNED_COMPLETE;
246 }
247
248 return STREAM_NONE;
249
250 case STREAM_COMPLETE:
251 return STREAM_COMPLETE;
252
253 case STREAM_UNPLANNED_COMPLETE:
254 return STREAM_UNPLANNED_COMPLETE;
255
256 case STREAM_FAILED:
257 return STREAM_FAILED;
258 }
259
260 fatal ("unreachable code\n");
261 return STREAM_NONE;
262}
263
264void
d3dddfb5 265Http::Stream::sendStartOfMessage(HttpReply *rep, StoreIOBuffer bodyData)
d6fdeb41
AJ
266{
267 prepareReply(rep);
268 assert(rep);
269 MemBuf *mb = rep->pack();
270
61beade2 271 // dump now, so we do not output any body.
d6fdeb41
AJ
272 debugs(11, 2, "HTTP Client " << clientConnection);
273 debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------");
274
275 /* Save length of headers for persistent conn checks */
276 http->out.headers_sz = mb->contentSize();
277#if HEADERS_LOG
278 headersLog(0, 0, http->request->method, rep);
279#endif
280
281 if (bodyData.data && bodyData.length) {
282 if (multipartRangeRequest())
283 packRange(bodyData, mb);
284 else if (http->request->flags.chunkedReply) {
285 packChunk(bodyData, *mb);
286 } else {
287 size_t length = lengthToSend(bodyData.range());
288 noteSentBodyBytes(length);
289 mb->append(bodyData.data, length);
290 }
291 }
b27668ec
EB
292#if USE_DELAY_POOLS
293 for (const auto &pool: MessageDelayPools::Instance()->pools) {
294 if (pool->access) {
295 std::unique_ptr<ACLFilledChecklist> chl(clientAclChecklistCreate(pool->access, http));
296 chl->reply = rep;
297 HTTPMSGLOCK(chl->reply);
329c128c 298 const auto answer = chl->fastCheck();
06bf5384 299 if (answer.allowed()) {
b27668ec
EB
300 writeQuotaHandler = pool->createBucket();
301 fd_table[clientConnection->fd].writeQuotaHandler = writeQuotaHandler;
302 break;
303 } else {
304 debugs(83, 4, "Response delay pool " << pool->poolName <<
305 " skipped because ACL " << answer);
306 }
307 }
308 }
309#endif
d6fdeb41
AJ
310
311 getConn()->write(mb);
312 delete mb;
313}
314
315void
d3dddfb5 316Http::Stream::sendBody(StoreIOBuffer bodyData)
d6fdeb41
AJ
317{
318 if (!multipartRangeRequest() && !http->request->flags.chunkedReply) {
319 size_t length = lengthToSend(bodyData.range());
320 noteSentBodyBytes(length);
321 getConn()->write(bodyData.data, length);
322 return;
323 }
324
325 MemBuf mb;
326 mb.init();
327 if (multipartRangeRequest())
328 packRange(bodyData, &mb);
329 else
330 packChunk(bodyData, mb);
331
332 if (mb.contentSize())
333 getConn()->write(&mb);
334 else
335 writeComplete(0);
336}
337
338size_t
d3dddfb5 339Http::Stream::lengthToSend(Range<int64_t> const &available) const
d6fdeb41
AJ
340{
341 // the size of available range can always fit into a size_t type
342 size_t maximum = available.size();
343
344 if (!http->request->range)
345 return maximum;
346
347 assert(canPackMoreRanges());
348
349 if (http->range_iter.debt() == -1)
350 return maximum;
351
352 assert(http->range_iter.debt() > 0);
353
354 /* TODO this + the last line could be a range intersection calculation */
355 if (available.start < http->range_iter.currentSpec()->offset)
356 return 0;
357
358 return min(http->range_iter.debt(), static_cast<int64_t>(maximum));
359}
360
361void
d3dddfb5 362Http::Stream::noteSentBodyBytes(size_t bytes)
d6fdeb41
AJ
363{
364 debugs(33, 7, bytes << " body bytes");
365 http->out.offset += bytes;
366
367 if (!http->request->range)
368 return;
369
370 if (http->range_iter.debt() != -1) {
371 http->range_iter.debt(http->range_iter.debt() - bytes);
372 assert (http->range_iter.debt() >= 0);
373 }
374
375 /* debt() always stops at -1, below that is a bug */
376 assert(http->range_iter.debt() >= -1);
377}
378
379/// \return true when If-Range specs match reply, false otherwise
380static bool
381clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep)
382{
383 const TimeOrTag spec = http->request->header.getTimeOrTag(Http::HdrType::IF_RANGE);
384
385 /* check for parsing falure */
386 if (!spec.valid)
387 return false;
388
389 /* got an ETag? */
390 if (spec.tag.str) {
391 ETag rep_tag = rep->header.getETag(Http::HdrType::ETAG);
392 debugs(33, 3, "ETags: " << spec.tag.str << " and " <<
393 (rep_tag.str ? rep_tag.str : "<none>"));
394
395 if (!rep_tag.str)
396 return false; // entity has no etag to compare with!
397
398 if (spec.tag.weak || rep_tag.weak) {
399 debugs(33, DBG_IMPORTANT, "Weak ETags are not allowed in If-Range: " <<
400 spec.tag.str << " ? " << rep_tag.str);
401 return false; // must use strong validator for sub-range requests
402 }
403
404 return etagIsStrongEqual(rep_tag, spec.tag);
405 }
406
407 /* got modification time? */
408 if (spec.time >= 0)
438b41ba 409 return !http->storeEntry()->modifiedSince(spec.time);
d6fdeb41
AJ
410
411 assert(0); /* should not happen */
412 return false;
413}
414
415// seems to be something better suited to Server logic
416/** adds appropriate Range headers if needed */
417void
d3dddfb5 418Http::Stream::buildRangeHeader(HttpReply *rep)
d6fdeb41
AJ
419{
420 HttpHeader *hdr = rep ? &rep->header : nullptr;
421 const char *range_err = nullptr;
422 HttpRequest *request = http->request;
423 assert(request->range);
424 /* check if we still want to do ranges */
425 int64_t roffLimit = request->getRangeOffsetLimit();
8341f96d 426 auto contentRange = rep ? rep->contentRange() : nullptr;
d6fdeb41
AJ
427
428 if (!rep)
429 range_err = "no [parse-able] reply";
430 else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
431 range_err = "wrong status code";
8341f96d
EB
432 else if (rep->sline.status() == Http::scPartialContent)
433 range_err = "too complex response"; // probably contains what the client needs
434 else if (rep->sline.status() != Http::scOkay)
435 range_err = "wrong status code";
436 else if (hdr->has(Http::HdrType::CONTENT_RANGE)) {
437 Must(!contentRange); // this is a 200, not 206 response
438 range_err = "meaningless response"; // the status code or the header is wrong
439 }
d6fdeb41
AJ
440 else if (rep->content_length < 0)
441 range_err = "unknown length";
442 else if (rep->content_length != http->memObject()->getReply()->content_length)
443 range_err = "INCONSISTENT length"; /* a bug? */
444
445 /* hits only - upstream CachePeer determines correct behaviour on misses,
446 * and client_side_reply determines hits candidates
447 */
448 else if (http->logType.isTcpHit() &&
449 http->request->header.has(Http::HdrType::IF_RANGE) &&
450 !clientIfRangeMatch(http, rep))
451 range_err = "If-Range match failed";
452
453 else if (!http->request->range->canonize(rep))
454 range_err = "canonization failed";
455 else if (http->request->range->isComplex())
456 range_err = "too complex range header";
457 else if (!http->logType.isTcpHit() && http->request->range->offsetLimitExceeded(roffLimit))
458 range_err = "range outside range_offset_limit";
459
460 /* get rid of our range specs on error */
461 if (range_err) {
462 /* XXX We do this here because we need canonisation etc. However, this current
463 * code will lead to incorrect store offset requests - the store will have the
464 * offset data, but we won't be requesting it.
465 * So, we can either re-request, or generate an error
466 */
467 http->request->ignoreRange(range_err);
468 } else {
469 /* XXX: TODO: Review, this unconditional set may be wrong. */
470 rep->sline.set(rep->sline.version, Http::scPartialContent);
471 // web server responded with a valid, but unexpected range.
472 // will (try-to) forward as-is.
473 //TODO: we should cope with multirange request/responses
8341f96d
EB
474 // TODO: review, since rep->content_range is always nil here.
475 bool replyMatchRequest = contentRange != nullptr ?
476 request->range->contains(contentRange->spec) :
d6fdeb41
AJ
477 true;
478 const int spec_count = http->request->range->specs.size();
479 int64_t actual_clen = -1;
480
481 debugs(33, 3, "range spec count: " << spec_count <<
482 " virgin clen: " << rep->content_length);
483 assert(spec_count > 0);
484 /* append appropriate header(s) */
485 if (spec_count == 1) {
486 if (!replyMatchRequest) {
8341f96d 487 hdr->putContRange(contentRange);
d6fdeb41
AJ
488 actual_clen = rep->content_length;
489 //http->range_iter.pos = rep->content_range->spec.begin();
8341f96d
EB
490 (*http->range_iter.pos)->offset = contentRange->spec.offset;
491 (*http->range_iter.pos)->length = contentRange->spec.length;
d6fdeb41
AJ
492
493 } else {
494 HttpHdrRange::iterator pos = http->request->range->begin();
495 assert(*pos);
496 /* append Content-Range */
497
8341f96d 498 if (!contentRange) {
d6fdeb41
AJ
499 /* No content range, so this was a full object we are
500 * sending parts of.
501 */
502 httpHeaderAddContRange(hdr, **pos, rep->content_length);
503 }
504
505 /* set new Content-Length to the actual number of bytes
506 * transmitted in the message-body */
507 actual_clen = (*pos)->length;
508 }
509 } else {
510 /* multipart! */
511 /* generate boundary string */
512 http->range_iter.boundary = http->rangeBoundaryStr();
513 /* delete old Content-Type, add ours */
514 hdr->delById(Http::HdrType::CONTENT_TYPE);
515 httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
516 "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
517 SQUIDSTRINGPRINT(http->range_iter.boundary));
518 /* Content-Length is not required in multipart responses
519 * but it is always nice to have one */
520 actual_clen = http->mRangeCLen();
521
522 /* http->out needs to start where we want data at */
523 http->out.offset = http->range_iter.currentSpec()->offset;
524 }
525
526 /* replace Content-Length header */
527 assert(actual_clen >= 0);
528 hdr->delById(Http::HdrType::CONTENT_LENGTH);
529 hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
530 debugs(33, 3, "actual content length: " << actual_clen);
531
532 /* And start the range iter off */
533 http->range_iter.updateSpec();
534 }
535}
536
537clientStreamNode *
d3dddfb5 538Http::Stream::getTail() const
d6fdeb41
AJ
539{
540 if (http->client_stream.tail)
541 return static_cast<clientStreamNode *>(http->client_stream.tail->data);
542
543 return nullptr;
544}
545
546clientStreamNode *
d3dddfb5 547Http::Stream::getClientReplyContext() const
d6fdeb41
AJ
548{
549 return static_cast<clientStreamNode *>(http->client_stream.tail->prev->data);
550}
551
552ConnStateData *
d3dddfb5 553Http::Stream::getConn() const
d6fdeb41
AJ
554{
555 assert(http && http->getConn());
556 return http->getConn();
557}
558
559/// remembers the abnormal connection termination for logging purposes
560void
d3dddfb5 561Http::Stream::noteIoError(const int xerrno)
d6fdeb41
AJ
562{
563 if (http) {
564 http->logType.err.timedout = (xerrno == ETIMEDOUT);
565 // aborted even if xerrno is zero (which means read abort/eof)
566 http->logType.err.aborted = (xerrno != ETIMEDOUT);
567 }
568}
569
570void
d3dddfb5 571Http::Stream::finished()
d6fdeb41
AJ
572{
573 ConnStateData *conn = getConn();
574
575 /* we can't handle any more stream data - detach */
576 clientStreamDetach(getTail(), http);
577
578 assert(connRegistered_);
579 connRegistered_ = false;
d3dddfb5 580 conn->pipeline.popMe(Http::StreamPointer(this));
d6fdeb41
AJ
581}
582
583/// called when we encounter a response-related error
584void
d3dddfb5 585Http::Stream::initiateClose(const char *reason)
d6fdeb41
AJ
586{
587 debugs(33, 4, clientConnection << " because " << reason);
588 getConn()->stopSending(reason); // closes ASAP
589}
590
591void
d3dddfb5 592Http::Stream::deferRecipientForLater(clientStreamNode *node, HttpReply *rep, StoreIOBuffer receivedData)
d6fdeb41
AJ
593{
594 debugs(33, 2, "Deferring request " << http->uri);
595 assert(flags.deferred == 0);
596 flags.deferred = 1;
597 deferredparams.node = node;
598 deferredparams.rep = rep;
599 deferredparams.queuedBuffer = receivedData;
600}
601
602void
d3dddfb5 603Http::Stream::prepareReply(HttpReply *rep)
d6fdeb41
AJ
604{
605 reply = rep;
606 if (http->request->range)
607 buildRangeHeader(rep);
608}
609
610/**
611 * Packs bodyData into mb using chunked encoding.
612 * Packs the last-chunk if bodyData is empty.
613 */
614void
d3dddfb5 615Http::Stream::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb)
d6fdeb41
AJ
616{
617 const uint64_t length =
618 static_cast<uint64_t>(lengthToSend(bodyData.range()));
619 noteSentBodyBytes(length);
620
621 mb.appendf("%" PRIX64 "\r\n", length);
622 mb.append(bodyData.data, length);
623 mb.append("\r\n", 2);
624}
625
626/**
627 * extracts a "range" from *buf and appends them to mb, updating
628 * all offsets and such.
629 */
630void
d3dddfb5 631Http::Stream::packRange(StoreIOBuffer const &source, MemBuf *mb)
d6fdeb41
AJ
632{
633 HttpHdrRangeIter * i = &http->range_iter;
634 Range<int64_t> available(source.range());
635 char const *buf = source.data;
636
637 while (i->currentSpec() && available.size()) {
638 const size_t copy_sz = lengthToSend(available);
639 if (copy_sz) {
640 // intersection of "have" and "need" ranges must not be empty
641 assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length);
642 assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset);
643
644 /*
645 * put boundary and headers at the beginning of a range in a
646 * multi-range
647 */
648 if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) {
649 assert(http->memObject());
650 clientPackRangeHdr(
651 http->memObject()->getReply(), /* original reply */
652 i->currentSpec(), /* current range */
653 i->boundary, /* boundary, the same for all */
654 mb);
655 }
656
657 // append content
658 debugs(33, 3, "appending " << copy_sz << " bytes");
659 noteSentBodyBytes(copy_sz);
660 mb->append(buf, copy_sz);
661
662 // update offsets
663 available.start += copy_sz;
664 buf += copy_sz;
665 }
666
667 if (!canPackMoreRanges()) {
668 debugs(33, 3, "Returning because !canPackMoreRanges.");
669 if (i->debt() == 0)
670 // put terminating boundary for multiparts
671 clientPackTermBound(i->boundary, mb);
672 return;
673 }
674
675 int64_t nextOffset = getNextRangeOffset();
676 assert(nextOffset >= http->out.offset);
677 int64_t skip = nextOffset - http->out.offset;
678 /* adjust for not to be transmitted bytes */
679 http->out.offset = nextOffset;
680
681 if (available.size() <= (uint64_t)skip)
682 return;
683
684 available.start += skip;
685 buf += skip;
686
687 if (copy_sz == 0)
688 return;
689 }
690}
691
692void
d3dddfb5 693Http::Stream::doClose()
d6fdeb41
AJ
694{
695 clientConnection->close();
696}
697