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