1 #ifndef CGI___NCBICGI__HPP
2 #define CGI___NCBICGI__HPP
3 
4 /*  $Id: ncbicgi.hpp 633608 2021-06-22 17:37:57Z ivanov $
5 * ===========================================================================
6 *
7 *                            PUBLIC DOMAIN NOTICE
8 *               National Center for Biotechnology Information
9 *
10 *  This software/database is a "United States Government Work" under the
11 *  terms of the United States Copyright Act.  It was written as part of
12 *  the author's official duties as a United States Government employee and
13 *  thus cannot be copyrighted.  This software/database is freely available
14 *  to the public for use. The National Library of Medicine and the U.S.
15 *  Government have not placed any restriction on its use or reproduction.
16 *
17 *  Although all reasonable efforts have been taken to ensure the accuracy
18 *  and reliability of the software and data, the NLM and the U.S.
19 *  Government do not and cannot warrant the performance or results that
20 *  may be obtained by using this software or data. The NLM and the U.S.
21 *  Government disclaim all warranties, express or implied, including
22 *  warranties of performance, merchantability or fitness for any particular
23 *  purpose.
24 *
25 *  Please cite the author in any work or product based on this material.
26 *
27 * ===========================================================================
28 *
29 * Author:  Denis Vakatov
30 *
31 * File Description:
32 *   NCBI C++ CGI API:
33 *      CCgiCookie    -- one CGI cookie
34 *      CCgiCookies   -- set of CGI cookies
35 *      CCgiRequest   -- full CGI request
36 */
37 
38 #include <corelib/rwstream.hpp>
39 #include <corelib/stream_utils.hpp>
40 #include <cgi/cgi_util.hpp>
41 #include <cgi/user_agent.hpp>
42 #include <set>
43 
44 
45 /** @addtogroup CGIReqRes
46  *
47  * @{
48  */
49 
50 
51 #define HTTP_EOL "\r\n"
52 
53 
54 BEGIN_NCBI_SCOPE
55 
56 class CTime;
57 class CCgiSession;
58 class CCgiEntryReaderContext;
59 
60 ///////////////////////////////////////////////////////
61 ///
62 ///  CCgiCookie::
63 ///
64 /// HTTP cookie class
65 ///
66 
67 
68 class NCBI_XCGI_EXPORT CCgiCookie
69 {
70 public:
71     /// Copy constructor
72     CCgiCookie(const CCgiCookie& cookie);
73 
74     /// Throw the "invalid_argument" if "name" or "value" have invalid format
75     ///  - the "name" must not be empty; it must not contain '='
76     ///  - "name", "value", "domain" -- must consist of printable ASCII
77     ///    characters, and not: semicolons(;), commas(,), or space characters.
78     ///  - "path" -- can have space characters.
79     CCgiCookie(const string& name, const string& value,
80                const string& domain = NcbiEmptyString,
81                const string& path   = NcbiEmptyString);
82 
83     /// The cookie name cannot be changed during its whole timelife
84     const string& GetName(void) const;
85 
86     /// Whether the cookie is sent as a part of HTTP request or HTTP response
87     enum EWriteMethod {
88         eHTTPResponse,
89         eHTTPRequest
90     };
91 
92     /// Compose and write to output stream "os":
93     /// @param wmethod
94     /// - eHTTPResponse:
95     ///  "Set-Cookie: name=value; expires=date; path=val_path; domain=dom_name;
96     ///   secure\r\n"
97     ///   Here, only "name=value" is mandatory, and other parts are optional
98     /// - eHTTPRequest:
99     ///  "name=value"
100     ///   Here, only "name=value" is printed, all other parts are ignored
101     CNcbiOstream& Write(CNcbiOstream& os,
102                         EWriteMethod wmethod = eHTTPResponse,
103                         EUrlEncode   flag = eUrlEncode_SkipMarkChars) const;
104 
105     /// Reset everything but name to default state like CCgiCookie(m_Name, "")
106     void Reset(void);
107 
108     /// Set all attribute values(but name!) to those from "cookie"
109     void CopyAttributes(const CCgiCookie& cookie);
110 
111     /// All SetXXX(const string&) methods beneath:
112     ///  - set the property to "str" if "str" has valid format
113     ///  - throw the "invalid_argument" if "str" has invalid format
114     void SetValue  (const string& str);
115     void SetDomain (const string& str);    // not spec'd by default
116     void SetPath   (const string& str);    // not spec'd by default
117     void SetExpDate(const tm& exp_date);   // GMT time (infinite if all zeros)
118     void SetExpTime(const CTime& exp_time);// GMT time (infinite if all zeros)
119     void SetSecure (bool secure);          // FALSE by default
120     void SetHttpOnly(bool http_only);      // FALSE by default
121 
122     /// All "const string& GetXXX(...)" methods beneath return reference
123     /// to "NcbiEmptyString" if the requested attributre is not set
124     const string& GetValue  (void) const;
125     const string& GetDomain (void) const;
126     const string& GetPath   (void) const;
127     /// Day, dd-Mon-yyyy hh:mm:ss GMT  (return empty string if not set)
128     string        GetExpDate(void) const;
129     /// If exp.date is not set then return FALSE and dont assign "*exp_date"
130     bool GetExpDate(tm* exp_date) const;
131     bool GetSecure(void)          const;
132     bool GetHttpOnly(void)        const;
133 
134     /// Compare two cookies
135     bool operator< (const CCgiCookie& cookie) const;
136 
137     /// Predicate for the cookie comparison
138     typedef const CCgiCookie* TCPtr;
139     struct PLessCPtr {
operator ()CCgiCookie::PLessCPtr140         bool operator() (const TCPtr& c1, const TCPtr& c2) const {
141             return (*c1 < *c2);
142         }
143     };
144 
145     enum EInvalidFlag {
146         fValid         = 0,
147         fInvalid_Name  = 1<<0,
148         fInvalid_Value = 1<<1,
149         fInvalid_Any   = fInvalid_Name | fInvalid_Value
150     };
151     typedef int TInvalidFlag;
152 
153     TInvalidFlag IsInvalid(void) const;
154     void SetInvalid(TInvalidFlag flag);   // Set invalid flag bit
155     void ResetInvalid(TInvalidFlag flag); // Clear invalid flag bit
156 
157 private:
158     string         m_Name;
159     string         m_Value;
160     string         m_Domain;
161     string         m_Path;
162     tm             m_Expires;  // GMT time zone
163     bool           m_Secure;
164     bool           m_HttpOnly;
165     TInvalidFlag   m_InvalidFlag;
166 
167     enum EFieldType {
168         eField_Name,          // Cookie name
169         eField_Value,         // Cookie value
170         eField_Other          // Other fields (domain etc.)
171     };
172     static void x_CheckField(const string& str,
173                              EFieldType    ftype,
174                              const char*   banned_symbols,
175                              const string* cookie_name = NULL);
176     static string x_EncodeCookie(const string& str,
177                                  EFieldType ftype,
178                                  NStr::EUrlEncode flag);
179 
180     static bool x_GetString(string* str, const string& val);
181     // prohibit default assignment
182     CCgiCookie& operator= (const CCgiCookie&);
183     friend class CCgiCookies;
184 
185 public:
186     // Cookie encoding style - CParam needs this to be public
187     enum ECookieEncoding {
188         eCookieEnc_Url,     // use URL encode/decode
189         eCookieEnc_Quote    // use quoting (don't encode names)
190     };
191 };  // CCgiCookie
192 
193 
194 /* @} */
195 
196 
operator <<(CNcbiOstream & os,const CCgiCookie & cookie)197 inline CNcbiOstream& operator<< (CNcbiOstream& os, const CCgiCookie& cookie)
198 {
199     return cookie.Write(os);
200 }
201 
202 
203 /** @addtogroup CGIReqRes
204  *
205  * @{
206  */
207 
208 
209 ///////////////////////////////////////////////////////
210 ///
211 ///  CCgiCookies::
212 ///
213 /// Set of CGI send-cookies
214 ///
215 ///  The cookie is uniquely identified by {name, domain, path}.
216 ///  "name" is mandatory and non-empty;  "domain" and "path" are optional.
217 ///  "name" and "domain" are not case-sensitive;  "path" is case-sensitive.
218 ///
219 
220 class NCBI_XCGI_EXPORT CCgiCookies
221 {
222 public:
223     typedef set<CCgiCookie*, CCgiCookie::PLessCPtr>  TSet;
224     typedef TSet::iterator         TIter;
225     typedef TSet::const_iterator   TCIter;
226     typedef pair<TIter,  TIter>    TRange;
227     typedef pair<TCIter, TCIter>   TCRange;
228 
229     /// How to handle badly formed cookies
230     enum EOnBadCookie {
231         eOnBadCookie_ThrowException, ///< Throw exception, ignore bad cookie
232         eOnBadCookie_SkipAndError,   ///< Report error, ignore bad cookie
233         eOnBadCookie_Skip,           ///< Silently ignore bad cookie
234         eOnBadCookie_StoreAndError,  ///< Report error, store bad cookie as-is
235         eOnBadCookie_Store           ///< Store bad cookie without URL-decoding
236     };
237 
238     /// Empty set of cookies
239     CCgiCookies(void);
240     /// Use the specified method of string encoding
241     CCgiCookies(EUrlEncode encode_flag);
242     /// Format of the string:  "name1=value1; name2=value2; ..."
243     CCgiCookies(const string& str,
244                 EOnBadCookie  on_bad_cookie = eOnBadCookie_SkipAndError,
245                 EUrlEncode    encode_flag = eUrlEncode_SkipMarkChars);
246     /// Destructor
247     ~CCgiCookies(void);
248 
249     /// Return TRUE if this set contains no cookies
250     bool Empty(void) const;
251 
252     EUrlEncode GetUrlEncodeFlag(void) const;
253     void SetUrlEncodeFlag(EUrlEncode encode_flag);
254 
255     /// All Add() functions:
256     /// if the added cookie has the same {name, domain, path} as an already
257     /// existing one then the new cookie will override the old one
258     CCgiCookie* Add(const string& name,
259                     const string& value,
260                     const string& domain        = kEmptyStr,
261                     const string& path          = kEmptyStr,
262                     EOnBadCookie  on_bad_cookie = eOnBadCookie_SkipAndError);
263 
264     ///
265     CCgiCookie* Add(const string& name,
266                     const string& value,
267                     EOnBadCookie  on_bad_cookie);
268 
269     /// Update with a copy of "cookie"
270     CCgiCookie* Add(const CCgiCookie& cookie);
271 
272     /// Update by a set of cookies
273     void Add(const CCgiCookies& cookies);
274 
275     /// Update with a HTTP request like string:
276     /// "name1=value1; name2=value2; ..."
277     void Add(const string& str,
278              EOnBadCookie  on_bad_cookie = eOnBadCookie_SkipAndError);
279 
280     /// Return NULL if cannot find this exact cookie
281     CCgiCookie*       Find(const string& name,
282                            const string& domain, const string& path);
283     const CCgiCookie* Find(const string& name,
284                            const string& domain, const string& path) const;
285 
286     /// Return the first matched cookie with name "name", or NULL if
287     /// there is no such cookie(s).
288     /// Also, if "range" is non-NULL then assign its "first" and
289     /// "second" fields to the beginning and the end of the range
290     /// of cookies matching the name "name".
291     /// NOTE:  if there is a cookie with empty domain and path then
292     ///        this cookie is guaranteed to be returned.
293     CCgiCookie*       Find(const string& name, TRange*  range=0);
294     const CCgiCookie* Find(const string& name, TCRange* range=0) const;
295 
296     /// Return the full range [begin():end()] on the underlying container
297     TCRange GetAll(void) const;
298 
299     /// Remove "cookie" from this set;  deallocate it if "destroy" is true
300     /// Return FALSE if can not find "cookie" in this set
301     bool Remove(CCgiCookie* cookie, bool destroy=true);
302 
303     /// Remove (and destroy if "destroy" is true) all cookies belonging
304     /// to range "range".  Return # of found and removed cookies.
305     size_t Remove(TRange& range, bool destroy=true);
306 
307     /// Remove (and destroy if "destroy" is true) all cookies with the
308     /// given "name".  Return # of found and removed cookies.
309     size_t Remove(const string& name, bool destroy=true);
310 
311     /// Remove all stored cookies
312     void Clear(void);
313 
314     /// Printout all cookies into the stream "os"
315     /// @sa CCgiCookie::Write
316     CNcbiOstream& Write(CNcbiOstream& os,
317                         CCgiCookie::EWriteMethod wmethod
318                                = CCgiCookie::eHTTPResponse) const;
319 
320     /// Set secure connection flag. Affects printing of cookies:
321     /// secure cookies can be sent only trough secure connections.
SetSecure(bool secure)322     void SetSecure(bool secure) { m_Secure = secure; }
323 
324 private:
325     enum ECheckResult {
326         eCheck_Valid,         // Cookie is valid
327         eCheck_SkipInvalid,   // Cookie is invalid and should be ignored
328         eCheck_StoreInvalid   // Cookie is invalid but should be stored
329     };
330     static ECheckResult x_CheckField(const string& str,
331                                      CCgiCookie::EFieldType ftype,
332                                      const char*   banned_symbols,
333                                      EOnBadCookie  on_bad_cookie,
334                                      const string* cookie_name = NULL);
335 
336     NStr::EUrlEncode m_EncodeFlag;
337     TSet             m_Cookies;
338     bool             m_Secure;
339 
340     /// prohibit default initialization and assignment
341     CCgiCookies(const CCgiCookies&);
342     CCgiCookies& operator= (const CCgiCookies&);
343 };
344 
345 
346 /// Parameter to control error handling of incoming cookies.
347 /// Does not affect error handling of outgoing cookies set by the
348 /// application.
349 NCBI_PARAM_ENUM_DECL_EXPORT(NCBI_XCGI_EXPORT,
350                             CCgiCookies::EOnBadCookie,
351                             CGI, On_Bad_Cookie);
352 
353 /* @} */
354 
355 
operator <<(CNcbiOstream & os,const CCgiCookies & cookies)356 inline CNcbiOstream& operator<< (CNcbiOstream& os, const CCgiCookies& cookies)
357 {
358     return cookies.Write(os);
359 }
360 
361 
362 /** @addtogroup CGIReqRes
363  *
364  * @{
365  */
366 
367 
368 /// Set of "standard" HTTP request properties
369 /// @sa CCgiRequest
370 enum ECgiProp {
371     // server properties
372     eCgi_ServerSoftware = 0,
373     eCgi_ServerName,
374     eCgi_GatewayInterface,
375     eCgi_ServerProtocol,
376     eCgi_ServerPort,        // see also "GetServerPort()"
377 
378     // client properties
379     eCgi_RemoteHost,
380     eCgi_RemoteAddr,        // see also "GetRemoteAddr()"
381 
382     // client data properties
383     eCgi_ContentType,
384     eCgi_ContentLength,     // see also "GetContentLength()"
385 
386     // request properties
387     eCgi_RequestMethod,
388     eCgi_PathInfo,
389     eCgi_PathTranslated,
390     eCgi_ScriptName,
391     eCgi_QueryString,
392 
393     // authentication info
394     eCgi_AuthType,
395     eCgi_RemoteUser,
396     eCgi_RemoteIdent,
397 
398     // semi-standard properties(from HTTP header)
399     eCgi_HttpAccept,
400     eCgi_HttpCookie,
401     eCgi_HttpIfModifiedSince,
402     eCgi_HttpReferer,
403     eCgi_HttpUserAgent,
404 
405     // # of CCgiRequest-supported standard properties
406     // for internal use only!
407     eCgi_NProperties
408 };  // ECgiProp
409 
410 
411 /// @sa CCgiRequest
412 class NCBI_XCGI_EXPORT CCgiEntry // copy-on-write semantics
413 {
414 private:
415     struct SData : public CObject
416     {
SDataCCgiEntry::SData417         SData(const string& value, const string& filename,
418               unsigned int position, const string& type)
419             : m_Value(value), m_Filename(filename), m_ContentType(type),
420               m_Position(position) { }
SDataCCgiEntry::SData421         SData(const SData& data)
422             : CObject(),
423               m_Value(data.m_Value), m_Filename(data.m_Filename),
424               m_ContentType(data.m_ContentType),
425               m_Position(data.m_Position)
426             { _ASSERT( !data.m_Reader.get() ); }
427 
428         string            m_Value, m_Filename, m_ContentType;
429         unsigned int      m_Position;
430         unique_ptr<IReader> m_Reader;
431     };
432 
433 public:
CCgiEntry(const string & value=kEmptyStr,const string & filename=kEmptyStr,unsigned int position=0,const string & type=kEmptyStr)434     CCgiEntry(const string& value    = kEmptyStr,
435               // default empty value for 1 constructor only
436               const string& filename = kEmptyStr,
437               unsigned int  position = 0,
438               const string& type     = kEmptyStr)
439         : m_Data(new SData(value, filename, position, type)) { }
CCgiEntry(const char * value,const string & filename=kEmptyStr,unsigned int position=0,const string & type=kEmptyStr)440     CCgiEntry(const char*   value,
441               const string& filename = kEmptyStr,
442               unsigned int  position = 0,
443               const string& type     = kEmptyStr)
444         : m_Data(new SData(value, filename, position, type)) { }
CCgiEntry(const CCgiEntry & e)445     CCgiEntry(const CCgiEntry& e)
446         : m_Data(e.m_Data) { }
447 
operator =(const CCgiEntry & e)448     CCgiEntry& operator=(const CCgiEntry& e)
449     {
450         SetValue(e.GetValue());
451         SetFilename(e.GetFilename());
452         SetContentType(e.GetContentType());
453         SetPosition(e.GetPosition());
454         return *this;
455     }
456 
457 
458     /// Get the value as a string, (necessarily) prefetching it all if
459     /// applicable; the result remains available for future calls to
460     /// GetValue and relatives.
461     /// @sa GetValueReader, GetValueStream
GetValue() const462     const string& GetValue() const
463         {
464             if (m_Data->m_Reader.get()) { x_ForceComplete(); }
465             return m_Data->m_Value;
466         }
SetValue()467     string&       SetValue()
468         { x_ForceUnique(); return m_Data->m_Value; }
SetValue(const string & v)469     void          SetValue(const string& v)
470         { x_ForceUnique(); m_Data->m_Value = v; }
SetValue(IReader * r)471     void          SetValue(IReader* r)
472         { x_ForceUnique(); m_Data->m_Reader.reset(r); }
SetValue(CNcbiIstream & is,EOwnership own=eNoOwnership)473     void          SetValue(CNcbiIstream& is, EOwnership own = eNoOwnership)
474         { x_ForceUnique(); m_Data->m_Reader.reset(new CStreamReader(is, own)); }
475 
476     /// Get the value via a reader, potentially on the fly -- in which
477     /// case the caller takes ownership of the source, and subsequent
478     /// calls to GetValue and relatives will yield NO data.  (In
479     /// either case, the caller owns the resulting object.)
480     /// @sa GetValue, GetValueStream
481     IReader* GetValueReader();
482 
483     /// Get the value as a stream, potentially on the fly -- in which
484     /// case the caller takes ownership of the source, and subsequent
485     /// calls to GetValue and relatives will yield NO data.  (In
486     /// either case, the caller owns the resulting object.)
487     /// @sa GetValue, GetValueReader
488     CNcbiIstream* GetValueStream();
489 
490     /// Action to perform if the explicit charset is not supported
491     enum EOnCharsetError {
492         eCharsetError_Ignore, ///< Ignore unknown charset (try to autodetect)
493         eCharsetError_Throw   ///< Throw exception if charset is not supported
494     };
495 
496     CStringUTF8 GetValueAsUTF8(EOnCharsetError on_error =
497         eCharsetError_Ignore) const;
498 
499     /// Only available for certain fields of POSTed forms
GetFilename() const500     const string& GetFilename() const
501         { return m_Data->m_Filename; }
SetFilename()502     string&       SetFilename()
503         { x_ForceUnique(); return m_Data->m_Filename; }
SetFilename(const string & f)504     void          SetFilename(const string& f)
505         { x_ForceUnique(); m_Data->m_Filename = f; }
506 
507     /// CGI parameter number -- automatic image name parameter is #0,
508     /// explicit parameters start at #1
GetPosition() const509     unsigned int  GetPosition() const
510         { return m_Data->m_Position; }
SetPosition()511     unsigned int& SetPosition()
512         { x_ForceUnique(); return m_Data->m_Position; }
SetPosition(int p)513     void          SetPosition(int p)
514         { x_ForceUnique(); m_Data->m_Position = p; }
515 
516     /// May be available for some fields of POSTed forms
GetContentType() const517     const string& GetContentType() const
518         { return m_Data->m_ContentType; }
SetContentType()519     string&       SetContentType()
520         { x_ForceUnique(); return m_Data->m_ContentType; }
SetContentType(const string & f)521     void          SetContentType(const string& f)
522         { x_ForceUnique(); m_Data->m_ContentType = f; }
523 
operator const string&() const524     operator const string&() const       { return GetValue(); }
operator string&()525     operator       string&()             { return SetValue(); }
operator const CTempStringEx() const526     operator const CTempStringEx() const { return CTempStringEx(GetValue()); }
527 
528     /// commonly-requested string:: operations...
size() const529     SIZE_TYPE size() const             { return GetValue().size(); }
empty() const530     bool empty() const                 { return GetValue().empty(); }
c_str() const531     const char* c_str() const          { return GetValue().c_str(); }
compare(const string & s) const532     int compare(const string& s) const { return GetValue().compare(s); }
compare(const char * s) const533     int compare(const char* s) const   { return GetValue().compare(s); }
substr(SIZE_TYPE i=0,SIZE_TYPE n=NPOS) const534     string substr(SIZE_TYPE i = 0, SIZE_TYPE n = NPOS) const
535         { return GetValue().substr(i, n); }
find(const char * s,SIZE_TYPE pos=0) const536     SIZE_TYPE find(const char* s, SIZE_TYPE pos = 0) const
537         { return GetValue().find(s, pos); }
find(const string & s,SIZE_TYPE pos=0) const538     SIZE_TYPE find(const string& s, SIZE_TYPE pos = 0) const
539         { return GetValue().find(s, pos); }
find(char c,SIZE_TYPE pos=0) const540     SIZE_TYPE find(char c, SIZE_TYPE pos = 0) const
541         { return GetValue().find(c, pos); }
find_first_of(const string & s,SIZE_TYPE pos=0) const542     SIZE_TYPE find_first_of(const string& s, SIZE_TYPE pos = 0) const
543         { return GetValue().find_first_of(s, pos); }
find_first_of(const char * s,SIZE_TYPE pos=0) const544     SIZE_TYPE find_first_of(const char* s, SIZE_TYPE pos = 0) const
545         { return GetValue().find_first_of(s, pos); }
546 
547 
operator ==(const CCgiEntry & e2) const548     bool operator ==(const CCgiEntry& e2) const
549         {
550             // conservative; some may be irrelevant in many cases
551             return (GetValue() == e2.GetValue()
552                     &&  GetFilename() == e2.GetFilename()
553                     // &&  GetPosition() == e2.GetPosition()
554                     &&  GetContentType() == e2.GetContentType());
555         }
556 
operator !=(const CCgiEntry & v) const557     bool operator !=(const CCgiEntry& v) const
558         {
559             return !(*this == v);
560         }
561 
operator ==(const string & v) const562     bool operator ==(const string& v) const
563         {
564             return GetValue() == v;
565         }
566 
operator !=(const string & v) const567     bool operator !=(const string& v) const
568         {
569             return !(*this == v);
570         }
571 
operator ==(const char * v) const572     bool operator ==(const char* v) const
573         {
574             return GetValue() == v;
575         }
576 
operator !=(const char * v) const577     bool operator !=(const char* v) const
578         {
579             return !(*this == v);
580         }
581 
582 private:
x_ForceUnique()583     void x_ForceUnique()
584         {
585             if ( !m_Data->ReferencedOnlyOnce() ) {
586                 if (m_Data->m_Reader.get()) { x_ForceComplete(); }
587                 m_Data = new SData(*m_Data);
588             }
589         }
590 
591     void x_ForceComplete() const;
592 
593     // Get charset from content type or empty string
594     string x_GetCharset(void) const;
595 
596     CRef<SData> m_Data;
597 };
598 
599 
600 /* @} */
601 
602 inline
operator +(const CCgiEntry & e,const string & s)603 string operator +(const CCgiEntry& e, const string& s)
604 {
605     return e.GetValue() + s;
606 }
607 
608 inline
operator +(const string & s,const CCgiEntry & e)609 string operator +(const string& s, const CCgiEntry& e)
610 {
611     return s + e.GetValue();
612 }
613 
614 inline
operator <<(CNcbiOstream & o,const CCgiEntry & e)615 CNcbiOstream& operator <<(CNcbiOstream& o, const CCgiEntry& e)
616 {
617     return o << e.GetValue();
618     // filename omitted in case anything depends on just getting the value
619 }
620 
621 
622 /** @addtogroup CGIReqRes
623  *
624  * @{
625  */
626 
627 
628 // Typedefs
629 typedef map<string, string>                              TCgiProperties;
630 typedef multimap<string, CCgiEntry, PNocase_Conditional> TCgiEntries;
631 typedef TCgiEntries::iterator                            TCgiEntriesI;
632 typedef TCgiEntries::const_iterator                      TCgiEntriesCI;
633 typedef list<string>                                     TCgiIndexes;
634 
635 // Forward class declarations
636 class CNcbiArguments;
637 class CNcbiEnvironment;
638 class CTrackingEnvHolder;
639 
640 
641 // Base helper class for building query string from request arguments.
642 class CEntryCollector_Base {
643 public:
~CEntryCollector_Base(void)644     virtual ~CEntryCollector_Base(void) {}
645     virtual void AddEntry(const string& name,
646                           const string& value,
647                           const string& filename,
648                           bool          is_index = false) = 0;
649 };
650 
651 
652 class NCBI_XCGI_EXPORT CExtraEntryCollector : public CEntryCollector_Base {
653 public:
CExtraEntryCollector(void)654     CExtraEntryCollector(void) {}
~CExtraEntryCollector(void)655     ~CExtraEntryCollector(void) override {}
656 
657     void AddEntry(const string& name,
658                   const string& value,
659                   const string& filename,
660                   bool          is_index) override;
661 
GetArgs(void)662     CDiagContext_Extra::TExtraArgs& GetArgs(void) { return m_Args; }
663 
664 private:
665     CDiagContext_Extra::TExtraArgs m_Args;
666 };
667 
668 
669 ///////////////////////////////////////////////////////
670 ///
671 ///  CCgiRequest::
672 ///
673 /// The CGI request class
674 ///
675 
676 class NCBI_XCGI_EXPORT CCgiRequest
677 {
678 public:
679     /// Startup initialization
680     ///
681     ///  - retrieve request's properties and cookies from environment;
682     ///  - retrieve request's entries from "$QUERY_STRING".
683     ///
684     /// If "$REQUEST_METHOD" == "POST" and "$CONTENT_TYPE" is empty or either
685     /// "application/x-www-form-urlencoded" or "multipart/form-data",
686     /// and "fDoNotParseContent" flag is not set,
687     /// then retrieve and parse entries from the input stream "istr".
688     /// If "$CONTENT_TYPE" is empty, the contents is not stripped from
689     /// the stream but remains available (pushed back) whether or not
690     /// the form parsing was successful.
691     ///
692     /// If "$REQUEST_METHOD" is undefined then try to retrieve the request's
693     /// entries from the 1st cmd.-line argument, and do not use "$QUERY_STRING"
694     /// and "istr" at all.
695     typedef int TFlags;
696     enum Flags {
697         /// do not handle indexes as regular FORM entries with empty value
698         fIndexesNotEntries          = (1 << 0),
699         /// do not parse $QUERY_STRING (or cmd.line if $REQUEST_METHOD not def)
700         fIgnoreQueryString          = (1 << 1),
701         /// own the passed "env" (and destroy it in the destructor)
702         fOwnEnvironment             = (1 << 2),
703         /// do not automatically parse the request's content body (from "istr")
704         fDoNotParseContent          = (1 << 3),
705         /// use case insensitive CGI arguments
706         fCaseInsensitiveArgs        = (1 << 4),
707         /// Do not use URL-encoding/decoding for cookies
708         fCookies_Unencoded          = (1 << 5),
709         /// Use hex code for encoding spaces rather than '+'
710         fCookies_SpaceAsHex         = (1 << 6),
711         /// Save request content (available through GetContent())
712         fSaveRequestContent         = (1 << 7),
713         /// Do not check if page hit id is present, do not generate one
714         /// if it's missing.
715         fIgnorePageHitId            = (1 << 8),
716         /// Set client-ip and session-id properties for logging.
717         fSkipDiagProperties         = (1 << 9),
718         /// Old (deprecated) flag controlling diag properties.
719         fSetDiagProperties          = 0,
720         /// Enable on-demand parsing via GetNextEntry()
721         fParseInputOnDemand         = (1 << 10),
722         /// Do not treat semicolon as query string argument separator
723         fSemicolonIsNotArgDelimiter = (1 << 11),
724         /// Do not set outgoing tracking cookie. This can also be
725         /// done per-request using CCgiResponce::DisableTrackingCookie().
726         fDisableTrackingCookie      = (1 << 12),
727         /// When parsing input on demand iterate all existing entries (e.g. those
728         /// read from QUERY_STRING) before parsing POST data.
729         /// @sa fParseInputOnDemand
730         fIncludePreparsedEntries = (1 << 13)
731     };
732     CCgiRequest(const         CNcbiArguments*   args = 0,
733                 const         CNcbiEnvironment* env  = 0,
734                 CNcbiIstream* istr  = 0 /*NcbiCin*/,
735                 TFlags        flags = 0,
736                 int           ifd   = -1,
737                 size_t        errbuf_size = 256);
738     /// args := CNcbiArguments(argc,argv), env := CNcbiEnvironment(envp)
739     CCgiRequest(int                argc,
740                 const char* const* argv,
741                 const char* const* envp  = 0,
742                 CNcbiIstream*      istr  = 0,
743                 TFlags             flags = 0,
744                 int                ifd   = -1,
745                 size_t             errbuf_size = 256);
746     CCgiRequest(CNcbiIstream& is,
747                 TFlags        flags = 0,
748                 size_t        errbuf_size = 256);
749 
750     /// Destructor
751     ~CCgiRequest(void);
752 
753     /// Get name (not value!) of a "standard" property
754     static const string GetPropertyName(ECgiProp prop);
755 
756     /// Get value of a "standard" property (return empty string if not defined)
757     const string& GetProperty(ECgiProp prop) const;
758 
759     /// Get value of a random client property;  if "http" is TRUE then add
760     /// prefix "HTTP_" to the property name.
761     //
762     /// NOTE: usually, the value is extracted from the environment variable
763     ///       named "$[HTTP]_<key>". Be advised, however, that in the case of
764     ///       FastCGI application, the set (and values) of env.variables change
765     ///       from request to request, and they differ from those returned
766     ///       by CNcbiApplication::GetEnvironment()!
767     const string& GetRandomProperty(const string& key, bool http = true) const;
768 
769     /// Get content length using value of the property 'eCgi_ContentLength'.
770     /// Return "kContentLengthUnknown" if Content-Length header is missing.
771     static const size_t kContentLengthUnknown;
772     size_t GetContentLength(void) const;
773 
774     /// Get request content. The content is saved only when fSaveRequestContent
775     /// flag is set. Otherwise the method will throw an exception.
776     const string& GetContent(void) const;
777 
778     /// Retrieve the request cookies
779     const CCgiCookies& GetCookies(void) const;
780     CCgiCookies& GetCookies(void);
781 
782     /// Get a set of entries(decoded) received from the client.
783     /// Also includes "indexes" if "indexes_as_entries" in the
784     /// constructor was TRUE (default).
785     /// NOTE: In the "fParseInputOnDemand" mode not all of the entries
786     ///       may be loaded at the time of the call.
787     const TCgiEntries& GetEntries(void) const;
788     TCgiEntries& GetEntries(void);
789 
790     /// Get entry value by name
791     ///
792     /// NOTE 1: There can be more than one entry with the same name;
793     ///         only one of these entry will be returned. To get all matches
794     ///         use GetEntries() and "multimap::" member functions.
795     /// NOTE 2: In the "fParseInputOnDemand" mode not all of the entries
796     ///         may be loaded at the time of the call, so -- use
797     ///         GetPossiblyUnparsedEntry() instead.
798     const CCgiEntry& GetEntry(const string& name, bool* is_found = 0) const;
799 
800     /// Get next entry when parsing input on demand.
801     ///
802     /// Returns GetEntries().end() when all entries have been parsed.
803     TCgiEntriesI GetNextEntry(void);
804 
805     /// Get entry value by name, calling GetNextEntry() as needed.
806     ///
807     /// NOTE:  There can be more than one entry with the same name;
808     ///        only one of these entry will be returned.
809     ///
810     /// To get all matches, use GetEntries() and "multimap::" member
811     /// functions.  If there is no such entry, this method will parse
812     /// (and save) any remaining input and return NULL.
813     CCgiEntry* GetPossiblyUnparsedEntry(const string& name);
814 
815     /// Parse any remaining POST content for use by GetEntries() et al.
816     void ParseRemainingContent(void);
817 
818     /// Get a set of indexes(decoded) received from the client.
819     ///
820     /// It will always be empty if "indexes_as_entries" in the constructor
821     /// was TRUE (default).
822     const TCgiIndexes& GetIndexes(void) const;
823     TCgiIndexes& GetIndexes(void);
824 
825     enum ESessionCreateMode {
826         /// If Session does not exist the new one will be created
827         eCreateIfNotExist,
828         /// If Session does not exist the exception will be thrown
829         eDontCreateIfNotExist,
830         ///< Do not try to load or create session
831         eDontLoad
832     };
833 
834     /// Get session
835     ///
836     CCgiSession& GetSession(ESessionCreateMode mode = eCreateIfNotExist) const;
837 
838     /// Return pointer to the input stream.
839     ///
840     /// Return NULL if the input stream is absent, or if it has been
841     /// automagically read and parsed already (the "POST" method, and
842     /// "application/x-www-form-urlencoded" or "multipart/form-data" type,
843     /// and "fDoNotParseContent" flag was not passed to the constructor).
844     CNcbiIstream* GetInputStream(void) const;
845     /// Returns file descriptor of input stream, or -1 if unavailable.
846     int           GetInputFD(void) const;
847 
848     /// Set input stream to "is".
849     ///
850     /// If "own" is set to TRUE then this stream will be destroyed
851     /// as soon as SetInputStream() gets called with another stream pointer.
852     /// NOTE: SetInputStream(0) will be called in ::~CCgiRequest().
853     void SetInputStream(CNcbiIstream* is, bool own = false, int fd = -1);
854 
855     /// Decode the URL-encoded(FORM or ISINDEX) string "str" into a set of
856     /// entries <"name", "value"> and add them to the "entries" set.
857     ///
858     /// The new entries are added without overriding the original ones, even
859     /// if they have the same names.
860     /// FORM    format:  "name1=value1&.....", ('=' and 'value' are optional)
861     /// ISINDEX format:  "val1+val2+val3+....."
862     /// If the "str" is in ISINDEX format then the entry "value" will be empty.
863     /// On success, return zero;  otherwise return location(1-based) of error
864     static SIZE_TYPE ParseEntries(const string& str, TCgiEntries& entries);
865 
866     /// Decode the URL-encoded string "str" into a set of ISINDEX-like entries
867     /// and add them to the "indexes" set.
868     /// @return
869     ///   zero on success;  otherwise, 1-based location of error
870     static SIZE_TYPE ParseIndexes(const string& str, TCgiIndexes& indexes);
871 
872     /// Return client tracking environment variables
873     /// These variables are stored in the form "name=value".
874     /// The last element in the returned array is 0.
875     const char* const* GetClientTrackingEnv(void) const;
876 
877     /// Serialize/Deserialize a request to/from a stream
878     void Serialize(CNcbiOstream& os) const;
879     void Deserialize(CNcbiIstream& is, TFlags flags = 0);
880 
GetEnvironment() const881     const CNcbiEnvironment& GetEnvironment() const { return *m_Env; }
882 
883     /// Get full set of arguments (both GET and POST), URL-encoded.
884     /// A &-separated list of exclusions can be set in CGI_LOG_EXCLUDE_ARGS
885     /// variable or [CGI] LOG_EXCLUDE_ARGS value in ini file.
886     void GetCGIEntries(CEntryCollector_Base& collector) const;
887 
888     /// Shortcut for collecting arguments into a URL-style string.
889     /// @sa GetCGIEntries
890     string GetCGIEntriesStr(void) const;
891 
892     bool CalcChecksum(string& checksum, string& content) const;
893 
894     /// Standard request methods.
895     enum ERequestMethod {
896         eMethod_GET,
897         eMethod_POST,
898         eMethod_HEAD,
899         eMethod_PUT,
900         eMethod_DELETE,
901         eMethod_OPTIONS,
902         eMethod_TRACE,
903         eMethod_CONNECT,
904         eMethod_Other    ///< Unknown method, use GetRequestMethodName to read.
905     };
906 
907     /// Get request method name.
908     const string& GetRequestMethodName(void) const;
909     /// Get request method.
910     ERequestMethod GetRequestMethod(void) const;
911 
912     /// Store/retrieve tracking cookie value.
913     /// Unlike CCgiResponse or CRequestContext, this value is not empty only
914     /// if the tracking ID was actually received in the request.
SetTrackingCookie(const string & cookie_value)915     void SetTrackingCookie(const string& cookie_value) { m_TrackingCookie = cookie_value; }
GetTrackingCookie(void) const916     const string& GetTrackingCookie(void) const { return m_TrackingCookie; }
917 
918 private:
919     /// set of environment variables
920     const CNcbiEnvironment*    m_Env;
921     unique_ptr<CNcbiEnvironment> m_OwnEnv;
922     /// Original request content or NULL if fSaveRequestContent is not set
923     unique_ptr<string>           m_Content;
924     /// set of the request FORM-like entries(already retrieved; cached)
925     TCgiEntries m_Entries;
926     /// set of the request ISINDEX-like indexes(already retrieved; cached)
927     TCgiIndexes m_Indexes;
928     /// set of the request cookies(already retrieved; cached)
929     CCgiCookies m_Cookies;
930     /// input stream
931     CNcbiIstream* m_Input;
932     /// input file descriptor, if available.
933     int           m_InputFD;
934     bool          m_OwnInput;
935     /// Request initialization error buffer size;
936     /// when initialization code hits unexpected EOF it will try to
937     /// add diagnostics and print out accumulated request buffer
938     /// 0 in this variable means no buffer diagnostics
939     size_t        m_ErrBufSize;
940     string        m_TrackingCookie;
941 
942     bool m_QueryStringParsed;
943     /// the real constructor code
944     void x_Init(const CNcbiArguments*   args,
945                 const CNcbiEnvironment* env,
946                 CNcbiIstream*           istr,
947                 TFlags                  flags,
948                 int                     ifd);
949 
950     /// retrieve(and cache) a property of given name
951     const string& x_GetPropertyByName(const string& name) const;
952     /// Parse entries or indexes from "$QUERY_STRING" or cmd.-line args
953     void x_ProcessQueryString(TFlags flags, const CNcbiArguments*  args);
954     /// Parse input stream if needed
955     void x_ProcessInputStream(TFlags flags, CNcbiIstream* istr, int ifd);
956 
957     /// Set client-ip property for logging
958     void x_SetClientIpProperty(TFlags flags) const;
959 
960     /// Set the properties of CRequestContext (HitId, Dtab etc.).
961     void x_InitRequestContext(TFlags flags);
962 
963     /// prohibit default initialization and assignment
964     CCgiRequest(const CCgiRequest&);
965     CCgiRequest& operator= (const CCgiRequest&);
966 
967     string x_RetrieveSessionId() const;
968 
969     mutable unique_ptr<CTrackingEnvHolder> m_TrackingEnvHolder;
970     CCgiSession* m_Session;
971     CCgiEntryReaderContext* m_EntryReaderContext;
972 
973 public:
974     void x_SetSession(CCgiSession& session);
975 
976 };  // CCgiRequest
977 
978 
979 /* @} */
980 
981 
982 /////////////////////////////////////////////////////////////////////////////
983 
984 /////////////////////////////////////////////////////////////////////////////
985 //  IMPLEMENTATION of INLINE functions
986 /////////////////////////////////////////////////////////////////////////////
987 
988 
989 
990 ///////////////////////////////////////////////////////
991 //  CCgiCookie::
992 //
993 
994 
995 // CCgiCookie::SetXXX()
996 
SetValue(const string & str)997 inline void CCgiCookie::SetValue(const string& str) {
998     m_Value = str;
999     m_InvalidFlag &= ~fInvalid_Value;
1000 }
SetDomain(const string & str)1001 inline void CCgiCookie::SetDomain(const string& str) {
1002     x_CheckField(str, eField_Other, " ;", &m_Name);
1003     m_Domain = str;
1004 }
SetPath(const string & str)1005 inline void CCgiCookie::SetPath(const string& str) {
1006     x_CheckField(str, eField_Other, ";,", &m_Name);
1007     m_Path = str;
1008 }
SetExpDate(const tm & exp_date)1009 inline void CCgiCookie::SetExpDate(const tm& exp_date) {
1010     m_Expires = exp_date;
1011 }
SetSecure(bool secure)1012 inline void CCgiCookie::SetSecure(bool secure) {
1013     m_Secure = secure;
1014 }
SetHttpOnly(bool http_only)1015 inline void CCgiCookie::SetHttpOnly(bool http_only) {
1016     m_HttpOnly = http_only;
1017 }
1018 
1019 // CCgiCookie::GetXXX()
1020 
GetName(void) const1021 inline const string& CCgiCookie::GetName(void) const {
1022     return m_Name;
1023 }
GetValue(void) const1024 inline const string& CCgiCookie::GetValue(void) const {
1025     return m_Value;
1026 }
GetDomain(void) const1027 inline const string& CCgiCookie::GetDomain(void) const {
1028     return m_Domain;
1029 }
GetPath(void) const1030 inline const string& CCgiCookie::GetPath(void) const {
1031     return m_Path;
1032 }
GetSecure(void) const1033 inline bool CCgiCookie::GetSecure(void) const {
1034     return m_Secure;
1035 }
GetHttpOnly(void) const1036 inline bool CCgiCookie::GetHttpOnly(void) const {
1037     return m_HttpOnly;
1038 }
1039 
IsInvalid(void) const1040 inline CCgiCookie::TInvalidFlag CCgiCookie::IsInvalid(void) const
1041 {
1042     return m_InvalidFlag;
1043 }
SetInvalid(TInvalidFlag flag)1044 inline void CCgiCookie::SetInvalid(TInvalidFlag flag)
1045 {
1046     m_InvalidFlag |= flag;
1047 }
ResetInvalid(TInvalidFlag flag)1048 inline void CCgiCookie::ResetInvalid(TInvalidFlag flag)
1049 {
1050     m_InvalidFlag &= ~flag;
1051 }
1052 
1053 
1054 ///////////////////////////////////////////////////////
1055 //  CCgiCookies::
1056 //
1057 
CCgiCookies(void)1058 inline CCgiCookies::CCgiCookies(void)
1059     : m_EncodeFlag(NStr::eUrlEnc_SkipMarkChars),
1060       m_Cookies(),
1061       m_Secure(false)
1062 {
1063     return;
1064 }
1065 
CCgiCookies(EUrlEncode encode_flag)1066 inline CCgiCookies::CCgiCookies(EUrlEncode encode_flag)
1067     : m_EncodeFlag(NStr::EUrlEncode(encode_flag)),
1068       m_Cookies(),
1069       m_Secure(false)
1070 {
1071     return;
1072 }
1073 
CCgiCookies(const string & str,EOnBadCookie on_bad_cookie,EUrlEncode encode_flag)1074 inline CCgiCookies::CCgiCookies(const string& str,
1075                                 EOnBadCookie on_bad_cookie,
1076                                 EUrlEncode   encode_flag)
1077     : m_EncodeFlag(NStr::EUrlEncode(encode_flag)),
1078       m_Cookies(),
1079       m_Secure(false)
1080 {
1081     Add(str, on_bad_cookie);
1082 }
1083 
1084 inline
GetUrlEncodeFlag() const1085 EUrlEncode CCgiCookies::GetUrlEncodeFlag() const
1086 {
1087     return EUrlEncode(m_EncodeFlag);
1088 }
1089 
1090 inline
SetUrlEncodeFlag(EUrlEncode encode_flag)1091 void CCgiCookies::SetUrlEncodeFlag(EUrlEncode encode_flag)
1092 {
1093     m_EncodeFlag = NStr::EUrlEncode(encode_flag);
1094 }
1095 
Empty(void) const1096 inline bool CCgiCookies::Empty(void) const
1097 {
1098     return m_Cookies.empty();
1099 }
1100 
Remove(const string & name,bool destroy)1101 inline size_t CCgiCookies::Remove(const string& name, bool destroy)
1102 {
1103     TRange range;
1104     return Find(name, &range) ? Remove(range, destroy) : 0;
1105 }
1106 
~CCgiCookies(void)1107 inline CCgiCookies::~CCgiCookies(void)
1108 {
1109     Clear();
1110 }
1111 
1112 
1113 
1114 ///////////////////////////////////////////////////////
1115 //  CCgiEntry::
1116 //
1117 
1118 
1119 inline
GetValueReader()1120 IReader* CCgiEntry::GetValueReader()
1121 {
1122     if (m_Data->m_Reader.get()) {
1123         return m_Data->m_Reader.release();
1124     } else {
1125         return new CStringReader(m_Data->m_Value);
1126     }
1127 }
1128 
1129 
1130 inline
GetValueStream()1131 CNcbiIstream* CCgiEntry::GetValueStream()
1132 {
1133     if (m_Data->m_Reader.get()) {
1134         return new CRStream(m_Data->m_Reader.release(), 0, 0,
1135                             CRWStreambuf::fOwnReader);
1136     } else {
1137         return new CNcbiIstrstream(GetValue());
1138     }
1139 }
1140 
1141 
1142 inline
x_ForceComplete() const1143 void CCgiEntry::x_ForceComplete() const
1144 {
1145     _ASSERT(m_Data->m_Reader.get());
1146     _ASSERT(m_Data->m_Value.empty());
1147     SData& data = const_cast<SData&>(*m_Data);
1148     unique_ptr<IReader> reader(data.m_Reader.release());
1149     g_ExtractReaderContents(*reader, data.m_Value);
1150 }
1151 
1152 
1153 ///////////////////////////////////////////////////////
1154 //  CCgiRequest::
1155 //
1156 
GetCookies(void) const1157 inline const CCgiCookies& CCgiRequest::GetCookies(void) const {
1158     return m_Cookies;
1159 }
GetCookies(void)1160 inline CCgiCookies& CCgiRequest::GetCookies(void) {
1161     return m_Cookies;
1162 }
1163 
1164 
GetEntries(void) const1165 inline const TCgiEntries& CCgiRequest::GetEntries(void) const {
1166     return m_Entries;
1167 }
GetEntries(void)1168 inline TCgiEntries& CCgiRequest::GetEntries(void) {
1169     return m_Entries;
1170 }
1171 
1172 
GetIndexes(void)1173 inline TCgiIndexes& CCgiRequest::GetIndexes(void) {
1174     return m_Indexes;
1175 }
GetIndexes(void) const1176 inline const TCgiIndexes& CCgiRequest::GetIndexes(void) const {
1177     return m_Indexes;
1178 }
1179 
GetInputStream(void) const1180 inline CNcbiIstream* CCgiRequest::GetInputStream(void) const {
1181     return m_Input;
1182 }
1183 
GetInputFD(void) const1184 inline int CCgiRequest::GetInputFD(void) const {
1185     return m_InputFD;
1186 }
1187 
x_SetSession(CCgiSession & session)1188 inline void CCgiRequest::x_SetSession(CCgiSession& session)
1189 {
1190     m_Session = &session;
1191 }
1192 
1193 END_NCBI_SCOPE
1194 
1195 #endif  /* CGI___NCBICGI__HPP */
1196