• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // request_parser.cpp
3 // ~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2021 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6 //
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
9 //
10 
11 #include "request_parser.hpp"
12 #include <algorithm>
13 #include <cctype>
14 #include <boost/lexical_cast.hpp>
15 #include "request.hpp"
16 
17 namespace http {
18 namespace server4 {
19 
20 // Enable the pseudo-keywords reenter, yield and fork.
21 #include <boost/asio/yield.hpp>
22 
23 std::string request_parser::content_length_name_ = "Content-Length";
24 
consume(request & req,char c)25 boost::tribool request_parser::consume(request& req, char c)
26 {
27   reenter (this)
28   {
29     req.method.clear();
30     req.uri.clear();
31     req.http_version_major = 0;
32     req.http_version_minor = 0;
33     req.headers.clear();
34     req.content.clear();
35     content_length_ = 0;
36 
37     // Request method.
38     while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ' ')
39     {
40       req.method.push_back(c);
41       yield return boost::indeterminate;
42     }
43     if (req.method.empty())
44       return false;
45 
46     // Space.
47     if (c != ' ') return false;
48     yield return boost::indeterminate;
49 
50     // URI.
51     while (!is_ctl(c) && c != ' ')
52     {
53       req.uri.push_back(c);
54       yield return boost::indeterminate;
55     }
56     if (req.uri.empty()) return false;
57 
58     // Space.
59     if (c != ' ') return false;
60     yield return boost::indeterminate;
61 
62     // HTTP protocol identifier.
63     if (c != 'H') return false;
64     yield return boost::indeterminate;
65     if (c != 'T') return false;
66     yield return boost::indeterminate;
67     if (c != 'T') return false;
68     yield return boost::indeterminate;
69     if (c != 'P') return false;
70     yield return boost::indeterminate;
71 
72     // Slash.
73     if (c != '/') return false;
74     yield return boost::indeterminate;
75 
76     // Major version number.
77     if (!is_digit(c)) return false;
78     while (is_digit(c))
79     {
80       req.http_version_major = req.http_version_major * 10 + c - '0';
81       yield return boost::indeterminate;
82     }
83 
84     // Dot.
85     if (c != '.') return false;
86     yield return boost::indeterminate;
87 
88     // Minor version number.
89     if (!is_digit(c)) return false;
90     while (is_digit(c))
91     {
92       req.http_version_minor = req.http_version_minor * 10 + c - '0';
93       yield return boost::indeterminate;
94     }
95 
96     // CRLF.
97     if (c != '\r') return false;
98     yield return boost::indeterminate;
99     if (c != '\n') return false;
100     yield return boost::indeterminate;
101 
102     // Headers.
103     while ((is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != '\r')
104         || (c == ' ' || c == '\t'))
105     {
106       if (c == ' ' || c == '\t')
107       {
108         // Leading whitespace. Must be continuation of previous header's value.
109         if (req.headers.empty()) return false;
110         while (c == ' ' || c == '\t')
111           yield return boost::indeterminate;
112       }
113       else
114       {
115         // Start the next header.
116         req.headers.push_back(header());
117 
118         // Header name.
119         while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ':')
120         {
121           req.headers.back().name.push_back(c);
122           yield return boost::indeterminate;
123         }
124 
125         // Colon and space separates the header name from the header value.
126         if (c != ':') return false;
127         yield return boost::indeterminate;
128         if (c != ' ') return false;
129         yield return boost::indeterminate;
130       }
131 
132       // Header value.
133       while (is_char(c) && !is_ctl(c) && c != '\r')
134       {
135         req.headers.back().value.push_back(c);
136         yield return boost::indeterminate;
137       }
138 
139       // CRLF.
140       if (c != '\r') return false;
141       yield return boost::indeterminate;
142       if (c != '\n') return false;
143       yield return boost::indeterminate;
144     }
145 
146     // CRLF.
147     if (c != '\r') return false;
148     yield return boost::indeterminate;
149     if (c != '\n') return false;
150 
151     // Check for optional Content-Length header.
152     for (std::size_t i = 0; i < req.headers.size(); ++i)
153     {
154       if (headers_equal(req.headers[i].name, content_length_name_))
155       {
156         try
157         {
158           content_length_ =
159             boost::lexical_cast<std::size_t>(req.headers[i].value);
160         }
161         catch (boost::bad_lexical_cast&)
162         {
163           return false;
164         }
165       }
166     }
167 
168     // Content.
169     while (req.content.size() < content_length_)
170     {
171       yield return boost::indeterminate;
172       req.content.push_back(c);
173     }
174   }
175 
176   return true;
177 }
178 
179 // Disable the pseudo-keywords reenter, yield and fork.
180 #include <boost/asio/unyield.hpp>
181 
is_char(int c)182 bool request_parser::is_char(int c)
183 {
184   return c >= 0 && c <= 127;
185 }
186 
is_ctl(int c)187 bool request_parser::is_ctl(int c)
188 {
189   return (c >= 0 && c <= 31) || (c == 127);
190 }
191 
is_tspecial(int c)192 bool request_parser::is_tspecial(int c)
193 {
194   switch (c)
195   {
196   case '(': case ')': case '<': case '>': case '@':
197   case ',': case ';': case ':': case '\\': case '"':
198   case '/': case '[': case ']': case '?': case '=':
199   case '{': case '}': case ' ': case '\t':
200     return true;
201   default:
202     return false;
203   }
204 }
205 
is_digit(int c)206 bool request_parser::is_digit(int c)
207 {
208   return c >= '0' && c <= '9';
209 }
210 
tolower_compare(char a,char b)211 bool request_parser::tolower_compare(char a, char b)
212 {
213   return std::tolower(a) == std::tolower(b);
214 }
215 
headers_equal(const std::string & a,const std::string & b)216 bool request_parser::headers_equal(const std::string& a, const std::string& b)
217 {
218   if (a.length() != b.length())
219     return false;
220 
221   return std::equal(a.begin(), a.end(), b.begin(),
222       &request_parser::tolower_compare);
223 }
224 
225 } // namespace server4
226 } // namespace http
227