1 /*  $Id: ncbicgi.cpp 633610 2021-06-22 17:38:13Z 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  * Author:  Denis Vakatov
27  *
28  * File Description:
29  *   NCBI C++ CGI API:
30  *      CCgiCookie    -- one CGI cookie
31  *      CCgiCookies   -- set of CGI cookies
32  *      CCgiRequest   -- full CGI request
33  */
34 
35 #include <ncbi_pch.hpp>
36 #include <corelib/ncbienv.hpp>
37 #include <corelib/ncbitime.hpp>
38 #include <corelib/ncbi_param.hpp>
39 #include <corelib/ncbiapp.hpp>
40 #include <corelib/ncbi_safe_static.hpp>
41 #include <corelib/request_ctx.hpp>
42 #include <corelib/ncbi_strings.h>
43 
44 #include <cgi/cgi_exception.hpp>
45 #include <cgi/ncbicgi.hpp>
46 #include <cgi/cgi_serial.hpp>
47 #include <cgi/cgi_session.hpp>
48 #include <cgi/error_codes.hpp>
49 #include <cgi/impl/cgi_entry_reader.hpp>
50 #include <util/checksum.hpp>
51 #include <util/ncbi_url.hpp>
52 
53 #include <algorithm>
54 
55 #include <stdio.h>
56 #include <time.h>
57 #ifdef HAVE_UNISTD_H
58 #  include <unistd.h>
59 #else
60 #  define STDIN_FILENO 0
61 #endif
62 
63 
64 #define NCBI_USE_ERRCODE_X   Cgi_API
65 
66 
67 BEGIN_NCBI_SCOPE
68 
69 
70 ///////////////////////////////////////////////////////
71 //  CCgiCookie::
72 //
73 
74 
75 // Severity level for cookie errors.
76 NCBI_PARAM_ENUM_DECL(EDiagSev, CGI, Cookie_Error_Severity);
NCBI_PARAM_ENUM_ARRAY(EDiagSev,CGI,Cookie_Error_Severity)77 NCBI_PARAM_ENUM_ARRAY(EDiagSev, CGI, Cookie_Error_Severity)
78 {
79     {"Info", eDiag_Info},
80     {"Warning", eDiag_Warning},
81     {"Error", eDiag_Error},
82     {"Critical", eDiag_Critical},
83     {"Fatal", eDiag_Fatal},
84     {"Trace", eDiag_Trace}
85 };
86 NCBI_PARAM_ENUM_DEF_EX(EDiagSev, CGI, Cookie_Error_Severity,
87                        eDiag_Error,
88                        eParam_NoThread, CGI_COOKIE_ERROR_SEVERITY);
89 typedef NCBI_PARAM_TYPE(CGI, Cookie_Error_Severity) TCookieErrorSeverity;
90 
91 
92 NCBI_PARAM_ENUM_DECL(CCgiCookie::ECookieEncoding, CGI, Cookie_Encoding);
NCBI_PARAM_ENUM_ARRAY(CCgiCookie::ECookieEncoding,CGI,Cookie_Encoding)93 NCBI_PARAM_ENUM_ARRAY(CCgiCookie::ECookieEncoding, CGI, Cookie_Encoding)
94 {
95     {"Url", CCgiCookie::eCookieEnc_Url},
96     {"Quote", CCgiCookie::eCookieEnc_Quote}
97 };
98 NCBI_PARAM_ENUM_DEF_EX(CCgiCookie::ECookieEncoding, CGI, Cookie_Encoding,
99                        CCgiCookie::eCookieEnc_Url,
100                        eParam_NoThread, CGI_COOKIE_ENCODING);
101 typedef NCBI_PARAM_TYPE(CGI, Cookie_Encoding) TCookieEncoding;
102 
103 
104 // Helper function for encoding cookie name/value
x_EncodeCookie(const string & str,EFieldType ftype,NStr::EUrlEncode flag)105 string CCgiCookie::x_EncodeCookie(const string& str,
106                                   EFieldType ftype,
107                                   NStr::EUrlEncode flag)
108 {
109     if (flag == NStr::eUrlEnc_SkipMarkChars) {
110         // Force encoding of comma.
111         flag = NStr::eUrlEnc_Cookie;
112     }
113     if (NStr::NeedsURLEncoding(str, flag)) {
114         switch (TCookieEncoding::GetDefault()) {
115         case eCookieEnc_Url:
116             return NStr::URLEncode(str, flag);
117         case eCookieEnc_Quote:
118             // don't encode names
119             if (ftype == eField_Name) {
120                 return str;
121             }
122             // escape quotes, quote the value
123             string esc = NStr::Replace(str, "\"", "\\\"");
124             return "\"" + esc + "\"";
125         }
126     }
127     return str;
128 }
129 
130 
131 // auxiliary zero "tm" struct
132 static const tm kZeroTime = { 0 };
133 
s_IsZeroTime(const tm & date)134 inline bool s_IsZeroTime(const tm& date)
135 {
136     return ::memcmp(&date, &kZeroTime, sizeof(tm)) == 0 ? true : false;
137 }
138 
139 
CCgiCookie(const CCgiCookie & cookie)140 CCgiCookie::CCgiCookie(const CCgiCookie& cookie)
141     : m_Name(cookie.m_Name),
142       m_Value(cookie.m_Value),
143       m_Domain(cookie.m_Domain),
144       m_Path(cookie.m_Path),
145       m_InvalidFlag(cookie.m_InvalidFlag)
146 {
147     m_Expires = cookie.m_Expires;
148     m_Secure  = cookie.m_Secure;
149     m_HttpOnly = cookie.m_HttpOnly;
150 }
151 
152 
CCgiCookie(const string & name,const string & value,const string & domain,const string & path)153 CCgiCookie::CCgiCookie(const string& name,   const string& value,
154                        const string& domain, const string& path)
155     : m_InvalidFlag(fValid)
156 {
157     if ( name.empty() ) {
158         NCBI_THROW2(CCgiCookieException, eValue, "Empty cookie name", 0);
159     }
160     m_Name = name;
161 
162     SetDomain(domain);
163     SetPath(path);
164     SetValue(value);
165     m_Expires = kZeroTime;
166     m_Secure = false;
167     m_HttpOnly = false;
168 }
169 
170 
Reset(void)171 void CCgiCookie::Reset(void)
172 {
173     m_Value.erase();
174     m_Domain.erase();
175     m_Path.erase();
176     m_Expires = kZeroTime;
177     m_Secure = false;
178     m_HttpOnly = false;
179     ResetInvalid(fInvalid_Any);
180 }
181 
182 
CopyAttributes(const CCgiCookie & cookie)183 void CCgiCookie::CopyAttributes(const CCgiCookie& cookie)
184 {
185     if (&cookie == this)
186         return;
187 
188     m_Value   = cookie.m_Value;
189     ResetInvalid(fInvalid_Value);
190     SetInvalid(cookie.IsInvalid() & fInvalid_Value);
191 
192     m_Domain  = cookie.m_Domain;
193     m_Path    = cookie.m_Path;
194     m_Expires = cookie.m_Expires;
195     m_Secure  = cookie.m_Secure;
196     m_HttpOnly = cookie.m_HttpOnly;
197 }
198 
199 
GetExpDate(void) const200 string CCgiCookie::GetExpDate(void) const
201 {
202     if ( s_IsZeroTime(m_Expires) )
203         return kEmptyStr;
204 
205     char str[30];
206     if ( !::strftime(str, sizeof(str),
207                      "%a, %d %b %Y %H:%M:%S GMT", &m_Expires) ) {
208         NCBI_THROW(CCgiErrnoException, eErrno,
209                    "CCgiCookie::GetExpDate() -- strftime() failed");
210     }
211     return string(str);
212 }
213 
214 
GetExpDate(tm * exp_date) const215 bool CCgiCookie::GetExpDate(tm* exp_date) const
216 {
217     if ( !exp_date )
218         NCBI_THROW(CCgiException, eUnknown, "Null cookie exp.date passed");
219     if ( s_IsZeroTime(m_Expires) )
220         return false;
221     *exp_date = m_Expires;
222     return true;
223 }
224 
225 
Write(CNcbiOstream & os,EWriteMethod wmethod,EUrlEncode flag) const226 CNcbiOstream& CCgiCookie::Write(CNcbiOstream& os,
227                                 EWriteMethod wmethod,
228                                 EUrlEncode   flag) const
229 {
230     // Check if name and value are valid
231     if ((m_InvalidFlag & fInvalid_Name) != 0) {
232         NCBI_THROW2(CCgiCookieException, eValue,
233                     "Banned symbol in the cookie's name: "
234                     + NStr::PrintableString(m_Name), 0);
235     }
236     if ((m_InvalidFlag & fInvalid_Value) != 0) {
237         NCBI_THROW2(CCgiCookieException, eValue,
238             "Banned symbol in the cookie's value (name: " + m_Name + "): "
239             + NStr::PrintableString(m_Value), 0);
240     }
241     if (wmethod == eHTTPResponse) {
242         os << "Set-Cookie: ";
243         os << x_EncodeCookie(m_Name, eField_Name,
244             NStr::EUrlEncode(flag)).c_str() << '=';
245         if ( !m_Value.empty() ) {
246             os << x_EncodeCookie(m_Value, eField_Value,
247                 NStr::EUrlEncode(flag)).c_str();
248         }
249 
250         if ( !m_Domain.empty() )
251             os << "; domain="  << m_Domain.c_str();
252         if ( !m_Path.empty() )
253             os << "; path="    << m_Path.c_str();
254         string x_ExpDate = GetExpDate();
255         if ( !x_ExpDate.empty() )
256             os << "; expires=" << x_ExpDate.c_str();
257         if ( m_Secure )
258             os << "; secure";
259         if ( m_HttpOnly )
260             os << "; HttpOnly";
261 
262         os << HTTP_EOL;
263 
264     } else {
265         os << x_EncodeCookie(m_Name, eField_Name,
266             NStr::EUrlEncode(flag)).c_str() << '=';
267         if ( !m_Value.empty() ) {
268             os << x_EncodeCookie(m_Value, eField_Value,
269                 NStr::EUrlEncode(flag)).c_str();
270         }
271     }
272     return os;
273 }
274 
275 
276 // Check if the cookie field is valid
x_CheckField(const string & str,EFieldType ftype,const char * banned_symbols,const string * cookie_name)277 void CCgiCookie::x_CheckField(const string& str,
278                               EFieldType    ftype,
279                               const char*   banned_symbols,
280                               const string* cookie_name)
281 {
282     if ( banned_symbols ) {
283         string::size_type pos = str.find_first_of(banned_symbols);
284         if (pos != NPOS) {
285             string msg = "Banned symbol '" +
286                 NStr::PrintableString(string(1, str[pos]))
287                 + "' in the cookie";
288             switch ( ftype ) {
289             case eField_Name:
290                 msg += " name";
291                 break;
292             case eField_Value:
293                 msg += " value";
294                 break;
295             default:
296                 break;
297             }
298             if ( cookie_name ) {
299                 msg += " (name: '" + *cookie_name + "')";
300             }
301             msg += ": " + NStr::PrintableString(str);
302             NCBI_THROW2(CCgiCookieException, eValue, msg, pos);
303         }
304     }
305     // Don't check unprintable symbols in value
306     if (ftype == eField_Value) return;
307 
308     for (const char* s = str.c_str();  *s;  s++) {
309         if ( !isprint((unsigned char)(*s)) ) {
310             string msg = "Banned symbol '" +
311                 NStr::PrintableString(string(1, *s))
312                 + "' in the cookie";
313             if (ftype == eField_Name) {
314                 msg += " name";
315             }
316             if ( cookie_name ) {
317                 msg += " (name: '" + *cookie_name + "')";
318             }
319             msg += ": " + NStr::PrintableString(str);
320             NCBI_THROW2(CCgiCookieException, eValue, msg, s - str.c_str());
321         }
322     }
323 }
324 
325 
s_CookieLess(const string & name1,const string & domain1,const string & path1,const string & name2,const string & domain2,const string & path2)326 static bool s_CookieLess
327     (const string& name1, const string& domain1, const string& path1,
328      const string& name2, const string& domain2, const string& path2)
329 {
330     PNocase nocase_less;
331     bool x_less;
332 
333     x_less = nocase_less(name1, name2);
334     if (x_less  ||  nocase_less(name2, name1))
335         return x_less;
336 
337     x_less = nocase_less(domain1, domain2);
338     if (x_less  ||  nocase_less(domain2, domain1))
339         return x_less;
340 
341     if ( path1.empty() )
342         return !path2.empty();
343     if ( path2.empty() )
344         return false;
345     return (path1.compare(path2) > 0);
346 }
347 
348 
operator <(const CCgiCookie & cookie) const349 bool CCgiCookie::operator< (const CCgiCookie& cookie)
350     const
351 {
352     return s_CookieLess(m_Name, m_Domain, m_Path,
353                         cookie.m_Name, cookie.m_Domain, cookie.m_Path);
354 }
355 
356 
SetExpTime(const CTime & exp_time)357 void CCgiCookie::SetExpTime(const CTime& exp_time)
358 {
359     _ASSERT(exp_time.IsGmtTime());
360 
361     m_Expires.tm_sec   = exp_time.Second();
362     m_Expires.tm_min   = exp_time.Minute();
363     m_Expires.tm_hour  = exp_time.Hour();
364     m_Expires.tm_mday  = exp_time.Day();
365     m_Expires.tm_mon   = exp_time.Month()-1;
366     m_Expires.tm_wday  = exp_time.DayOfWeek();
367     m_Expires.tm_year  = exp_time.Year()-1900;
368     m_Expires.tm_isdst = -1;
369 }
370 
371 
372 
373 ///////////////////////////////////////////////////////
374 //  CCgiCookies::
375 //
376 
Add(const string & name,const string & value,const string & domain,const string & path,EOnBadCookie on_bad_cookie)377 CCgiCookie* CCgiCookies::Add(const string& name,    const string& value,
378                              const string& domain , const string& path,
379                              EOnBadCookie  on_bad_cookie)
380 {
381     CCgiCookie* ck = Find(name, domain, path);
382     try {
383         if ( ck ) {  // override existing CCgiCookie
384             ck->SetValue(value);
385         }
386         else {  // create new CCgiCookie and add it
387             ck = new CCgiCookie(name, value);
388             ck->SetDomain(domain);
389             ck->SetPath(path);
390             _VERIFY( m_Cookies.insert(ck).second );
391         }
392     } catch (const CCgiCookieException& ex) {
393         // This can only happen if cookie has empty name, ignore
394         // Store/StoreAndError flags in this case.
395         switch ( on_bad_cookie ) {
396         case eOnBadCookie_ThrowException:
397             throw;
398         case eOnBadCookie_StoreAndError:
399         case eOnBadCookie_SkipAndError: {
400             const CException& cex = ex;  // GCC 3.4.0 can't guess it for ERR_POST
401             ERR_POST_X(1, Severity(TCookieErrorSeverity::GetDefault()) << cex);
402             return NULL;
403         }
404         case eOnBadCookie_Store:
405         case eOnBadCookie_Skip:
406             return NULL;
407         default:
408             _TROUBLE;
409         }
410     }
411     return ck;
412 }
413 
414 
Add(const string & name,const string & value,EOnBadCookie on_bad_cookie)415 CCgiCookie* CCgiCookies::Add(const string& name,
416                              const string& value,
417                              EOnBadCookie  on_bad_cookie)
418 {
419     return Add(name, value, kEmptyStr, kEmptyStr, on_bad_cookie);
420 }
421 
422 
Add(const CCgiCookie & cookie)423 CCgiCookie* CCgiCookies::Add(const CCgiCookie& cookie)
424 {
425     CCgiCookie* ck = Find
426         (cookie.GetName(), cookie.GetDomain(), cookie.GetPath());
427     if ( ck ) {  // override existing CCgiCookie
428         ck->CopyAttributes(cookie);
429     } else {  // create new CCgiCookie and add it
430         ck = new CCgiCookie(cookie);
431         _VERIFY( m_Cookies.insert(ck).second );
432     }
433     return ck;
434 }
435 
436 
Add(const CCgiCookies & cookies)437 void CCgiCookies::Add(const CCgiCookies& cookies)
438 {
439     ITERATE (TSet, cookie, cookies.m_Cookies) {
440         Add(**cookie);
441     }
442 }
443 
444 
445 // Check if the cookie name or value is valid
446 CCgiCookies::ECheckResult
x_CheckField(const string & str,CCgiCookie::EFieldType ftype,const char * banned_symbols,EOnBadCookie on_bad_cookie,const string * cookie_name)447 CCgiCookies::x_CheckField(const string&          str,
448                           CCgiCookie::EFieldType ftype,
449                           const char*            banned_symbols,
450                           EOnBadCookie           on_bad_cookie,
451                           const string*          cookie_name)
452 {
453     try {
454         CCgiCookie::x_CheckField(str, ftype, banned_symbols, cookie_name);
455     } catch (const CCgiCookieException& ex) {
456         switch ( on_bad_cookie ) {
457         case eOnBadCookie_ThrowException:
458             throw;
459         case eOnBadCookie_SkipAndError: {
460             const CException& cex = ex;  // GCC 3.4.0 can't guess it for ERR_POST
461             ERR_POST_X(2, Severity(TCookieErrorSeverity::GetDefault()) << cex);
462             return eCheck_SkipInvalid;
463         }
464         case eOnBadCookie_Skip:
465             return eCheck_SkipInvalid;
466         case eOnBadCookie_StoreAndError: {
467             const CException& cex = ex;  // GCC 3.4.0 can't guess it for ERR_POST
468             ERR_POST_X(3, Severity(TCookieErrorSeverity::GetDefault()) << cex);
469             return eCheck_StoreInvalid;
470         }
471         case eOnBadCookie_Store:
472             return eCheck_StoreInvalid;
473         default:
474             _TROUBLE;
475         }
476     }
477     return eCheck_Valid;
478 }
479 
480 
481 NCBI_PARAM_DECL(string, CGI, Cookie_Name_Banned_Symbols);
482 NCBI_PARAM_DEF_EX(string, CGI, Cookie_Name_Banned_Symbols, " ,;=",
483                   eParam_NoThread, CGI_COOKIE_NAME_BANNED_SYMBOLS);
484 typedef NCBI_PARAM_TYPE(CGI, Cookie_Name_Banned_Symbols) TCookieNameBannedSymbols;
485 
s_GetCookieNameBannedSymbols(void)486 const char* s_GetCookieNameBannedSymbols(void)
487 {
488     static CSafeStatic<string> s_BannedSymbols;
489     static bool s_BannedSymbolsSet = false;
490     if ( !s_BannedSymbolsSet ) {
491         *s_BannedSymbols = TCookieNameBannedSymbols::GetDefault();
492         s_BannedSymbolsSet = true;
493     }
494     return s_BannedSymbols.Get().c_str();
495 }
496 
497 
Add(const string & str,EOnBadCookie on_bad_cookie)498 void CCgiCookies::Add(const string& str, EOnBadCookie on_bad_cookie)
499 {
500     NStr::EUrlDecode dec_flag = m_EncodeFlag == NStr::eUrlEnc_PercentOnly ?
501         NStr::eUrlDec_Percent : NStr::eUrlDec_All;
502     const char* banned_symbols = s_GetCookieNameBannedSymbols();
503 
504     SIZE_TYPE pos = str.find_first_not_of(" \t\n");
505     for (;;) {
506         bool need_decode = true;
507         SIZE_TYPE pos_beg = str.find_first_not_of(' ', pos);
508         if (pos_beg == NPOS)
509             return; // done
510 
511         SIZE_TYPE pos_mid = str.find_first_of("=;,\r\n", pos_beg);
512         if (pos_mid == NPOS) {
513             string name = str.substr(pos_beg);
514             switch ( x_CheckField(name, CCgiCookie::eField_Name,
515                 banned_symbols, on_bad_cookie) ) {
516             case eCheck_Valid:
517                 Add(NStr::URLDecode(name, dec_flag), kEmptyStr, on_bad_cookie);
518                 break;
519             case eCheck_StoreInvalid:
520                 {
521                     CCgiCookie* cookie = Add(name, kEmptyStr, on_bad_cookie);
522                     if ( cookie ) {
523                         cookie->SetInvalid(CCgiCookie::fInvalid_Name);
524                     }
525                     break;
526                 }
527             default:
528                 break;
529             }
530             return; // done
531         }
532         if (str[pos_mid] != '=') {
533             string name = str.substr(pos_beg, pos_mid - pos_beg);
534             switch ( x_CheckField(name, CCgiCookie::eField_Name,
535                 banned_symbols, on_bad_cookie) ) {
536             case eCheck_Valid:
537                 Add(NStr::URLDecode(name, dec_flag), kEmptyStr, on_bad_cookie);
538                 break;
539             case eCheck_StoreInvalid:
540                 {
541                     CCgiCookie* cookie = Add(name, kEmptyStr, on_bad_cookie);
542                     if ( cookie ) {
543                         cookie->SetInvalid(CCgiCookie::fInvalid_Name);
544                     }
545                     break;
546                 }
547             default:
548                 break;
549             }
550             if ((str[pos_mid] != ';'  &&  str[pos_mid] != ',')  ||
551                 ++pos_mid == str.length())
552                 return; // done
553             pos = pos_mid;
554             continue;
555         }
556         string name = str.substr(pos_beg, pos_mid - pos_beg);
557         bool quoted_value = false;
558         SIZE_TYPE pos_end = str.find_first_of(";,", pos_mid);
559         // Check for quoted value
560         if (pos_mid + 1 < str.length()  &&  str[pos_mid + 1] == '"') {
561             quoted_value = true;
562             // Find the closing quote
563             SIZE_TYPE pos_q = str.find('"', pos_mid + 2);
564             // Skip any escaped quotes
565             while (pos_q != NPOS  &&  str[pos_q - 1] == '\\') {
566                 pos_q = str.find('"', pos_q + 1);
567             }
568             bool valid_quotes = (pos_q != NPOS);
569             string msg;
570             if (valid_quotes) {
571                 pos_end = str.find_first_of(";,", pos_q + 1);
572                 size_t val_end = pos_end;
573                 if (val_end == NPOS) {
574                     val_end = str.size();
575                 }
576                 if (val_end > pos_q + 1) {
577                     // Make sure there are only spaces between the closing quote
578                     // and the semicolon.
579                     string extra = str.substr(pos_q + 1, val_end - pos_q - 1);
580                     if (extra.find_first_not_of(" \t\n") != NPOS) {
581                         valid_quotes = false;
582                         msg = "Unescaped quote in cookie value (name: " +
583                             name + "): " +
584                             NStr::PrintableString(str.substr(pos_mid + 1));
585                     }
586                 }
587             }
588             else {
589                 msg = "Missing closing quote in cookie value (name: " +
590                     name + "): " +
591                     NStr::PrintableString(str.substr(pos_mid + 1));
592             }
593             if ( valid_quotes ) {
594                 need_decode = false;
595             }
596             else {
597                 quoted_value = false;
598                 // Error - missing closing quote
599                 switch ( on_bad_cookie ) {
600                 case eOnBadCookie_ThrowException:
601                     NCBI_THROW2(CCgiCookieException, eValue, msg, pos_mid + 1);
602                 case eOnBadCookie_SkipAndError:
603                     ERR_POST_X(9, Severity(TCookieErrorSeverity::GetDefault()) <<
604                         msg);
605                     // Do not break, proceed to the next case
606                 case eOnBadCookie_Skip:
607                     return;
608                 case eOnBadCookie_StoreAndError:
609                     ERR_POST_X(10, Severity(TCookieErrorSeverity::GetDefault()) <<
610                         msg);
611                     // Do not break, proceed to the next case
612                 case eOnBadCookie_Store:
613                     pos_end = NPOS; // Use the whole string
614                     break;
615                 default:
616                     _TROUBLE;
617                 }
618             }
619         }
620         if (pos_end != NPOS) {
621             pos = pos_end + 1;
622             pos_end--;
623         } else {
624             pos_end = str.find_last_not_of(" \t\n", str.length());
625             _ASSERT(pos_end != NPOS);
626             pos = NPOS; // about to finish
627         }
628         NStr::TruncateSpacesInPlace(name, NStr::eTrunc_End);
629         string val = str.substr(pos_mid + 1, pos_end - pos_mid);
630         if (quoted_value) {
631             NStr::TruncateSpacesInPlace(val, NStr::eTrunc_End);
632             _ASSERT(val[0] == '"');
633             _ASSERT(val[val.size() - 1] == '"');
634             val = NStr::Replace(val.substr(1, val.size() - 2), "\\\"", "\"");
635         }
636         ECheckResult valid_name = x_CheckField(name, CCgiCookie::eField_Name,
637             banned_symbols, on_bad_cookie);
638         ECheckResult valid_value = quoted_value ? eCheck_Valid :
639             x_CheckField(val, CCgiCookie::eField_Value, ";,",
640             on_bad_cookie, &name);
641         if ( valid_name == eCheck_Valid  &&  valid_value == eCheck_Valid ) {
642             Add(NStr::URLDecode(name, dec_flag),
643                 need_decode ? NStr::URLDecode(val, dec_flag) : val,
644                 on_bad_cookie);
645         }
646         else if ( valid_name != eCheck_SkipInvalid  &&
647             valid_value != eCheck_SkipInvalid ) {
648             // Do not URL-decode bad cookies
649             CCgiCookie* cookie = Add(name, val, on_bad_cookie);
650             if ( cookie ) {
651                 if (valid_name == eCheck_StoreInvalid) {
652                     cookie->SetInvalid(CCgiCookie::fInvalid_Name);
653                 }
654                 if (valid_value == eCheck_StoreInvalid) {
655                     cookie->SetInvalid(CCgiCookie::fInvalid_Value);
656                 }
657             }
658         }
659     }
660     // ...never reaches here...
661 }
662 
663 
Write(CNcbiOstream & os,CCgiCookie::EWriteMethod wmethod) const664 CNcbiOstream& CCgiCookies::Write(CNcbiOstream& os,
665                                  CCgiCookie::EWriteMethod wmethod) const
666 {
667     ITERATE (TSet, cookie, m_Cookies) {
668         if (wmethod == CCgiCookie::eHTTPResponse) {
669             // Don't send secure cookies over non-secure connections.
670             if (!m_Secure  &&  (*cookie)->GetSecure()) {
671                 continue;
672             }
673         }
674         if (wmethod == CCgiCookie::eHTTPRequest && cookie != m_Cookies.begin())
675             os << "; ";
676         (*cookie)->Write(os, wmethod, EUrlEncode(m_EncodeFlag));
677         //        os << **cookie;
678     }
679     return os;
680 }
681 
682 
Find(const string & name,const string & domain,const string & path)683 CCgiCookie* CCgiCookies::Find
684 (const string& name, const string& domain, const string& path)
685 {
686     TCIter iter = m_Cookies.begin();
687     while (iter != m_Cookies.end()  &&
688            s_CookieLess((*iter)->GetName(), (*iter)->GetDomain(),
689                         (*iter)->GetPath(), name, domain, path)) {
690         iter++;
691     }
692 
693     // find exact match
694     if (iter != m_Cookies.end()  &&
695         !s_CookieLess(name, domain, path, (*iter)->GetName(),
696                       (*iter)->GetDomain(), (*iter)->GetPath())) {
697         _ASSERT( AStrEquiv(name,   (*iter)->GetName(),   PNocase()) );
698         _ASSERT( AStrEquiv(domain, (*iter)->GetDomain(), PNocase()) );
699         _ASSERT( path.compare((*iter)->GetPath()) == 0 );
700         return *iter;
701     }
702     return 0;
703 }
704 
705 
Find(const string & name,const string & domain,const string & path) const706 const CCgiCookie* CCgiCookies::Find
707 (const string& name, const string& domain, const string& path)
708     const
709 {
710     return const_cast<CCgiCookies*>(this)->Find(name, domain, path);
711 }
712 
713 
Find(const string & name,TRange * range)714 CCgiCookie* CCgiCookies::Find(const string& name, TRange* range)
715 {
716     PNocase nocase_less;
717 
718     // find the first match
719     TIter beg = m_Cookies.begin();
720     while (beg != m_Cookies.end()  &&  nocase_less((*beg)->GetName(), name))
721         beg++;
722 
723     // get this first match only
724     if ( !range ) {
725         return (beg != m_Cookies.end()  &&
726                 !nocase_less(name, (*beg)->GetName())) ? *beg : 0;
727     }
728 
729     // get the range of equal names
730     TIter end = beg;
731     while (end != m_Cookies.end()  &&
732            !nocase_less(name, (*end)->GetName()))
733         end++;
734     range->first  = beg;
735     range->second = end;
736     return (beg == end) ? 0 : *beg;
737 }
738 
739 
Find(const string & name,TCRange * range) const740 const CCgiCookie* CCgiCookies::Find(const string& name, TCRange* range)
741     const
742 {
743     CCgiCookies& nonconst_This = const_cast<CCgiCookies&> (*this);
744     if ( range ) {
745         TRange x_range;
746         const CCgiCookie* ck = nonconst_This.Find(name, &x_range);
747         range->first  = x_range.first;
748         range->second = x_range.second;
749         return ck;
750     } else {
751         return nonconst_This.Find(name, 0);
752     }
753 }
754 
755 
756 
GetAll(void) const757 CCgiCookies::TCRange CCgiCookies::GetAll(void)
758     const
759 {
760     return TCRange(m_Cookies.begin(), m_Cookies.end());
761 }
762 
763 
Remove(CCgiCookie * cookie,bool destroy)764 bool CCgiCookies::Remove(CCgiCookie* cookie, bool destroy)
765 {
766     if (!cookie  ||  m_Cookies.erase(cookie) == 0)
767         return false;
768     if ( destroy )
769         delete cookie;
770     return true;
771 }
772 
773 
Remove(TRange & range,bool destroy)774 size_t CCgiCookies::Remove(TRange& range, bool destroy)
775 {
776     size_t count = 0;
777     for (TIter iter = range.first;  iter != range.second;  iter++, count++) {
778         if ( destroy )
779             delete *iter;
780     }
781     m_Cookies.erase(range.first, range.second);
782     return count;
783 }
784 
785 
Clear(void)786 void CCgiCookies::Clear(void)
787 {
788     ITERATE (TSet, cookie, m_Cookies) {
789         delete *cookie;
790     }
791     m_Cookies.clear();
792 }
793 
794 
795 
796 ////////////////////////////////////////////////////////
797 //  CTrackingEnvHolder
798 //
799 
800 class CTrackingEnvHolder
801 {
802 public:
803     CTrackingEnvHolder(const CNcbiEnvironment* env);
804     ~CTrackingEnvHolder();
805 
GetTrackingEnv(void) const806     const char* const* GetTrackingEnv(void) const { return m_TrackingEnv; }
807 
808 private:
809     void x_Destroy(void);
810     const CNcbiEnvironment* m_Env;
811     char**                  m_TrackingEnv;
812 };
813 
814 
815 // Must be in correspondence with variables checked in NcbiGetClientIP[Ex]()
816 // (header: <connect/ext/ncbi_localnet.h>, source: connect/ext/ncbi_localnet.c,
817 // library: [x]connext)
818 static const char* kTrackingVars[] =
819 {
820     "HTTP_CAF_PROXIED_HOST",
821     "HTTP_X_FORWARDED_FOR",
822     "PROXIED_IP",
823     "HTTP_X_FWD_IP_ADDR",
824     "HTTP_CLIENT_HOST",
825     "HTTP_X_REAL_IP",
826     "REMOTE_HOST",
827     "REMOTE_ADDR",
828     "NI_CLIENT_IPADDR",
829     NULL
830 };
831 
832 
CTrackingEnvHolder(const CNcbiEnvironment * env)833 CTrackingEnvHolder::CTrackingEnvHolder(const CNcbiEnvironment* env)
834 	: m_Env(env), m_TrackingEnv(NULL)
835 {
836     if (!m_Env)
837         return;
838 
839     try {
840         size_t size = sizeof(kTrackingVars) / sizeof(kTrackingVars[0]);
841         m_TrackingEnv = new char*[size];
842         memset(m_TrackingEnv, 0, sizeof(m_TrackingEnv[0]) * size);
843 
844         int i = 0;
845         for (const char* const* name = kTrackingVars;  *name;  ++name) {
846             const string& value = m_Env->Get(*name);
847             if (value.empty())
848                 continue;
849 
850             string str(*name);
851             str += '=';
852             str += value;
853             size = str.length() + 1;
854             m_TrackingEnv[i] = new char[size];
855             memcpy(m_TrackingEnv[i++], str.c_str(), size);
856         }
857     }
858     catch (...) {
859         x_Destroy();
860         throw;
861     }
862 }
863 
864 
x_Destroy(void)865 void CTrackingEnvHolder::x_Destroy(void)
866 {
867     char** env;
868     if (!(env = m_TrackingEnv))
869         return;
870     m_TrackingEnv = 0;
871 
872     for (char** ptr = env;  *ptr;  ++ptr) {
873         char* del = *ptr;
874         *ptr = 0;
875         delete[] del;
876     }
877     delete[] env;
878 }
879 
880 
~CTrackingEnvHolder()881 CTrackingEnvHolder::~CTrackingEnvHolder()
882 {
883     x_Destroy();
884 }
885 
886 
887 
888 ////////////////////////////////////////////////////////
889 //  CCgiRequest
890 //
891 
892 // Standard property names
893 static const char* s_PropName[eCgi_NProperties + 1] = {
894     "SERVER_SOFTWARE",
895     "SERVER_NAME",
896     "GATEWAY_INTERFACE",
897     "SERVER_PROTOCOL",
898     "SERVER_PORT",
899 
900     "REMOTE_HOST",
901     "REMOTE_ADDR",
902 
903     "CONTENT_TYPE",
904     "CONTENT_LENGTH",
905 
906     "REQUEST_METHOD",
907     "PATH_INFO",
908     "PATH_TRANSLATED",
909     "SCRIPT_NAME",
910     "QUERY_STRING",
911 
912     "AUTH_TYPE",
913     "REMOTE_USER",
914     "REMOTE_IDENT",
915 
916     "HTTP_ACCEPT",
917     "HTTP_COOKIE",
918     "HTTP_IF_MODIFIED_SINCE",
919     "HTTP_REFERER",
920     "HTTP_USER_AGENT",
921 
922     ""  // eCgi_NProperties
923 };
924 
925 
GetPropertyName(ECgiProp prop)926 const string CCgiRequest::GetPropertyName(ECgiProp prop)
927 {
928     if ((unsigned int) eCgi_NProperties <= (unsigned int) prop) {
929         _TROUBLE;
930         NCBI_THROW(CCgiException, eUnknown,
931                    "CCgiRequest::GetPropertyName(BadPropIdx)");
932     }
933     return s_PropName[prop];
934 }
935 
936 
937 // Add another entry to the container of entries
s_AddEntry(TCgiEntries & entries,const string & name,const string & value,unsigned int position,const string & filename=kEmptyStr,const string & type=kEmptyStr)938 static void s_AddEntry(TCgiEntries& entries, const string& name,
939                        const string& value, unsigned int position,
940                        const string& filename = kEmptyStr,
941                        const string& type     = kEmptyStr)
942 {
943     entries.insert(TCgiEntries::value_type
944                    (name, CCgiEntry(value, filename, position, type)));
945 }
946 
947 
948 class CCgiEntries_Parser : public CUrlArgs_Parser
949 {
950 public:
951     CCgiEntries_Parser(TCgiEntries* entries,
952                        TCgiIndexes* indexes,
953                        bool indexes_as_entries);
954 protected:
955     virtual void AddArgument(unsigned int position,
956                              const string& name,
957                              const string& value,
958                              EArgType arg_type);
959 private:
960     TCgiEntries* m_Entries;
961     TCgiIndexes* m_Indexes;
962     bool         m_IndexesAsEntries;
963 };
964 
965 
CCgiEntries_Parser(TCgiEntries * entries,TCgiIndexes * indexes,bool indexes_as_entries)966 CCgiEntries_Parser::CCgiEntries_Parser(TCgiEntries* entries,
967                                        TCgiIndexes* indexes,
968                                        bool indexes_as_entries)
969     : m_Entries(entries),
970       m_Indexes(indexes),
971       m_IndexesAsEntries(indexes_as_entries  ||  !indexes)
972 {
973     return;
974 }
975 
976 
AddArgument(unsigned int position,const string & name,const string & value,EArgType arg_type)977 void CCgiEntries_Parser::AddArgument(unsigned int position,
978                                      const string& name,
979                                      const string& value,
980                                      EArgType arg_type)
981 {
982     if (m_Entries  &&
983         (arg_type == eArg_Value  ||  m_IndexesAsEntries)) {
984         m_Entries->insert(TCgiEntries::value_type(
985             name, CCgiEntry(value, kEmptyStr, position, kEmptyStr)));
986     }
987     else {
988         _ASSERT(m_Indexes);
989         m_Indexes->push_back(name);
990     }
991 }
992 
993 
~CCgiRequest(void)994 CCgiRequest::~CCgiRequest(void)
995 {
996     SetInputStream(0);
997 }
998 
999 
CCgiRequest(const CNcbiArguments * args,const CNcbiEnvironment * env,CNcbiIstream * istr,TFlags flags,int ifd,size_t errbuf_size)1000 CCgiRequest::CCgiRequest
1001 (const CNcbiArguments*   args,
1002  const CNcbiEnvironment* env,
1003  CNcbiIstream*           istr,
1004  TFlags                  flags,
1005  int                     ifd,
1006  size_t                  errbuf_size)
1007     : m_Env(0),
1008       m_Entries(PNocase_Conditional((flags & fCaseInsensitiveArgs) ?
1009                                     NStr::eNocase : NStr::eCase)),
1010       m_Input(0),
1011       m_InputFD(0),
1012       m_OwnInput(false),
1013       m_ErrBufSize(errbuf_size),
1014       m_QueryStringParsed(false),
1015       m_Session(NULL),
1016       m_EntryReaderContext(NULL)
1017 {
1018     x_Init(args, env, istr, flags, ifd);
1019 }
1020 
1021 
CCgiRequest(int argc,const char * const * argv,const char * const * envp,CNcbiIstream * istr,TFlags flags,int ifd,size_t errbuf_size)1022 CCgiRequest::CCgiRequest
1023 (int                argc,
1024  const char* const* argv,
1025  const char* const* envp,
1026  CNcbiIstream*      istr,
1027  TFlags             flags,
1028  int                ifd,
1029  size_t             errbuf_size)
1030     : m_Env(0),
1031       m_Entries(PNocase_Conditional(
1032            (flags & fCaseInsensitiveArgs) ?
1033                     NStr::eNocase : NStr::eCase)),
1034       m_Input(0),
1035       m_InputFD(0),
1036       m_OwnInput(false),
1037       m_ErrBufSize(errbuf_size),
1038       m_QueryStringParsed(false),
1039       m_Session(NULL),
1040       m_EntryReaderContext(NULL)
1041 {
1042     CNcbiArguments args(argc, argv);
1043 
1044     CNcbiEnvironment* env = new CNcbiEnvironment(envp);
1045     flags |= fOwnEnvironment;
1046 
1047     x_Init(&args, env, istr, flags, ifd);
1048 }
1049 
1050 
CCgiRequest(CNcbiIstream & is,TFlags flags,size_t errbuf_size)1051 CCgiRequest::CCgiRequest
1052 (CNcbiIstream&      is,
1053  TFlags             flags,
1054  size_t             errbuf_size)
1055     : m_Env(0),
1056       m_Entries(PNocase_Conditional((flags & fCaseInsensitiveArgs) ?
1057                                     NStr::eNocase : NStr::eCase)),
1058       m_Input(0),
1059       m_InputFD(0),
1060       m_OwnInput(false),
1061       m_ErrBufSize(errbuf_size),
1062       m_QueryStringParsed(false),
1063       m_Session(NULL),
1064       m_EntryReaderContext(NULL)
1065 {
1066     Deserialize(is, flags);
1067 
1068     // XXX Should "standard" properties be cached as in x_Init?
1069 
1070     x_SetClientIpProperty(flags);
1071 
1072     x_InitRequestContext(flags);
1073 }
1074 
1075 
NCBI_PARAM_ENUM_ARRAY(CCgiCookies::EOnBadCookie,CGI,On_Bad_Cookie)1076 NCBI_PARAM_ENUM_ARRAY(CCgiCookies::EOnBadCookie, CGI, On_Bad_Cookie)
1077 {
1078     {"Throw",         CCgiCookies::eOnBadCookie_ThrowException},
1079     {"SkipAndError",  CCgiCookies::eOnBadCookie_SkipAndError},
1080     {"Skip",          CCgiCookies::eOnBadCookie_Skip},
1081     {"StoreAndError", CCgiCookies::eOnBadCookie_StoreAndError},
1082     {"Store",         CCgiCookies::eOnBadCookie_Store}
1083 };
1084 NCBI_PARAM_ENUM_DEF_EX(CCgiCookies::EOnBadCookie, CGI, On_Bad_Cookie,
1085                        CCgiCookies::eOnBadCookie_Store,
1086                        eParam_NoThread, CGI_ON_BAD_COOKIE);
1087 typedef NCBI_PARAM_TYPE(CGI, On_Bad_Cookie) TOnBadCookieParam;
1088 
x_Init(const CNcbiArguments * args,const CNcbiEnvironment * env,CNcbiIstream * istr,TFlags flags,int ifd)1089 void CCgiRequest::x_Init
1090 (const CNcbiArguments*   args,
1091  const CNcbiEnvironment* env,
1092  CNcbiIstream*           istr,
1093  TFlags                  flags,
1094  int                     ifd)
1095 {
1096     // Setup environment variables
1097     _ASSERT( !m_Env );
1098     m_Env = env;
1099     if ( !m_Env ) {
1100         // create a dummy environment, if is not specified
1101         m_OwnEnv.reset(new CNcbiEnvironment);
1102         m_Env = m_OwnEnv.get();
1103     } else if ((flags & fOwnEnvironment) != 0) {
1104         // take ownership over the passed environment object
1105         m_OwnEnv.reset(const_cast<CNcbiEnvironment*>(m_Env));
1106     }
1107 
1108     // Cache "standard" properties
1109     for (size_t prop = 0;  prop < (size_t) eCgi_NProperties;  prop++) {
1110         x_GetPropertyByName(GetPropertyName((ECgiProp) prop));
1111     }
1112 
1113     x_SetClientIpProperty(flags);
1114 
1115     // Parse HTTP cookies
1116     if ((flags & fCookies_Unencoded) != 0) {
1117         m_Cookies.SetUrlEncodeFlag(eUrlEncode_None);
1118     }
1119     else if ((flags & fCookies_SpaceAsHex) != 0) {
1120         m_Cookies.SetUrlEncodeFlag(eUrlEncode_PercentOnly);
1121     }
1122     try {
1123         m_Cookies.Add(GetProperty(eCgi_HttpCookie),
1124             TOnBadCookieParam::GetDefault());
1125     } catch (const CCgiCookieException& e) {
1126         NCBI_RETHROW(e, CCgiRequestException, eCookie,
1127                      "Error in parsing HTTP request cookies");
1128     }
1129 
1130     // Parse entries or indexes from "$QUERY_STRING" or cmd.-line args
1131     x_ProcessQueryString(flags, args);
1132 
1133     x_ProcessInputStream(flags, istr, ifd);
1134 
1135     x_InitRequestContext(flags);
1136 
1137     // Check for an IMAGEMAP input entry like: "Command.x=5&Command.y=3" and
1138     // put them with empty string key for better access
1139     TCgiEntries::const_iterator empty_it = m_Entries.find(kEmptyStr);
1140     if (empty_it != m_Entries.end()) {
1141         // there is already empty name key
1142         ERR_POST_X(5, "Encountered query parameter with empty name, "
1143             "its value is: '" << empty_it->second << "'. ATTENTION: "
1144             "Because of this, check for image names will be disabled.");
1145         return;
1146     }
1147     string image_name;
1148     ITERATE (TCgiEntries, i, m_Entries) {
1149         const string& entry = i->first;
1150 
1151         // check for our case ("*.x")
1152         if ( !NStr::EndsWith(entry, ".x") )
1153             continue;
1154 
1155         // get base name of IMAGE, check for the presence of ".y" part
1156         string name = entry.substr(0, entry.size() - 2);
1157         if (m_Entries.find(name + ".y") == m_Entries.end())
1158             continue;
1159 
1160         // it is a correct IMAGE name
1161         if ( !image_name.empty() ) {
1162             ERR_POST_X(6, "duplicated IMAGE name: \"" << image_name <<
1163                           "\" and \"" << name << "\"");
1164             return;
1165         }
1166         image_name = name;
1167     }
1168     s_AddEntry(m_Entries, kEmptyStr, image_name, 0);
1169 }
1170 
1171 
x_FirstWord(const CTempStringEx & forward)1172 static CTempString x_FirstWord(const CTempStringEx& forward)
1173 {
1174     if (forward.empty()) {
1175         return CTempString();
1176     }
1177 
1178     vector<CTempStringEx> words;
1179     NStr::Split(forward, ", \t", words,
1180         NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1181     for (size_t i = 0;  i < words.size();  ++i) {
1182         if (NStr::IsIPAddress(words[i])) {
1183             return words[i];
1184         }
1185     }
1186     return CTempStringEx();
1187 }
1188 
1189 
1190 #if 0 // unused
1191 static CTempString x_LastWord(const CTempStringEx& forward)
1192 {
1193     if ( forward.empty() ) {
1194         return CTempString();
1195     }
1196 
1197     vector<CTempStringEx> words;
1198     NStr::Split(forward, ", \t", words,
1199                 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1200     if ( !words.size() ) {
1201         return CTempString();
1202     }
1203 
1204     size_t i;
1205     for (i = 0;  i < words.size();  ++i) {
1206         if (words[i].find(':') == NPOS  ||  !NStr::IsIPAddress(words[i])) {
1207             break;
1208         }
1209     }
1210     return i ? words[i - 1] : CTempStringEx();
1211 }
1212 #endif // unused
1213 
1214 
x_SetClientIpProperty(TFlags flags) const1215 void CCgiRequest::x_SetClientIpProperty(TFlags flags) const
1216 {
1217     if ((flags & fSkipDiagProperties) != 0) {
1218         return;
1219     }
1220     // Don't try to change the ip if already set.
1221     if (CDiagContext::GetRequestContext().IsSetClientIP()) {
1222         return;
1223     }
1224     // Set client IP for diagnostics
1225     bool internal = ! x_GetPropertyByName("HTTP_CAF_INTERNAL").empty();
1226     bool external = !(x_GetPropertyByName("HTTP_CAF_EXTERNAL").empty()  &&
1227                       x_GetPropertyByName("HTTP_NCBI_EXTERNAL").empty());
1228     string client;
1229     if ( internal  ||  !external ) {
1230         client = x_GetPropertyByName("HTTP_CLIENT_HOST");
1231     }
1232     if ( client.empty() ) {
1233         client = x_GetPropertyByName("HTTP_CAF_PROXIED_HOST");
1234     }
1235     if ( client.empty() ) {
1236         client = x_GetPropertyByName("PROXIED_IP");
1237     }
1238     if ( client.empty() ) {
1239         client = x_FirstWord(x_GetPropertyByName("HTTP_X_FORWARDED_FOR"));
1240     }
1241     if (client.empty()) {
1242         client = x_GetPropertyByName("HTTP_X_REAL_IP");
1243     }
1244     if ( client.empty() ) {
1245         client = x_GetPropertyByName(GetPropertyName(eCgi_RemoteAddr));
1246     }
1247     if ( !client.empty() ) {
1248         CDiagContext::GetRequestContext().SetClientIP(client);
1249     }
1250 }
1251 
1252 
x_InitRequestContext(TFlags flags)1253 void CCgiRequest::x_InitRequestContext(TFlags flags)
1254 {
1255     // NOTE: NCBI_CONTEXT is parsed before individual properties (e.g. NCBI_PHID)
1256     // so that their values can override those from NCBI_CONTEXT.
1257     CRequestContext_PassThrough pt;
1258     string pt_data = GetRandomProperty("NCBI_CONTEXT", true);
1259     if ( !pt_data.empty() ) {
1260         pt.Deserialize(pt_data, CRequestContext_PassThrough::eFormat_UrlEncoded);
1261     }
1262 
1263     CRequestContext& rctx = CDiagContext::GetRequestContext();
1264     if ( !rctx.IsSetHitID(CRequestContext::eHitID_Request) ) {
1265         if ((flags & fIgnorePageHitId) == 0) {
1266             string phid;
1267             // Check if page hit id is present. If not, generate one.
1268             auto phid_rg = m_Entries.equal_range(g_GetNcbiString(eNcbiStrings_PHID));
1269             while (phid_rg.first != phid_rg.second) {
1270                 phid = phid_rg.first->second;
1271                 ++phid_rg.first;
1272             }
1273             if (phid.empty()) {
1274                 // Try HTTP_NCBI_PHID
1275                 phid = CRequestContext::SelectLastHitID(
1276                     GetRandomProperty("NCBI_PHID", true));
1277             }
1278             if ( phid.empty() ) {
1279                 rctx.SetHitID();
1280             }
1281             else {
1282                 rctx.SetHitID(phid);
1283             }
1284         }
1285     }
1286     if ( !rctx.IsSetDtab() ) {
1287         string dtab = x_GetPropertyByName("HTTP_DTAB_LOCAL");
1288         if ( !dtab.empty() ) {
1289             rctx.SetDtab(dtab);
1290         }
1291     }
1292 }
1293 
1294 
x_ProcessQueryString(TFlags flags,const CNcbiArguments * args)1295 void CCgiRequest::x_ProcessQueryString(TFlags flags, const CNcbiArguments* args)
1296 {
1297     // Parse entries or indexes from "$QUERY_STRING" or cmd.-line args
1298     if ( !(flags & fIgnoreQueryString) && !m_QueryStringParsed) {
1299         m_QueryStringParsed = true;
1300         const string* query_string = NULL;
1301 
1302         if ( GetProperty(eCgi_RequestMethod).empty() ) {
1303             // special case: "$REQUEST_METHOD" undefined, so use cmd.-line args
1304             if (args  &&  args->Size() == 2)
1305                 query_string = &(*args)[1];
1306         }
1307         else {
1308             // regular case -- read from "$QUERY_STRING"
1309             query_string = &GetProperty(eCgi_QueryString);
1310         }
1311 
1312         if ( query_string ) {
1313             CCgiEntries_Parser parser(&m_Entries, &m_Indexes,
1314                 (flags & fIndexesNotEntries) == 0);
1315             if (flags & fSemicolonIsNotArgDelimiter) {
1316                 parser.SetSemicolonIsNotArgDelimiter(true);
1317             }
1318             parser.SetQueryString(*query_string);
1319         }
1320     }
1321 }
1322 
1323 
x_ProcessInputStream(TFlags flags,CNcbiIstream * istr,int ifd)1324 void CCgiRequest::x_ProcessInputStream(TFlags flags, CNcbiIstream* istr, int ifd)
1325 {
1326     m_Content.reset();
1327     // POST method?
1328     if (AStrEquiv(GetProperty(eCgi_RequestMethod), "POST", PNocase())  ||
1329         AStrEquiv(GetProperty(eCgi_RequestMethod), "PUT", PNocase())) {
1330 
1331         if ( !istr ) {
1332             istr = &NcbiCin;  // default input stream
1333             ifd = STDIN_FILENO;
1334         }
1335 
1336         const string& content_type = GetProperty(eCgi_ContentType);
1337         if ((flags & fDoNotParseContent) == 0  &&
1338             (content_type.empty()  ||
1339              NStr::StartsWith(content_type,
1340                               "application/x-www-form-urlencoded")  ||
1341              NStr::StartsWith(content_type,
1342                               "multipart/form-data"))) {
1343             // Automagically retrieve and parse content into entries
1344             unique_ptr<string> temp_str;
1345             string* pstr = 0;
1346             // Check if the content must be saved
1347             if (flags & fSaveRequestContent) {
1348                 m_Content.reset(new string);
1349                 pstr = m_Content.get();
1350             } else if (content_type.empty()
1351                        &&  (flags & fParseInputOnDemand) == 0) {
1352                 temp_str.reset(new string);
1353                 pstr = temp_str.get();
1354             }
1355             m_EntryReaderContext = new CCgiEntryReaderContext
1356                 (*istr, m_Entries, content_type, GetContentLength(), pstr);
1357             if ( (flags & fParseInputOnDemand) != 0) {
1358                 m_Input   =  0;
1359                 m_InputFD = -1;
1360                 if ( (flags & fIncludePreparsedEntries) != 0 ) {
1361                     m_EntryReaderContext->IncludePreparsedEntries();
1362                 }
1363             } else if (content_type.empty()) {
1364                 // allow interpretation as either application/octet-stream
1365                 // or application/x-www-form-urlencoded
1366                 try {
1367                     ParseRemainingContent();
1368                 } NCBI_CATCH_ALL_X(8, "CCgiRequest: POST/PUT with no content type");
1369                 CStreamUtils::Pushback(*istr, pstr->data(), pstr->length());
1370                 m_Input    = istr;
1371                 // m_InputFD  = ifd; // would be exhausted
1372                 m_InputFD  = -1;
1373                 m_OwnInput = false;
1374             } else {
1375                 // parse query from the POST content
1376                 ParseRemainingContent();
1377                 m_Input   =  0;
1378                 m_InputFD = -1;
1379             }
1380         }
1381         else {
1382             if ( (flags & fSaveRequestContent) ) {
1383                 // Save content to string
1384                 CNcbiOstrstream buf;
1385                 if ( !NcbiStreamCopy(buf, *istr) ) {
1386                     NCBI_THROW2(CCgiParseException, eRead,
1387                                 "Failed read of HTTP request body",
1388                                 (size_t)istr->gcount());
1389                 }
1390                 string temp = CNcbiOstrstreamToString(buf);
1391                 m_Content.reset(new string);
1392                 m_Content->swap(temp);
1393             }
1394             // Let the user to retrieve and parse the content
1395             m_Input    = istr;
1396             m_InputFD  = ifd;
1397             m_OwnInput = false;
1398         }
1399     } else {
1400         m_Input   = 0;
1401         m_InputFD = -1;
1402     }
1403 }
1404 
1405 
GetContent(void) const1406 const string& CCgiRequest::GetContent(void) const
1407 {
1408     if ( !m_Content.get() ) {
1409         NCBI_THROW(CCgiRequestException, eRead,
1410                    "Request content is not available");
1411     }
1412     return *m_Content;
1413 }
1414 
1415 
x_GetPropertyByName(const string & name) const1416 const string& CCgiRequest::x_GetPropertyByName(const string& name) const
1417 {
1418     return m_Env->Get(name);
1419 }
1420 
1421 
GetProperty(ECgiProp property) const1422 const string& CCgiRequest::GetProperty(ECgiProp property) const
1423 {
1424     return x_GetPropertyByName(GetPropertyName(property));
1425 }
1426 
1427 
GetRandomProperty(const string & key,bool http) const1428 const string& CCgiRequest::GetRandomProperty(const string& key, bool http)
1429     const
1430 {
1431     if ( http ) {
1432         return x_GetPropertyByName("HTTP_" + key);
1433     } else {
1434         return x_GetPropertyByName(key);
1435     }
1436 }
1437 
1438 
GetEntry(const string & name,bool * is_found) const1439 const CCgiEntry& CCgiRequest::GetEntry(const string& name, bool* is_found)
1440     const
1441 {
1442     static CSafeStatic<CCgiEntry> s_EmptyCgiEntry;
1443     TCgiEntriesCI it = GetEntries().find(name);
1444     bool x_found = (it != GetEntries().end());
1445     if ( is_found ) {
1446         *is_found = x_found;
1447     }
1448     return x_found ? it->second : s_EmptyCgiEntry.Get();
1449 }
1450 
1451 
GetNextEntry(void)1452 TCgiEntriesI CCgiRequest::GetNextEntry(void)
1453 {
1454     return m_EntryReaderContext ? m_EntryReaderContext->GetNextEntry()
1455         : m_Entries.end();
1456 }
1457 
1458 
GetPossiblyUnparsedEntry(const string & name)1459 CCgiEntry* CCgiRequest::GetPossiblyUnparsedEntry(const string& name)
1460 {
1461     TCgiEntriesI it = m_Entries.find(name);
1462     if (it == m_Entries.end()) {
1463         do {
1464             it = GetNextEntry();
1465             if (it == m_Entries.end()) {
1466                 return NULL;
1467             }
1468         } while (it->first != name);
1469     }
1470     return &it->second;
1471 }
1472 
1473 
ParseRemainingContent(void)1474 void CCgiRequest::ParseRemainingContent(void)
1475 {
1476     while (GetNextEntry() != m_Entries.end())
1477         ;
1478 }
1479 
1480 
1481 const size_t CCgiRequest::kContentLengthUnknown = (size_t)(-1);
1482 
1483 
GetContentLength(void) const1484 size_t CCgiRequest::GetContentLength(void) const
1485 {
1486     const string& str = GetProperty(eCgi_ContentLength);
1487     if ( str.empty() ) {
1488         return kContentLengthUnknown;
1489     }
1490 
1491     size_t content_length;
1492     try {
1493         content_length = (size_t) NStr::StringToUInt(str);
1494     } catch (const CStringException& e) {
1495         NCBI_RETHROW(e, CCgiRequestException, eFormat,
1496                      "Malformed Content-Length value in HTTP request: " + str);
1497     }
1498 
1499     return content_length;
1500 }
1501 
1502 
SetInputStream(CNcbiIstream * is,bool own,int fd)1503 void CCgiRequest::SetInputStream(CNcbiIstream* is, bool own, int fd)
1504 {
1505     if (is != m_Input  ||  is == NULL) {
1506         if (m_EntryReaderContext) {
1507             delete m_EntryReaderContext;
1508             m_EntryReaderContext = NULL;
1509         }
1510         if (m_Input  &&  m_OwnInput) {
1511             delete m_Input;
1512         }
1513     }
1514     m_Input    = is;
1515     m_InputFD  = fd;
1516     m_OwnInput = own;
1517 }
1518 
1519 
ParseEntries(const string & str,TCgiEntries & entries)1520 SIZE_TYPE CCgiRequest::ParseEntries(const string& str, TCgiEntries& entries)
1521 {
1522     CCgiEntries_Parser parser(&entries, 0, true);
1523     try {
1524         parser.SetQueryString(str);
1525     }
1526     catch (const CUrlParserException& ae) {
1527         return ae.GetPos();
1528     }
1529     return 0;
1530 }
1531 
1532 
ParseIndexes(const string & str,TCgiIndexes & indexes)1533 SIZE_TYPE CCgiRequest::ParseIndexes(const string& str, TCgiIndexes& indexes)
1534 {
1535     CCgiEntries_Parser parser(0, &indexes, false);
1536     try {
1537         parser.SetQueryString(str);
1538     }
1539     catch (const CUrlParserException& ae) {
1540         return ae.GetPos();
1541     }
1542     return 0;
1543 }
1544 
1545 
1546 
GetClientTrackingEnv(void) const1547 const char* const* CCgiRequest::GetClientTrackingEnv(void) const
1548 {
1549     if (!m_TrackingEnvHolder.get()) {
1550         m_TrackingEnvHolder.reset(new CTrackingEnvHolder(m_Env));
1551     }
1552     return m_TrackingEnvHolder->GetTrackingEnv();
1553 }
1554 
1555 
Serialize(CNcbiOstream & os) const1556 void CCgiRequest::Serialize(CNcbiOstream& os) const
1557 {
1558     WriteMap(os, GetEntries());
1559     WriteCgiCookies(os, GetCookies());
1560     CNcbiEnvironment env;
1561     WriteEnvironment(os, env);
1562     //    WriteEnvironment(os, *m_Env);
1563     WriteContainer(os, GetIndexes());
1564     os << (int)m_QueryStringParsed;
1565     CNcbiIstream* istrm = GetInputStream();
1566     if (istrm) {
1567         char buf[1024];
1568         while(!istrm->eof()) {
1569             istrm->read(buf, sizeof(buf));
1570             os.write(buf, istrm->gcount());
1571         }
1572     }
1573 
1574 }
1575 
Deserialize(CNcbiIstream & is,TFlags flags)1576 void CCgiRequest::Deserialize(CNcbiIstream& is, TFlags flags)
1577 {
1578     ReadMap(is, GetEntries());
1579     ReadCgiCookies(is, GetCookies());
1580     m_OwnEnv.reset(new CNcbiEnvironment(0));
1581     ReadEnvironment(is,*m_OwnEnv);
1582     ReadContainer(is, GetIndexes());
1583     if (!is.eof() && is.good()) {
1584         char c;
1585         is.get(c);
1586         m_QueryStringParsed = c == '1' ? true : false;
1587         (void)is.peek();
1588     }
1589     m_Env = m_OwnEnv.get();
1590     x_ProcessQueryString(flags, NULL);
1591     if (!is.eof() && is.good())
1592         x_ProcessInputStream(flags, &is, -1);
1593 }
1594 
GetSession(ESessionCreateMode mode) const1595 CCgiSession& CCgiRequest::GetSession(ESessionCreateMode mode) const
1596 {
1597     _ASSERT(m_Session);
1598     if (mode == eDontLoad)
1599         return *m_Session;
1600 
1601     try {
1602         m_Session->Load();
1603     } catch (const CCgiSessionException& ex) {
1604         if (ex.GetErrCode() != CCgiSessionException::eSessionId) {
1605             NCBI_RETHROW(ex, CCgiSessionException, eImplException,
1606                          "Session implementation error");
1607         }
1608     }
1609     if (!m_Session->Exists()) {
1610         if (mode != eCreateIfNotExist)
1611             NCBI_THROW(CCgiSessionException, eSessionDoesnotExist,
1612                        "Session doesn't exist.");
1613         else
1614             m_Session->CreateNewSession();
1615     }
1616 
1617     return *m_Session;
1618 }
1619 
1620 
1621 // Arguments listed here as 'arg1&arg2...' are completely removed from
1622 // log message. If '*' is listed, all arguments are excluded.
1623 NCBI_PARAM_DECL(string, CGI, LOG_EXCLUDE_ARGS);
1624 NCBI_PARAM_DEF_EX(string, CGI, LOG_EXCLUDE_ARGS, "", eParam_NoThread,
1625                   CGI_LOG_EXCLUDE_ARGS);
1626 typedef NCBI_PARAM_TYPE(CGI, LOG_EXCLUDE_ARGS) TCGI_LogExcludeArgs;
1627 
1628 // Arguments to be listed with restructed size.
1629 // Value format is arg1:size1&arg2:size2...&*:size
1630 // The listed arguments are truncated to the size specified.
1631 // '*' may be used to limit size of all unlisted arguments.
1632 NCBI_PARAM_DECL(string, CGI, LOG_LIMIT_ARGS);
1633 NCBI_PARAM_DEF_EX(string, CGI, LOG_LIMIT_ARGS, "*:1000000", eParam_NoThread,
1634                   CGI_LOG_LIMIT_ARGS);
1635 typedef NCBI_PARAM_TYPE(CGI, LOG_LIMIT_ARGS) TCGI_LogLimitArgs;
1636 
1637 
GetCGIEntries(CEntryCollector_Base & collector) const1638 void CCgiRequest::GetCGIEntries(CEntryCollector_Base& collector) const
1639 {
1640     // If there are any indexes, ignore entries and limits
1641     if ( !m_Indexes.empty() ) {
1642         ITERATE(TCgiIndexes, idx, m_Indexes) {
1643             if ( idx->empty() ) {
1644                 continue;
1645             }
1646             collector.AddEntry(*idx, kEmptyStr, kEmptyStr, true);
1647         }
1648         return;
1649     }
1650 
1651     list<string> excluded, limited;
1652     // Map argument name to its limit. Limit of -2 indicates excluded
1653     // arguments, limit = -1 means no limit.
1654     typedef map<string, int> TArgLimits;
1655     TArgLimits arg_limits;
1656     int lim_unlisted = -1;
1657 
1658     NStr::Split(TCGI_LogLimitArgs::GetDefault(), "&", limited,
1659                 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1660     ITERATE(list<string>, it, limited) {
1661         string arg, val;
1662         NStr::SplitInTwo(*it, ":", arg, val);
1663         if ( arg.empty() ) {
1664             ERR_POST(Error << "Missing argument name before size limit: "
1665                 << *it);
1666             continue;
1667         }
1668         if ( val.empty() ) {
1669             ERR_POST(Error << "Missing argument size limit: " << *it);
1670             continue;
1671         }
1672         int ival;
1673         try {
1674             ival = NStr::StringToInt(val);
1675         }
1676         catch (const CStringException&) {
1677             ERR_POST(Error << "Invalid argument size limit: " << *it);
1678             continue;
1679         }
1680         if (arg == "*") {
1681             lim_unlisted = ival;
1682             continue;
1683         }
1684         arg_limits[arg] = ival;
1685     }
1686 
1687     NStr::Split(TCGI_LogExcludeArgs::GetDefault(), "&", excluded,
1688                 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1689     ITERATE(list<string>, it, excluded) {
1690         if (*it == "*") {
1691             return;
1692         }
1693         arg_limits[*it] = -2;
1694     }
1695 
1696     ITERATE(TCgiEntries, entry, m_Entries) {
1697         if (entry->first.empty()  &&  entry->second.empty()) {
1698             continue;
1699         }
1700         TArgLimits::const_iterator lim_it = arg_limits.find(entry->first);
1701         int lim = (lim_it == arg_limits.end()) ? lim_unlisted : lim_it->second;
1702         if (lim == -2) {
1703             // Excluded argument
1704             continue;
1705         }
1706         collector.AddEntry(entry->first,
1707             lim >= 0 ? entry->second.substr(0, lim) : string(entry->second),
1708             entry->second.GetFilename(),
1709             false);
1710     }
1711 }
1712 
1713 
1714 class CStringEntryCollector : public CEntryCollector_Base {
1715 public:
CStringEntryCollector(void)1716     CStringEntryCollector(void) {}
~CStringEntryCollector(void)1717     virtual ~CStringEntryCollector(void) {}
1718 
1719     virtual void AddEntry(const string& name,
1720                           const string& value,
1721                           const string& filename,
1722                           bool          is_index);
1723 
GetArgs(void) const1724     const string& GetArgs(void) const { return m_Args; }
1725 
1726 private:
1727     string m_Args;
1728 };
1729 
1730 
AddEntry(const string & name,const string & value,const string &,bool is_index)1731 void CStringEntryCollector::AddEntry(const string& name,
1732                                      const string& value,
1733                                      const string& /*filename*/,
1734                                      bool          is_index)
1735 {
1736     if ( is_index ) {
1737         if ( !m_Args.empty() ) {
1738             m_Args += '+';
1739         }
1740         m_Args += NStr::URLEncode(name, NStr::eUrlEnc_PercentOnly);
1741     }
1742     else {
1743         if ( !m_Args.empty() ) {
1744             m_Args += '&';
1745         }
1746         m_Args += NStr::URLEncode(name, NStr::eUrlEnc_URIQueryName);
1747         m_Args += '=';
1748         m_Args += NStr::URLEncode(value, NStr::eUrlEnc_URIQueryValue);
1749     }
1750 }
1751 
1752 
GetCGIEntriesStr(void) const1753 string CCgiRequest::GetCGIEntriesStr(void) const
1754 {
1755     CStringEntryCollector collector;
1756     GetCGIEntries(collector);
1757     return collector.GetArgs();
1758 }
1759 
1760 
CalcChecksum(string & checksum,string & content) const1761 bool CCgiRequest::CalcChecksum(string& checksum, string& content) const
1762 {
1763     if( AStrEquiv(GetProperty(eCgi_RequestMethod), "POST", PNocase()) )
1764         return false;
1765 
1766     TCgiEntries entries;
1767     string query_string = GetProperty(eCgi_QueryString);
1768     CCgiRequest::ParseEntries(query_string, entries);
1769 
1770     content.erase();
1771     ITERATE(TCgiEntries, entry, entries) {
1772         content += entry->first + '=' + entry->second;
1773     }
1774     string url = GetProperty(eCgi_ServerName);
1775     url += ':';
1776     url += GetProperty(eCgi_ServerPort);
1777     url += GetProperty(eCgi_ScriptName);
1778     if ( url == ":" ) {
1779          CNcbiApplication* app =  CNcbiApplication::Instance();
1780         if (app)
1781             url = app->GetProgramDisplayName();
1782     }
1783     content += url;
1784 
1785     CChecksum cs(CChecksum::eMD5);
1786     cs.AddLine(content);
1787     CNcbiOstrstream oss;
1788     cs.WriteChecksumData(oss);
1789     checksum = CNcbiOstrstreamToString(oss);
1790     return true;
1791 }
1792 
1793 
GetRequestMethodName(void) const1794 const string& CCgiRequest::GetRequestMethodName(void) const
1795 {
1796     return GetProperty(eCgi_RequestMethod);
1797 }
1798 
1799 
GetRequestMethod(void) const1800 CCgiRequest::ERequestMethod CCgiRequest::GetRequestMethod(void) const
1801 {
1802     const char* s_Request_Method_Names[8] = {
1803         "GET",
1804         "POST",
1805         "HEAD",
1806         "PUT",
1807         "DELETE",
1808         "OPTIONS",
1809         "TRACE",
1810         "CONNECT"
1811     };
1812     const ERequestMethod s_Request_Methods[8] = {
1813         eMethod_GET,
1814         eMethod_POST,
1815         eMethod_HEAD,
1816         eMethod_PUT,
1817         eMethod_DELETE,
1818         eMethod_OPTIONS,
1819         eMethod_TRACE,
1820         eMethod_CONNECT
1821     };
1822     const string& method = GetRequestMethodName();
1823     for (int i = 0; i < 8; i++) {
1824         if ( AStrEquiv(method, s_Request_Method_Names[i], PNocase()) ) {
1825             return s_Request_Methods[i];
1826         }
1827     }
1828     return eMethod_Other;
1829 }
1830 
1831 
x_GetCharset(void) const1832 string CCgiEntry::x_GetCharset(void) const
1833 {
1834     string type = GetContentType();
1835     SIZE_TYPE pos = NStr::FindNoCase(type, "charset=");
1836     if (pos == NPOS) {
1837         return kEmptyStr;
1838     }
1839     pos += 8;
1840     SIZE_TYPE pos2 = type.find(";", pos);
1841     return type.substr(pos, pos2 == NPOS ? pos2 : pos2 - pos);
1842 }
1843 
1844 
1845 inline
s_Is_ISO_8859_1(const string & charset)1846 bool s_Is_ISO_8859_1(const string& charset)
1847 {
1848     const char* s_ISO_8859_1_Names[8] = {
1849         "ISO-8859-1",
1850         "iso-ir-100",
1851         "ISO_8859-1",
1852         "latin1",
1853         "l1",
1854         "IBM819",
1855         "CP819",
1856         "csISOLatin1"
1857     };
1858     for (int i = 0; i < 8; i++) {
1859         if (NStr::CompareNocase(s_ISO_8859_1_Names[i], charset) == 0) {
1860             return true;
1861         }
1862     }
1863     return false;
1864 }
1865 
1866 
1867 inline
s_Is_Windows_1252(const string & charset)1868 bool s_Is_Windows_1252(const string& charset)
1869 {
1870     const char* s_Windows_1252_Name = "windows-1252";
1871     return NStr::CompareNocase(s_Windows_1252_Name, charset) == 0;
1872 }
1873 
1874 
1875 inline
s_Is_UTF_8(const string & charset)1876 bool s_Is_UTF_8(const string& charset)
1877 {
1878     const char* s_UTF_8_Name = "utf-8";
1879     return NStr::CompareNocase(s_UTF_8_Name, charset) == 0;
1880 }
1881 
1882 
GetCharsetEncodingForm(const string & charset,CCgiEntry::EOnCharsetError on_error)1883 EEncodingForm GetCharsetEncodingForm(const string& charset,
1884                                      CCgiEntry::EOnCharsetError on_error)
1885 {
1886     if ( charset.empty() ) {
1887         return eEncodingForm_Unknown;
1888     }
1889     if ( s_Is_ISO_8859_1(charset) ) {
1890         return eEncodingForm_ISO8859_1;
1891     }
1892     if ( s_Is_Windows_1252(charset) ) {
1893         return eEncodingForm_Windows_1252;
1894     }
1895     if ( s_Is_UTF_8(charset) ) {
1896         return eEncodingForm_Utf8;
1897     }
1898     // UTF-16BE
1899     // UTF-16LE
1900     // UTF-16
1901     union {
1902         unsigned char u1[2];
1903         Uint2 u2;
1904     } s_BE_test;
1905     s_BE_test.u1[0] = 0xFF;
1906     s_BE_test.u1[1] = 0xFE;
1907     static bool s_BE = (s_BE_test.u2 == 0xFFFE);
1908     if (NStr::CompareNocase(charset, "UTF-16BE") == 0) {
1909         return s_BE ? eEncodingForm_Utf16Native : eEncodingForm_Utf16Foreign;
1910     }
1911     if (NStr::CompareNocase(charset, "UTF-16LE") == 0) {
1912         return s_BE ? eEncodingForm_Utf16Foreign : eEncodingForm_Utf16Native;
1913     }
1914     if (NStr::CompareNocase(charset, "UTF-16") == 0) {
1915         // Try to autodetect UTF-16 byte order
1916         return eEncodingForm_Unknown;
1917     }
1918     if (on_error == CCgiEntry::eCharsetError_Throw) {
1919         NCBI_THROW(CCgiException, eUnknown, "Unsupported charset: " + charset);
1920     }
1921     return eEncodingForm_Unknown;
1922 }
1923 
1924 
GetValueAsUTF8(EOnCharsetError on_error) const1925 CStringUTF8 CCgiEntry::GetValueAsUTF8(EOnCharsetError on_error) const
1926 {
1927     CNcbiIstrstream is(GetValue());
1928     EEncodingForm enc = GetCharsetEncodingForm(x_GetCharset(), on_error);
1929     CStringUTF8 utf_str;
1930     try {
1931         ReadIntoUtf8(is, &utf_str, enc);
1932     }
1933     catch (const CException&) {
1934         if (on_error == eCharsetError_Throw) {
1935             throw;
1936         }
1937         return CStringUTF8();
1938     }
1939     return utf_str;
1940 }
1941 
1942 
AddEntry(const string & name,const string & value,const string & filename,bool is_index)1943 void CExtraEntryCollector::AddEntry(const string& name,
1944                                     const string& value,
1945                                     const string& filename,
1946                                     bool          is_index)
1947 {
1948     _ASSERT(!is_index  ||  value.empty());
1949     m_Args.push_back(CDiagContext_Extra::TExtraArg(name,
1950         filename.empty() ? value : filename + "/" + value));
1951 }
1952 
1953 
1954 END_NCBI_SCOPE
1955