root/http/request.cc

Revision 19a2630635324aeed23f74061133ba433a639cd4, 10.6 KB (checked in by Antti-Juhani Kaijanaho <ajk@…>, 21 months ago)

[http::request] Handle POST form Content-Types with parameters

Signed-off-by: Antti-Juhani Kaijanaho <ajk@…>

  • Property mode set to 100644
Line 
1/*  This file is part of Alue, the multiprotocol Internet discussion daemon
2
3    Copyright © 2009, 2010 Antti-Juhani Kaijanaho
4
5    Alue is free software: you can redistribute it and/or modify it
6    under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    Alue is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with Alue.  If not, see <http://www.gnu.org/licenses/>.
17
18 */
19
20#include "bad_request.hh"
21#include "request.hh"
22#include "session.hh"
23#include "unsupported_version.hh"
24
25#include "../logger/logline.hh"
26#include "../util.hh"
27
28#include <boost/algorithm/string/case_conv.hpp>
29#include <boost/format.hpp>
30#include <boost/lexical_cast.hpp>
31
32#include <iostream>
33
34#include "../assert.hh"
35
36namespace
37{
38        bool is_token_char(char c)
39        {
40                return  c != '('  && c != ')' && c != '<' && c != '>' &&
41                        c != '@'  && c != ',' && c != ';' && c != ':' &&
42                        c != '\\' && c != '"' && c != '/' && c != '[' &&
43                        c != '?'  && c != '=' && c != '{' && c != '}' &&
44                        c != '\t' && c != ' ' &&
45                        31 < c && c < 127;
46        }
47        bool is_wsp(char c) { return c == ' ' || c == '\t'; }
48}
49
50namespace http
51{
52        request::request(std::string str, server::conn_cb cb,
53                         boost::asio::ip::address peer)
54                : cb(cb), peer(peer), v11(true), close(false)
55                , form_data_ready(false)
56        {
57                typedef bad_request fv;
58
59                size_t lend = str.find("\r\n");
60
61                parse_header(str.substr(lend+2));
62               
63                size_t sp1 = str.find(' ');
64                size_t sp2 = str.find(' ', sp1+1);
65
66                if (sp1 >= lend || sp2 == lend)
67                        throw fv("malformed request line");
68
69                method = str.substr(0, sp1);
70
71                if (! (sp1 + 1 == sp2 - 1 && str[sp1+1] == '*'))
72                        uri = str.substr(sp1+1, sp2 - sp1-1);
73
74                std::string ver = str.substr(sp2+1, lend-sp2-1);
75
76                boost::to_upper(method);
77                boost::to_upper(ver);
78
79                size_t dot = ver.find('.', 5);
80                if (ver.length() < 5 || ver.substr(0,5) != "HTTP/" ||
81                    dot == std::string::npos)
82                        throw fv("malformed HTTP-version %" + ver);
83
84                if (boost::lexical_cast<unsigned>(ver.substr(5, dot-5)) != 1)
85                        throw unsupported_version();
86
87                if (boost::lexical_cast<unsigned>(ver.substr(dot+1)) == 0)
88                        v11 = false;
89
90                if (v11 && header.find("host") == header.end())
91                        throw fv("missing Host header");
92
93                if (uri.get_host().empty())
94                {
95                        std::string hostport = header["host"];
96                        host = util::split1R(hostport, ":");
97                        if (hostport.empty())
98                                port = -1;
99                        else
100                                port = boost::lexical_cast<int>(hostport);
101                }
102
103                std::string cs = header["connection"];
104                while (!cs.empty())
105                {
106                        std::string token = util::split(cs, ", \t");
107                        boost::to_lower(token);
108                        if (!v11) header.erase(token);
109                        if (v11 && token == "close") close = true;
110                }
111
112        }
113
114        void request::parse_form() const
115        {
116                if (form_data_ready) return;
117                std::string ct = get_header("content-type");
118                size_t sc = ct.find(';');
119                if (sc != std::string::npos) ct.erase(sc);
120                if (method != "POST" || ct == "application/x-www-form-urlencoded")
121                {
122                        std::string bd = method == "POST"
123                                ? body
124                                : uri.get_query();
125                        while (!bd.empty())
126                        {
127                                std::string entry = util::split1R(bd, "&");
128                                for (size_t i = 0; i < entry.length(); i++)
129                                        if (entry[i] == '+') entry[i] = ' ';
130                                std::string name = util::split1R(entry, "=");
131                                name = uri::percent_decode(name);
132                                entry = uri::percent_decode(entry);
133                                form_data.insert(std::make_pair(name, entry));
134                        }
135                }
136                form_data_ready = true;
137        }
138
139        std::string request::get_form_as_query() const
140        {
141                if (!form_data_ready) parse_form();
142                std::string rv;
143                for (std::multimap<std::string, std::string>::const_iterator it
144                             = form_data.begin();
145                     it != form_data.end(); it++)
146                {
147                        std::string name = uri::percent_encode(it->first);
148                        std::string value = uri::percent_encode(it->second);
149                        if (!rv.empty()) rv += '&';
150                        rv += name;
151                        rv += "=";
152                        rv += value;
153                }
154                return rv;
155        }
156
157        std::string request::get_header(std::string s) const
158        {
159                boost::to_lower(s);
160                std::map<std::string, std::string>::const_iterator it
161                        = header.find(s);
162                if (it == header.end()) return "";
163                return it->second;
164        }
165
166        void request::parse_header(std::string str)
167        {
168                typedef bad_request fv;
169
170                size_t inx = 0;
171                std::string field_name;
172                std::string field_body;
173
174        FIELD_NAME:
175                if (inx >= str.length())
176                        throw fv("request ends in the middle of a header name");
177                if (is_token_char(str[inx]))
178                {
179                        field_name += str[inx];
180                        inx++;
181                        goto FIELD_NAME;
182                }
183                if (str[inx] != ':') {
184                        throw fv(boost::str(boost::format
185                                            ("invalid character (0x%02x) "
186                                             "in header field name")
187                                            % (unsigned)str[inx]));
188                }
189                inx++;
190                goto FIELD_BODY;
191
192        FIELD_BODY:
193                while (inx < str.length() && is_wsp(str[inx])) inx++;
194                goto FIELD_BODY_INLINE;
195
196        FIELD_BODY_INLINE:
197                if (inx >= str.length())
198                        throw fv("header field '" + field_name + "' "
199                                 "terminates abruptly");
200                if (str[inx] != '\r')
201                {
202                        field_body += str[inx];
203                        inx++;
204                        goto FIELD_BODY_INLINE;
205                }
206                assert(inx  < str.length());
207                assert(str[inx] == '\r');
208                inx++;
209                if (inx >= str.length() || str[inx] != '\n')
210                        throw fv("header field '" + field_name + "' "
211                                 "contains a stray CR");
212                assert(1 <= inx);
213                assert(inx < str.length());
214                assert(str[inx-1] == '\r');
215                assert(str[inx-0] == '\n');
216                inx++;
217                if (inx < str.length() && is_wsp(str[inx]))
218                {
219                        // folded header field
220                        goto FIELD_BODY;
221                }
222                // header field ends
223                boost::to_lower(field_name);
224                header[field_name] = (header[field_name] != ""
225                                      ? header[field_name] + ", "
226                                      : "") + field_body;
227                if (inx + 1 < str.length() &&
228                    str[inx+0] == '\r' && str[inx+1] == '\n')
229                {
230                        inx += 2;
231                        assert(inx == str.length());
232                        return;
233                }
234                field_name.clear();
235                field_body.clear();
236                goto FIELD_NAME;
237        }
238
239
240        std::string request::get_sessid() const
241        {
242                std::string cookie = get_header("Cookie");
243
244                std::string sessid;
245                while (!cookie.empty())
246                {
247                        // FIXME too simplistic parsing
248                        std::string item = util::split1R(cookie, ";,");
249                        std::string name = util::split1L(item, "=");
250                        if (name == "ALUE_SESSION") sessid = item;
251                }
252                if (sessid.empty() || !cb.get_session(sessid)) return "";
253                std::string fd_session = get_form_field("alue_sessid");
254                if (method != "POST" && !fd_session.empty())
255                {
256                        logger::logline ll;
257                        ll << "expiring session due to exposure";
258                        cb.get_session(fd_session).reset();
259                }
260                if (method == "POST" && fd_session != sessid)
261                {
262                        logger::logline ll;
263                        ll << "POST and sessid not in query data - "
264                           << "expiring session";
265                        cb.get_session(sessid).reset();
266                        return "";
267                }
268                return sessid;
269        }
270
271        boost::shared_ptr<session> &request::get_session() const
272        {
273                return cb.get_session(get_sessid());
274        }
275
276        bool request::is_authenticated() const
277        {
278                boost::shared_ptr<session> sess = get_session();
279                return sess && sess->is_authenticated();
280        }
281
282        boost::shared_ptr<db::user> request::get_user() const
283        {
284                boost::shared_ptr<session> sess = get_session();
285                if (!sess || !sess->is_authenticated())
286                        return boost::shared_ptr<db::user>();
287                return sess->get_user();
288        }
289
290}
Note: See TracBrowser for help on using the browser.