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