1 /*  $Id: cgiapp.cpp 620674 2020-11-26 04:50:29Z lavr $
2 * ===========================================================================
3 *
4 *                            PUBLIC DOMAIN NOTICE
5 *               National Center for Biotechnology Information
6 *
7 *  This software/database is a "United States Government Work" under the
8 *  terms of the United States Copyright Act.  It was written as part of
9 *  the author's official duties as a United States Government employee and
10 *  thus cannot be copyrighted.  This software/database is freely available
11 *  to the public for use. The National Library of Medicine and the U.S.
12 *  Government have not placed any restriction on its use or reproduction.
13 *
14 *  Although all reasonable efforts have been taken to ensure the accuracy
15 *  and reliability of the software and data, the NLM and the U.S.
16 *  Government do not and cannot warrant the performance or results that
17 *  may be obtained by using this software or data. The NLM and the U.S.
18 *  Government disclaim all warranties, express or implied, including
19 *  warranties of performance, merchantability or fitness for any particular
20 *  purpose.
21 *
22 *  Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Eugene Vasilchenko, Denis Vakatov, Anatoliy Kuznetsov
27 *
28 * File Description:
29 *   Definition CGI application class and its context class.
30 */
31 
32 #include <ncbi_pch.hpp>
33 #include <corelib/ncbienv.hpp>
34 #include <corelib/rwstream.hpp>
35 #include <corelib/stream_utils.hpp>
36 #include <corelib/ncbi_system.hpp> // for SuppressSystemMessageBox
37 #include <corelib/rwstream.hpp>
38 #include <corelib/ncbi_safe_static.hpp>
39 #include <corelib/request_ctx.hpp>
40 #include <corelib/ncbi_strings.h>
41 
42 #include <util/multi_writer.hpp>
43 #include <util/cache/cache_ref.hpp>
44 
45 #include <cgi/cgictx.hpp>
46 #include <cgi/cgi_exception.hpp>
47 #include <cgi/cgi_serial.hpp>
48 #include <cgi/error_codes.hpp>
49 #include <signal.h>
50 
51 
52 #ifdef NCBI_OS_UNIX
53 #  include <unistd.h>
54 #endif
55 
56 
57 #define NCBI_USE_ERRCODE_X   Cgi_Application
58 
59 
60 BEGIN_NCBI_SCOPE
61 
62 
63 NCBI_PARAM_DECL(bool, CGI, Print_Http_Referer);
64 NCBI_PARAM_DEF_EX(bool, CGI, Print_Http_Referer, true, eParam_NoThread,
65                   CGI_PRINT_HTTP_REFERER);
66 typedef NCBI_PARAM_TYPE(CGI, Print_Http_Referer) TPrintRefererParam;
67 
68 
69 NCBI_PARAM_DECL(bool, CGI, Print_User_Agent);
70 NCBI_PARAM_DEF_EX(bool, CGI, Print_User_Agent, true, eParam_NoThread,
71                   CGI_PRINT_USER_AGENT);
72 typedef NCBI_PARAM_TYPE(CGI, Print_User_Agent) TPrintUserAgentParam;
73 
74 
75 NCBI_PARAM_DECL(bool, CGI, Print_Self_Url);
76 NCBI_PARAM_DEF_EX(bool, CGI, Print_Self_Url, true, eParam_NoThread,
77                   CGI_PRINT_SELF_URL);
78 typedef NCBI_PARAM_TYPE(CGI, Print_Self_Url) TPrintSelfUrlParam;
79 
80 
81 NCBI_PARAM_DECL(bool, CGI, Print_Request_Method);
82 NCBI_PARAM_DEF_EX(bool, CGI, Print_Request_Method, true, eParam_NoThread,
83     CGI_PRINT_REQUEST_METHOD);
84 typedef NCBI_PARAM_TYPE(CGI, Print_Request_Method) TPrintRequestMethodParam;
85 
86 
87 NCBI_PARAM_DECL(bool, CGI, Allow_Sigpipe);
88 NCBI_PARAM_DEF_EX(bool, CGI, Allow_Sigpipe, false, eParam_NoThread,
89                   CGI_ALLOW_SIGPIPE);
90 typedef NCBI_PARAM_TYPE(CGI, Allow_Sigpipe) TParamAllowSigpipe;
91 
92 // Log any broken connection with status 299 rather than 499.
93 NCBI_PARAM_DEF_EX(bool, CGI, Client_Connection_Interruption_Okay, false,
94                   eParam_NoThread,
95                   CGI_CLIENT_CONNECTION_INTERRUPTION_OKAY);
96 
97 // Severity for logging broken connection errors.
NCBI_PARAM_ENUM_ARRAY(EDiagSev,CGI,Client_Connection_Interruption_Severity)98 NCBI_PARAM_ENUM_ARRAY(EDiagSev, CGI, Client_Connection_Interruption_Severity)
99 {
100     {"Info", eDiag_Info},
101     {"Warning", eDiag_Warning},
102     {"Error", eDiag_Error},
103     {"Critical", eDiag_Critical},
104     {"Fatal", eDiag_Fatal},
105     {"Trace", eDiag_Trace}
106 };
107 NCBI_PARAM_ENUM_DEF_EX(EDiagSev, CGI, Client_Connection_Interruption_Severity,
108                        eDiag_Critical, eParam_NoThread,
109                        CGI_CLIENT_CONNECTION_INTERRUPTION_SEVERITY);
110 
111 
112 ///////////////////////////////////////////////////////
113 // IO streams with byte counting for CGI applications
114 //
115 
116 
117 class CCGIStreamReader : public IReader
118 {
119 public:
CCGIStreamReader(istream & is)120     CCGIStreamReader(istream& is) : m_IStr(is) { }
121 
122     virtual ERW_Result Read(void*   buf,
123                             size_t  count,
124                             size_t* bytes_read = 0);
PendingCount(size_t *)125     virtual ERW_Result PendingCount(size_t* /*count*/)
126     { return eRW_NotImplemented; }
127 
128 protected:
129     istream& m_IStr;
130 };
131 
132 
Read(void * buf,size_t count,size_t * bytes_read)133 ERW_Result CCGIStreamReader::Read(void*   buf,
134                                   size_t  count,
135                                   size_t* bytes_read)
136 {
137     size_t x_read = (size_t)CStreamUtils::Readsome(m_IStr, (char*)buf, count);
138     ERW_Result result;
139     if (x_read > 0  ||  count == 0) {
140         result = eRW_Success;
141     }
142     else {
143         result = m_IStr.eof() ? eRW_Eof : eRW_Error;
144     }
145     if (bytes_read) {
146         *bytes_read = x_read;
147     }
148     return result;
149 }
150 
151 
152 ///////////////////////////////////////////////////////
153 // CCgiStreamWrapper, CCgiStreamWrapperWriter
154 //
155 
156 NCBI_PARAM_DECL(bool, CGI, Count_Transfered);
157 NCBI_PARAM_DEF_EX(bool, CGI, Count_Transfered, true, eParam_NoThread,
158                   CGI_COUNT_TRANSFERED);
159 typedef NCBI_PARAM_TYPE(CGI, Count_Transfered) TCGI_Count_Transfered;
160 
161 
162 // Helper writer used to:
163 // - count bytes read/written to the stream;
164 // - disable output stream during HEAD requests after sending headers
165 //   so that the application can not output more data or clear 'bad' bit;
166 // - send chunked data to the client;
167 // - copy data to cache stream when necessary.
168 class CCgiStreamWrapperWriter : public IWriter
169 {
170 public:
171     CCgiStreamWrapperWriter(CNcbiOstream& out);
172     virtual ~CCgiStreamWrapperWriter(void);
173 
174     virtual ERW_Result Write(const void* buf,
175                              size_t      count,
176                              size_t*     bytes_written = 0);
177 
178     virtual ERW_Result Flush(void);
179 
GetMode(void) const180     CCgiStreamWrapper::EStreamMode GetMode(void) const { return m_Mode; }
181 
182     void SetMode(CCgiStreamWrapper::EStreamMode mode);
183     void SetCacheStream(CNcbiOstream& stream);
184 
185     void FinishChunkedTransfer(const CCgiStreamWrapper::TTrailer* trailer);
186     void AbortChunkedTransfer(void);
187 
188 private:
189     void x_SetChunkSize(size_t sz);
190     void x_WriteChunk(const char* buf, size_t count);
191 
192     CCgiStreamWrapper::EStreamMode m_Mode;
193     CNcbiOstream* m_Out;
194     // block mode
195     bool m_ErrorReported;
196     // chunked mode
197     size_t m_ChunkSize;
198     char* m_Chunk;
199     size_t m_Count;
200     bool m_UsedChunkedTransfer; // remember if chunked transfer was enabled.
201 };
202 
203 
CCgiStreamWrapperWriter(CNcbiOstream & out)204 CCgiStreamWrapperWriter::CCgiStreamWrapperWriter(CNcbiOstream& out)
205     : m_Mode(CCgiStreamWrapper::eNormal),
206       m_Out(&out),
207       m_ErrorReported(false),
208       m_ChunkSize(0),
209       m_Chunk(0),
210       m_Count(0),
211       m_UsedChunkedTransfer(false)
212 {
213 }
214 
215 
~CCgiStreamWrapperWriter(void)216 CCgiStreamWrapperWriter::~CCgiStreamWrapperWriter(void)
217 {
218     if (m_Mode == CCgiStreamWrapper::eChunkedWrites) {
219         x_SetChunkSize(0); // cleanup
220     }
221 }
222 
223 
x_WriteChunk(const char * buf,size_t count)224 void CCgiStreamWrapperWriter::x_WriteChunk(const char* buf, size_t count)
225 {
226     if (!buf || count == 0) return;
227     *m_Out << NStr::NumericToString(count, 0, 16) << HTTP_EOL;
228     m_Out->write(buf, count);
229     *m_Out << HTTP_EOL;
230 }
231 
232 
Write(const void * buf,size_t count,size_t * bytes_written)233 ERW_Result CCgiStreamWrapperWriter::Write(const void* buf,
234                                           size_t      count,
235                                           size_t*     bytes_written)
236 {
237     ERW_Result result = eRW_Success;
238     size_t written = 0;
239 
240     switch (m_Mode) {
241     case CCgiStreamWrapper::eNormal:
242         {
243             if (!m_Out->write((char*)buf, count)) {
244                 result = eRW_Error;
245             }
246             else {
247                 result = eRW_Success;
248                 written = count;
249             }
250             break;
251         }
252     case CCgiStreamWrapper::eBlockWrites:
253         {
254             if ( !m_ErrorReported ) {
255                 if ( m_UsedChunkedTransfer ) {
256                     ERR_POST_X(16, "CCgiStreamWrapperWriter::Write() -- attempt to "
257                         "write data after finishing chunked transfer.");
258                 }
259                 else {
260                     ERR_POST_X(15, "CCgiStreamWrapperWriter::Write() -- attempt to "
261                         "write data after sending headers on HEAD request.");
262                 }
263                 m_ErrorReported = true;
264             }
265             // Pretend the operation was successfull so that applications
266             // which check I/O result do not fail.
267             written = count;
268             break;
269         }
270     case CCgiStreamWrapper::eChunkedWrites:
271         {
272             const char* cbuf = static_cast<const char*>(buf);
273             if (m_Chunk  &&  m_ChunkSize > 0) {
274                 // Copy data to own buffer
275                 while (count && result == eRW_Success) {
276                     size_t chunk_count = min(count, m_ChunkSize - m_Count);
277                     memcpy(m_Chunk + m_Count, cbuf, chunk_count);
278                     cbuf += chunk_count;
279                     m_Count += chunk_count;
280                     count -= chunk_count;
281                     written += chunk_count;
282                     if (m_Count >= m_ChunkSize) {
283                         x_WriteChunk(m_Chunk, m_Count);
284                         if (!m_Out->good()) {
285                             result = eRW_Error;
286                             written -= chunk_count;
287                         }
288                         m_Count = 0;
289                     }
290                 }
291             }
292             else {
293                 // If chunk size is zero, use the whole original buffer.
294                 x_WriteChunk(cbuf, count);
295                 if (m_Out->good()) {
296                     written = count;
297                 }
298                 else {
299                     result = eRW_Error;
300                 }
301             }
302             break;
303         }
304     }
305 
306     if (bytes_written) {
307         *bytes_written = written;
308     }
309     return result;
310 }
311 
312 
Flush(void)313 ERW_Result CCgiStreamWrapperWriter::Flush(void)
314 {
315     switch (m_Mode) {
316     case CCgiStreamWrapper::eNormal:
317         break;
318     case CCgiStreamWrapper::eBlockWrites:
319         // Report success
320         return eRW_Success;
321     case CCgiStreamWrapper::eChunkedWrites:
322         if (m_Count) {
323             x_WriteChunk(m_Chunk, m_Count);
324             m_Count = 0;
325         }
326         break;
327     }
328     return m_Out->flush() ? eRW_Success : eRW_Error;
329 }
330 
331 
SetMode(CCgiStreamWrapper::EStreamMode mode)332 void CCgiStreamWrapperWriter::SetMode(CCgiStreamWrapper::EStreamMode mode)
333 {
334     switch (mode) {
335     case CCgiStreamWrapper::eNormal:
336         break;
337     case CCgiStreamWrapper::eBlockWrites:
338         _ASSERT(m_Mode == CCgiStreamWrapper::eNormal);
339         m_Out->flush();
340         // Prevent output from writing anything, disable exceptions -
341         // an attemp to write will be reported by the wrapper.
342         m_Out->exceptions(ios_base::goodbit);
343         m_Out->setstate(ios_base::badbit);
344         break;
345     case CCgiStreamWrapper::eChunkedWrites:
346         _ASSERT(m_Mode == CCgiStreamWrapper::eNormal);
347         // Use default chunk size.
348         x_SetChunkSize(CCgiResponse::GetChunkSize());
349         m_UsedChunkedTransfer = true;
350         break;
351     }
352     m_Mode = mode;
353 }
354 
355 
SetCacheStream(CNcbiOstream & stream)356 void CCgiStreamWrapperWriter::SetCacheStream(CNcbiOstream& stream)
357 {
358     list<CNcbiOstream*> slist;
359     slist.push_back(m_Out);
360     slist.push_back(&stream);
361     m_Out = new CWStream(new CMultiWriter(slist), 1, 0,
362         CRWStreambuf::fOwnWriter);
363 }
364 
365 
FinishChunkedTransfer(const CCgiStreamWrapper::TTrailer * trailer)366 void CCgiStreamWrapperWriter::FinishChunkedTransfer(
367     const CCgiStreamWrapper::TTrailer* trailer)
368 {
369     if (m_Mode == CCgiStreamWrapper::eChunkedWrites) {
370         Flush();
371         // Zero chunk indicates end of chunked data.
372         *m_Out << "0" << HTTP_EOL;
373         x_SetChunkSize(0);
374         // Allow to write trailers after the last chunk.
375         SetMode(CCgiStreamWrapper::eNormal);
376         if ( trailer ) {
377             ITERATE(CCgiStreamWrapper::TTrailer, it, *trailer) {
378                 *m_Out << it->first << ": " << it->second << HTTP_EOL;
379             }
380         }
381         // Finish chunked data/trailer.
382         *m_Out << HTTP_EOL;
383     }
384 }
385 
386 
AbortChunkedTransfer(void)387 void CCgiStreamWrapperWriter::AbortChunkedTransfer(void)
388 {
389     if (m_Mode == CCgiStreamWrapper::eChunkedWrites) {
390         x_SetChunkSize(0);
391     }
392     // Disable any writes.
393     SetMode(CCgiStreamWrapper::eBlockWrites);
394 }
395 
396 
x_SetChunkSize(size_t sz)397 void CCgiStreamWrapperWriter::x_SetChunkSize(size_t sz)
398 {
399     if (m_Chunk) {
400         x_WriteChunk(m_Chunk, m_Count);
401         delete[](m_Chunk);
402         m_Chunk = 0;
403     }
404     m_Count = 0;
405     m_ChunkSize = sz;
406     if (m_ChunkSize) {
407         m_Chunk = new char[m_ChunkSize];
408     }
409 }
410 
411 
CCgiStreamWrapper(CNcbiOstream & out)412 CCgiStreamWrapper::CCgiStreamWrapper(CNcbiOstream& out)
413     : CWStream(m_Writer = new CCgiStreamWrapperWriter(out),
414     1, 0, CRWStreambuf::fOwnWriter) // '1, 0' disables buffering by the wrapper
415 {
416 }
417 
418 
GetWriterMode(void)419 CCgiStreamWrapper::EStreamMode CCgiStreamWrapper::GetWriterMode(void)
420 {
421     return m_Writer->GetMode();
422 }
423 
424 
SetWriterMode(CCgiStreamWrapper::EStreamMode mode)425 void CCgiStreamWrapper::SetWriterMode(CCgiStreamWrapper::EStreamMode mode)
426 {
427     flush();
428     m_Writer->SetMode(mode);
429 }
430 
431 
SetCacheStream(CNcbiOstream & stream)432 void CCgiStreamWrapper::SetCacheStream(CNcbiOstream& stream)
433 {
434     m_Writer->SetCacheStream(stream);
435 }
436 
437 
FinishChunkedTransfer(const TTrailer * trailer)438 void CCgiStreamWrapper::FinishChunkedTransfer(const TTrailer* trailer)
439 {
440     m_Writer->FinishChunkedTransfer(trailer);
441 }
442 
443 
AbortChunkedTransfer(void)444 void CCgiStreamWrapper::AbortChunkedTransfer(void)
445 {
446     m_Writer->AbortChunkedTransfer();
447 }
448 
449 
450 ///////////////////////////////////////////////////////
451 // CCgiApplication
452 //
453 
454 
Instance(void)455 CCgiApplication* CCgiApplication::Instance(void)
456 {
457     return dynamic_cast<CCgiApplication*> (CParent::Instance());
458 }
459 
460 
SigTermHandler(int)461 extern "C" void SigTermHandler(int)
462 {
463 }
464 
465 
Run(void)466 int CCgiApplication::Run(void)
467 {
468     // Value to return from this method Run()
469     int result;
470 
471     // Try to run as a Fast-CGI loop
472     if ( x_RunFastCGI(&result) ) {
473         return result;
474     }
475 
476     /// Run as a plain CGI application
477 
478     // Make sure to restore old diagnostic state after the Run()
479     CDiagRestorer diag_restorer;
480 
481 #if defined(NCBI_OS_UNIX)
482     // Disable SIGPIPE if not allowed.
483     if ( !TParamAllowSigpipe::GetDefault() ) {
484         signal(SIGPIPE, SIG_IGN);
485         struct sigaction sigterm,  sigtermold;
486         memset(&sigterm, 0, sizeof(sigterm));
487         sigterm.sa_handler = SigTermHandler;
488         sigterm.sa_flags = SA_RESETHAND;
489         if (sigaction(SIGTERM, &sigterm, &sigtermold) == 0
490             &&  sigtermold.sa_handler != SIG_DFL) {
491             sigaction(SIGTERM, &sigtermold, 0);
492         }
493     }
494 
495     // Compose diagnostics prefix
496     PushDiagPostPrefix(NStr::IntToString(getpid()).c_str());
497 #endif
498     PushDiagPostPrefix(GetEnvironment().Get(m_DiagPrefixEnv).c_str());
499 
500     // Timing
501     CTime start_time(CTime::eCurrent);
502 
503     // Logging for statistics
504     bool is_stat_log = GetConfig().GetBool("CGI", "StatLog", false,
505                                            0, CNcbiRegistry::eReturn);
506     bool skip_stat_log = false;
507     unique_ptr<CCgiStatistics> stat(is_stat_log ? CreateStat() : 0);
508 
509     CNcbiOstream* orig_stream = NULL;
510     //int orig_fd = -1;
511     CNcbiStrstream result_copy;
512     unique_ptr<CNcbiOstream> new_stream;
513 
514     try {
515         _TRACE("(CGI) CCgiApplication::Run: calling ProcessRequest");
516         GetDiagContext().SetAppState(eDiagAppState_RequestBegin);
517 
518         m_Context.reset( CreateContext() );
519         _ASSERT(m_Context.get());
520 
521         ConfigureDiagnostics(*m_Context);
522         x_AddLBCookie();
523         try {
524             // Print request start message
525             x_OnEvent(eStartRequest, 0);
526 
527             VerifyCgiContext(*m_Context);
528             ProcessHttpReferer();
529             LogRequest();
530 
531             m_Context->CheckStatus();
532 
533             try {
534                 m_Cache.reset( GetCacheStorage() );
535             } catch (const exception& ex) {
536                 ERR_POST_X(1, "Couldn't create cache : " << ex.what());
537             }
538             bool skip_process_request = false;
539             bool caching_needed = IsCachingNeeded(m_Context->GetRequest());
540             if (m_Cache.get() && caching_needed) {
541                 skip_process_request = GetResultFromCache(m_Context->GetRequest(),
542                                                            m_Context->GetResponse().out());
543             }
544             if (!skip_process_request) {
545                 if( m_Cache.get() ) {
546                     CCgiStreamWrapper* wrapper = dynamic_cast<CCgiStreamWrapper*>(
547                         m_Context->GetResponse().GetOutput());
548                     if ( wrapper ) {
549                         wrapper->SetCacheStream(result_copy);
550                     }
551                     else {
552                         list<CNcbiOstream*> slist;
553                         orig_stream = m_Context->GetResponse().GetOutput();
554                         slist.push_back(orig_stream);
555                         slist.push_back(&result_copy);
556                         new_stream.reset(new CWStream(new CMultiWriter(slist), 1, 0,
557                                                       CRWStreambuf::fOwnWriter));
558                         m_Context->GetResponse().SetOutput(new_stream.get());
559                     }
560                 }
561                 GetDiagContext().SetAppState(eDiagAppState_Request);
562                 if (x_ProcessHelpRequest() ||
563                     x_ProcessVersionRequest() ||
564                     CCgiContext::ProcessCORSRequest(m_Context->GetRequest(), m_Context->GetResponse()) ||
565                     x_ProcessAdminRequest()) {
566                     result = 0;
567                 }
568                 else {
569                     if (!ValidateSynchronizationToken()) {
570                         NCBI_CGI_THROW_WITH_STATUS(CCgiRequestException, eData,
571                             "Invalid or missing CSRF token.", CCgiException::e403_Forbidden);
572                     }
573                     result = ProcessRequest(*m_Context);
574                 }
575                 GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
576                 m_Context->GetResponse().Finalize();
577                 if (result != 0) {
578                     SetHTTPStatus(500);
579                     m_ErrorStatus = true;
580                     m_Context->GetResponse().AbortChunkedTransfer();
581                 } else {
582                     m_Context->GetResponse().FinishChunkedTransfer();
583                     if (m_Cache.get()) {
584                         m_Context->GetResponse().Flush();
585                         if (m_IsResultReady) {
586                             if(caching_needed)
587                                 SaveResultToCache(m_Context->GetRequest(), result_copy);
588                             else {
589                                 unique_ptr<CCgiRequest> request(GetSavedRequest(m_RID));
590                                 if (request.get())
591                                     SaveResultToCache(*request, result_copy);
592                             }
593                         } else if (caching_needed) {
594                             SaveRequest(m_RID, m_Context->GetRequest());
595                         }
596                     }
597                 }
598             }
599         }
600         catch (const CCgiException& e) {
601             if ( x_DoneHeadRequest() ) {
602                 // Ignore errors after HEAD request has been finished.
603                 GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
604             }
605             else {
606                 if ( e.GetStatusCode() <  CCgiException::e200_Ok  ||
607                      e.GetStatusCode() >= CCgiException::e400_BadRequest ) {
608                     throw;
609                 }
610                 m_Context->GetResponse().FinishChunkedTransfer();
611                 GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
612                 // If for some reason exception with status 2xx was thrown,
613                 // set the result to 0, update HTTP status and continue.
614                 m_Context->GetResponse().SetStatus(e.GetStatusCode(),
615                                                    e.GetStatusMessage());
616             }
617             result = 0;
618         }
619 
620 #ifdef NCBI_OS_MSWIN
621         // Logging - on MSWin this must be done before flushing the output.
622         if ( is_stat_log  &&  !skip_stat_log ) {
623             stat->Reset(start_time, result);
624             stat->Submit(stat->Compose());
625         }
626         is_stat_log = false;
627 #endif
628 
629         _TRACE("CCgiApplication::Run: flushing");
630         m_Context->GetResponse().Flush();
631         _TRACE("CCgiApplication::Run: return " << result);
632         x_OnEvent(result == 0 ? eSuccess : eError, result);
633         x_OnEvent(eExit, result);
634     }
635     catch (exception& e) {
636         if ( m_Context.get() ) {
637             m_Context->GetResponse().AbortChunkedTransfer();
638         }
639         GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
640         if ( x_DoneHeadRequest() ) {
641             // Ignore errors after HEAD request has been finished.
642             result = 0;
643             x_OnEvent(eSuccess, result);
644         }
645         else {
646             // Call the exception handler and set the CGI exit code
647             result = OnException(e, NcbiCout);
648             x_OnEvent(eException, result);
649 
650             // Logging
651             {{
652                 string msg = "(CGI) CCgiApplication::ProcessRequest() failed: ";
653                 msg += e.what();
654 
655                 if ( is_stat_log ) {
656                     stat->Reset(start_time, result, &e);
657                     msg = stat->Compose();
658                     stat->Submit(msg);
659                     skip_stat_log = true; // Don't print the same message again
660                 }
661             }}
662 
663             // Exception reporting. Use different severity for broken connection.
664             ios_base::failure* fex = dynamic_cast<ios_base::failure*>(&e);
665             CNcbiOstream* os = m_Context.get() ? m_Context->GetResponse().GetOutput() : NULL;
666             if ((fex  &&  os  &&  !os->good())  ||  m_OutputBroken) {
667                 if ( !TClientConnIntOk::GetDefault() ) {
668                     ERR_POST_X(13, Severity(TClientConnIntSeverity::GetDefault()) <<
669                         "Connection interrupted");
670                 }
671             }
672             else {
673                 NCBI_REPORT_EXCEPTION_X(13, "(CGI) CCgiApplication::Run", e);
674             }
675         }
676     }
677 
678 #ifndef NCBI_OS_MSWIN
679     // Logging
680     if ( is_stat_log  &&  !skip_stat_log ) {
681         stat->Reset(start_time, result);
682         stat->Submit(stat->Compose());
683     }
684 #endif
685 
686     x_OnEvent(eEndRequest, 120);
687     x_OnEvent(eExit, result);
688 
689     if (m_Context.get()) {
690         m_Context->GetResponse().SetOutput(NULL);
691     }
692     return result;
693 }
694 
695 
696 const char* kExtraType_CGI = "NCBICGI";
697 
ProcessHttpReferer(void)698 void CCgiApplication::ProcessHttpReferer(void)
699 {
700     // Set HTTP_REFERER
701     CCgiContext& ctx = GetContext();
702     string ref = ctx.GetSelfURL();
703     if ( !ref.empty() ) {
704         string args =
705             ctx.GetRequest().GetProperty(eCgi_QueryString);
706         if ( !args.empty() ) {
707             ref += "?" + args;
708         }
709         GetRWConfig().Set("CONN", "HTTP_REFERER", ref);
710     }
711 }
712 
713 
LogRequest(void) const714 void CCgiApplication::LogRequest(void) const
715 {
716     const CCgiContext& ctx = GetContext();
717     string str;
718     if (TPrintRequestMethodParam::GetDefault()) {
719         // Print request method
720         string method = ctx.GetRequest().GetRequestMethodName();
721         if (!method.empty()) {
722             GetDiagContext().Extra().Print("REQUEST_METHOD", method);
723         }
724     }
725     if ( TPrintSelfUrlParam::GetDefault() ) {
726         // Print script URL
727         string self_url = ctx.GetSelfURL();
728         if ( !self_url.empty() ) {
729             string args =
730                 ctx.GetRequest().GetRandomProperty("REDIRECT_QUERY_STRING", false);
731             if ( args.empty() ) {
732                 args = ctx.GetRequest().GetProperty(eCgi_QueryString);
733             }
734             if ( !args.empty() ) {
735                 self_url += "?" + args;
736             }
737         }
738         // Add target url
739         string target_url = ctx.GetRequest().GetProperty(eCgi_ScriptName);
740         if ( !target_url.empty() ) {
741             bool secure = AStrEquiv(ctx.GetRequest().GetRandomProperty("HTTPS",
742                 false), "on", PNocase());
743             string host = (secure ? "https://" : "http://") + GetDiagContext().GetHost();
744             string port = ctx.GetRequest().GetProperty(eCgi_ServerPort);
745             if (!port.empty()  &&  port != (secure ? "443" : "80")) {
746                 host += ":" + port;
747             }
748             target_url = host + target_url;
749         }
750         if ( !self_url.empty()  ||  !target_url.empty() ) {
751             GetDiagContext().Extra().Print("SELF_URL", self_url).
752                 Print("TARGET_URL", target_url);
753         }
754     }
755     // Print HTTP_REFERER
756     if ( TPrintRefererParam::GetDefault() ) {
757         str = ctx.GetRequest().GetProperty(eCgi_HttpReferer);
758         if ( !str.empty() ) {
759             GetDiagContext().Extra().Print("HTTP_REFERER", str);
760         }
761     }
762     // Print USER_AGENT
763     if ( TPrintUserAgentParam::GetDefault() ) {
764         str = ctx.GetRequest().GetProperty(eCgi_HttpUserAgent);
765         if ( !str.empty() ) {
766             GetDiagContext().Extra().Print("USER_AGENT", str);
767         }
768     }
769     // Print NCBI_LOG_FIELDS
770     CNcbiLogFields f("http");
771     map<string, string> env;
772     list<string> names;
773     const CNcbiEnvironment& rq_env = ctx.GetRequest().GetEnvironment();
774     rq_env.Enumerate(names);
775     ITERATE(list<string>, it, names) {
776         if (!NStr::StartsWith(*it, "HTTP_")) continue;
777         string name = it->substr(5);
778         NStr::ToLower(name);
779         NStr::ReplaceInPlace(name, "_", "-");
780         env[name] = rq_env.Get(*it);
781     }
782     f.LogFields(env);
783 }
784 
785 
SetupArgDescriptions(CArgDescriptions * arg_desc)786 void CCgiApplication::SetupArgDescriptions(CArgDescriptions* arg_desc)
787 {
788     arg_desc->SetArgsType(CArgDescriptions::eCgiArgs);
789 
790     CParent::SetupArgDescriptions(arg_desc);
791 }
792 
793 
x_GetContext(void) const794 CCgiContext& CCgiApplication::x_GetContext( void ) const
795 {
796     if ( !m_Context.get() ) {
797         ERR_POST_X(2, "CCgiApplication::GetContext: no context set");
798         throw runtime_error("no context set");
799     }
800     return *m_Context;
801 }
802 
803 
x_GetResource(void) const804 CNcbiResource& CCgiApplication::x_GetResource( void ) const
805 {
806     if ( !m_Resource.get() ) {
807         ERR_POST_X(3, "CCgiApplication::GetResource: no resource set");
808         throw runtime_error("no resource set");
809     }
810     return *m_Resource;
811 }
812 
813 
814 NCBI_PARAM_DECL(bool, CGI, Merge_Log_Lines);
815 NCBI_PARAM_DEF_EX(bool, CGI, Merge_Log_Lines, true, eParam_NoThread,
816                   CGI_MERGE_LOG_LINES);
817 typedef NCBI_PARAM_TYPE(CGI, Merge_Log_Lines) TMergeLogLines;
818 
819 
Init(void)820 void CCgiApplication::Init(void)
821 {
822     if ( TMergeLogLines::GetDefault() ) {
823         // Convert multi-line diagnostic messages into one-line ones by default.
824         SetDiagPostFlag(eDPF_MergeLines);
825     }
826 
827     CParent::Init();
828 
829     m_Resource.reset(LoadResource());
830 
831     m_DiagPrefixEnv = GetConfig().Get("CGI", "DiagPrefixEnv");
832 }
833 
834 
Exit(void)835 void CCgiApplication::Exit(void)
836 {
837     m_Resource.reset(0);
838     CParent::Exit();
839 }
840 
841 
LoadResource(void)842 CNcbiResource* CCgiApplication::LoadResource(void)
843 {
844     return 0;
845 }
846 
847 
LoadServerContext(CCgiContext &)848 CCgiServerContext* CCgiApplication::LoadServerContext(CCgiContext& /*context*/)
849 {
850     return 0;
851 }
852 
853 
CreateContext(CNcbiArguments * args,CNcbiEnvironment * env,CNcbiIstream * inp,CNcbiOstream * out,int ifd,int ofd)854 CCgiContext* CCgiApplication::CreateContext
855 (CNcbiArguments*   args,
856  CNcbiEnvironment* env,
857  CNcbiIstream*     inp,
858  CNcbiOstream*     out,
859  int               ifd,
860  int               ofd)
861 {
862     return CreateContextWithFlags(args, env,
863         inp, out, ifd, ofd, m_RequestFlags);
864 }
865 
CreateContextWithFlags(CNcbiArguments * args,CNcbiEnvironment * env,CNcbiIstream * inp,CNcbiOstream * out,int ifd,int ofd,int flags)866 CCgiContext* CCgiApplication::CreateContextWithFlags
867 (CNcbiArguments*   args,
868  CNcbiEnvironment* env,
869  CNcbiIstream*     inp,
870  CNcbiOstream*     out,
871  int               ifd,
872  int               ofd,
873  int               flags)
874 {
875     m_OutputBroken = false; // reset failure flag
876 
877     int errbuf_size =
878         GetConfig().GetInt("CGI", "RequestErrBufSize", 256, 0,
879                            CNcbiRegistry::eReturn);
880 
881     bool need_output_wrapper =
882         TCGI_Count_Transfered::GetDefault()  ||
883         (env && CCgiResponse::x_ClientSupportsChunkedTransfer(*env))  ||
884         (env &&
885         NStr::EqualNocase("HEAD",
886         env->Get(CCgiRequest::GetPropertyName(eCgi_RequestMethod))));
887 
888     if ( TCGI_Count_Transfered::GetDefault() ) {
889         if ( !inp ) {
890             if ( !m_InputStream.get() ) {
891                 m_InputStream.reset(
892                     new CRStream(new CCGIStreamReader(std::cin), 0, 0,
893                                 CRWStreambuf::fOwnReader));
894             }
895             inp = m_InputStream.get();
896             ifd = 0;
897         }
898     }
899     if ( need_output_wrapper ) {
900         if ( !out ) {
901             if ( !m_OutputStream.get() ) {
902                 m_OutputStream.reset(new CCgiStreamWrapper(std::cout));
903             }
904             out = m_OutputStream.get();
905             ofd = 1;
906             if ( m_InputStream.get() ) {
907                 // If both streams are created by the application, tie them.
908                 inp->tie(out);
909             }
910         }
911         else {
912             m_OutputStream.reset(new CCgiStreamWrapper(*out));
913             out = m_OutputStream.get();
914         }
915     }
916     return
917         new CCgiContext(*this, args, env, inp, out, ifd, ofd,
918                         (errbuf_size >= 0) ? (size_t) errbuf_size : 256,
919                         flags);
920 }
921 
922 
SetCafService(CCookieAffinity * caf)923 void CCgiApplication::SetCafService(CCookieAffinity* caf)
924 {
925     m_Caf.reset(caf);
926 }
927 
928 
929 
930 // Flexible diagnostics support
931 //
932 
933 class CStderrDiagFactory : public CDiagFactory
934 {
935 public:
New(const string &)936     virtual CDiagHandler* New(const string&) {
937         return new CStreamDiagHandler(&NcbiCerr);
938     }
939 };
940 
941 
942 class CAsBodyDiagFactory : public CDiagFactory
943 {
944 public:
CAsBodyDiagFactory(CCgiApplication * app)945     CAsBodyDiagFactory(CCgiApplication* app) : m_App(app) {}
New(const string &)946     virtual CDiagHandler* New(const string&) {
947         CCgiResponse& response = m_App->GetContext().GetResponse();
948         CDiagHandler* result   = new CStreamDiagHandler(&response.out());
949         if (!response.IsHeaderWritten()) {
950             response.SetContentType("text/plain");
951             response.WriteHeader();
952         }
953         response.SetOutput(0); // suppress normal output
954         return result;
955     }
956 
957 private:
958     CCgiApplication* m_App;
959 };
960 
961 
CCgiApplication(const SBuildInfo & build_info)962 CCgiApplication::CCgiApplication(const SBuildInfo& build_info)
963  : CNcbiApplication(build_info),
964    m_RequestFlags(0),
965    m_HostIP(0),
966    m_Iteration(0),
967    m_ArgContextSync(false),
968    m_OutputBroken(false),
969    m_IsResultReady(true),
970    m_ShouldExit(false),
971    m_CaughtSigterm(false),
972    m_RequestStartPrinted(false),
973    m_ErrorStatus(false)
974 {
975     // Disable system popup messages
976     SuppressSystemMessageBox();
977 
978     // Turn on iteration number
979     SetDiagPostFlag(eDPF_RequestId);
980     SetDiagTraceFlag(eDPF_RequestId);
981 
982     SetStdioFlags(fBinaryCin | fBinaryCout);
983     DisableArgDescriptions();
984     RegisterDiagFactory("stderr", new CStderrDiagFactory);
985     RegisterDiagFactory("asbody", new CAsBodyDiagFactory(this));
986     cerr.tie(0);
987 }
988 
989 
~CCgiApplication(void)990 CCgiApplication::~CCgiApplication(void)
991 {
992     ITERATE (TDiagFactoryMap, it, m_DiagFactories) {
993         delete it->second;
994     }
995     if ( m_HostIP )
996         free(m_HostIP);
997 }
998 
999 
OnException(exception & e,CNcbiOstream & os)1000 int CCgiApplication::OnException(exception& e, CNcbiOstream& os)
1001 {
1002     // Discriminate between different types of error
1003     string status_str = "500 Server Error";
1004     string message = "";
1005 
1006     // Save current HTTP status. Later it may be changed to 299 or 499
1007     // depending on this value.
1008     m_ErrorStatus = CDiagContext::GetRequestContext().GetRequestStatus() >= 400;
1009     SetHTTPStatus(500);
1010 
1011     CException* ce = dynamic_cast<CException*> (&e);
1012     if ( ce ) {
1013         message = ce->GetMsg();
1014         CCgiException* cgi_e = dynamic_cast<CCgiException*>(&e);
1015         if ( cgi_e ) {
1016             if ( cgi_e->GetStatusCode() != CCgiException::eStatusNotSet ) {
1017                 SetHTTPStatus(cgi_e->GetStatusCode());
1018                 status_str = NStr::IntToString(cgi_e->GetStatusCode()) +
1019                     " " + cgi_e->GetStatusMessage();
1020             }
1021             else {
1022                 // Convert CgiRequestException and CCgiArgsException
1023                 // to error 400
1024                 if (dynamic_cast<CCgiRequestException*> (&e)  ||
1025                     dynamic_cast<CUrlException*> (&e)) {
1026                     SetHTTPStatus(400);
1027                     status_str = "400 Malformed HTTP Request";
1028                 }
1029             }
1030         }
1031     }
1032     else {
1033         message = e.what();
1034     }
1035 
1036     // Don't try to write to a broken output
1037     if (!os.good()  ||  m_OutputBroken) {
1038         return -1;
1039     }
1040 
1041     try {
1042         // HTTP header
1043         os << "Status: " << status_str << HTTP_EOL;
1044         os << "Content-Type: text/plain" HTTP_EOL HTTP_EOL;
1045 
1046         // Message
1047         os << "ERROR:  " << status_str << " " HTTP_EOL HTTP_EOL;
1048         os << NStr::HtmlEncode(message);
1049 
1050         if ( dynamic_cast<CArgException*> (&e) ) {
1051             string ustr;
1052             const CArgDescriptions* descr = GetArgDescriptions();
1053             if (descr) {
1054                 os << descr->PrintUsage(ustr) << HTTP_EOL HTTP_EOL;
1055             }
1056         }
1057 
1058         // Check for problems in sending the response
1059         if ( !os.good() ) {
1060             ERR_POST_X(4, "CCgiApplication::OnException() failed to send error page"
1061                           " back to the client");
1062             return -1;
1063         }
1064     }
1065     catch (const exception& ex) {
1066         NCBI_REPORT_EXCEPTION_X(14, "(CGI) CCgiApplication::Run", ex);
1067     }
1068     return 0;
1069 }
1070 
1071 
GetArgs(void) const1072 const CArgs& CCgiApplication::GetArgs(void) const
1073 {
1074     // Are there no argument descriptions or no CGI context (yet?)
1075     if (!GetArgDescriptions()  ||  !m_Context.get())
1076         return CParent::GetArgs();
1077 
1078     // Is everything already in-sync
1079     if ( m_ArgContextSync )
1080         return *m_CgiArgs;
1081 
1082     // Create CGI version of args, if necessary
1083     if ( !m_CgiArgs.get() )
1084         m_CgiArgs.reset(new CArgs());
1085 
1086     // Copy cmd-line arg values to CGI args
1087     m_CgiArgs->Assign(CParent::GetArgs());
1088 
1089     // Add CGI parameters to the CGI version of args
1090     GetArgDescriptions()->ConvertKeys(m_CgiArgs.get(),
1091                                       GetContext().GetRequest().GetEntries(),
1092                                       true /*update=yes*/);
1093 
1094     m_ArgContextSync = true;
1095     return *m_CgiArgs;
1096 }
1097 
1098 
x_OnEvent(EEvent event,int status)1099 void CCgiApplication::x_OnEvent(EEvent event, int status)
1100 {
1101     switch ( event ) {
1102     case eStartRequest:
1103         {
1104             // Set context properties
1105             const CCgiRequest& req = m_Context->GetRequest();
1106 
1107             // Print request start message
1108             if ( !CDiagContext::IsSetOldPostFormat() ) {
1109                 CExtraEntryCollector collector;
1110                 req.GetCGIEntries(collector);
1111                 GetDiagContext().PrintRequestStart()
1112                     .AllowBadSymbolsInArgNames()
1113                     .Print(collector.GetArgs());
1114                 m_RequestStartPrinted = true;
1115             }
1116 
1117             // Set default HTTP status code (reset above by PrintRequestStart())
1118             SetHTTPStatus(200);
1119             m_ErrorStatus = false;
1120 
1121             // This will log ncbi_phid as a separate 'extra' message
1122             // if not yet logged.
1123             CDiagContext::GetRequestContext().GetHitID();
1124 
1125             // Check if ncbi_st cookie is set
1126             const CCgiCookie* st = req.GetCookies().Find(
1127                 g_GetNcbiString(eNcbiStrings_Stat));
1128             if ( st ) {
1129                 CUrlArgs pg_info(st->GetValue());
1130                 // Log ncbi_st values
1131                 CDiagContext_Extra extra = GetDiagContext().Extra();
1132                 // extra.SetType("NCBICGI");
1133                 ITERATE(CUrlArgs::TArgs, it, pg_info.GetArgs()) {
1134                     extra.Print(it->name, it->value);
1135                 }
1136                 extra.Flush();
1137             }
1138             break;
1139         }
1140     case eSuccess:
1141     case eError:
1142     case eException:
1143         {
1144             CRequestContext& rctx = GetDiagContext().GetRequestContext();
1145             try {
1146                 if ( m_InputStream.get() ) {
1147                     if ( !m_InputStream->good() ) {
1148                         m_InputStream->clear();
1149                     }
1150                     rctx.SetBytesRd(NcbiStreamposToInt8(m_InputStream->tellg()));
1151                 }
1152             }
1153             catch (const exception&) {
1154             }
1155             try {
1156                 if ( m_OutputStream.get() ) {
1157                     if ( !m_OutputStream->good() ) {
1158                         m_OutputBroken = true; // set flag to indicate broken output
1159                         m_OutputStream->clear();
1160                     }
1161                     rctx.SetBytesWr(NcbiStreamposToInt8(m_OutputStream->tellp()));
1162                 }
1163             }
1164             catch (const exception&) {
1165             }
1166             break;
1167         }
1168     case eEndRequest:
1169         {
1170             CDiagContext& ctx = GetDiagContext();
1171             CRequestContext& rctx = ctx.GetRequestContext();
1172             // If an error status has been set by ProcessRequest, don't try
1173             // to check the output stream and change the status to 299/499.
1174             if ( !m_ErrorStatus ) {
1175                 // Log broken connection as 299/499 status
1176                 CNcbiOstream* os = m_Context.get() ?
1177                     m_Context->GetResponse().GetOutput() : NULL;
1178                 if ((os  &&  !os->good())  ||  m_OutputBroken) {
1179                     // 'Accept-Ranges: bytes' indicates a request for
1180                     // content length, broken connection is OK.
1181                     // If Content-Range is also set, the client was downloading
1182                     // partial data. Broken connection is not OK in this case.
1183                     if (TClientConnIntOk::GetDefault()  ||
1184                         (m_Context->GetResponse().AcceptRangesBytes()  &&
1185                         !m_Context->GetResponse().HaveContentRange())) {
1186                         rctx.SetRequestStatus(
1187                             CRequestStatus::e299_PartialContentBrokenConnection);
1188                     }
1189                     else {
1190                         rctx.SetRequestStatus(
1191                             CRequestStatus::e499_BrokenConnection);
1192                     }
1193                 }
1194             }
1195             if (!CDiagContext::IsSetOldPostFormat()) {
1196                 if (m_RequestStartPrinted) {
1197                     // This will also reset request context
1198                     ctx.PrintRequestStop();
1199                     m_RequestStartPrinted = false;
1200                 }
1201                 rctx.Reset();
1202             }
1203             break;
1204         }
1205     case eExit:
1206     case eExecutable:
1207     case eWatchFile:
1208     case eExitOnFail:
1209     case eExitRequest:
1210     case eWaiting:
1211         {
1212             break;
1213         }
1214     }
1215 
1216     OnEvent(event, status);
1217 }
1218 
1219 
OnEvent(EEvent,int)1220 void CCgiApplication::OnEvent(EEvent /*event*/,
1221                               int    /*exit_code*/)
1222 {
1223     return;
1224 }
1225 
1226 
RegisterDiagFactory(const string & key,CDiagFactory * fact)1227 void CCgiApplication::RegisterDiagFactory(const string& key,
1228                                           CDiagFactory* fact)
1229 {
1230     m_DiagFactories[key] = fact;
1231 }
1232 
1233 
FindDiagFactory(const string & key)1234 CDiagFactory* CCgiApplication::FindDiagFactory(const string& key)
1235 {
1236     TDiagFactoryMap::const_iterator it = m_DiagFactories.find(key);
1237     if (it == m_DiagFactories.end())
1238         return 0;
1239     return it->second;
1240 }
1241 
1242 
ConfigureDiagnostics(CCgiContext & context)1243 void CCgiApplication::ConfigureDiagnostics(CCgiContext& context)
1244 {
1245     // Disable for production servers?
1246     ConfigureDiagDestination(context);
1247     ConfigureDiagThreshold(context);
1248     ConfigureDiagFormat(context);
1249 }
1250 
1251 
ConfigureDiagDestination(CCgiContext & context)1252 void CCgiApplication::ConfigureDiagDestination(CCgiContext& context)
1253 {
1254     const CCgiRequest& request = context.GetRequest();
1255 
1256     bool   is_set;
1257     string dest   = request.GetEntry("diag-destination", &is_set);
1258     if ( !is_set )
1259         return;
1260 
1261     SIZE_TYPE colon = dest.find(':');
1262     CDiagFactory* factory = FindDiagFactory(dest.substr(0, colon));
1263     if ( factory ) {
1264         SetDiagHandler(factory->New(dest.substr(colon + 1)));
1265     }
1266 }
1267 
1268 
ConfigureDiagThreshold(CCgiContext & context)1269 void CCgiApplication::ConfigureDiagThreshold(CCgiContext& context)
1270 {
1271     const CCgiRequest& request = context.GetRequest();
1272 
1273     bool   is_set;
1274     string threshold = request.GetEntry("diag-threshold", &is_set);
1275     if ( !is_set )
1276         return;
1277 
1278     if (threshold == "fatal") {
1279         SetDiagPostLevel(eDiag_Fatal);
1280     } else if (threshold == "critical") {
1281         SetDiagPostLevel(eDiag_Critical);
1282     } else if (threshold == "error") {
1283         SetDiagPostLevel(eDiag_Error);
1284     } else if (threshold == "warning") {
1285         SetDiagPostLevel(eDiag_Warning);
1286     } else if (threshold == "info") {
1287         SetDiagPostLevel(eDiag_Info);
1288     } else if (threshold == "trace") {
1289         SetDiagPostLevel(eDiag_Info);
1290         SetDiagTrace(eDT_Enable);
1291     }
1292 }
1293 
1294 
ConfigureDiagFormat(CCgiContext & context)1295 void CCgiApplication::ConfigureDiagFormat(CCgiContext& context)
1296 {
1297     const CCgiRequest& request = context.GetRequest();
1298 
1299     typedef map<string, TDiagPostFlags> TFlagMap;
1300     static CSafeStatic<TFlagMap> s_FlagMap;
1301     TFlagMap& flagmap = s_FlagMap.Get();
1302 
1303     TDiagPostFlags defaults = (eDPF_Prefix | eDPF_Severity
1304                                | eDPF_ErrCode | eDPF_ErrSubCode);
1305 
1306     if ( !CDiagContext::IsSetOldPostFormat() ) {
1307         defaults |= (eDPF_UID | eDPF_PID | eDPF_RequestId |
1308             eDPF_SerialNo | eDPF_ErrorID);
1309     }
1310 
1311     TDiagPostFlags new_flags = 0;
1312 
1313     bool   is_set;
1314     string format = request.GetEntry("diag-format", &is_set);
1315     if ( !is_set )
1316         return;
1317 
1318     if (flagmap.empty()) {
1319         flagmap["file"]        = eDPF_File;
1320         flagmap["path"]        = eDPF_LongFilename;
1321         flagmap["line"]        = eDPF_Line;
1322         flagmap["prefix"]      = eDPF_Prefix;
1323         flagmap["severity"]    = eDPF_Severity;
1324         flagmap["code"]        = eDPF_ErrCode;
1325         flagmap["subcode"]     = eDPF_ErrSubCode;
1326         flagmap["time"]        = eDPF_DateTime;
1327         flagmap["omitinfosev"] = eDPF_OmitInfoSev;
1328         flagmap["all"]         = eDPF_All;
1329         flagmap["trace"]       = eDPF_Trace;
1330         flagmap["log"]         = eDPF_Log;
1331         flagmap["errorid"]     = eDPF_ErrorID;
1332         flagmap["location"]    = eDPF_Location;
1333         flagmap["pid"]         = eDPF_PID;
1334         flagmap["tid"]         = eDPF_TID;
1335         flagmap["serial"]      = eDPF_SerialNo;
1336         flagmap["serial_thr"]  = eDPF_SerialNo_Thread;
1337         flagmap["iteration"]   = eDPF_RequestId;
1338         flagmap["uid"]         = eDPF_UID;
1339     }
1340     list<string> flags;
1341     NStr::Split(format, " ", flags,
1342                 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1343     ITERATE(list<string>, flag, flags) {
1344         TFlagMap::const_iterator it;
1345         if ((it = flagmap.find(*flag)) != flagmap.end()) {
1346             new_flags |= it->second;
1347         } else if ((*flag)[0] == '!'
1348                    &&  ((it = flagmap.find(flag->substr(1)))
1349                         != flagmap.end())) {
1350             new_flags &= ~(it->second);
1351         } else if (*flag == "default") {
1352             new_flags |= defaults;
1353         }
1354     }
1355     SetDiagPostAllFlags(new_flags);
1356 }
1357 
1358 
GetLogOpt() const1359 CCgiApplication::ELogOpt CCgiApplication::GetLogOpt() const
1360 {
1361     string log = GetConfig().Get("CGI", "Log");
1362 
1363     CCgiApplication::ELogOpt logopt = eNoLog;
1364     if ((NStr::CompareNocase(log, "On") == 0) ||
1365         (NStr::CompareNocase(log, "true") == 0)) {
1366         logopt = eLog;
1367     } else if (NStr::CompareNocase(log, "OnError") == 0) {
1368         logopt = eLogOnError;
1369     }
1370 #ifdef _DEBUG
1371     else if (NStr::CompareNocase(log, "OnDebug") == 0) {
1372         logopt = eLog;
1373     }
1374 #endif
1375 
1376     return logopt;
1377 }
1378 
1379 
CreateStat()1380 CCgiStatistics* CCgiApplication::CreateStat()
1381 {
1382     return new CCgiStatistics(*this);
1383 }
1384 
1385 
1386 ICgiSessionStorage*
GetSessionStorage(CCgiSessionParameters &) const1387 CCgiApplication::GetSessionStorage(CCgiSessionParameters&) const
1388 {
1389     return 0;
1390 }
1391 
1392 
IsCachingNeeded(const CCgiRequest &) const1393 bool CCgiApplication::IsCachingNeeded(const CCgiRequest& /*request*/) const
1394 {
1395     return true;
1396 }
1397 
1398 
GetCacheStorage() const1399 ICache* CCgiApplication::GetCacheStorage() const
1400 {
1401     return NULL;
1402 }
1403 
1404 
1405 CCgiApplication::EPreparseArgs
PreparseArgs(int argc,const char * const * argv)1406 CCgiApplication::PreparseArgs(int                argc,
1407                               const char* const* argv)
1408 {
1409     static const char* s_ArgVersion = "-version";
1410     static const char* s_ArgFullVersion = "-version-full";
1411 
1412     if (argc != 2  ||  !argv[1]) {
1413         return ePreparse_Continue;
1414     }
1415     if ( NStr::strcmp(argv[1], s_ArgVersion) == 0 ) {
1416         // Print VERSION
1417         cout << GetFullVersion().Print(GetProgramDisplayName(),
1418             CVersion::fVersionInfo | CVersion::fPackageShort);
1419         return ePreparse_Exit;
1420     }
1421     else if ( NStr::strcmp(argv[1], s_ArgFullVersion) == 0 ) {
1422         // Print full VERSION
1423         cout << GetFullVersion().Print(GetProgramDisplayName());
1424         return ePreparse_Exit;
1425     }
1426     return ePreparse_Continue;
1427 }
1428 
1429 
SetRequestId(const string & rid,bool is_done)1430 void CCgiApplication::SetRequestId(const string& rid, bool is_done)
1431 {
1432     m_RID = rid;
1433     m_IsResultReady = is_done;
1434 }
1435 
1436 
GetResultFromCache(const CCgiRequest & request,CNcbiOstream & os)1437 bool CCgiApplication::GetResultFromCache(const CCgiRequest& request, CNcbiOstream& os)
1438 {
1439     string checksum, content;
1440     if (!request.CalcChecksum(checksum, content))
1441         return false;
1442 
1443     try {
1444         CCacheHashedContent helper(*m_Cache);
1445         unique_ptr<IReader> reader( helper.GetHashedContent(checksum, content));
1446         if (reader.get()) {
1447             //cout << "(Read) " << checksum << " --- " << content << endl;
1448             CRStream cache_reader(reader.get());
1449             return NcbiStreamCopy(os, cache_reader);
1450         }
1451     } catch (const exception& ex) {
1452         ERR_POST_X(5, "Couldn't read cached request : " << ex.what());
1453     }
1454     return false;
1455 }
1456 
1457 
SaveResultToCache(const CCgiRequest & request,CNcbiIstream & is)1458 void CCgiApplication::SaveResultToCache(const CCgiRequest& request, CNcbiIstream& is)
1459 {
1460     string checksum, content;
1461     if ( !request.CalcChecksum(checksum, content) )
1462         return;
1463     try {
1464         CCacheHashedContent helper(*m_Cache);
1465         unique_ptr<IWriter> writer( helper.StoreHashedContent(checksum, content) );
1466         if (writer.get()) {
1467             //        cout << "(Write) : " << checksum << " --- " << content << endl;
1468             CWStream cache_writer(writer.get());
1469             NcbiStreamCopy(cache_writer, is);
1470         }
1471     } catch (const exception& ex) {
1472         ERR_POST_X(6, "Couldn't cache request : " << ex.what());
1473     }
1474 }
1475 
1476 
SaveRequest(const string & rid,const CCgiRequest & request)1477 void CCgiApplication::SaveRequest(const string& rid, const CCgiRequest& request)
1478 {
1479     if (rid.empty())
1480         return;
1481     try {
1482         unique_ptr<IWriter> writer( m_Cache->GetWriteStream(rid, 0, "NS_JID") );
1483         if (writer.get()) {
1484             CWStream cache_stream(writer.get());
1485             request.Serialize(cache_stream);
1486         }
1487     } catch (const exception& ex) {
1488         ERR_POST_X(7, "Couldn't save request : " << ex.what());
1489     }
1490 }
1491 
1492 
GetSavedRequest(const string & rid)1493 CCgiRequest* CCgiApplication::GetSavedRequest(const string& rid)
1494 {
1495     if (rid.empty())
1496         return NULL;
1497     try {
1498         unique_ptr<IReader> reader(m_Cache->GetReadStream(rid, 0, "NS_JID"));
1499         if (reader.get()) {
1500             CRStream cache_stream(reader.get());
1501             unique_ptr<CCgiRequest> request(new CCgiRequest);
1502             request->Deserialize(cache_stream, 0);
1503             return request.release();
1504         }
1505     } catch (const exception& ex) {
1506         ERR_POST_X(8, "Couldn't read saved request : " << ex.what());
1507     }
1508     return NULL;
1509 }
1510 
1511 
x_AddLBCookie()1512 void CCgiApplication::x_AddLBCookie()
1513 {
1514     const CNcbiRegistry& reg = GetConfig();
1515 
1516     string cookie_name = GetConfig().Get("CGI-LB", "Name");
1517     if ( cookie_name.empty() )
1518         return;
1519 
1520     int life_span = reg.GetInt("CGI-LB", "LifeSpan", 0, 0,
1521                                CNcbiRegistry::eReturn);
1522 
1523     string domain = reg.GetString("CGI-LB", "Domain", ".ncbi.nlm.nih.gov");
1524 
1525     if ( domain.empty() ) {
1526         ERR_POST_X(9, "CGI-LB: 'Domain' not specified.");
1527     } else {
1528         if (domain[0] != '.') {     // domain must start with dot
1529             domain.insert(0, ".");
1530         }
1531     }
1532 
1533     string path = reg.Get("CGI-LB", "Path");
1534 
1535     bool secure = reg.GetBool("CGI-LB", "Secure", false,
1536                               0, CNcbiRegistry::eErrPost);
1537 
1538     string host;
1539 
1540     // Getting host configuration can take some time
1541     // for fast CGIs we try to avoid overhead and call it only once
1542     // m_HostIP variable keeps the cached value
1543 
1544     if ( m_HostIP ) {     // repeated call
1545         host = m_HostIP;
1546     }
1547     else {               // first time call
1548         host = reg.Get("CGI-LB", "Host");
1549         if ( host.empty() ) {
1550             if ( m_Caf.get() ) {
1551                 char  host_ip[64] = {0,};
1552                 m_Caf->GetHostIP(host_ip, sizeof(host_ip));
1553                 m_HostIP = m_Caf->Encode(host_ip, 0);
1554                 host = m_HostIP;
1555             }
1556             else {
1557                 ERR_POST_X(10, "CGI-LB: 'Host' not specified.");
1558             }
1559         }
1560     }
1561 
1562 
1563     CCgiCookie cookie(cookie_name, host, domain, path);
1564     if (life_span > 0) {
1565         CTime exp_time(CTime::eCurrent, CTime::eGmt);
1566         exp_time.AddSecond(life_span);
1567         cookie.SetExpTime(exp_time);
1568     }
1569     cookie.SetSecure(secure);
1570 
1571     GetContext().GetResponse().Cookies().Add(cookie);
1572 }
1573 
1574 
VerifyCgiContext(CCgiContext & context)1575 void CCgiApplication::VerifyCgiContext(CCgiContext& context)
1576 {
1577     string x_moz = context.GetRequest().GetRandomProperty("X_MOZ");
1578     if ( NStr::EqualNocase(x_moz, "prefetch") ) {
1579         NCBI_EXCEPTION_VAR(ex, CCgiRequestException, eData,
1580             "Prefetch is not allowed for CGIs");
1581         ex.SetStatus(CCgiException::e403_Forbidden);
1582         ex.SetSeverity(eDiag_Info);
1583         NCBI_EXCEPTION_THROW(ex);
1584     }
1585 }
1586 
1587 
AppStart(void)1588 void CCgiApplication::AppStart(void)
1589 {
1590     // Print application start message
1591     if ( !CDiagContext::IsSetOldPostFormat() ) {
1592         GetDiagContext().PrintStart(kEmptyStr);
1593     }
1594 }
1595 
1596 
AppStop(int exit_code)1597 void CCgiApplication::AppStop(int exit_code)
1598 {
1599     GetDiagContext().SetExitCode(exit_code);
1600 }
1601 
1602 
1603 const char* kToolkitRcPath = "/etc/toolkitrc";
1604 const char* kWebDirToPort = "Web_dir_to_port";
1605 
GetDefaultLogPath(void) const1606 string CCgiApplication::GetDefaultLogPath(void) const
1607 {
1608     string log_path = "/log/";
1609 
1610     string exe_path = GetProgramExecutablePath();
1611     CNcbiIfstream is(kToolkitRcPath, ios::binary);
1612     CNcbiRegistry reg(is);
1613     list<string> entries;
1614     reg.EnumerateEntries(kWebDirToPort, &entries);
1615     size_t min_pos = exe_path.length();
1616     string web_dir;
1617     // Find the first dir name corresponding to one of the entries
1618     ITERATE(list<string>, it, entries) {
1619         if (!it->empty()  &&  (*it)[0] != '/') {
1620             // not an absolute path
1621             string mask = "/" + *it;
1622             if (mask[mask.length() - 1] != '/') {
1623                 mask += "/";
1624             }
1625             size_t pos = exe_path.find(mask);
1626             if (pos < min_pos) {
1627                 min_pos = pos;
1628                 web_dir = *it;
1629             }
1630         }
1631         else {
1632             // absolute path
1633             if (exe_path.substr(0, it->length()) == *it) {
1634                 web_dir = *it;
1635                 break;
1636             }
1637         }
1638     }
1639     if ( !web_dir.empty() ) {
1640         return log_path + reg.GetString(kWebDirToPort, web_dir, kEmptyStr);
1641     }
1642     // Could not find a valid web-dir entry, use port or 'srv'
1643     const char* port = ::getenv("SERVER_PORT");
1644     return port ? log_path + string(port) : log_path + "srv";
1645 }
1646 
1647 
SetHTTPStatus(unsigned int status,const string & reason)1648 void CCgiApplication::SetHTTPStatus(unsigned int status, const string& reason)
1649 {
1650     if ( m_Context.get() ) {
1651         m_Context->GetResponse().SetStatus(status, reason);
1652     }
1653     else {
1654         CDiagContext::GetRequestContext().SetRequestStatus(status);
1655     }
1656 }
1657 
1658 
x_DoneHeadRequest(void) const1659 bool CCgiApplication::x_DoneHeadRequest(void) const
1660 {
1661     if (!m_Context.get()) return false; // There was an error initializing context
1662     const CCgiContext& ctx = GetContext();
1663     const CCgiRequest& req = ctx.GetRequest();
1664     const CCgiResponse& res = ctx.GetResponse();
1665     if (req.GetRequestMethod() != CCgiRequest::eMethod_HEAD  ||
1666         !res.IsHeaderWritten() ) {
1667         return false;
1668     }
1669     return true;
1670 }
1671 
1672 
1673 inline
operator <(const SAcceptEntry & entry) const1674 bool CCgiApplication::SAcceptEntry::operator<(const SAcceptEntry& entry) const
1675 {
1676     // Prefer specific type over wildcard.
1677     bool this_wc = m_Type == "*";
1678     bool other_wc = entry.m_Type == "*";
1679     if (this_wc != other_wc) return !this_wc;
1680     // Prefer specific subtype over wildcard.
1681     this_wc = m_Subtype == "*";
1682     other_wc = entry.m_Subtype == "*";
1683     if (this_wc != other_wc) return !this_wc;
1684     // Prefer more specific media range params.
1685     if (m_MediaRangeParams.empty() != entry.m_MediaRangeParams.empty()) {
1686         return !m_MediaRangeParams.empty();
1687     }
1688     // Prefer higher quality factor.
1689     if (m_Quality != entry.m_Quality) return m_Quality > entry.m_Quality;
1690     // Otherwise sort by type/subtype values.
1691     if (m_Type != entry.m_Type) return m_Type < entry.m_Type;
1692     if (m_Subtype != entry.m_Subtype) return m_Subtype < entry.m_Subtype;
1693     // Otherwise (same type/subtype, quality factor and number of params)
1694     // consider entries equal.
1695     return false;
1696 }
1697 
1698 
ParseAcceptHeader(TAcceptEntries & entries) const1699 void CCgiApplication::ParseAcceptHeader(TAcceptEntries& entries) const {
1700     string accept = m_Context->GetRequest().GetProperty(eCgi_HttpAccept);
1701     if (accept.empty()) return;
1702     list<string> types;
1703     NStr::Split(accept, ",", types, NStr::fSplit_MergeDelimiters);
1704     ITERATE(list<string>, type_it, types) {
1705         list<string> parts;
1706         NStr::Split(NStr::TruncateSpaces(*type_it), ";", parts, NStr::fSplit_MergeDelimiters);
1707         if ( parts.empty() ) continue;
1708         entries.push_back(SAcceptEntry());
1709         SAcceptEntry& entry = entries.back();
1710         NStr::SplitInTwo(NStr::TruncateSpaces(parts.front()), "/", entry.m_Type, entry.m_Subtype);
1711         NStr::TruncateSpacesInPlace(entry.m_Type);
1712         NStr::TruncateSpacesInPlace(entry.m_Subtype);
1713         list<string>::const_iterator ext_it = parts.begin();
1714         ++ext_it; // skip type/subtype
1715         bool aparams = false;
1716         while (ext_it != parts.end()) {
1717             string name, value;
1718             NStr::SplitInTwo(NStr::TruncateSpaces(*ext_it), "=", name, value);
1719             NStr::TruncateSpacesInPlace(name);
1720             NStr::TruncateSpacesInPlace(value);
1721             if (name == "q") {
1722                 entry.m_Quality = NStr::StringToNumeric<float>(value, NStr::fConvErr_NoThrow);
1723                 if (entry.m_Quality == 0 && errno != 0) {
1724                     entry.m_Quality = 1;
1725                 }
1726                 aparams = true;
1727                 ++ext_it;
1728                 continue;
1729             }
1730             if (aparams) {
1731                 entry.m_AcceptParams[name] = value;
1732             }
1733             else {
1734                  entry.m_MediaRangeParams += ";" + name + "=" + value;
1735             }
1736             ++ext_it;
1737         }
1738     }
1739     entries.sort();
1740 }
1741 
1742 
1743 NCBI_PARAM_DECL(bool, CGI, EnableHelpRequest);
1744 NCBI_PARAM_DEF_EX(bool, CGI, EnableHelpRequest, true,
1745                   eParam_NoThread, CGI_ENABLE_HELP_REQUEST);
1746 typedef NCBI_PARAM_TYPE(CGI, EnableHelpRequest) TEnableHelpRequest;
1747 
1748 
x_ProcessHelpRequest(void)1749 bool CCgiApplication::x_ProcessHelpRequest(void)
1750 {
1751     if (!TEnableHelpRequest::GetDefault()) return false;
1752     CCgiRequest& request = m_Context->GetRequest();
1753     if (request.GetRequestMethod() != CCgiRequest::eMethod_GET) return false;
1754     bool found = false;
1755     string format = request.GetEntry("ncbi_help", &found);
1756     if ( !found ) return false;
1757     ProcessHelpRequest(format);
1758     return true;
1759 }
1760 
1761 
1762 static const char* kStdFormats[] = { "html", "xml", "json" };
1763 static const char* kStdContentTypes[] = { "text/html", "text/xml", "application/json" };
1764 
FindContentType(CTempString format)1765 inline string FindContentType(CTempString format) {
1766     for (size_t i = 0; i < sizeof(kStdFormats) / sizeof(kStdFormats[0]); ++i) {
1767         if (format == kStdFormats[i]) return kStdContentTypes[i];
1768     }
1769     return kEmptyStr;
1770 }
1771 
1772 
ProcessHelpRequest(const string & format)1773 void CCgiApplication::ProcessHelpRequest(const string& format)
1774 {
1775     string base_name = GetProgramExecutablePath();
1776     string fname;
1777     string content_type;
1778     // If 'format' is set, try to find <basename>.help.<format> or help.<format> file.
1779     if ( !format.empty() ) {
1780         string fname_fmt = base_name + ".help." + format;
1781         if ( CFile(fname_fmt).Exists() ) {
1782             fname = fname_fmt;
1783             content_type = FindContentType(format);
1784         }
1785         else {
1786             fname_fmt = "help." + format;
1787             if ( CFile(fname_fmt).Exists() ) {
1788                 fname = fname_fmt;
1789                 content_type = FindContentType(format);
1790             }
1791         }
1792     }
1793 
1794     // If 'format' is not set or there's no file of the specified format, check
1795     // 'Accept:' header.
1796     if ( fname.empty() ) {
1797         TAcceptEntries entries;
1798         ParseAcceptHeader(entries);
1799         ITERATE(TAcceptEntries, it, entries) {
1800             string fname_accept = base_name + ".help." + it->m_Subtype + it->m_MediaRangeParams;
1801             if ( CFile(fname_accept).Exists() ) {
1802                 fname = fname_accept;
1803                 content_type = it->m_Type + "/" + it->m_Subtype;
1804                 break;
1805             }
1806         }
1807         if ( fname.empty() ) {
1808             ITERATE(TAcceptEntries, it, entries) {
1809                 string fname_accept = "help." + it->m_Subtype + it->m_MediaRangeParams;
1810                 if ( CFile(fname_accept).Exists() ) {
1811                     fname = fname_accept;
1812                     content_type = it->m_Type + "/" + it->m_Subtype;
1813                     break;
1814                 }
1815             }
1816         }
1817     }
1818 
1819     // Finally, check standard formats: html/xml/json
1820     if ( fname.empty() ) {
1821         for (size_t i = 0; i < sizeof(kStdFormats) / sizeof(kStdFormats[0]); ++i) {
1822             string fname_std = base_name + ".help." + kStdFormats[i];
1823             if ( CFile(fname_std).Exists() ) {
1824                 fname = fname_std;
1825                 content_type = kStdContentTypes[i];
1826                 break;
1827             }
1828         }
1829     }
1830     if ( fname.empty() ) {
1831         for (size_t i = 0; i < sizeof(kStdFormats) / sizeof(kStdFormats[0]); ++i) {
1832             string fname_std = string("help.") + kStdFormats[i];
1833             if ( CFile(fname_std).Exists() ) {
1834                 fname = fname_std;
1835                 content_type = kStdContentTypes[i];
1836                 break;
1837             }
1838         }
1839     }
1840 
1841     CCgiResponse& response = m_Context->GetResponse();
1842     if ( !fname.empty() ) {
1843         CNcbiIfstream in(fname.c_str());
1844 
1845         // Check if the file starts with "Content-type:" header followed by double-eol.
1846         bool ct_found = false;
1847         string ct;
1848         getline(in, ct);
1849         if ( NStr::StartsWith(ct, "content-type:", NStr::eNocase) ) {
1850             string eol;
1851             getline(in, eol);
1852             if ( eol.empty() ) {
1853                 ct_found = true;
1854                 content_type = NStr::TruncateSpaces(ct.substr(13));
1855             }
1856         }
1857         if ( !ct_found ) {
1858             in.seekg(0);
1859         }
1860 
1861         if ( !content_type.empty()) {
1862             response.SetContentType(content_type);
1863         }
1864         response.WriteHeader();
1865         NcbiStreamCopy(*response.GetOutput(), in);
1866     }
1867     else {
1868         // Could not find help file, use arg descriptions instead.
1869         const CArgDescriptions* args = GetArgDescriptions();
1870         if ( args ) {
1871             response.SetContentType("text/xml");
1872             response.WriteHeader();
1873             args->PrintUsageXml(*response.GetOutput());
1874         }
1875         else {
1876             NCBI_THROW(CCgiRequestException, eData,
1877                 "Can not find help for CGI application");
1878         }
1879     }
1880 }
1881 
1882 
1883 NCBI_PARAM_DECL(string, CGI, EnableVersionRequest);
1884 NCBI_PARAM_DEF_EX(string, CGI, EnableVersionRequest, "t",
1885                   eParam_NoThread, CGI_ENABLE_VERSION_REQUEST);
1886 typedef NCBI_PARAM_TYPE(CGI, EnableVersionRequest) TEnableVersionRequest;
1887 
1888 
x_ProcessVersionRequest(void)1889 bool CCgiApplication::x_ProcessVersionRequest(void)
1890 {
1891     CCgiRequest& request = m_Context->GetRequest();
1892     if (request.GetRequestMethod() != CCgiRequest::eMethod_GET) return false;
1893 
1894     // If param value is a bool, enable the default ncbi_version CGI arg.
1895     // Otherwise try to use the value as the arg's name, then fallback to
1896     // ncbi_version.
1897     bool use_alt_name = false;
1898     string vparam = TEnableVersionRequest::GetDefault();
1899     if ( vparam.empty() ) return false;
1900     try {
1901         bool is_enabled = NStr::StringToBool(vparam);
1902         if (!is_enabled) return false;
1903     }
1904     catch (const CStringException&) {
1905         use_alt_name = true;
1906     }
1907 
1908     string ver_type;
1909     bool found = false;
1910     if ( use_alt_name ) {
1911         ver_type = request.GetEntry(vparam, &found);
1912     }
1913     if ( !found ) {
1914         ver_type = request.GetEntry("ncbi_version", &found);
1915     }
1916     if ( !found ) return false;
1917 
1918     EVersionType vt;
1919     if (ver_type.empty() || ver_type == "short") {
1920         vt = eVersion_Short;
1921     }
1922     else if (ver_type == "full") {
1923         vt = eVersion_Full;
1924     }
1925     else {
1926         NCBI_THROW(CCgiRequestException, eEntry,
1927             "Unsupported ncbi_version argument value");
1928     }
1929     ProcessVersionRequest(vt);
1930     return true;
1931 }
1932 
1933 
ProcessVersionRequest(EVersionType ver_type)1934 void CCgiApplication::ProcessVersionRequest(EVersionType ver_type)
1935 {
1936     string format = "plain";
1937     string content_type = "text/plain";
1938     TAcceptEntries entries;
1939     ParseAcceptHeader(entries);
1940     ITERATE(TAcceptEntries, it, entries) {
1941         if (it->m_Subtype == "xml" || it->m_Subtype == "json" ||
1942             (it->m_Type == "text" && it->m_Subtype == "plain")) {
1943             format = it->m_Subtype;
1944             content_type = it->m_Type + "/" + it->m_Subtype;
1945             break;
1946         }
1947     }
1948 
1949     CCgiResponse& response = m_Context->GetResponse();
1950     response.SetContentType(content_type);
1951     response.WriteHeader();
1952     CNcbiOstream& out = *response.GetOutput();
1953     if (format == "plain") {
1954         switch (ver_type) {
1955         case eVersion_Short:
1956             out << GetVersion().Print();
1957             break;
1958         case eVersion_Full:
1959             out << GetFullVersion().Print(GetAppName());
1960             break;
1961         }
1962     }
1963     else if (format == "xml") {
1964         switch (ver_type) {
1965         case eVersion_Short:
1966             out << GetFullVersion().PrintXml(kEmptyStr, CVersion::fVersionInfo);
1967             break;
1968         case eVersion_Full:
1969             out << GetFullVersion().PrintXml(GetAppName());
1970             break;
1971         }
1972     }
1973     else if (format == "json") {
1974         switch (ver_type) {
1975         case eVersion_Short:
1976             out << GetFullVersion().PrintJson(kEmptyStr, CVersion::fVersionInfo);
1977             break;
1978         case eVersion_Full:
1979             out << GetFullVersion().PrintJson(GetAppName());
1980             break;
1981         }
1982     }
1983     else {
1984         NCBI_THROW(CCgiRequestException, eData,
1985             "Unsupported version format");
1986     }
1987 }
1988 
1989 
x_ProcessAdminRequest(void)1990 bool CCgiApplication::x_ProcessAdminRequest(void)
1991 {
1992     CCgiRequest& request = m_Context->GetRequest();
1993     if (request.GetRequestMethod() != CCgiRequest::eMethod_GET) return false;
1994 
1995     bool found = false;
1996     string cmd_name = request.GetEntry("ncbi_admin_cmd", &found);
1997     if ( !found ) {
1998         // Check if PATH_INFO contains a command.
1999         string path_info = request.GetProperty(eCgi_PathInfo);
2000         NStr::TrimSuffixInPlace(path_info, "/");
2001         NStr::TrimPrefixInPlace(path_info, "/");
2002         if ( path_info.empty() ) return false;
2003         cmd_name = path_info;
2004     }
2005     EAdminCommand cmd = eAdmin_Unknown;
2006     if ( NStr::EqualNocase(cmd_name, "health") ) {
2007         cmd = eAdmin_Health;
2008     }
2009     else if ( NStr::EqualNocase(cmd_name, "deep-health") ) {
2010         cmd = eAdmin_HealthDeep;
2011     }
2012 
2013     // If the overriden method failed or refused to process a command,
2014     // fallback to the default processing which returns true for all known commands.
2015     return ProcessAdminRequest(cmd) || CCgiApplication::ProcessAdminRequest(cmd);
2016 }
2017 
2018 
ProcessAdminRequest(EAdminCommand cmd)2019 bool CCgiApplication::ProcessAdminRequest(EAdminCommand cmd)
2020 {
2021     if (cmd == eAdmin_Unknown) return false;
2022 
2023     // By default report status 200 and write headers for any command.
2024     CCgiResponse& response = m_Context->GetResponse();
2025     response.SetContentType("text/plain");
2026     SetHTTPStatus(CCgiException::e200_Ok,
2027         CCgiException::GetStdStatusMessage(CCgiException::e200_Ok));
2028     response.WriteHeader();
2029     return true;
2030 }
2031 
2032 
2033 NCBI_PARAM_DECL(bool, CGI, ValidateCSRFToken);
2034 NCBI_PARAM_DEF_EX(bool, CGI, ValidateCSRFToken, false, eParam_NoThread,
2035     CGI_VALIDATE_CSRF_TOKEN);
2036 typedef NCBI_PARAM_TYPE(CGI, ValidateCSRFToken) TParamValidateCSRFToken;
2037 
2038 static const char* kCSRFTokenName = "NCBI_CSRF_TOKEN";
2039 
ValidateSynchronizationToken(void)2040 bool CCgiApplication::ValidateSynchronizationToken(void)
2041 {
2042     if (!TParamValidateCSRFToken::GetDefault()) return true;
2043     const CCgiRequest& req = GetContext().GetRequest();
2044     const string& token = req.GetRandomProperty(kCSRFTokenName, false);
2045     return !token.empty() && token == req.GetTrackingCookie();
2046 }
2047 
2048 
2049 ///////////////////////////////////////////////////////
2050 // CCgiStatistics
2051 //
2052 
2053 
CCgiStatistics(CCgiApplication & cgi_app)2054 CCgiStatistics::CCgiStatistics(CCgiApplication& cgi_app)
2055     : m_CgiApp(cgi_app), m_LogDelim(";")
2056 {
2057 }
2058 
2059 
~CCgiStatistics()2060 CCgiStatistics::~CCgiStatistics()
2061 {
2062 }
2063 
2064 
Reset(const CTime & start_time,int result,const std::exception * ex)2065 void CCgiStatistics::Reset(const CTime& start_time,
2066                            int          result,
2067                            const std::exception*  ex)
2068 {
2069     m_StartTime = start_time;
2070     m_Result    = result;
2071     m_ErrMsg    = ex ? ex->what() : kEmptyStr;
2072 }
2073 
2074 
Compose(void)2075 string CCgiStatistics::Compose(void)
2076 {
2077     const CNcbiRegistry& reg = m_CgiApp.GetConfig();
2078     CTime end_time(CTime::eCurrent);
2079 
2080     // Check if it is assigned NOT to log the requests took less than
2081     // cut off time threshold
2082     TSeconds time_cutoff = reg.GetInt("CGI", "TimeStatCutOff", 0, 0,
2083                                       CNcbiRegistry::eReturn);
2084     if (time_cutoff > 0) {
2085         TSeconds diff = end_time.DiffSecond(m_StartTime);
2086         if (diff < time_cutoff) {
2087             return kEmptyStr;  // do nothing if it is a light weight request
2088         }
2089     }
2090 
2091     string msg, tmp_str;
2092 
2093     tmp_str = Compose_ProgramName();
2094     if ( !tmp_str.empty() ) {
2095         msg.append(tmp_str);
2096         msg.append(m_LogDelim);
2097     }
2098 
2099     tmp_str = Compose_Result();
2100     if ( !tmp_str.empty() ) {
2101         msg.append(tmp_str);
2102         msg.append(m_LogDelim);
2103     }
2104 
2105     bool is_timing =
2106         reg.GetBool("CGI", "TimeStamp", false, 0, CNcbiRegistry::eErrPost);
2107     if ( is_timing ) {
2108         tmp_str = Compose_Timing(end_time);
2109         if ( !tmp_str.empty() ) {
2110             msg.append(tmp_str);
2111             msg.append(m_LogDelim);
2112         }
2113     }
2114 
2115     tmp_str = Compose_Entries();
2116     if ( !tmp_str.empty() ) {
2117         msg.append(tmp_str);
2118     }
2119 
2120     tmp_str = Compose_ErrMessage();
2121     if ( !tmp_str.empty() ) {
2122         msg.append(tmp_str);
2123         msg.append(m_LogDelim);
2124     }
2125 
2126     return msg;
2127 }
2128 
2129 
Submit(const string & message)2130 void CCgiStatistics::Submit(const string& message)
2131 {
2132     LOG_POST_X(11, message);
2133 }
2134 
2135 
Compose_ProgramName(void)2136 string CCgiStatistics::Compose_ProgramName(void)
2137 {
2138     return m_CgiApp.GetArguments().GetProgramName();
2139 }
2140 
2141 
Compose_Timing(const CTime & end_time)2142 string CCgiStatistics::Compose_Timing(const CTime& end_time)
2143 {
2144     CTimeSpan elapsed = end_time.DiffTimeSpan(m_StartTime);
2145     return m_StartTime.AsString() + m_LogDelim + elapsed.AsString();
2146 }
2147 
2148 
Compose_Entries(void)2149 string CCgiStatistics::Compose_Entries(void)
2150 {
2151     const CCgiContext* ctx = m_CgiApp.m_Context.get();
2152     if ( !ctx )
2153         return kEmptyStr;
2154 
2155     const CCgiRequest& cgi_req = ctx->GetRequest();
2156 
2157     // LogArgs - list of CGI arguments to log.
2158     // Can come as list of arguments (LogArgs = param1;param2;param3),
2159     // or be supplemented with aliases (LogArgs = param1=1;param2=2;param3).
2160     // When alias is provided we use it for logging purposes (this feature
2161     // can be used to save logging space or reduce the net traffic).
2162     const CNcbiRegistry& reg = m_CgiApp.GetConfig();
2163     string log_args = reg.Get("CGI", "LogArgs");
2164     if ( log_args.empty() )
2165         return kEmptyStr;
2166 
2167     list<string> vars;
2168     NStr::Split(log_args, ",; \t", vars,
2169         NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
2170 
2171     string msg;
2172     ITERATE (list<string>, i, vars) {
2173         bool is_entry_found;
2174         const string& arg = *i;
2175 
2176         size_t pos = arg.find_last_of('=');
2177         if (pos == 0) {
2178             return "<misconf>" + m_LogDelim;
2179         } else if (pos != string::npos) {   // alias assigned
2180             string key = arg.substr(0, pos);
2181             const CCgiEntry& entry = cgi_req.GetEntry(key, &is_entry_found);
2182             if ( is_entry_found ) {
2183                 string alias = arg.substr(pos+1, arg.length());
2184                 msg.append(alias);
2185                 msg.append("='");
2186                 msg.append(entry.GetValue());
2187                 msg.append("'");
2188                 msg.append(m_LogDelim);
2189             }
2190         } else {
2191             const CCgiEntry& entry = cgi_req.GetEntry(arg, &is_entry_found);
2192             if ( is_entry_found ) {
2193                 msg.append(arg);
2194                 msg.append("='");
2195                 msg.append(entry.GetValue());
2196                 msg.append("'");
2197                 msg.append(m_LogDelim);
2198             }
2199         }
2200     }
2201 
2202     return msg;
2203 }
2204 
2205 
Compose_Result(void)2206 string CCgiStatistics::Compose_Result(void)
2207 {
2208     return NStr::IntToString(m_Result);
2209 }
2210 
2211 
Compose_ErrMessage(void)2212 string CCgiStatistics::Compose_ErrMessage(void)
2213 {
2214     return m_ErrMsg;
2215 }
2216 
2217 /////////////////////////////////////////////////////////////////////////////
2218 //  Tracking Environment
2219 
2220 NCBI_PARAM_DEF(bool, CGI, DisableTrackingCookie, false);
2221 NCBI_PARAM_DEF(string, CGI, TrackingCookieName, "ncbi_sid");
2222 NCBI_PARAM_DEF(string, CGI, TrackingTagName, "NCBI-SID");
2223 NCBI_PARAM_DEF(string, CGI, TrackingCookieDomain, ".nih.gov");
2224 NCBI_PARAM_DEF(string, CGI, TrackingCookiePath, "/");
2225 
2226 
2227 END_NCBI_SCOPE
2228