ncbicgi.cpp
上传用户:yhdzpy8989
上传日期:2007-06-13
资源大小:13604k
文件大小:47k
源码类别:

生物技术

开发平台:

C/C++

  1. /*
  2.  * ===========================================================================
  3.  * PRODUCTION $Log: ncbicgi.cpp,v $
  4.  * PRODUCTION Revision 1000.2  2004/06/01 18:39:19  gouriano
  5.  * PRODUCTION PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.76
  6.  * PRODUCTION
  7.  * ===========================================================================
  8.  */
  9. /*  $Id: ncbicgi.cpp,v 1000.2 2004/06/01 18:39:19 gouriano Exp $
  10. * ===========================================================================
  11. *
  12. *                            PUBLIC DOMAIN NOTICE
  13. *               National Center for Biotechnology Information
  14. *
  15. *  This software/database is a "United States Government Work" under the
  16. *  terms of the United States Copyright Act.  It was written as part of
  17. *  the author's official duties as a United States Government employee and
  18. *  thus cannot be copyrighted.  This software/database is freely available
  19. *  to the public for use. The National Library of Medicine and the U.S.
  20. *  Government have not placed any restriction on its use or reproduction.
  21. *
  22. *  Although all reasonable efforts have been taken to ensure the accuracy
  23. *  and reliability of the software and data, the NLM and the U.S.
  24. *  Government do not and cannot warrant the performance or results that
  25. *  may be obtained by using this software or data. The NLM and the U.S.
  26. *  Government disclaim all warranties, express or implied, including
  27. *  warranties of performance, merchantability or fitness for any particular
  28. *  purpose.
  29. *
  30. *  Please cite the author in any work or product based on this material.
  31. *
  32. * ===========================================================================
  33. *
  34. * Author:  Denis Vakatov
  35. *
  36. * File Description:
  37. *   NCBI C++ CGI API:
  38. *      CCgiCookie    -- one CGI cookie
  39. *      CCgiCookies   -- set of CGI cookies
  40. *      CCgiRequest   -- full CGI request
  41. */
  42. #include <ncbi_pch.hpp>
  43. #include <corelib/ncbienv.hpp>
  44. #include <corelib/ncbitime.hpp>
  45. #include <cgi/cgi_exception.hpp>
  46. #include <cgi/ncbicgi.hpp>
  47. #include <stdio.h>
  48. #include <time.h>
  49. #ifdef HAVE_UNISTD_H
  50. #  include <unistd.h>
  51. #else
  52. #  define STDIN_FILENO 0
  53. #endif
  54. // Mac OS has unistd.h, but STDIN_FILENO is not defined
  55. #ifdef NCBI_OS_MAC
  56. #  define STDIN_FILENO 0
  57. #endif
  58. BEGIN_NCBI_SCOPE
  59. ///////////////////////////////////////////////////////
  60. //  CCgiCookie::
  61. //
  62. // auxiliary zero "tm" struct
  63. const tm kZeroTime = { 0,0,0,0,0,0,0,0,0 };
  64. inline bool s_ZeroTime(const tm& date) {
  65.     return (::memcmp(&date, &kZeroTime, sizeof(tm)) == 0);
  66. }
  67. CCgiCookie::CCgiCookie(const CCgiCookie& cookie)
  68.     : m_Name(cookie.m_Name),
  69.       m_Value(cookie.m_Value),
  70.       m_Domain(cookie.m_Domain),
  71.       m_Path(cookie.m_Path)
  72. {
  73.     m_Expires = cookie.m_Expires;
  74.     m_Secure  = cookie.m_Secure;
  75. }
  76. CCgiCookie::CCgiCookie(const string& name,   const string& value,
  77.                        const string& domain, const string& path)
  78. {
  79.     if ( name.empty() )
  80.         throw invalid_argument("Empty cookie name");
  81.     x_CheckField(name, " ;,=");
  82.     m_Name = name;
  83.     SetDomain(domain);
  84.     SetPath(path);
  85.     SetValue(value);
  86.     m_Expires = kZeroTime;
  87.     m_Secure = false;
  88. }
  89. void CCgiCookie::Reset(void)
  90. {
  91.     m_Value.erase();
  92.     m_Domain.erase();
  93.     m_Path.erase();
  94.     m_Expires = kZeroTime;
  95.     m_Secure = false;
  96. }
  97. void CCgiCookie::CopyAttributes(const CCgiCookie& cookie)
  98. {
  99.     if (&cookie == this)
  100.         return;
  101.     m_Value   = cookie.m_Value;
  102.     m_Domain  = cookie.m_Domain;
  103.     m_Path    = cookie.m_Path;
  104.     m_Expires = cookie.m_Expires;
  105.     m_Secure  = cookie.m_Secure;
  106. }
  107. string CCgiCookie::GetExpDate(void) const
  108. {
  109.     if ( s_ZeroTime(m_Expires) )
  110.         return kEmptyStr;
  111.     char str[30];
  112.     if ( !::strftime(str, sizeof(str),
  113.                      "%a, %d %b %Y %H:%M:%S GMT", &m_Expires) ) {
  114.         NCBI_THROW(CCgiErrnoException, eErrno,
  115.                    "CCgiCookie::GetExpDate() -- strftime() failed");
  116.     }
  117.     return string(str);
  118. }
  119. bool CCgiCookie::GetExpDate(tm* exp_date) const
  120. {
  121.     if ( !exp_date )
  122.         throw invalid_argument("Null cookie exp.date");
  123.     if ( s_ZeroTime(m_Expires) )
  124.         return false;
  125.     *exp_date = m_Expires;
  126.     return true;
  127. }
  128. CNcbiOstream& CCgiCookie::Write(CNcbiOstream& os) const
  129. {
  130.     os << "Set-Cookie: ";
  131.     os << m_Name.c_str() << '=';
  132.     if ( !m_Value.empty() )
  133.         os << m_Value.c_str();
  134.     if ( !m_Domain.empty() )
  135.         os << "; domain="  << m_Domain.c_str();
  136.     if ( !m_Path.empty() )
  137.         os << "; path="    << m_Path.c_str();
  138.     string x_ExpDate = GetExpDate();
  139.     if ( !x_ExpDate.empty() )
  140.         os << "; expires=" << x_ExpDate.c_str();
  141.     if ( m_Secure )
  142.         os << "; secure";
  143.     os << HTTP_EOL;
  144.     return os;
  145. }
  146. // Check if the cookie field is valid
  147. void CCgiCookie::x_CheckField(const string& str, const char* banned_symbols)
  148. {
  149.     if (banned_symbols  &&  str.find_first_of(banned_symbols) != NPOS) {
  150.         throw invalid_argument("CCgiCookie::CheckValidCookieField() [1]");
  151.     }
  152.     for (const char* s = str.c_str();  *s;  s++) {
  153.         if ( !isprint(*s) )
  154.             throw invalid_argument("CCgiCookie::CheckValidCookieField() [2]");
  155.     }
  156. }
  157. static bool s_CookieLess
  158. (const string& name1, const string& domain1, const string& path1,
  159.  const string& name2, const string& domain2, const string& path2)
  160. {
  161.     PNocase nocase_less;
  162.     bool x_less;
  163.     x_less = nocase_less(name1, name2);
  164.     if (x_less  ||  nocase_less(name2, name1))
  165.         return x_less;
  166.     x_less = nocase_less(domain1, domain2);
  167.     if (x_less  ||  nocase_less(domain2, domain1))
  168.         return x_less;
  169.     if ( path1.empty() )
  170.         return !path2.empty();
  171.     if ( path2.empty() )
  172.         return false;
  173.     return (path1.compare(path2) > 0);
  174. }
  175. bool CCgiCookie::operator< (const CCgiCookie& cookie)
  176.     const
  177. {
  178.     return s_CookieLess(m_Name, m_Domain, m_Path,
  179.                         cookie.m_Name, cookie.m_Domain, cookie.m_Path);
  180. }
  181. void CCgiCookie::SetExpTime(const CTime& exp_time)
  182. {
  183.     _ASSERT(exp_time.IsGmtTime());
  184.     m_Expires.tm_sec   = exp_time.Second();
  185.     m_Expires.tm_min   = exp_time.Minute();
  186.     m_Expires.tm_hour  = exp_time.Hour();
  187.     m_Expires.tm_mday  = exp_time.Day();
  188.     m_Expires.tm_mon   = exp_time.Month()-1;
  189.     m_Expires.tm_wday  = exp_time.DayOfWeek();
  190.     m_Expires.tm_year  = exp_time.Year()-1900;
  191.     m_Expires.tm_isdst = -1;
  192. }
  193. ///////////////////////////////////////////////////////
  194. //  CCgiCookies::
  195. //
  196. CCgiCookie* CCgiCookies::Add(const string& name,    const string& value,
  197.                              const string& domain , const string& path)
  198. {
  199.     CCgiCookie* ck = Find(name, domain, path);
  200.     if ( ck ) {  // override existing CCgiCookie
  201.         ck->SetValue(value);
  202.     } else {  // create new CCgiCookie and add it
  203.         ck = new CCgiCookie(name, value);
  204.         ck->SetDomain(domain);
  205.         ck->SetPath(path);
  206.         _VERIFY( m_Cookies.insert(ck).second );
  207.     }
  208.     return ck;
  209. }
  210. CCgiCookie* CCgiCookies::Add(const CCgiCookie& cookie)
  211. {
  212.     CCgiCookie* ck = Find
  213.         (cookie.GetName(), cookie.GetDomain(), cookie.GetPath());
  214.     if ( ck ) {  // override existing CCgiCookie
  215.         ck->CopyAttributes(cookie);
  216.     } else {  // create new CCgiCookie and add it
  217.         ck = new CCgiCookie(cookie);
  218.         _VERIFY( m_Cookies.insert(ck).second );
  219.     }
  220.     return ck;
  221. }
  222. void CCgiCookies::Add(const CCgiCookies& cookies)
  223. {
  224.     ITERATE (TSet, cookie, cookies.m_Cookies) {
  225.         Add(**cookie);
  226.     }
  227. }
  228. void CCgiCookies::Add(const string& str)
  229. {
  230.     SIZE_TYPE pos;
  231.     for (pos = str.find_first_not_of(" tn"); ; ){
  232.         SIZE_TYPE pos_beg = str.find_first_not_of(' ', pos);
  233.         if (pos_beg == NPOS)
  234.             return; // done
  235.         SIZE_TYPE pos_mid = str.find_first_of("=;rn", pos_beg);
  236.         if (pos_mid == NPOS) {
  237.             Add(str.substr(pos_beg), kEmptyStr);
  238.             return; // done
  239.         }
  240.         if (str[pos_mid] != '=') {
  241.             Add(str.substr(pos_beg, pos_mid-pos_beg), kEmptyStr);
  242.             if (str[pos_mid] != ';'  ||  ++pos_mid == str.length())
  243.                 return; // done
  244.             pos = pos_mid;
  245.             continue;
  246.         }
  247.         SIZE_TYPE pos_end = str.find_first_of(';', pos_mid);
  248.         if (pos_end != NPOS) {
  249.             pos = pos_end + 1;
  250.             pos_end--;
  251.         } else {
  252.             pos_end = str.find_last_not_of(" tn", str.length());
  253.             if (pos_end == NPOS)
  254.                 break; // error
  255.             pos = NPOS; // about to finish
  256.         }
  257.         Add(str.substr(pos_beg, pos_mid-pos_beg),
  258.             str.substr(pos_mid+1, pos_end-pos_mid));
  259.     }
  260.     NCBI_THROW2(CCgiParseException, eCookie,
  261.                 "Invalid cookie string: `" + str + "'", pos);
  262. }
  263. CNcbiOstream& CCgiCookies::Write(CNcbiOstream& os)
  264.     const
  265. {
  266.     ITERATE (TSet, cookie, m_Cookies) {
  267.         os << **cookie;
  268.     }
  269.     return os;
  270. }
  271. CCgiCookie* CCgiCookies::Find
  272. (const string& name, const string& domain, const string& path)
  273. {
  274.     TCIter iter = m_Cookies.begin();
  275.     while (iter != m_Cookies.end()  &&
  276.            s_CookieLess((*iter)->GetName(), (*iter)->GetDomain(),
  277.                         (*iter)->GetPath(), name, domain, path)) {
  278.         iter++;
  279.     }
  280.     // find exact match
  281.     if (iter != m_Cookies.end()  &&
  282.         !s_CookieLess(name, domain, path, (*iter)->GetName(),
  283.                       (*iter)->GetDomain(), (*iter)->GetPath())) {
  284.         _ASSERT( AStrEquiv(name, (*iter)->GetName(), PNocase()) );
  285.         _ASSERT( AStrEquiv(domain, (*iter)->GetDomain(), PNocase()) );
  286.         _ASSERT( path.compare((*iter)->GetPath()) == 0 );
  287.         return *iter;
  288.     }
  289.     return 0;
  290. }
  291. const CCgiCookie* CCgiCookies::Find
  292. (const string& name, const string& domain, const string& path)
  293.     const
  294. {
  295.     return const_cast<CCgiCookies*>(this)->Find(name, domain, path);
  296. }
  297. CCgiCookie* CCgiCookies::Find(const string& name, TRange* range)
  298. {
  299.     PNocase nocase_less;
  300.     // find the first match
  301.     TIter beg = m_Cookies.begin();
  302.     while (beg != m_Cookies.end()  &&  nocase_less((*beg)->GetName(), name))
  303.         beg++;
  304.     // get this first match only
  305.     if ( !range ) {
  306.         return (beg != m_Cookies.end()  &&
  307.                 !nocase_less(name, (*beg)->GetName())) ? *beg : 0;
  308.     }
  309.     // get the range of equal names
  310.     TIter end = beg;
  311.     while (end != m_Cookies.end()  &&
  312.            !nocase_less(name, (*end)->GetName()))
  313.         end++;
  314.     range->first  = beg;
  315.     range->second = end;
  316.     return (beg == end) ? 0 : *beg;
  317. }
  318. const CCgiCookie* CCgiCookies::Find(const string& name, TCRange* range)
  319.     const
  320. {
  321.     CCgiCookies& nonconst_This = const_cast<CCgiCookies&>(*this);
  322.     if ( range ) {
  323.         TRange x_range;
  324.         const CCgiCookie* ck = nonconst_This.Find(name, &x_range);
  325.         range->first  = x_range.first;
  326.         range->second = x_range.second;
  327.         return ck;
  328.     } else {
  329.         return nonconst_This.Find(name, 0);
  330.     }
  331. }
  332. bool CCgiCookies::Remove(CCgiCookie* cookie, bool destroy)
  333. {
  334.     if (!cookie  ||  m_Cookies.erase(cookie) == 0)
  335.         return false;
  336.     if ( destroy )
  337.         delete cookie;
  338.     return true;
  339. }
  340. size_t CCgiCookies::Remove(TRange& range, bool destroy)
  341. {
  342.     size_t count = 0;
  343.     for (TIter iter = range.first;  iter != range.second;  iter++, count++) {
  344.         if ( destroy )
  345.             delete *iter;
  346.     }
  347.     m_Cookies.erase(range.first, range.second);
  348.     return count;
  349. }
  350. void CCgiCookies::Clear(void)
  351. {
  352.     ITERATE (TSet, cookie, m_Cookies) {
  353.         delete *cookie;
  354.     }
  355.     m_Cookies.clear();
  356. }
  357. ///////////////////////////////////////////////////////
  358. //  CCgiRequest
  359. //
  360. // Standard property names
  361. static const string s_PropName[eCgi_NProperties + 1] = {
  362.     "SERVER_SOFTWARE",
  363.     "SERVER_NAME",
  364.     "GATEWAY_INTERFACE",
  365.     "SERVER_PROTOCOL",
  366.     "SERVER_PORT",
  367.     "REMOTE_HOST",
  368.     "REMOTE_ADDR",
  369.     "CONTENT_TYPE",
  370.     "CONTENT_LENGTH",
  371.     "REQUEST_METHOD",
  372.     "PATH_INFO",
  373.     "PATH_TRANSLATED",
  374.     "SCRIPT_NAME",
  375.     "QUERY_STRING",
  376.     "AUTH_TYPE",
  377.     "REMOTE_USER",
  378.     "REMOTE_IDENT",
  379.     "HTTP_ACCEPT",
  380.     "HTTP_COOKIE",
  381.     "HTTP_IF_MODIFIED_SINCE",
  382.     "HTTP_REFERER",
  383.     "HTTP_USER_AGENT",
  384.     ""  // eCgi_NProperties
  385. };
  386. const string& CCgiRequest::GetPropertyName(ECgiProp prop)
  387. {
  388.     if ((long) prop < 0  ||  (long) eCgi_NProperties <= (long) prop) {
  389.         _TROUBLE;
  390.         throw logic_error("CCgiRequest::GetPropertyName(BadPropIdx)");
  391.     }
  392.     return s_PropName[prop];
  393. }
  394. // Return integer (0..15) corresponding to the "ch" as a hex digit
  395. // Return -1 on error
  396. static int s_HexChar(char ch) THROWS_NONE
  397. {
  398.     if ('0' <= ch  &&  ch <= '9')
  399.         return ch - '0';
  400.     if ('a' <= ch  &&  ch <= 'f')
  401.         return 10 + (ch - 'a');
  402.     if ('A' <= ch  &&  ch <= 'F')
  403.         return 10 + (ch - 'A');
  404.     return -1;
  405. }
  406. // URL-decode string "str" into itself
  407. // Return 0 on success;  otherwise, return 1-based error position
  408. static SIZE_TYPE s_URL_Decode(string& str)
  409. {
  410.     SIZE_TYPE len = str.length();
  411.     if ( !len )
  412.         return 0;
  413.     SIZE_TYPE p = 0;
  414.     for (SIZE_TYPE pos = 0;  pos < len;  p++) {
  415.         switch ( str[pos] ) {
  416.         case '%': {
  417.             if (pos + 2 > len)
  418.                 return (pos + 1);
  419.             int i1 = s_HexChar(str[pos+1]);
  420.             if (i1 < 0  ||  15 < i1)
  421.                 return (pos + 2);
  422.             int i2 = s_HexChar(str[pos+2]);
  423.             if (i2 < 0  ||  15 < i2)
  424.                 return (pos + 3);
  425.             str[p] = s_HexChar(str[pos+1]) * 16 + s_HexChar(str[pos+2]);
  426.             pos += 3;
  427.             break;
  428.         }
  429.         case '+': {
  430.             str[p] = ' ';
  431.             pos++;
  432.             break;
  433.         }
  434.         default:
  435.             str[p] = str[pos++];
  436.         }
  437.     }
  438.     if (p < len) {
  439.         str[p] = '';
  440.         str.resize(p);
  441.     }
  442.     return 0;
  443. }
  444. // Add another entry to the container of entries
  445. void s_AddEntry(TCgiEntries& entries, const string& name,
  446.                 const string& value, unsigned int position,
  447.                 const string& filename = kEmptyStr)
  448. {
  449.     entries.insert(TCgiEntries::value_type
  450.                    (name, CCgiEntry(value, filename, position)));
  451. }
  452. // Parse ISINDEX-like query string into "indexes" XOR "entries" --
  453. // whichever is not null
  454. // Return 0 on success;  return 1-based error position otherwise
  455. static SIZE_TYPE s_ParseIsIndex(const string& str,
  456.                                 TCgiIndexes* indexes, TCgiEntries* entries)
  457. {
  458.     _ASSERT( !indexes != !entries );
  459.     SIZE_TYPE len = str.length();
  460.     if ( !len )
  461.         return 0;
  462.     // No '=' and spaces must present in the parsed string
  463.     SIZE_TYPE err_pos = str.find_first_of("=& trn");
  464.     if (err_pos != NPOS)
  465.         return err_pos + 1;
  466.     // Parse into indexes
  467.     unsigned int num = 1;
  468.     for (SIZE_TYPE beg = 0;  beg < len;  ) {
  469.         // parse and URL-decode ISINDEX name
  470.         SIZE_TYPE end = str.find_first_of('+', beg);
  471.         if (end == beg  ||  end == len-1)
  472.             return end + 1;  // error
  473.         if (end == NPOS)
  474.             end = len;
  475.         string name = str.substr(beg, end - beg);
  476.         if ((err_pos = s_URL_Decode(name)) != 0)
  477.             return beg + err_pos;  // error
  478.         // store
  479.         if ( indexes ) {
  480.             indexes->push_back(name);
  481.         } else {
  482.             s_AddEntry(*entries, name, kEmptyStr, num++);
  483.         }
  484.         // continue
  485.         beg = end + 1;
  486.     }
  487.     return 0;
  488. }
  489. // Guess if "str" is ISINDEX- or FORM-like, then fill out
  490. // "entries" XOR "indexes";  if "indexes_as_entries" is "true" then
  491. // interprete indexes as FORM-like entries(with empty value) and so
  492. // put them to "entries"
  493. static void s_ParseQuery(const string& str,
  494.                          TCgiEntries&  entries,
  495.                          TCgiIndexes&  indexes,
  496.                          bool          indexes_as_entries)
  497. {
  498.     if (str.find_first_of("=&") == NPOS) { // ISINDEX
  499.         SIZE_TYPE err_pos = indexes_as_entries ?
  500.             s_ParseIsIndex(str, 0, &entries) :
  501.             s_ParseIsIndex(str, &indexes, 0);
  502.         if (err_pos != 0)
  503.             NCBI_THROW2(CCgiParseException, eIndex,
  504.                         "Init CCgiRequest::ParseISINDEX("" +
  505.                         str + """, err_pos);
  506.     } else {  // regular(FORM) entries
  507.         SIZE_TYPE err_pos = CCgiRequest::ParseEntries(str, entries);
  508.         if (err_pos != 0)
  509.             NCBI_THROW2(CCgiParseException, eEntry,
  510.                         "Init CCgiRequest::ParseFORM("" +
  511.                         str + "")", err_pos);
  512.     }
  513. }
  514. static string s_FindAttribute(const string& str, const string& name,
  515.                               SIZE_TYPE start, SIZE_TYPE end,
  516.                               bool required)
  517. {
  518.     SIZE_TYPE att_pos = str.find("; " + name + "="", start);
  519.     if (att_pos == NPOS  ||  att_pos >= end) {
  520.         if (required) {
  521.             NCBI_THROW2(CCgiParseException, eAttribute,
  522.                         "s_FindAttribute: missing " + name + " in "
  523.                         + str.substr(start, end - start),
  524.                         start);
  525.         } else {
  526.             return kEmptyStr;
  527.         }
  528.     }
  529.     SIZE_TYPE att_start = att_pos + name.size() + 4;
  530.     SIZE_TYPE att_end   = str.find('"', att_start);
  531.     if (att_end == NPOS  ||  att_end >= end) {
  532.         NCBI_THROW2(CCgiParseException, eFormat,
  533.                     "s_FindAttribute: malformatted " + name + " in "
  534.                     + str.substr(att_pos, end - att_pos),
  535.                     att_start);
  536.     }
  537.     return str.substr(att_start, att_end - att_start);
  538. }
  539. static void s_ParseMultipartEntries(const string& boundary,
  540.                                     const string& str,
  541.                                     TCgiEntries&  entries)
  542. {
  543.     const string    s_Me("s_ParseMultipartEntries");
  544.     const string    s_Eol(HTTP_EOL);
  545.     const SIZE_TYPE eol_size = s_Eol.size();
  546.     SIZE_TYPE    pos           = 0;
  547.     SIZE_TYPE    boundary_size = boundary.size();
  548.     SIZE_TYPE    end_pos;
  549.     unsigned int num           = 1;
  550.     if (NStr::Compare(str, 0, boundary_size + eol_size, boundary + s_Eol)
  551.         != 0) {
  552.         if (NStr::StartsWith(str, boundary + "--")) {
  553.             // potential corner case: no actual data
  554.             return;
  555.         }
  556.         NCBI_THROW2(CCgiParseException, eEntry,
  557.                     s_Me + ": input does not start with boundary line "
  558.                     + boundary,
  559.                     0);
  560.     }
  561.     {{
  562.         SIZE_TYPE tail_size = boundary_size + eol_size + 2;
  563.         SIZE_TYPE tail_start = str.find(s_Eol + boundary + "--");
  564.         if (tail_start == NPOS) {
  565.             NCBI_THROW2(CCgiParseException, eEntry,
  566.                         s_Me + ": input does not contain trailing boundary "
  567.                         + boundary + "--",
  568.                         0);
  569.         }
  570.         end_pos = tail_start + tail_size;
  571.     }}
  572.     pos += boundary_size + eol_size;
  573.     while (pos < end_pos) {
  574.         SIZE_TYPE next_boundary = str.find(s_Eol + boundary, pos);
  575.         _ASSERT(next_boundary != NPOS);
  576.         string name, filename;
  577.         bool found = false;
  578.         for (;;) {
  579.             SIZE_TYPE bol_pos = pos, eol_pos = str.find(s_Eol, pos);
  580.             _ASSERT(eol_pos != NPOS);
  581.             if (pos == eol_pos) {
  582.                 // blank -- end of headers
  583.                 pos += eol_size;
  584.                 break;
  585.             }
  586.             pos = str.find(':', bol_pos);
  587.             if (pos == NPOS  ||  pos >= eol_pos) {
  588.                 NCBI_THROW2(CCgiParseException, eEntry,
  589.                             s_Me + ": no colon in header "
  590.                             + str.substr(bol_pos, eol_pos - bol_pos),
  591.                             bol_pos);
  592.             }
  593.             if (NStr::CompareNocase(str, bol_pos, pos - bol_pos,
  594.                                     "Content-Disposition") != 0) {
  595.                 ERR_POST(Warning << s_Me << ": ignoring unrecognized header "
  596.                          + str.substr(bol_pos, eol_pos - bol_pos));
  597.                 pos = eol_pos + eol_size;
  598.                 continue;
  599.             }
  600.             if (NStr::CompareNocase(str, pos, 13, ": form-data; ") != 0) {
  601.                 NCBI_THROW2(CCgiParseException, eEntry,
  602.                             s_Me + ": bad Content-Disposition header "
  603.                             + str.substr(bol_pos, eol_pos - bol_pos),
  604.                             pos);
  605.             }
  606.             pos += 11;
  607.             name     = s_FindAttribute(str, "name",     pos, eol_pos, true);
  608.             filename = s_FindAttribute(str, "filename", pos, eol_pos, false);
  609.             found = true;
  610.             pos = eol_pos + eol_size;
  611.         }
  612.         if (!found) {
  613.             NCBI_THROW2(CCgiParseException, eEntry,
  614.                         s_Me + ": missing Content-Disposition header", pos);
  615.         }
  616.         s_AddEntry(entries, name, str.substr(pos, next_boundary - pos),
  617.                    num++, filename);
  618.         pos = next_boundary + 2*eol_size + boundary_size;
  619.     }
  620. }
  621. static void s_ParsePostQuery(const string& content_type, const string& str,
  622.                              TCgiEntries& entries)
  623. {
  624.     if (content_type.empty()  ||
  625.         content_type == "application/x-www-form-urlencoded") {
  626.         // remove trailing end of line 'r' and/or 'n'
  627.         // (this can happen if Content-Length: was not specified)
  628.         SIZE_TYPE err_pos;
  629.         SIZE_TYPE eol = str.find_first_of("rn");
  630.         if (eol != NPOS) {
  631.             err_pos = CCgiRequest::ParseEntries(str.substr(0, eol), entries);
  632.         } else {
  633.             err_pos = CCgiRequest::ParseEntries(str, entries);
  634.         }
  635.         if ( err_pos != 0 ) {
  636.             NCBI_THROW2(CCgiParseException, eEntry,
  637.                         "Init CCgiRequest::ParseFORM("" +
  638.                         str + "")", err_pos);
  639.         }
  640.         return;
  641.     }
  642.     if ( NStr::StartsWith(content_type, "multipart/form-data") ) {
  643.         string start = "boundary=";
  644.         SIZE_TYPE pos = content_type.find(start);
  645.         if ( pos == NPOS )
  646.             NCBI_THROW2(CCgiParseException, eEntry,
  647.                         "CCgiRequest::ParsePostQuery("" +
  648.                         content_type + ""): no boundary field", 0);
  649.         s_ParseMultipartEntries("--" + content_type.substr(pos + start.size()),
  650.                                 str, entries);
  651.         return;
  652.     }
  653.     // The caller function thinks that s_ParsePostQuery() knows how to parse
  654.     // this content type, but s_ParsePostQuery() apparently cannot do it...
  655.     _TROUBLE;
  656. }
  657. CCgiRequest::~CCgiRequest(void)
  658. {
  659.     SetInputStream(0);
  660. }
  661. CCgiRequest::CCgiRequest
  662. (const CNcbiArguments*   args,
  663.  const CNcbiEnvironment* env,
  664.  CNcbiIstream*           istr,
  665.  TFlags                  flags,
  666.  int                     ifd,
  667.  size_t                  errbuf_size)
  668.     : m_Env(0),
  669.       m_ErrBufSize(errbuf_size)
  670. {
  671.     x_Init(args, env, istr, flags, ifd);
  672. }
  673. CCgiRequest::CCgiRequest
  674. (int                argc,
  675.  const char* const* argv,
  676.  const char* const* envp,
  677.  CNcbiIstream*      istr,
  678.  TFlags             flags,
  679.  int                ifd,
  680.  size_t             errbuf_size)
  681.     : m_Env(0),
  682.       m_ErrBufSize(errbuf_size)
  683. {
  684.     CNcbiArguments args(argc, argv);
  685.     CNcbiEnvironment* env = new CNcbiEnvironment(envp);
  686.     flags |= fOwnEnvironment;
  687.     x_Init(&args, env, istr, flags, ifd);
  688. }
  689. void CCgiRequest::x_Init
  690. (const CNcbiArguments*   args,
  691.  const CNcbiEnvironment* env,
  692.  CNcbiIstream*           istr,
  693.  TFlags                  flags,
  694.  int                     ifd)
  695. {
  696.     // Setup environment variables
  697.     _ASSERT( !m_Env );
  698.     m_Env = env;
  699.     if ( !m_Env ) {
  700.         // create a dummy environment, if is not specified
  701.         m_OwnEnv.reset(new CNcbiEnvironment);
  702.         m_Env = m_OwnEnv.get();
  703.     } else if ((flags & fOwnEnvironment) != 0) {
  704.         // take ownership over the passed environment object
  705.         m_OwnEnv.reset(const_cast<CNcbiEnvironment*>(m_Env));
  706.     }
  707.     // Cache "standard" properties
  708.     for (size_t prop = 0;  prop < (size_t) eCgi_NProperties;  prop++) {
  709.         x_GetPropertyByName(GetPropertyName((ECgiProp) prop));
  710.     }
  711.     // Compose cookies
  712.     m_Cookies.Add(GetProperty(eCgi_HttpCookie));
  713.     // Parse entries or indexes from "$QUERY_STRING" or cmd.-line args
  714.     {{
  715.         const string* query_string = 0;
  716.         if ( GetProperty(eCgi_RequestMethod).empty() ) {
  717.             // special case: "$REQUEST_METHOD" undefined, so use cmd.-line args
  718.             if (args  &&  args->Size() > 1)
  719.                 query_string = &(*args)[1];
  720.         }
  721.         else if ( !(flags & fIgnoreQueryString) ) {
  722.             // regular case -- read from "$QUERY_STRING"
  723.             query_string = &GetProperty(eCgi_QueryString);
  724.         }
  725.         if ( query_string ) {
  726.             s_ParseQuery(*query_string, m_Entries, m_Indexes,
  727.                          (flags & fIndexesNotEntries) == 0);
  728.         }
  729.     }}
  730.     // POST method?
  731.     if ( AStrEquiv(GetProperty(eCgi_RequestMethod), "POST", PNocase()) ) {
  732.         if ( !istr ) {
  733.             istr = &NcbiCin;  // default input stream
  734.             ifd = STDIN_FILENO;
  735.         }
  736.         const string& content_type = GetProperty(eCgi_ContentType);
  737.         if ((flags & fDoNotParseContent) == 0  &&
  738.             (content_type.empty()  ||
  739.              content_type == "application/x-www-form-urlencoded"  ||
  740.              NStr::StartsWith(content_type, "multipart/form-data"))) {
  741.             // Automagically retrieve and parse content into entries
  742.             string str;
  743.             size_t len = GetContentLength();
  744.             if (len == kContentLengthUnknown) {
  745.                 // read data until end of file
  746.                 for (;;) {
  747.                     char buffer[1024];
  748.                     istr->read(buffer, sizeof(buffer));
  749.                     size_t count = istr->gcount();
  750.                     if ( count == 0 ) {
  751.                         if ( istr->eof() ) {
  752.                             break; // end of data
  753.                         }
  754.                         else {
  755.                             THROW1_TRACE(runtime_error, "
  756. CCgiRequest::x_Init() -- error in reading POST content: read fault");
  757.                         }
  758.                     }
  759.                     str.append(buffer, count);
  760.                 }
  761.             }
  762.             else {
  763.                 str.resize(len);
  764.                 for (size_t pos = 0;  pos < len;  ) {
  765.                     size_t rlen = len - pos;
  766.                     istr->read(&str[pos], rlen);
  767.                     size_t count = istr->gcount();
  768.                     if ( count == 0 ) {
  769.                         str.resize(pos);
  770.                         if ( istr->eof() ) {
  771.                             string err = "
  772. CCgiRequest::x_Init() -- error in reading POST content: unexpected EOF";
  773.                             string pos_str("(pos=");
  774.                             pos_str.append(NStr::UIntToString(pos));
  775.                             pos_str.append("; content_length=");
  776.                             pos_str.append(NStr::UIntToString(len));
  777.                             pos_str.append("):n");
  778.                             if (m_ErrBufSize  &&  pos) {
  779.                                 pos_str.append(NStr::PrintableString(str),
  780.                                                0, min(m_ErrBufSize, pos));
  781.                                 if (m_ErrBufSize < pos) {
  782.                                     pos_str.append("n[truncated...]");
  783.                                 }
  784.                             }
  785.                             err.append(pos_str);
  786.                             THROW1_TRACE(runtime_error, err);
  787.                         }
  788.                         else {
  789.                             THROW1_TRACE(runtime_error, "
  790. CCgiRequest::x_Init() -- error in reading POST content: read fault");
  791.                         }
  792.                     }
  793.                     pos += count;
  794.                 }
  795.             }
  796.             // parse query from the POST content
  797.             s_ParsePostQuery(content_type, str, m_Entries);
  798.             m_Input    = 0;
  799.             m_InputFD = -1;
  800.         }
  801.         else {
  802.             // Let the user to retrieve and parse the content
  803.             m_Input    = istr;
  804.             m_InputFD  = ifd;
  805.             m_OwnInput = false;
  806.         }
  807.     } else {
  808.         m_Input   = 0;
  809.         m_InputFD = -1;
  810.     }
  811.     // Check for an IMAGEMAP input entry like: "Command.x=5&Command.y=3" and
  812.     // put them with empty string key for better access
  813.     if (m_Entries.find(kEmptyStr) != m_Entries.end()) {
  814.         // there is already empty name key
  815.         ERR_POST("empty key name:  we will not check for IMAGE names");
  816.         return;
  817.     }
  818.     string image_name;
  819.     ITERATE (TCgiEntries, i, m_Entries) {
  820.         const string& entry = i->first;
  821.         // check for our case ("*.x")
  822.         if ( !NStr::EndsWith(entry, ".x") )
  823.             continue;
  824.         // get base name of IMAGE, check for the presence of ".y" part
  825.         string name = entry.substr(0, entry.size() - 2);
  826.         if (m_Entries.find(name + ".y") == m_Entries.end())
  827.             continue;
  828.         // it is a correct IMAGE name
  829.         if ( !image_name.empty() ) {
  830.             ERR_POST("duplicated IMAGE name: "" << image_name <<
  831.                      "" and "" << name << """);
  832.             return;
  833.         }
  834.         image_name = name;
  835.     }
  836.     s_AddEntry(m_Entries,   kEmptyStr, image_name, 0);
  837. }
  838. const string& CCgiRequest::x_GetPropertyByName(const string& name) const
  839. {
  840.     return m_Env->Get(name);
  841. }
  842. const string& CCgiRequest::GetProperty(ECgiProp property) const
  843. {
  844.     return x_GetPropertyByName(GetPropertyName(property));
  845. }
  846. const string& CCgiRequest::GetRandomProperty(const string& key, bool http)
  847.     const
  848. {
  849.     if ( http ) {
  850.         return x_GetPropertyByName("HTTP_" + key);
  851.     } else {
  852.         return x_GetPropertyByName(key);
  853.     }
  854. }
  855. const CCgiEntry& CCgiRequest::GetEntry(const string& name, bool* is_found)
  856.     const
  857. {
  858.     static const CCgiEntry kEmptyEntry(kEmptyStr);
  859.     TCgiEntriesCI it = GetEntries().find(name);
  860.     bool x_found = (it != GetEntries().end());
  861.     if ( is_found ) {
  862.         *is_found = x_found;
  863.     }
  864.     return x_found ? it->second : kEmptyEntry;
  865. }
  866. const size_t CCgiRequest::kContentLengthUnknown = (size_t)(-1);
  867. size_t CCgiRequest::GetContentLength(void) const
  868. {
  869.     const string& str = GetProperty(eCgi_ContentLength);
  870.     if ( str.empty() ) {
  871.         return kContentLengthUnknown;
  872.     }
  873.     return (size_t) NStr::StringToUInt(str);
  874. }
  875. void CCgiRequest::SetInputStream(CNcbiIstream* is, bool own, int fd)
  876. {
  877.     if (m_Input  &&  m_OwnInput  &&  is != m_Input) {
  878.         delete m_Input;
  879.     }
  880.     m_Input    = is;
  881.     m_InputFD  = fd;
  882.     m_OwnInput = own;
  883. }
  884. SIZE_TYPE CCgiRequest::ParseEntries(const string& str, TCgiEntries& entries)
  885. {
  886.     SIZE_TYPE len = str.length();
  887.     if ( !len )
  888.         return 0;
  889.     // If no '=' present in the parsed string then try to parse it as ISINDEX
  890.     if (str.find_first_of("&=") == NPOS)
  891.         return s_ParseIsIndex(str, 0, &entries);
  892.     // No spaces are allowed in the parsed string
  893.     SIZE_TYPE err_pos = str.find_first_of(" trn");
  894.     if (err_pos != NPOS)
  895.         return err_pos + 1;
  896.     // Parse into entries
  897.     unsigned int num = 1;
  898.     for (SIZE_TYPE beg = 0;  beg < len;  ) {
  899.         // ignore 1st ampersand (it is just a temp. slack to some biz. clients)
  900.         if (beg == 0  &&  str[0] == '&') {
  901.             beg = 1;
  902.             continue;
  903.         }
  904.         // kludge for the sake of some older browsers, which fail to decode
  905.         // "&amp;" within hrefs.
  906.         if ( !NStr::CompareNocase(str, beg, 4, "amp;") ) {
  907.             beg += 4;
  908.         }
  909.         // parse and URL-decode name
  910.         SIZE_TYPE mid = str.find_first_of("=&", beg);
  911.         if (mid == beg  ||
  912.             (mid != NPOS  &&  str[mid] == '&'  &&  mid == len-1))
  913.             return mid + 1;  // error
  914.         if (mid == NPOS)
  915.             mid = len;
  916.         string name = str.substr(beg, mid - beg);
  917.         if ((err_pos = s_URL_Decode(name)) != 0)
  918.             return beg + err_pos;  // error
  919.         // parse and URL-decode value(if any)
  920.         string value;
  921.         if (str[mid] == '=') { // has a value
  922.             mid++;
  923.             SIZE_TYPE end = str.find_first_of(" &", mid);
  924.             if (end != NPOS  &&  (str[end] != '&'  ||  end == len-1))
  925.                 return end + 1;  // error
  926.             if (end == NPOS)
  927.                 end = len;
  928.             value = str.substr(mid, end - mid);
  929.             if ((err_pos = s_URL_Decode(value)) != 0)
  930.                 return mid + err_pos;  // error
  931.             beg = end + 1;
  932.         } else {  // has no value
  933.             beg = mid + 1;
  934.         }
  935.         // store the name-value pair
  936.         s_AddEntry(entries, name, value, num++);
  937.     }
  938.     return 0;
  939. }
  940. SIZE_TYPE CCgiRequest::ParseIndexes(const string& str, TCgiIndexes& indexes)
  941. {
  942.     return s_ParseIsIndex(str, &indexes, 0);
  943. }
  944. extern string URL_DecodeString(const string& str)
  945. {
  946.     string    x_str = str;
  947.     SIZE_TYPE err_pos = s_URL_Decode(x_str);
  948.     if (err_pos != 0)
  949.         NCBI_THROW2(CCgiParseException, eFormat,
  950.                     "URL_DecodeString(<badly_formatted_str>)",err_pos);
  951.     return x_str;
  952. }
  953. extern string URL_EncodeString(const string& str, EUrlEncode encode_mark_chars)
  954. {
  955.     static const char s_Encode[256][4] = {
  956.         "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
  957.         "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
  958.         "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
  959.         "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
  960.         "+",   "!",   "%22", "%23", "$",   "%25", "%26", "'",
  961.         "(",   ")",   "*",   "%2B", ",",   "-",   ".",   "%2F",
  962.         "0",   "1",   "2",   "3",   "4",   "5",   "6",   "7",
  963.         "8",   "9",   "%3A", "%3B", "%3C", "%3D", "%3E", "%3F",
  964.         "%40", "A",   "B",   "C",   "D",   "E",   "F",   "G",
  965.         "H",   "I",   "J",   "K",   "L",   "M",   "N",   "O",
  966.         "P",   "Q",   "R",   "S",   "T",   "U",   "V",   "W",
  967.         "X",   "Y",   "Z",   "%5B", "%5C", "%5D", "%5E", "_",
  968.         "%60", "a",   "b",   "c",   "d",   "e",   "f",   "g",
  969.         "h",   "i",   "j",   "k",   "l",   "m",   "n",   "o",
  970.         "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
  971.         "x",   "y",   "z",   "%7B", "%7C", "%7D", "%7E", "%7F",
  972.         "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
  973.         "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
  974.         "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
  975.         "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
  976.         "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
  977.         "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
  978.         "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
  979.         "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
  980.         "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
  981.         "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
  982.         "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
  983.         "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
  984.         "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
  985.         "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
  986.         "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
  987.         "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
  988.     };
  989.     static const char s_EncodeMarkChars[256][4] = {
  990.         "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
  991.         "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
  992.         "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
  993.         "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
  994.         "+",   "%21", "%22", "%23", "%24", "%25", "%26", "%27",
  995.         "%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F",
  996.         "0",   "1",   "2",   "3",   "4",   "5",   "6",   "7",
  997.         "8",   "9",   "%3A", "%3B", "%3C", "%3D", "%3E", "%3F",
  998.         "%40", "A",   "B",   "C",   "D",   "E",   "F",   "G",
  999.         "H",   "I",   "J",   "K",   "L",   "M",   "N",   "O",
  1000.         "P",   "Q",   "R",   "S",   "T",   "U",   "V",   "W",
  1001.         "X",   "Y",   "Z",   "%5B", "%5C", "%5D", "%5E", "%5F",
  1002.         "%60", "a",   "b",   "c",   "d",   "e",   "f",   "g",
  1003.         "h",   "i",   "j",   "k",   "l",   "m",   "n",   "o",
  1004.         "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
  1005.         "x",   "y",   "z",   "%7B", "%7C", "%7D", "%7E", "%7F",
  1006.         "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
  1007.         "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
  1008.         "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
  1009.         "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
  1010.         "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
  1011.         "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
  1012.         "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
  1013.         "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
  1014.         "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
  1015.         "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
  1016.         "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
  1017.         "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
  1018.         "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
  1019.         "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
  1020.         "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
  1021.         "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
  1022.     };
  1023.     string url_str;
  1024.     SIZE_TYPE len = str.length();
  1025.     if ( !len )
  1026.         return url_str;
  1027.     const char (*encode_table)[4] =
  1028.         encode_mark_chars == eUrlEncode_SkipMarkChars
  1029.         ? s_Encode : s_EncodeMarkChars;
  1030.     SIZE_TYPE pos;
  1031.     SIZE_TYPE url_len = len;
  1032.     const unsigned char* cstr = (const unsigned char*)str.c_str();
  1033.     for (pos = 0;  pos < len;  pos++) {
  1034.         if (encode_table[cstr[pos]][0] == '%')
  1035.             url_len += 2;
  1036.     }
  1037.     url_str.reserve(url_len + 1);
  1038.     url_str.resize(url_len);
  1039.     SIZE_TYPE p = 0;
  1040.     for (pos = 0;  pos < len;  pos++, p++) {
  1041.         const char* subst = encode_table[cstr[pos]];
  1042.         if (*subst != '%') {
  1043.             url_str[p] = *subst;
  1044.         } else {
  1045.             url_str[  p] = '%';
  1046.             url_str[++p] = *(++subst);
  1047.             url_str[++p] = *(++subst);
  1048.         }
  1049.     }
  1050.     _ASSERT( p == url_len );
  1051.     url_str[url_len] = '';
  1052.     return url_str;
  1053. }
  1054. END_NCBI_SCOPE
  1055. /*
  1056. * ===========================================================================
  1057. * $Log: ncbicgi.cpp,v $
  1058. * Revision 1000.2  2004/06/01 18:39:19  gouriano
  1059. * PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.76
  1060. *
  1061. * Revision 1.76  2004/05/17 20:56:50  gorelenk
  1062. * Added include of PCH ncbi_pch.hpp
  1063. *
  1064. * Revision 1.75  2003/11/24 18:14:58  ucko
  1065. * CCgiRequest::ParseEntries: Swallow "amp;" (case-insensitive) after &
  1066. * for the sake of some older browsers that misparse HREF attributes.
  1067. *
  1068. * Revision 1.74  2003/10/15 17:06:02  ucko
  1069. * CCgiRequest::x_Init: truncate str properly in the case of unexpected EOF.
  1070. *
  1071. * Revision 1.73  2003/08/20 19:41:34  ucko
  1072. * CCgiRequest::ParseEntries: handle unencoded = signs in values.
  1073. *
  1074. * Revision 1.72  2003/07/14 20:28:46  vakatov
  1075. * CCgiCookie::GetExpDate() -- date format to conform with RFC1123
  1076. *
  1077. * Revision 1.71  2003/07/08 19:04:12  ivanov
  1078. * Added optional parameter to the URL_Encode() to enable mark charactres
  1079. * encoding
  1080. *
  1081. * Revision 1.70  2003/06/04 00:22:53  ucko
  1082. * Improve diagnostics in CCgiRequest::x_Init.
  1083. *
  1084. * Revision 1.69  2003/04/16 21:48:19  vakatov
  1085. * Slightly improved logging format, and some minor coding style fixes.
  1086. *
  1087. * Revision 1.68  2003/03/12 16:10:23  kuznets
  1088. * iterate -> ITERATE
  1089. *
  1090. * Revision 1.67  2003/03/11 19:17:31  kuznets
  1091. * Improved error diagnostics in CCgiRequest
  1092. *
  1093. * Revision 1.66  2003/02/24 20:01:54  gouriano
  1094. * use template-based exceptions instead of errno and parse exceptions
  1095. *
  1096. * Revision 1.65  2003/02/19 17:50:47  kuznets
  1097. * Added function AddExpTime to CCgiCookie class
  1098. *
  1099. * Revision 1.64  2002/09/17 19:57:50  ucko
  1100. * Add position field to CGI entries; minor reformatting.
  1101. *
  1102. * Revision 1.63  2002/08/08 19:26:04  ucko
  1103. * When parsing multipart forms, allow (but ignore) data after the
  1104. * trailing boundary.
  1105. *
  1106. * Revision 1.62  2002/07/19 14:50:23  ucko
  1107. * Substitute NStr::Compare for uses of string::compare added in last revision.
  1108. *
  1109. * Revision 1.61  2002/07/18 21:15:24  ucko
  1110. * Rewrite multipart/form-data parser; the old line-by-line approach was
  1111. * somewhat clumsy and inefficient.
  1112. *
  1113. * Revision 1.60  2002/07/18 20:18:09  lebedev
  1114. * NCBI_OS_MAC: STDIN_FILENO define added
  1115. *
  1116. * Revision 1.59  2002/07/17 17:02:26  ucko
  1117. * Reinstate more lost changes from R1.56.
  1118. *
  1119. * Revision 1.58  2002/07/11 17:39:56  gouriano
  1120. * corrected wrong merge of v1.56 and v1.57
  1121. *
  1122. * Revision 1.57  2002/07/11 14:22:59  gouriano
  1123. * exceptions replaced by CNcbiException-type ones
  1124. *
  1125. * Revision 1.56  2002/07/10 18:40:21  ucko
  1126. * Made CCgiEntry-based functions the only version; kept "Ex" names as
  1127. * temporary synonyms, to go away in a few days.
  1128. *
  1129. * Revision 1.55  2002/07/05 20:45:46  ucko
  1130. * Add a couple of missing calls to the extended version of s_AddEntry.
  1131. *
  1132. * Revision 1.54  2002/07/03 20:24:31  ucko
  1133. * Extend to support learning uploaded files' names; move CVS logs to end.
  1134. *
  1135. * Revision 1.53  2002/04/26 21:18:31  lavr
  1136. * Do not enforce the last line of the form to have HTTP_EOL (but just EOF)
  1137. *
  1138. * Revision 1.52  2002/01/10 23:48:55  vakatov
  1139. * s_ParseMultipartEntries() -- allow for empty parts
  1140. *
  1141. * Revision 1.51  2001/12/06 00:19:55  vakatov
  1142. * CCgiRequest::ParseEntries() -- allow leading '&' in the query string (temp.)
  1143. *
  1144. * Revision 1.50  2001/10/04 18:17:53  ucko
  1145. * Accept additional query parameters for more flexible diagnostics.
  1146. * Support checking the readiness of CGI input and output streams.
  1147. *
  1148. * Revision 1.49  2001/06/19 20:08:30  vakatov
  1149. * CCgiRequest::{Set,Get}InputStream()  -- to provide safe access to the
  1150. * requests' content body
  1151. *
  1152. * Revision 1.48  2001/06/13 21:04:37  vakatov
  1153. * Formal improvements and general beautifications of the CGI lib sources.
  1154. *
  1155. * Revision 1.47  2001/05/17 15:01:49  lavr
  1156. * Typos corrected
  1157. *
  1158. * Revision 1.46  2001/02/02 20:55:13  vakatov
  1159. * CCgiRequest::GetEntry() -- "const"
  1160. *
  1161. * Revision 1.45  2001/01/30 23:17:31  vakatov
  1162. * + CCgiRequest::GetEntry()
  1163. *
  1164. * Revision 1.44  2000/11/01 20:36:31  vasilche
  1165. * Added HTTP_EOL string macro.
  1166. *
  1167. * Revision 1.43  2000/06/26 16:34:27  vakatov
  1168. * CCgiCookies::Add(const string&) -- maimed to workaround MS IE bug
  1169. * (it sent empty cookies w/o "=" in versions prior to 5.5)
  1170. *
  1171. * Revision 1.42  2000/05/04 16:29:12  vakatov
  1172. * s_ParsePostQuery():  do not throw on an unknown Content-Type
  1173. *
  1174. * Revision 1.41  2000/05/02 16:10:07  vasilche
  1175. * CGI: Fixed URL parsing when Content-Length header is missing.
  1176. *
  1177. * Revision 1.40  2000/05/01 16:58:18  vasilche
  1178. * Allow missing Content-Length and Content-Type headers.
  1179. *
  1180. * Revision 1.39  2000/02/15 19:25:33  vakatov
  1181. * CCgiRequest::ParseEntries() -- fixed UMR
  1182. *
  1183. * Revision 1.38  2000/02/01 22:19:57  vakatov
  1184. * CCgiRequest::GetRandomProperty() -- allow to retrieve value of
  1185. * properties whose names are not prefixed by "HTTP_" (optional).
  1186. * Get rid of the aux.methods GetServerPort() and GetRemoteAddr() which
  1187. * are obviously not widely used (but add to the volume of API).
  1188. *
  1189. * Revision 1.37  2000/02/01 16:53:29  vakatov
  1190. * CCgiRequest::x_Init() -- parse "$QUERY_STRING"(or cmd.-line arg) even
  1191. * in the case of "POST" method, in order to catch possible "ACTION" args.
  1192. *
  1193. * Revision 1.36  2000/01/20 17:52:07  vakatov
  1194. * Two CCgiRequest:: constructors:  one using raw "argc", "argv", "envp",
  1195. * and another using auxiliary classes "CNcbiArguments" and "CNcbiEnvironment".
  1196. * + constructor flag CCgiRequest::fOwnEnvironment to take ownership over
  1197. * the passed "CNcbiEnvironment" object.
  1198. *
  1199. * Revision 1.35  1999/12/30 22:11:17  vakatov
  1200. * CCgiCookie::GetExpDate() -- use a more standard time string format.
  1201. * CCgiCookie::CCgiCookie() -- check the validity of passed cookie attributes
  1202. *
  1203. * Revision 1.34  1999/11/22 19:07:47  vakatov
  1204. * CCgiRequest::CCgiRequest() -- check for the NULL "query_string"
  1205. *
  1206. * Revision 1.33  1999/11/02 22:15:50  vakatov
  1207. * const CCgiCookie* CCgiCookies::Find() -- forgot to cast to non-const "this"
  1208. *
  1209. * Revision 1.32  1999/11/02 20:35:42  vakatov
  1210. * Redesigned of CCgiCookie and CCgiCookies to make them closer to the
  1211. * cookie standard, smarter, and easier in use
  1212. *
  1213. * Revision 1.31  1999/06/21 16:04:17  vakatov
  1214. * CCgiRequest::CCgiRequest() -- the last(optional) arg is of type
  1215. * "TFlags" rather than the former "bool"
  1216. *
  1217. * Revision 1.30  1999/05/11 03:11:51  vakatov
  1218. * Moved the CGI API(along with the relevant tests) from "corelib/" to "cgi/"
  1219. *
  1220. * Revision 1.29  1999/05/04 16:14:45  vasilche
  1221. * Fixed problems with program environment.
  1222. * Added class CNcbiEnvironment for cached access to C environment.
  1223. *
  1224. * Revision 1.28  1999/05/04 00:03:12  vakatov
  1225. * Removed the redundant severity arg from macro ERR_POST()
  1226. *
  1227. * Revision 1.27  1999/05/03 20:32:29  vakatov
  1228. * Use the (newly introduced) macro from <corelib/ncbidbg.h>:
  1229. *   RETHROW_TRACE,
  1230. *   THROW0_TRACE(exception_class),
  1231. *   THROW1_TRACE(exception_class, exception_arg),
  1232. *   THROW_TRACE(exception_class, exception_args)
  1233. * instead of the former (now obsolete) macro _TRACE_THROW.
  1234. *
  1235. * Revision 1.26  1999/04/30 19:21:03  vakatov
  1236. * Added more details and more control on the diagnostics
  1237. * See #ERR_POST, EDiagPostFlag, and ***DiagPostFlag()
  1238. *
  1239. * Revision 1.25  1999/04/28 16:54:42  vasilche
  1240. * Implemented stream input processing for FastCGI applications.
  1241. * Fixed POST request parsing
  1242. *
  1243. * Revision 1.24  1999/04/14 21:01:22  vakatov
  1244. * s_HexChar():  get rid of "::tolower()"
  1245. *
  1246. * Revision 1.23  1999/04/14 20:11:56  vakatov
  1247. * + <stdio.h>
  1248. * Changed all "str.compare(...)" to "NStr::Compare(str, ...)"
  1249. *
  1250. * Revision 1.22  1999/04/14 17:28:58  vasilche
  1251. * Added parsing of CGI parameters from IMAGE input tag like "cmd.x=1&cmd.y=2"
  1252. * As a result special parameter is added with empty name: "=cmd"
  1253. *
  1254. * Revision 1.21  1999/03/01 21:02:22  vasilche
  1255. * Added parsing of 'form-data' requests.
  1256. *
  1257. * Revision 1.20  1999/01/07 22:03:42  vakatov
  1258. * s_URL_Decode():  typo fixed
  1259. *
  1260. * Revision 1.19  1999/01/07 21:15:22  vakatov
  1261. * Changed prototypes for URL_DecodeString() and URL_EncodeString()
  1262. *
  1263. * Revision 1.18  1999/01/07 20:06:04  vakatov
  1264. * + URL_DecodeString()
  1265. * + URL_EncodeString()
  1266. *
  1267. * Revision 1.17  1998/12/28 17:56:36  vakatov
  1268. * New CVS and development tree structure for the NCBI C++ projects
  1269. *
  1270. * Revision 1.16  1998/12/04 23:38:35  vakatov
  1271. * Workaround SunPro's "buggy const"(see "BW_01")
  1272. * Renamed "CCgiCookies::Erase()" method to "...Clear()"
  1273. *
  1274. * Revision 1.15  1998/12/01 00:27:19  vakatov
  1275. * Made CCgiRequest::ParseEntries() to read ISINDEX data, too.
  1276. * Got rid of now redundant CCgiRequest::ParseIndexesAsEntries()
  1277. *
  1278. * Revision 1.14  1998/11/30 21:23:19  vakatov
  1279. * CCgiRequest:: - by default, interprete ISINDEX data as regular FORM entries
  1280. * + CCgiRequest::ParseIndexesAsEntries()
  1281. * Allow FORM entry in format "name1&name2....." (no '=' necessary after name)
  1282. *
  1283. * Revision 1.13  1998/11/27 20:55:21  vakatov
  1284. * CCgiRequest::  made the input stream arg. be optional(std.input by default)
  1285. *
  1286. * Revision 1.12  1998/11/27 19:44:34  vakatov
  1287. * CCgiRequest::  Engage cmd.-line args if "$REQUEST_METHOD" is undefined
  1288. *
  1289. * Revision 1.11  1998/11/27 15:54:05  vakatov
  1290. * Cosmetics in the ParseEntries() diagnostics
  1291. *
  1292. * Revision 1.10  1998/11/26 00:29:53  vakatov
  1293. * Finished NCBI CGI API;  successfully tested on MSVC++ and SunPro C++ 5.0
  1294. *
  1295. * Revision 1.9  1998/11/24 23:07:30  vakatov
  1296. * Draft(almost untested) version of CCgiRequest API
  1297. *
  1298. * Revision 1.8  1998/11/24 21:31:32  vakatov
  1299. * Updated with the ISINDEX-related code for CCgiRequest::
  1300. * TCgiEntries, ParseIndexes(), GetIndexes(), etc.
  1301. *
  1302. * Revision 1.7  1998/11/24 17:52:17  vakatov
  1303. * Starting to implement CCgiRequest::
  1304. * Fully implemented CCgiRequest::ParseEntries() static function
  1305. *
  1306. * Revision 1.6  1998/11/20 22:36:40  vakatov
  1307. * Added destructor to CCgiCookies:: class
  1308. * + Save the works on CCgiRequest:: class in a "compilable" state
  1309. *
  1310. * Revision 1.4  1998/11/19 23:41:12  vakatov
  1311. * Tested version of "CCgiCookie::" and "CCgiCookies::"
  1312. *
  1313. * Revision 1.3  1998/11/19 20:02:51  vakatov
  1314. * Logic typo:  actually, the cookie string does not contain "Cookie: "
  1315. *
  1316. * Revision 1.2  1998/11/19 19:50:03  vakatov
  1317. * Implemented "CCgiCookies::"
  1318. * Slightly changed "CCgiCookie::" API
  1319. *
  1320. * Revision 1.1  1998/11/18 21:47:53  vakatov
  1321. * Draft version of CCgiCookie::
  1322. * ==========================================================================
  1323. */