1 /*  $Id: http_server_transport.cpp 629837 2021-04-22 12:47:49Z ivanov $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Dmitri Dmitrienko
27  *
28  * File Description:
29  *
30  */
31 
32 #include <ncbi_pch.hpp>
33 
34 #include <vector>
35 
36 #include <connect/ext/ncbi_localnet.h>
37 
38 #include "pending_operation.hpp"
39 #include "http_server_transport.hpp"
40 #include "pubseq_gateway.hpp"
41 
42 #include <h2o.h>
43 
44 
45 using namespace std;
46 
47 
48 #define HTTP_RAW_PARAM_BUF_SIZE 8192
49 
50 
s_HttpUrlDecode(const char * what,size_t len,char * buf,size_t buf_size,size_t * result_len)51 static bool s_HttpUrlDecode(const char *  what, size_t  len, char *  buf,
52                             size_t  buf_size, size_t *  result_len)
53 {
54     const char *        pch = what;
55     const char *        end = what + len;
56     char *              dest = buf;
57     char *              dest_end = buf + buf_size;
58 
59     while (pch < end) {
60         char                ch = *pch;
61         unsigned char       v;
62         switch (ch) {
63             case '%':
64                 ++pch;
65                 if (pch >= end - 1 || dest >= dest_end)
66                     return false;
67                 ch = *pch;
68                 // To avoid CLang static analizer report on dead assignment
69                 // v = 0;
70                 if (ch >= '0' && ch <= '9')
71                     v = (ch - '0') * 16;
72                 else if (ch >= 'A' && ch <= 'F')
73                     v = (ch - 'A' + 10) * 16;
74                 else if (ch >= 'a' && ch <= 'f')
75                     v = (ch - 'a' + 10) * 16;
76                 else
77                     return false;
78                 ++pch;
79                 ch = *pch;
80                 if (ch >= '0' && ch <= '9')
81                     v += (ch - '0');
82                 else if (ch >= 'A' && ch <= 'F')
83                     v += (ch - 'A' + 10);
84                 else if (ch >= 'a' && ch <= 'f')
85                     v += (ch - 'a' + 10);
86                 else
87                     return false;
88                 *dest = v;
89                 ++dest;
90                 break;
91             case '+':
92                 if (dest >= dest_end)
93                     return false;
94                 *dest = ' ';
95                 ++dest;
96                 break;
97             default:
98                 if (dest >= dest_end)
99                     return false;
100                 *dest = ch;
101                 ++dest;
102         }
103         ++pch;
104     }
105     *result_len = dest - buf;
106     if (dest < dest_end)
107         *dest = 0;
108     return true;
109 }
110 
111 
112 /** CHttpGetParser */
113 
Parse(CHttpRequest & Req,char * Data,size_t Length) const114 void CHttpGetParser::Parse(CHttpRequest &  Req, char *  Data,
115                            size_t  Length) const
116 {
117     bool            has_encodings;
118     char *          buf = nullptr;
119     ssize_t         bufsize = 0;
120 
121     Req.GetRawBuffer(&buf, &bufsize);
122     bufsize--;
123 
124     char *          begin = Data;
125     const char *    end = Data + Length;
126 
127     if (*begin == '?')
128         begin++;
129 
130     char *          ch = begin;
131     bool            is_name = true;
132 
133     CQueryParam *   prm = nullptr;
134     while (ch < end) {
135         const char *    c_begin = ch;
136         has_encodings = false;
137     // NAME
138         while (ch < end) {
139             switch (*ch) {
140                 case '%':
141                 case '+':
142                     has_encodings = true;
143                     break;
144                 case '=':
145                     if (is_name)
146                         goto done;
147                     break;
148                 case '&':
149                     goto done;
150             }
151             ch++;
152         }
153     done:
154         if (is_name)
155             prm = Req.AddParam();
156         if (prm == nullptr)
157             break;
158 
159         if (!has_encodings) {
160             *ch = '\0';
161             if (is_name) {
162                 prm->m_Name = c_begin;
163                 prm->m_NameLen = ch - c_begin;
164                 prm->m_ValLen = 0;
165                 prm->m_Val = nullptr;
166             }
167             else {
168                 prm->m_Val = c_begin;
169                 prm->m_ValLen = ch - c_begin;
170             }
171         } else {
172             size_t      len;
173             if (!s_HttpUrlDecode(c_begin, ch - c_begin, buf, bufsize, &len))
174                 return;
175             if (is_name) {
176                 prm->m_Name = buf;
177                 prm->m_NameLen = len;
178                 prm->m_ValLen = 0;
179                 prm->m_Val = nullptr;
180             }
181             else {
182                 prm->m_Val = buf;
183                 prm->m_ValLen = len;
184             }
185 
186             buf += len;
187             bufsize -= len;
188             if (bufsize > 0) {
189                 *buf = '\0';
190                 ++buf;
191                 --bufsize;
192             }
193             else {
194                 Req.RevokeParam();
195                 return;
196             }
197         }
198         is_name = !is_name;
199         ++ch;
200     }
201 }
202 
203 
204 /** CHttpRequest */
205 
ParseParams(void)206 void CHttpRequest::ParseParams(void)
207 {
208     m_ParamParsed = true;
209     m_ParamCount = 0;
210 
211     if (h2o_memis(m_Req->method.base, m_Req->method.len,
212                   H2O_STRLIT("POST")) && m_PostParser) {
213         ssize_t     cursor = h2o_find_header(&m_Req->headers,
214                                              H2O_TOKEN_CONTENT_TYPE, -1);
215         if (cursor == -1)
216             return;
217 
218         if (m_PostParser->Supports(m_Req->headers.entries[cursor].value.base,
219                                     m_Req->headers.entries[cursor].value.len))
220             m_PostParser->Parse(*this, m_Req->entity.base, m_Req->entity.len);
221     } else if (h2o_memis(m_Req->method.base, m_Req->method.len,
222                          H2O_STRLIT("GET")) && m_GetParser) {
223         if (m_Req->query_at == SIZE_MAX || m_Req->query_at >= m_Req->path.len)
224             return;
225 
226         char *          begin = &m_Req->path.base[m_Req->query_at];
227         const char *    end = &m_Req->path.base[m_Req->path.len];
228         m_GetParser->Parse(*this, begin, end - begin);
229     }
230 }
231 
232 
GetParam(const char * name,size_t len,bool required,const char ** value,size_t * value_len)233 bool CHttpRequest::GetParam(const char *  name, size_t  len, bool  required,
234                             const char **  value, size_t *  value_len)
235 {
236     if (!m_ParamParsed)
237         ParseParams();
238 
239     for (size_t i = 0; i < m_ParamCount; ++i) {
240         if (m_Params[i].m_NameLen == len &&
241             memcmp(m_Params[i].m_Name, name, len) == 0) {
242             *value = m_Params[i].m_Val;
243             if (value_len)
244                 *value_len = m_Params[i].m_ValLen;
245             return true;
246         }
247     }
248     *value = nullptr;
249     if (value_len)
250         *value_len = 0;
251 
252     return !required;
253 }
254 
255 
GetMultipleValuesParam(const char * name,size_t len,vector<string> & values)256 bool CHttpRequest::GetMultipleValuesParam(const char *  name, size_t  len,
257                                           vector<string> &  values)
258 {
259     if (!m_ParamParsed)
260         ParseParams();
261 
262     bool        found = false;
263     for (size_t i = 0; i < m_ParamCount; ++i) {
264         if (m_Params[i].m_NameLen == len &&
265             memcmp(m_Params[i].m_Name, name, len) == 0) {
266             values.push_back(string(m_Params[i].m_Val,
267                                     m_Params[i].m_ValLen));
268             found = true;
269         }
270     }
271     return found;
272 }
273 
274 
PrintParams(CDiagContext_Extra & extra)275 CDiagContext_Extra &  CHttpRequest::PrintParams(CDiagContext_Extra &  extra)
276 {
277     if (!m_ParamParsed)
278         ParseParams();
279 
280     for (size_t i = 0; i < m_ParamCount; i++) {
281         extra.Print(string(m_Params[i].m_Name, m_Params[i].m_NameLen),
282                     string(m_Params[i].m_Val, m_Params[i].m_ValLen));
283     }
284     return extra;
285 }
286 
287 
PrintLogFields(const CNcbiLogFields & log_fields)288 void  CHttpRequest::PrintLogFields(const CNcbiLogFields &  log_fields)
289 {
290     map<string, string>     env;
291 
292     for (size_t  index = 0; index < m_Req->headers.size; ++index) {
293         string      name(m_Req->headers.entries[index].name->base,
294                          m_Req->headers.entries[index].name->len);
295         NStr::ToLower(name);
296         NStr::ReplaceInPlace(name, "_", "-");
297         env[name] = string(m_Req->headers.entries[index].value.base,
298                            m_Req->headers.entries[index].value.len);
299     }
300 
301     log_fields.LogFields(env);
302 }
303 
304 
GetPath(void)305 string CHttpRequest::GetPath(void)
306 {
307     return string(m_Req->path_normalized.base,
308                   m_Req->path_normalized.len);
309 }
310 
311 
GetHeaderValue(const string & name)312 string CHttpRequest::GetHeaderValue(const string &  name)
313 {
314     string      value;
315     size_t      name_size = name.size();
316 
317     for (size_t  index = 0; index < m_Req->headers.size; ++index) {
318         if (m_Req->headers.entries[index].name->len == name_size) {
319             if (strncasecmp(m_Req->headers.entries[index].name->base,
320                             name.data(), name_size) == 0) {
321                 value.assign(m_Req->headers.entries[index].value.base,
322                              m_Req->headers.entries[index].value.len);
323                 break;
324             }
325         }
326     }
327     return value;
328 }
329 
330 
331 // The method to extract the IP address needs an array of pointers to the
332 // strings like <name>=<value>. The h2o headers are stored in a different
333 // format so a conversion is required
GetClientIP(void)334 TNCBI_IPv6Addr CHttpRequest::GetClientIP(void)
335 {
336     // "There are no headers larger than 50k", so
337     const size_t        buf_size = 50 * 1024;
338 
339     const char *        tracking_env[m_Req->headers.size + 1];
340     unique_ptr<char[]>  buffer(new char[buf_size]);
341     char *              raw_buffer = buffer.get();
342 
343     size_t              pos = 0;
344     for (size_t  index = 0; index < m_Req->headers.size; ++index) {
345         size_t      name_val_size = m_Req->headers.entries[index].name->len +
346                                     m_Req->headers.entries[index].value.len +
347                                     2;  // '=' and '\0'
348         if (pos + name_val_size > buf_size) {
349             PSG_WARNING("The buffer for request headers is too small (" <<
350                         buf_size << " bytes)");
351             return TNCBI_IPv6Addr{0};
352         }
353 
354         tracking_env[index] = raw_buffer + pos;
355         memcpy(raw_buffer + pos, m_Req->headers.entries[index].name->base,
356                m_Req->headers.entries[index].name->len);
357         pos += m_Req->headers.entries[index].name->len;
358         raw_buffer[pos] = '=';
359         ++pos;
360         memcpy(raw_buffer + pos, m_Req->headers.entries[index].value.base,
361                m_Req->headers.entries[index].value.len);
362         pos += m_Req->headers.entries[index].value.len;
363         raw_buffer[pos] = '\0';
364         ++pos;
365     }
366     tracking_env[m_Req->headers.size] = nullptr;
367 
368     return NcbiGetCgiClientIPv6(eCgiClientIP_TryMost, tracking_env);
369 }
370 
371 
GetPeerIP(void)372 string CHttpRequest::GetPeerIP(void)
373 {
374     struct sockaddr     sock_addr;
375     if (m_Req->conn->callbacks->get_peername(m_Req->conn, &sock_addr) == 0)
376         return kEmptyStr;
377 
378     char                buf[256];
379     switch (sock_addr.sa_family) {
380         case AF_INET:
381             if (inet_ntop(AF_INET,
382                           &(((struct sockaddr_in *)&sock_addr)->sin_addr),
383                           buf, 256) == NULL)
384                 return kEmptyStr;
385             break;
386         case AF_INET6:
387             if (inet_ntop(AF_INET6,
388                           &(((struct sockaddr_in6 *)&sock_addr)->sin6_addr),
389                           buf, 256) == NULL)
390                 return kEmptyStr;
391             break;
392         default:
393             return kEmptyStr;
394     }
395 
396     return buf;
397 }
398 
399 
GetSSLSettings(bool & enabled,string & cert_file,string & key_file,string & ciphers)400 void GetSSLSettings(bool &  enabled,
401                     string &  cert_file,
402                     string &  key_file,
403                     string &  ciphers)
404 {
405     auto *  app = CPubseqGatewayApp::GetInstance();
406     enabled = app->GetSSLEnable();
407     cert_file = app->GetSSLCertFile();
408     key_file = app->GetSSLKeyFile();
409     ciphers = app->GetSSLCiphers();
410 }
411 
412