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