Line data Source code
1 : //
2 : // Copyright (c) 2021 Vinnie Falco (vinnie dot falco at gmail dot com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/CPPAlliance/http_proto
8 : //
9 :
10 : #ifndef BOOST_HTTP_PROTO_IMPL_FIELDS_BASE_IPP
11 : #define BOOST_HTTP_PROTO_IMPL_FIELDS_BASE_IPP
12 :
13 : #include <boost/http_proto/fields.hpp>
14 : #include <boost/http_proto/field.hpp>
15 : #include <boost/http_proto/detail/copied_strings.hpp>
16 : #include <boost/http_proto/detail/except.hpp>
17 : #include <boost/http_proto/detail/number_string.hpp>
18 : #include <boost/http_proto/detail/move_chars.hpp>
19 : #include <boost/assert.hpp>
20 : #include <boost/assert/source_location.hpp>
21 : #include <string>
22 :
23 : namespace boost {
24 : namespace http_proto {
25 :
26 : class fields_base::
27 : op_t
28 : {
29 : fields_base& self_;
30 : string_view* s0_;
31 : string_view* s1_;
32 : char* buf_ = nullptr;
33 : char const* cbuf_ = nullptr;
34 : std::size_t cap_ = 0;
35 :
36 : public:
37 : explicit
38 601 : op_t(
39 : fields_base& self,
40 : string_view* s0 = nullptr,
41 : string_view* s1 = nullptr) noexcept
42 601 : : self_(self)
43 : , s0_(s0)
44 601 : , s1_(s1)
45 : {
46 601 : }
47 :
48 601 : ~op_t()
49 601 : {
50 601 : if(buf_)
51 63 : delete[] buf_;
52 601 : }
53 :
54 : char const*
55 6 : buf() const noexcept
56 : {
57 6 : return buf_;
58 : }
59 :
60 : char const*
61 114 : cbuf() const noexcept
62 : {
63 114 : return cbuf_;
64 : }
65 :
66 : char*
67 9 : end() const noexcept
68 : {
69 9 : return buf_ + cap_;
70 : }
71 :
72 : table
73 3 : tab() const noexcept
74 : {
75 3 : return table(end());
76 : }
77 :
78 : static
79 : std::size_t
80 : growth(
81 : std::size_t n0,
82 : std::size_t m) noexcept;
83 :
84 : bool
85 : reserve(std::size_t bytes);
86 :
87 : bool
88 : grow(
89 : std::size_t extra_char,
90 : std::size_t extra_field);
91 :
92 : void
93 : copy_prefix(
94 : std::size_t n,
95 : std::size_t i) noexcept;
96 :
97 : void
98 : move_chars(
99 : char* dest,
100 : char const* src,
101 : std::size_t n) const noexcept;
102 : };
103 :
104 : /* Growth functions for containers
105 :
106 : N1 = g( N0, M );
107 :
108 : g = growth function
109 : M = minimum capacity
110 : N0 = old size
111 : N1 = new size
112 : */
113 : std::size_t
114 1129 : fields_base::
115 : op_t::
116 : growth(
117 : std::size_t n0,
118 : std::size_t m) noexcept
119 : {
120 1129 : auto const E = alignof(entry);
121 1129 : auto const m1 =
122 1129 : E * ((m + E - 1) / E);
123 1129 : BOOST_ASSERT(m1 >= m);
124 1129 : if(n0 == 0)
125 : {
126 : // exact
127 940 : return m1;
128 : }
129 189 : if(m1 > n0)
130 119 : return m1;
131 70 : return n0;
132 : }
133 :
134 : bool
135 585 : fields_base::
136 : op_t::
137 : reserve(
138 : std::size_t bytes)
139 : {
140 585 : if(bytes > max_capacity_in_bytes())
141 : {
142 : // max capacity exceeded
143 1 : detail::throw_length_error();
144 : }
145 584 : auto n = growth(
146 584 : self_.h_.cap, bytes);
147 584 : if(n <= self_.h_.cap)
148 48 : return false;
149 536 : auto buf = new char[n];
150 536 : buf_ = self_.h_.buf;
151 536 : cbuf_ = self_.h_.cbuf;
152 536 : cap_ = self_.h_.cap;
153 536 : self_.h_.buf = buf;
154 536 : self_.h_.cbuf = buf;
155 536 : self_.h_.cap = n;
156 536 : return true;
157 : }
158 :
159 : bool
160 547 : fields_base::
161 : op_t::
162 : grow(
163 : std::size_t extra_char,
164 : std::size_t extra_field)
165 : {
166 : // extra_field is naturally limited
167 : // by max_off_t, since each field
168 : // is at least 4 bytes
169 547 : BOOST_ASSERT(
170 : extra_field <= max_off_t &&
171 : extra_field <= static_cast<
172 : std::size_t>(
173 : max_off_t - self_.h_.count));
174 547 : if( extra_char > max_off_t ||
175 545 : extra_char > static_cast<std::size_t>(
176 545 : max_off_t - self_.h_.size))
177 2 : detail::throw_length_error();
178 1090 : auto n1 = growth(
179 545 : self_.h_.cap,
180 : detail::header::bytes_needed(
181 545 : self_.h_.size + extra_char,
182 545 : self_.h_.count + extra_field));
183 545 : return reserve(n1);
184 : }
185 :
186 : void
187 0 : fields_base::
188 : op_t::
189 : copy_prefix(
190 : std::size_t n,
191 : std::size_t i) noexcept
192 : {
193 : // copy first n chars
194 0 : std::memcpy(
195 0 : self_.h_.buf,
196 0 : cbuf_,
197 : n);
198 : // copy first i entries
199 0 : if(i > 0)
200 0 : std::memcpy(
201 0 : self_.h_.tab_() - i,
202 : reinterpret_cast<entry*>(
203 0 : buf_ + cap_) - i,
204 : i * sizeof(entry));
205 0 : }
206 :
207 : void
208 37 : fields_base::
209 : op_t::
210 : move_chars(
211 : char* dest,
212 : char const* src,
213 : std::size_t n) const noexcept
214 : {
215 37 : detail::move_chars(
216 37 : dest, src, n, s0_, s1_);
217 37 : }
218 :
219 : //------------------------------------------------
220 :
221 69 : fields_base::
222 : fields_base(
223 0 : detail::kind k) noexcept
224 0 : : fields_view_base(&h_)
225 69 : , h_(k)
226 : {
227 69 : }
228 :
229 : // copy s and parse it
230 453 : fields_base::
231 : fields_base(
232 : detail::kind k,
233 0 : string_view s)
234 0 : : fields_view_base(&h_)
235 453 : , h_(detail::empty{k})
236 : {
237 453 : auto n = detail::header::count_crlf(s);
238 453 : if(h_.kind == detail::kind::fields)
239 : {
240 197 : if(n < 1)
241 0 : detail::throw_invalid_argument();
242 197 : n -= 1;
243 : }
244 : else
245 : {
246 256 : if(n < 2)
247 0 : detail::throw_invalid_argument();
248 256 : n -= 2;
249 : }
250 906 : op_t op(*this);
251 453 : op.grow(s.size(), n);
252 453 : s.copy(h_.buf, s.size());
253 453 : error_code ec;
254 453 : detail::header::config cfg;
255 453 : h_.parse(cfg, s.size(), ec);
256 453 : if(ec.failed())
257 0 : detail::throw_system_error(ec);
258 453 : }
259 :
260 : // construct a complete copy of h
261 18 : fields_base::
262 : fields_base(
263 12 : detail::header const& h)
264 12 : : fields_view_base(&h_)
265 18 : , h_(h.kind)
266 : {
267 18 : if(h.is_default())
268 : {
269 6 : BOOST_ASSERT(h.cap == 0);
270 6 : BOOST_ASSERT(h.buf == nullptr);
271 6 : h_ = h;
272 6 : return;
273 : }
274 :
275 : // allocate and copy the buffer
276 24 : op_t op(*this);
277 12 : op.grow(h.size, h.count);
278 12 : h.assign_to(h_);
279 12 : std::memcpy(
280 12 : h_.buf, h.cbuf, h.size);
281 12 : h.copy_table(h_.buf + h_.cap);
282 : }
283 :
284 : //------------------------------------------------
285 :
286 540 : fields_base::
287 552 : ~fields_base()
288 : {
289 540 : if(h_.buf)
290 477 : delete[] h_.buf;
291 540 : }
292 :
293 : //------------------------------------------------
294 : //
295 : // Capacity
296 : //
297 : //------------------------------------------------
298 :
299 : void
300 8 : fields_base::
301 : clear() noexcept
302 : {
303 8 : if(! h_.buf)
304 4 : return;
305 : using H =
306 : detail::header;
307 : auto const& h =
308 4 : *H::get_default(
309 4 : h_.kind);
310 4 : h.assign_to(h_);
311 4 : std::memcpy(
312 4 : h_.buf,
313 4 : h.cbuf,
314 4 : h_.size);
315 : }
316 :
317 : void
318 40 : fields_base::
319 : reserve_bytes(
320 : std::size_t n)
321 : {
322 41 : op_t op(*this);
323 40 : if(! op.reserve(n))
324 25 : return;
325 28 : std::memcpy(
326 14 : h_.buf, op.cbuf(), h_.size);
327 14 : auto const nt =
328 14 : sizeof(entry) * h_.count;
329 14 : if(nt > 0)
330 6 : std::memcpy(
331 6 : h_.buf + h_.cap - nt,
332 6 : op.end() - nt,
333 : nt);
334 : }
335 :
336 : void
337 7 : fields_base::
338 : shrink_to_fit() noexcept
339 : {
340 14 : if(detail::header::bytes_needed(
341 7 : h_.size, h_.count) >=
342 7 : h_.cap)
343 3 : return;
344 8 : fields_base tmp(h_);
345 4 : tmp.h_.swap(h_);
346 : }
347 :
348 : //------------------------------------------------
349 : //
350 : // Modifiers
351 : //
352 : //------------------------------------------------
353 :
354 : std::size_t
355 24 : fields_base::
356 : erase(
357 : field id) noexcept
358 : {
359 24 : BOOST_ASSERT(
360 : id != field::unknown);
361 : #if 1
362 24 : auto const end_ = end();
363 24 : auto it = find_last(end_, id);
364 24 : if(it == end_)
365 3 : return 0;
366 21 : std::size_t n = 1;
367 21 : auto const begin_ = begin();
368 21 : raw_erase(it.i_);
369 57 : while(it != begin_)
370 : {
371 36 : --it;
372 36 : if(it->id == id)
373 : {
374 25 : raw_erase(it.i_);
375 25 : ++n;
376 : }
377 : }
378 21 : h_.on_erase_all(id);
379 21 : return n;
380 : #else
381 : std::size_t n = 0;
382 : auto it0 = find(id);
383 : auto const end_ = end();
384 : if(it0 != end_)
385 : {
386 : auto it1 = it0;
387 : std::size_t total = 0;
388 : std::size_t size = 0;
389 : // [it0, it1) run of id
390 : for(;;)
391 : {
392 : size += length(it1.i_);
393 : ++it1;
394 : if(it1 == end_)
395 : goto finish;
396 : if(it1->id != id)
397 : break;
398 : }
399 : std::memmove(
400 : h_.buf + offset(it0.i_),
401 : h_.buf + offset(it1.i_),
402 : h_.size - offset(it2.i_));
403 :
404 : finish:
405 : h_.size -= size;
406 : h_.count -= n;
407 : }
408 : return n;
409 : #endif
410 : }
411 :
412 : std::size_t
413 18 : fields_base::
414 : erase(
415 : string_view name) noexcept
416 : {
417 18 : auto it0 = find(name);
418 18 : auto const end_ = end();
419 18 : if(it0 == end_)
420 3 : return 0;
421 15 : auto it = end_;
422 15 : std::size_t n = 1;
423 15 : auto const id = it0->id;
424 15 : if(id == field::unknown)
425 : {
426 : // fix self-intersection
427 6 : name = it0->name;
428 :
429 : for(;;)
430 : {
431 24 : --it;
432 24 : if(it == it0)
433 6 : break;
434 18 : if(grammar::ci_is_equal(
435 36 : it->name, name))
436 : {
437 9 : raw_erase(it.i_);
438 9 : ++n;
439 : }
440 : }
441 6 : raw_erase(it.i_);
442 : }
443 : else
444 : {
445 : for(;;)
446 : {
447 21 : --it;
448 21 : if(it == it0)
449 9 : break;
450 12 : if(it->id == id)
451 : {
452 6 : raw_erase(it.i_);
453 6 : ++n;
454 : }
455 : }
456 9 : raw_erase(it.i_);
457 9 : h_.on_erase_all(id);
458 : }
459 15 : return n;
460 : }
461 :
462 : //------------------------------------------------
463 :
464 : void
465 17 : fields_base::
466 : set(
467 : iterator it,
468 : string_view value)
469 : {
470 17 : auto const i = it.i_;
471 17 : auto const& e0 = h_.tab()[i];
472 17 : auto const pos0 = offset(i);
473 17 : auto const pos1 = offset(i + 1 );
474 : std::ptrdiff_t dn =
475 17 : value.size() -
476 17 : it->value.size();
477 17 : if( value.empty() &&
478 17 : ! it->value.empty())
479 0 : --dn; // remove SP
480 17 : else if(
481 17 : it->value.empty() &&
482 0 : ! value.empty())
483 0 : ++dn; // add SP
484 :
485 34 : op_t op(*this, &value);
486 20 : if( dn > 0 &&
487 6 : op.grow(value.size() -
488 20 : it->value.size(), 0))
489 : {
490 : // reallocated
491 3 : auto dest = h_.buf +
492 3 : pos0 + e0.nn + 1;
493 6 : std::memcpy(
494 3 : h_.buf,
495 3 : op.buf(),
496 3 : dest - h_.buf);
497 3 : if(! value.empty())
498 : {
499 3 : *dest++ = ' ';
500 3 : value.copy(
501 : dest,
502 : value.size());
503 3 : dest += value.size();
504 : }
505 3 : *dest++ = '\r';
506 3 : *dest++ = '\n';
507 6 : std::memcpy(
508 3 : h_.buf + pos1 + dn,
509 6 : op.buf() + pos1,
510 3 : h_.size - pos1);
511 6 : std::memcpy(
512 3 : h_.buf + h_.cap -
513 3 : sizeof(entry) * h_.count,
514 3 : &op.tab()[h_.count - 1],
515 3 : sizeof(entry) * h_.count);
516 : }
517 : else
518 : {
519 : // copy the value first
520 28 : auto dest = h_.buf + pos0 +
521 14 : it->name.size() + 1;
522 14 : if(! value.empty())
523 : {
524 14 : *dest++ = ' ';
525 14 : value.copy(
526 : dest,
527 : value.size());
528 14 : dest += value.size();
529 : }
530 14 : op.move_chars(
531 14 : h_.buf + pos1 + dn,
532 14 : h_.buf + pos1,
533 14 : h_.size - pos1);
534 14 : *dest++ = '\r';
535 14 : *dest++ = '\n';
536 : }
537 : {
538 : // update tab
539 17 : auto ft = h_.tab();
540 22 : for(std::size_t j = h_.count - 1;
541 22 : j > i; --j)
542 5 : ft[j] = ft[j] + dn;
543 17 : auto& e = ft[i];
544 34 : e.vp = e.np + e.nn +
545 17 : 1 + ! value.empty();
546 17 : e.vn = static_cast<
547 17 : off_t>(value.size());
548 17 : h_.size = static_cast<
549 17 : off_t>(h_.size + dn);
550 : }
551 17 : auto const id = it->id;
552 17 : if(h_.is_special(id))
553 : {
554 : // replace first char of name
555 : // with null to hide metadata
556 7 : char saved = h_.buf[pos0];
557 7 : auto& e = h_.tab()[i];
558 7 : e.id = field::unknown;
559 7 : h_.buf[pos0] = '\0';
560 7 : h_.on_erase(id);
561 7 : h_.buf[pos0] = saved;
562 7 : e.id = id;
563 7 : h_.on_insert(id, it->value);
564 : }
565 17 : }
566 :
567 : // erase existing fields with id
568 : // and then add the field with value
569 : void
570 18 : fields_base::
571 : set(
572 : field id,
573 : string_view value)
574 : {
575 18 : BOOST_ASSERT(
576 : id != field::unknown);
577 18 : auto const i0 = h_.find(id);
578 18 : if(i0 != h_.count)
579 : {
580 : // field exists
581 12 : auto const ft = h_.tab();
582 : {
583 : // provide strong guarantee
584 : auto const n0 =
585 12 : h_.size - length(i0);
586 : auto const n =
587 12 : ft[i0].nn + 2 +
588 12 : value.size() + 2;
589 : // VFALCO missing overflow check
590 12 : reserve_bytes(n0 + n);
591 : }
592 12 : erase_all_impl(i0, id);
593 : }
594 18 : insert_impl(id, to_string(id),
595 18 : value, h_.count);
596 18 : }
597 :
598 : // erase existing fields with name
599 : // and then add the field with value
600 : void
601 13 : fields_base::
602 : set(
603 : string_view name,
604 : string_view value)
605 : {
606 13 : auto const i0 = h_.find(name);
607 13 : if(i0 != h_.count)
608 : {
609 : // field exists
610 9 : auto const ft = h_.tab();
611 9 : auto const id = ft[i0].id;
612 : {
613 : // provide strong guarantee
614 : auto const n0 =
615 9 : h_.size - length(i0);
616 : auto const n =
617 9 : ft[i0].nn + 2 +
618 9 : value.size() + 2;
619 : // VFALCO missing overflow check
620 9 : reserve_bytes(n0 + n);
621 : }
622 : // VFALCO simple algorithm but
623 : // costs one extra memmove
624 9 : erase_all_impl(i0, id);
625 : }
626 13 : insert_impl(
627 : string_to_field(name),
628 13 : name, value, h_.count);
629 12 : }
630 :
631 : //------------------------------------------------
632 : //
633 : // (implementation)
634 : //
635 : //------------------------------------------------
636 :
637 : // copy start line and fields
638 : void
639 9 : fields_base::
640 : copy_impl(
641 : detail::header const& h)
642 : {
643 9 : BOOST_ASSERT(
644 : h.kind == ph_->kind);
645 9 : if(! h.is_default())
646 : {
647 : auto const n =
648 6 : detail::header::bytes_needed(
649 6 : h.size, h.count);
650 6 : if(n <= h_.cap)
651 : {
652 : // no realloc
653 1 : h.assign_to(h_);
654 1 : h.copy_table(
655 1 : h_.buf + h_.cap);
656 1 : std::memcpy(
657 1 : h_.buf,
658 1 : h.cbuf,
659 1 : h.size);
660 1 : return;
661 : }
662 : }
663 16 : fields_base tmp(h);
664 8 : tmp.h_.swap(h_);
665 : }
666 :
667 : void
668 79 : fields_base::
669 : insert_impl(
670 : field id,
671 : string_view name,
672 : string_view value,
673 : std::size_t before)
674 : {
675 79 : auto const tab0 = h_.tab_();
676 79 : auto const pos = offset(before);
677 : auto const n =
678 79 : name.size() + // name
679 79 : 1 + // ':'
680 79 : ! value.empty() + // [SP]
681 79 : value.size() + // value
682 79 : 2; // CRLF
683 :
684 158 : op_t op(*this, &name, &value);
685 79 : if(op.grow(n, 1))
686 : {
687 : // reallocated
688 54 : if(pos > 0)
689 46 : std::memcpy(
690 46 : h_.buf,
691 46 : op.cbuf(),
692 : pos);
693 54 : if(before > 0)
694 36 : std::memcpy(
695 18 : h_.tab_() - before,
696 18 : tab0 - before,
697 : before * sizeof(entry));
698 108 : std::memcpy(
699 54 : h_.buf + pos + n,
700 54 : op.cbuf() + pos,
701 54 : h_.size - pos);
702 : }
703 : else
704 : {
705 23 : op.move_chars(
706 23 : h_.buf + pos + n,
707 23 : h_.buf + pos,
708 23 : h_.size - pos);
709 : }
710 :
711 : // serialize
712 : {
713 77 : auto dest = h_.buf + pos;
714 77 : name.copy(dest, name.size());
715 77 : dest += name.size();
716 77 : *dest++ = ':';
717 77 : if(! value.empty())
718 : {
719 74 : *dest++ = ' ';
720 74 : value.copy(
721 : dest, value.size());
722 74 : dest += value.size();
723 : }
724 77 : *dest++ = '\r';
725 77 : *dest = '\n';
726 : }
727 :
728 : // update table
729 77 : auto const tab = h_.tab_();
730 : {
731 77 : auto i = h_.count - before;
732 77 : if(i > 0)
733 : {
734 18 : auto p0 = tab0 - h_.count;
735 18 : auto p = tab - h_.count - 1;
736 18 : do
737 : {
738 36 : *p++ = *p0++ + n;
739 : }
740 36 : while(--i);
741 : }
742 : }
743 77 : auto& e = tab[0 - before - 1];
744 77 : e.np = static_cast<off_t>(
745 77 : pos - h_.prefix);
746 77 : e.nn = static_cast<
747 77 : off_t>(name.size());
748 77 : e.vp = static_cast<off_t>(
749 154 : pos - h_.prefix +
750 77 : name.size() + 1 +
751 77 : ! value.empty());
752 77 : e.vn = static_cast<
753 77 : off_t>(value.size());
754 77 : e.id = id;
755 :
756 : // update container
757 77 : h_.count++;
758 77 : h_.size = static_cast<
759 77 : off_t>(h_.size + n);
760 77 : if( id != field::unknown)
761 68 : h_.on_insert(id, value);
762 77 : }
763 :
764 : // erase i and update metadata
765 : void
766 31 : fields_base::
767 : erase_impl(
768 : std::size_t i,
769 : field id) noexcept
770 : {
771 31 : raw_erase(i);
772 31 : if(id != field::unknown)
773 31 : h_.on_erase(id);
774 31 : }
775 :
776 : //------------------------------------------------
777 :
778 : void
779 141 : fields_base::
780 : raw_erase(
781 : std::size_t i) noexcept
782 : {
783 141 : BOOST_ASSERT(i < h_.count);
784 141 : BOOST_ASSERT(h_.buf != nullptr);
785 141 : auto const p0 = offset(i);
786 141 : auto const p1 = offset(i + 1);
787 141 : std::memmove(
788 141 : h_.buf + p0,
789 141 : h_.buf + p1,
790 141 : h_.size - p1);
791 141 : auto const n = p1 - p0;
792 141 : --h_.count;
793 141 : auto ft = h_.tab();
794 216 : for(;i < h_.count; ++i)
795 75 : ft[i] = ft[i + 1] - n;
796 141 : h_.size = static_cast<
797 141 : off_t>(h_.size - n);
798 141 : }
799 :
800 : //------------------------------------------------
801 :
802 : // erase all fields with id
803 : // and update metadata
804 : std::size_t
805 21 : fields_base::
806 : erase_all_impl(
807 : std::size_t i0,
808 : field id) noexcept
809 : {
810 21 : BOOST_ASSERT(
811 : id != field::unknown);
812 21 : std::size_t n = 1;
813 21 : std::size_t i = h_.count - 1;
814 21 : auto const ft = h_.tab();
815 46 : while(i > i0)
816 : {
817 25 : if(ft[i].id == id)
818 : {
819 13 : raw_erase(i);
820 13 : ++n;
821 : }
822 : // go backwards to
823 : // reduce memmoves
824 25 : --i;
825 : }
826 21 : raw_erase(i0);
827 21 : h_.on_erase_all(id);
828 21 : return n;
829 : }
830 :
831 : // return i-th field absolute offset
832 : std::size_t
833 437 : fields_base::
834 : offset(
835 : std::size_t i) const noexcept
836 : {
837 437 : if(i == 0)
838 140 : return h_.prefix;
839 297 : if(i < h_.count)
840 348 : return h_.prefix +
841 174 : h_.tab_()[0-(i + 1)].np;
842 : // make final CRLF the last "field"
843 : //BOOST_ASSERT(i == h_.count);
844 123 : return h_.size - 2;
845 : }
846 :
847 : // return i-th field absolute length
848 : std::size_t
849 21 : fields_base::
850 : length(
851 : std::size_t i) const noexcept
852 : {
853 : return
854 21 : offset(i + 1) -
855 21 : offset(i);
856 : }
857 :
858 : //------------------------------------------------
859 :
860 : // erase n fields matching id
861 : // without updating metadata
862 : void
863 0 : fields_base::
864 : raw_erase_n(
865 : field id,
866 : std::size_t n) noexcept
867 : {
868 : // iterate in reverse
869 0 : auto e = &h_.tab()[h_.count];
870 0 : auto const e0 = &h_.tab()[0];
871 0 : while(n > 0)
872 : {
873 0 : BOOST_ASSERT(e != e0);
874 0 : ++e; // decrement
875 0 : if(e->id == id)
876 : {
877 0 : raw_erase(e0 - e);
878 0 : --n;
879 : }
880 : }
881 0 : }
882 :
883 : } // http_proto
884 : } // boost
885 :
886 : #endif
|