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