• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2// Copyright (c) 2016-2019 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/boostorg/beast
8//
9
10#ifndef BOOST_BEAST_HTTP_IMPL_BASIC_PARSER_IPP
11#define BOOST_BEAST_HTTP_IMPL_BASIC_PARSER_IPP
12
13#include <boost/beast/http/basic_parser.hpp>
14#include <boost/beast/http/error.hpp>
15#include <boost/beast/http/rfc7230.hpp>
16#include <boost/beast/core/buffer_traits.hpp>
17#include <boost/beast/core/detail/clamp.hpp>
18#include <boost/beast/core/detail/config.hpp>
19#include <boost/beast/core/detail/string.hpp>
20#include <boost/asio/buffer.hpp>
21#include <algorithm>
22#include <utility>
23
24namespace boost {
25namespace beast {
26namespace http {
27
28template<bool isRequest>
29bool
30basic_parser<isRequest>::
31keep_alive() const
32{
33    BOOST_ASSERT(is_header_done());
34    if(f_ & flagHTTP11)
35    {
36        if(f_ & flagConnectionClose)
37            return false;
38    }
39    else
40    {
41        if(! (f_ & flagConnectionKeepAlive))
42            return false;
43    }
44    return (f_ & flagNeedEOF) == 0;
45}
46
47template<bool isRequest>
48boost::optional<std::uint64_t>
49basic_parser<isRequest>::
50content_length() const
51{
52    BOOST_ASSERT(is_header_done());
53    return content_length_unchecked();
54}
55
56template<bool isRequest>
57boost::optional<std::uint64_t>
58basic_parser<isRequest>::
59content_length_remaining() const
60{
61    BOOST_ASSERT(is_header_done());
62    if(! (f_ & flagContentLength))
63        return boost::none;
64    return len_;
65}
66
67template<bool isRequest>
68void
69basic_parser<isRequest>::
70skip(bool v)
71{
72    BOOST_ASSERT(! got_some());
73    if(v)
74        f_ |= flagSkipBody;
75    else
76        f_ &= ~flagSkipBody;
77}
78
79template<bool isRequest>
80std::size_t
81basic_parser<isRequest>::
82put(net::const_buffer buffer,
83    error_code& ec)
84{
85    BOOST_ASSERT(state_ != state::complete);
86    auto p = static_cast<char const*>(buffer.data());
87    auto n = buffer.size();
88    auto const p0 = p;
89    auto const p1 = p0 + n;
90    ec = {};
91loop:
92    switch(state_)
93    {
94    case state::nothing_yet:
95        if(n == 0)
96        {
97            ec = error::need_more;
98            return 0;
99        }
100        state_ = state::start_line;
101        BOOST_FALLTHROUGH;
102
103    case state::start_line:
104    {
105        maybe_need_more(p, n, ec);
106        if(ec)
107            goto done;
108        parse_start_line(p, p + (std::min<std::size_t>)(
109            header_limit_, n), ec, is_request{});
110        if(ec)
111        {
112            if(ec == error::need_more)
113            {
114                if(n >= header_limit_)
115                {
116                    ec = error::header_limit;
117                    goto done;
118                }
119                if(p + 3 <= p1)
120                    skip_ = static_cast<
121                        std::size_t>(p1 - p - 3);
122            }
123            goto done;
124        }
125        BOOST_ASSERT(! is_done());
126        n = static_cast<std::size_t>(p1 - p);
127        if(p >= p1)
128        {
129            ec = error::need_more;
130            goto done;
131        }
132        BOOST_FALLTHROUGH;
133    }
134
135    case state::fields:
136        maybe_need_more(p, n, ec);
137        if(ec)
138            goto done;
139        parse_fields(p, p + (std::min<std::size_t>)(
140            header_limit_, n), ec);
141        if(ec)
142        {
143            if(ec == error::need_more)
144            {
145                if(n >= header_limit_)
146                {
147                    ec = error::header_limit;
148                    goto done;
149                }
150                if(p + 3 <= p1)
151                    skip_ = static_cast<
152                        std::size_t>(p1 - p - 3);
153            }
154            goto done;
155        }
156        finish_header(ec, is_request{});
157        break;
158
159    case state::body0:
160        BOOST_ASSERT(! skip_);
161        this->on_body_init_impl(content_length(), ec);
162        if(ec)
163            goto done;
164        state_ = state::body;
165        BOOST_FALLTHROUGH;
166
167    case state::body:
168        BOOST_ASSERT(! skip_);
169        parse_body(p, n, ec);
170        if(ec)
171            goto done;
172        break;
173
174    case state::body_to_eof0:
175        BOOST_ASSERT(! skip_);
176        this->on_body_init_impl(content_length(), ec);
177        if(ec)
178            goto done;
179        state_ = state::body_to_eof;
180        BOOST_FALLTHROUGH;
181
182    case state::body_to_eof:
183        BOOST_ASSERT(! skip_);
184        parse_body_to_eof(p, n, ec);
185        if(ec)
186            goto done;
187        break;
188
189    case state::chunk_header0:
190        this->on_body_init_impl(content_length(), ec);
191        if(ec)
192            goto done;
193        state_ = state::chunk_header;
194        BOOST_FALLTHROUGH;
195
196    case state::chunk_header:
197        parse_chunk_header(p, n, ec);
198        if(ec)
199            goto done;
200        break;
201
202    case state::chunk_body:
203        parse_chunk_body(p, n, ec);
204        if(ec)
205            goto done;
206        break;
207
208    case state::complete:
209        ec = {};
210        goto done;
211    }
212    if(p < p1 && ! is_done() && eager())
213    {
214        n = static_cast<std::size_t>(p1 - p);
215        goto loop;
216    }
217done:
218    return static_cast<std::size_t>(p - p0);
219}
220
221template<bool isRequest>
222void
223basic_parser<isRequest>::
224put_eof(error_code& ec)
225{
226    BOOST_ASSERT(got_some());
227    if( state_ == state::start_line ||
228        state_ == state::fields)
229    {
230        ec = error::partial_message;
231        return;
232    }
233    if(f_ & (flagContentLength | flagChunked))
234    {
235        if(state_ != state::complete)
236        {
237            ec = error::partial_message;
238            return;
239        }
240        ec = {};
241        return;
242    }
243    ec = {};
244    this->on_finish_impl(ec);
245    if(ec)
246        return;
247    state_ = state::complete;
248}
249
250template<bool isRequest>
251void
252basic_parser<isRequest>::
253maybe_need_more(
254    char const* p, std::size_t n,
255        error_code& ec)
256{
257    if(skip_ == 0)
258        return;
259    if( n > header_limit_)
260        n = header_limit_;
261    if(n < skip_ + 4)
262    {
263        ec = error::need_more;
264        return;
265    }
266    auto const term =
267        find_eom(p + skip_, p + n);
268    if(! term)
269    {
270        skip_ = n - 3;
271        if(skip_ + 4 > header_limit_)
272        {
273            ec = error::header_limit;
274            return;
275        }
276        ec = error::need_more;
277        return;
278    }
279    skip_ = 0;
280}
281
282template<bool isRequest>
283void
284basic_parser<isRequest>::
285parse_start_line(
286    char const*& in, char const* last,
287    error_code& ec, std::true_type)
288{
289/*
290    request-line   = method SP request-target SP HTTP-version CRLF
291    method         = token
292*/
293    auto p = in;
294
295    string_view method;
296    parse_method(p, last, method, ec);
297    if(ec)
298        return;
299
300    string_view target;
301    parse_target(p, last, target, ec);
302    if(ec)
303        return;
304
305    int version = 0;
306    parse_version(p, last, version, ec);
307    if(ec)
308        return;
309    if(version < 10 || version > 11)
310    {
311        ec = error::bad_version;
312        return;
313    }
314
315    if(p + 2 > last)
316    {
317        ec = error::need_more;
318        return;
319    }
320    if(p[0] != '\r' || p[1] != '\n')
321    {
322        ec = error::bad_version;
323        return;
324    }
325    p += 2;
326
327    if(version >= 11)
328        f_ |= flagHTTP11;
329
330    this->on_request_impl(string_to_verb(method),
331        method, target, version, ec);
332    if(ec)
333        return;
334
335    in = p;
336    state_ = state::fields;
337}
338
339template<bool isRequest>
340void
341basic_parser<isRequest>::
342parse_start_line(
343    char const*& in, char const* last,
344    error_code& ec, std::false_type)
345{
346/*
347     status-line    = HTTP-version SP status-code SP reason-phrase CRLF
348     status-code    = 3*DIGIT
349     reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
350*/
351    auto p = in;
352
353    int version = 0;
354    parse_version(p, last, version, ec);
355    if(ec)
356        return;
357    if(version < 10 || version > 11)
358    {
359        ec = error::bad_version;
360        return;
361    }
362
363    // SP
364    if(p + 1 > last)
365    {
366        ec = error::need_more;
367        return;
368    }
369    if(*p++ != ' ')
370    {
371        ec = error::bad_version;
372        return;
373    }
374
375    parse_status(p, last, status_, ec);
376    if(ec)
377        return;
378
379    // parse reason CRLF
380    string_view reason;
381    parse_reason(p, last, reason, ec);
382    if(ec)
383        return;
384
385    if(version >= 11)
386        f_ |= flagHTTP11;
387
388    this->on_response_impl(
389        status_, reason, version, ec);
390    if(ec)
391        return;
392
393    in = p;
394    state_ = state::fields;
395}
396
397template<bool isRequest>
398void
399basic_parser<isRequest>::
400parse_fields(char const*& in,
401    char const* last, error_code& ec)
402{
403    string_view name;
404    string_view value;
405    // https://stackoverflow.com/questions/686217/maximum-on-http-header-values
406    beast::detail::char_buffer<max_obs_fold> buf;
407    auto p = in;
408    for(;;)
409    {
410        if(p + 2 > last)
411        {
412            ec = error::need_more;
413            return;
414        }
415        if(p[0] == '\r')
416        {
417            if(p[1] != '\n')
418                ec = error::bad_line_ending;
419            in = p + 2;
420            return;
421        }
422        parse_field(p, last, name, value, buf, ec);
423        if(ec)
424            return;
425        auto const f = string_to_field(name);
426        do_field(f, value, ec);
427        if(ec)
428            return;
429        this->on_field_impl(f, name, value, ec);
430        if(ec)
431            return;
432        in = p;
433    }
434}
435
436template<bool isRequest>
437void
438basic_parser<isRequest>::
439finish_header(error_code& ec, std::true_type)
440{
441    // RFC 7230 section 3.3
442    // https://tools.ietf.org/html/rfc7230#section-3.3
443
444    if(f_ & flagSkipBody)
445    {
446        state_ = state::complete;
447    }
448    else if(f_ & flagContentLength)
449    {
450        if(len_ > body_limit_)
451        {
452            ec = error::body_limit;
453            return;
454        }
455        if(len_ > 0)
456        {
457            f_ |= flagHasBody;
458            state_ = state::body0;
459        }
460        else
461        {
462            state_ = state::complete;
463        }
464    }
465    else if(f_ & flagChunked)
466    {
467        f_ |= flagHasBody;
468        state_ = state::chunk_header0;
469    }
470    else
471    {
472        len_ = 0;
473        len0_ = 0;
474        state_ = state::complete;
475    }
476
477    ec = {};
478    this->on_header_impl(ec);
479    if(ec)
480        return;
481    if(state_ == state::complete)
482    {
483        this->on_finish_impl(ec);
484        if(ec)
485            return;
486    }
487}
488
489template<bool isRequest>
490void
491basic_parser<isRequest>::
492finish_header(error_code& ec, std::false_type)
493{
494    // RFC 7230 section 3.3
495    // https://tools.ietf.org/html/rfc7230#section-3.3
496
497    if( (f_ & flagSkipBody) ||  // e.g. response to a HEAD request
498        status_  / 100 == 1 ||   // 1xx e.g. Continue
499        status_ == 204 ||        // No Content
500        status_ == 304)          // Not Modified
501    {
502        // VFALCO Content-Length may be present, but we
503        //        treat the message as not having a body.
504        //        https://github.com/boostorg/beast/issues/692
505        state_ = state::complete;
506    }
507    else if(f_ & flagContentLength)
508    {
509        if(len_ > 0)
510        {
511            f_ |= flagHasBody;
512            state_ = state::body0;
513
514            if(len_ > body_limit_)
515            {
516                ec = error::body_limit;
517                return;
518            }
519        }
520        else
521        {
522            state_ = state::complete;
523        }
524    }
525    else if(f_ & flagChunked)
526    {
527        f_ |= flagHasBody;
528        state_ = state::chunk_header0;
529    }
530    else
531    {
532        f_ |= flagHasBody;
533        f_ |= flagNeedEOF;
534        state_ = state::body_to_eof0;
535    }
536
537    ec = {};
538    this->on_header_impl(ec);
539    if(ec)
540        return;
541    if(state_ == state::complete)
542    {
543        this->on_finish_impl(ec);
544        if(ec)
545            return;
546    }
547}
548
549template<bool isRequest>
550void
551basic_parser<isRequest>::
552parse_body(char const*& p,
553    std::size_t n, error_code& ec)
554{
555    ec = {};
556    n = this->on_body_impl(string_view{p,
557        beast::detail::clamp(len_, n)}, ec);
558    p += n;
559    len_ -= n;
560    if(ec)
561        return;
562    if(len_ > 0)
563        return;
564    this->on_finish_impl(ec);
565    if(ec)
566        return;
567    state_ = state::complete;
568}
569
570template<bool isRequest>
571void
572basic_parser<isRequest>::
573parse_body_to_eof(char const*& p,
574    std::size_t n, error_code& ec)
575{
576    if(body_limit_.has_value())
577    {
578        if (n > *body_limit_)
579        {
580            ec = error::body_limit;
581            return;
582        }
583        *body_limit_ -= n;
584    }
585    ec = {};
586    n = this->on_body_impl(string_view{p, n}, ec);
587    p += n;
588    if(ec)
589        return;
590}
591
592template<bool isRequest>
593void
594basic_parser<isRequest>::
595parse_chunk_header(char const*& p0,
596    std::size_t n, error_code& ec)
597{
598/*
599    chunked-body   = *chunk last-chunk trailer-part CRLF
600
601    chunk          = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
602    last-chunk     = 1*("0") [ chunk-ext ] CRLF
603    trailer-part   = *( header-field CRLF )
604
605    chunk-size     = 1*HEXDIG
606    chunk-data     = 1*OCTET ; a sequence of chunk-size octets
607    chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
608    chunk-ext-name = token
609    chunk-ext-val  = token / quoted-string
610*/
611
612    auto p = p0;
613    auto const pend = p + n;
614    char const* eol;
615
616    if(! (f_ & flagFinalChunk))
617    {
618        if(n < skip_ + 2)
619        {
620            ec = error::need_more;
621            return;
622        }
623        if(f_ & flagExpectCRLF)
624        {
625            // Treat the last CRLF in a chunk as
626            // part of the next chunk, so p can
627            // be parsed in one call instead of two.
628            if(! parse_crlf(p))
629            {
630                ec = error::bad_chunk;
631                return;
632            }
633        }
634        eol = find_eol(p0 + skip_, pend, ec);
635        if(ec)
636            return;
637        if(! eol)
638        {
639            ec = error::need_more;
640            skip_ = n - 1;
641            return;
642        }
643        skip_ = static_cast<
644            std::size_t>(eol - 2 - p0);
645
646        std::uint64_t size;
647        if(! parse_hex(p, size))
648        {
649            ec = error::bad_chunk;
650            return;
651        }
652        if(size != 0)
653        {
654            if (body_limit_.has_value())
655            {
656                if (size > *body_limit_)
657                {
658                    ec = error::body_limit;
659                    return;
660                }
661                *body_limit_ -= size;
662            }
663            auto const start = p;
664            parse_chunk_extensions(p, pend, ec);
665            if(ec)
666                return;
667            if(p != eol -2 )
668            {
669                ec = error::bad_chunk_extension;
670                return;
671            }
672            auto const ext = make_string(start, p);
673            this->on_chunk_header_impl(size, ext, ec);
674            if(ec)
675                return;
676            len_ = size;
677            skip_ = 2;
678            p0 = eol;
679            f_ |= flagExpectCRLF;
680            state_ = state::chunk_body;
681            return;
682        }
683
684        f_ |= flagFinalChunk;
685    }
686    else
687    {
688        BOOST_ASSERT(n >= 5);
689        if(f_ & flagExpectCRLF)
690            BOOST_VERIFY(parse_crlf(p));
691        std::uint64_t size;
692        BOOST_VERIFY(parse_hex(p, size));
693        eol = find_eol(p, pend, ec);
694        BOOST_ASSERT(! ec);
695    }
696
697    auto eom = find_eom(p0 + skip_, pend);
698    if(! eom)
699    {
700        BOOST_ASSERT(n >= 3);
701        skip_ = n - 3;
702        ec = error::need_more;
703        return;
704    }
705
706    auto const start = p;
707    parse_chunk_extensions(p, pend, ec);
708    if(ec)
709        return;
710    if(p != eol - 2)
711    {
712        ec = error::bad_chunk_extension;
713        return;
714    }
715    auto const ext = make_string(start, p);
716    this->on_chunk_header_impl(0, ext, ec);
717    if(ec)
718        return;
719    p = eol;
720    parse_fields(p, eom, ec);
721    if(ec)
722        return;
723    BOOST_ASSERT(p == eom);
724    p0 = eom;
725
726    this->on_finish_impl(ec);
727    if(ec)
728        return;
729    state_ = state::complete;
730}
731
732template<bool isRequest>
733void
734basic_parser<isRequest>::
735parse_chunk_body(char const*& p,
736    std::size_t n, error_code& ec)
737{
738    ec = {};
739    n = this->on_chunk_body_impl(
740        len_, string_view{p,
741            beast::detail::clamp(len_, n)}, ec);
742    p += n;
743    len_ -= n;
744    if(len_ == 0)
745        state_ = state::chunk_header;
746}
747
748template<bool isRequest>
749void
750basic_parser<isRequest>::
751do_field(field f,
752    string_view value, error_code& ec)
753{
754    using namespace beast::detail::string_literals;
755    // Connection
756    if(f == field::connection ||
757        f == field::proxy_connection)
758    {
759        auto const list = opt_token_list{value};
760        if(! validate_list(list))
761        {
762            // VFALCO Should this be a field specific error?
763            ec = error::bad_value;
764            return;
765        }
766        for(auto const& s : list)
767        {
768            if(beast::iequals("close"_sv, s))
769            {
770                f_ |= flagConnectionClose;
771                continue;
772            }
773
774            if(beast::iequals("keep-alive"_sv, s))
775            {
776                f_ |= flagConnectionKeepAlive;
777                continue;
778            }
779
780            if(beast::iequals("upgrade"_sv, s))
781            {
782                f_ |= flagConnectionUpgrade;
783                continue;
784            }
785        }
786        ec = {};
787        return;
788    }
789
790    // Content-Length
791    if(f == field::content_length)
792    {
793        auto bad_content_length = [&ec]
794        {
795            ec = error::bad_content_length;
796        };
797
798        // conflicting field
799        if(f_ & flagChunked)
800            return bad_content_length();
801
802        // Content-length may be a comma-separated list of integers
803        auto tokens_unprocessed = 1 +
804            std::count(value.begin(), value.end(), ',');
805        auto tokens = opt_token_list(value);
806        if (tokens.begin() == tokens.end() ||
807            !validate_list(tokens))
808                return bad_content_length();
809
810        auto existing = this->content_length_unchecked();
811        for (auto tok : tokens)
812        {
813            std::uint64_t v;
814            if (!parse_dec(tok, v))
815                return bad_content_length();
816            --tokens_unprocessed;
817            if (existing.has_value())
818            {
819                if (v != *existing)
820                    return bad_content_length();
821            }
822            else
823            {
824                existing = v;
825            }
826        }
827
828        if (tokens_unprocessed)
829            return bad_content_length();
830
831        BOOST_ASSERT(existing.has_value());
832        ec = {};
833        len_ = *existing;
834        len0_ = *existing;
835        f_ |= flagContentLength;
836        return;
837    }
838
839    // Transfer-Encoding
840    if(f == field::transfer_encoding)
841    {
842        if(f_ & flagChunked)
843        {
844            // duplicate
845            ec = error::bad_transfer_encoding;
846            return;
847        }
848
849        if(f_ & flagContentLength)
850        {
851            // conflicting field
852            ec = error::bad_transfer_encoding;
853            return;
854        }
855
856        ec = {};
857        auto const v = token_list{value};
858        auto const p = std::find_if(v.begin(), v.end(),
859            [&](string_view const& s)
860            {
861                return beast::iequals("chunked"_sv, s);
862            });
863        if(p == v.end())
864            return;
865        if(std::next(p) != v.end())
866            return;
867        len_ = 0;
868        f_ |= flagChunked;
869        return;
870    }
871
872    // Upgrade
873    if(f == field::upgrade)
874    {
875        ec = {};
876        f_ |= flagUpgrade;
877        return;
878    }
879
880    ec = {};
881}
882
883#ifdef BOOST_BEAST_SOURCE
884template class http::basic_parser<true>;
885template class http::basic_parser<false>;
886#endif
887
888} // http
889} // beast
890} // boost
891
892#endif
893