diff --git a/parser/Makefile.generic b/parser/Makefile.generic index 96b6d73..6b3ab09 100644 --- a/parser/Makefile.generic +++ b/parser/Makefile.generic @@ -57,22 +57,22 @@ $(OBJDIR)/$(PREFIX)_parser.prof.o: $(OBJDIR)/$(PREFIX)_scanner.prof.o $(OBJDIR)/$(CPREFIX)_Interface.dbg.o $(OBJDIR)/$(CPREFIX)_Interface.rel.o $(OBJDIR)/$(CPREFIX)_Interface.prof.o : $(PREFIX)_parser.hpp $(PREFIX)_parser.hpp : $(PREFIX)_parser.cpp -$(FWD_TOOL)-dbg: $($(CPREFIX)2JSN_DBG_OBJ) $(DBG_TOOL_OBJ) - $(LD) -o $@ $^ $(COMMON_DBG_OBJ) $(LDFLAGS) +$(FWD_TOOL)-dbg: $($(CPREFIX)2JSN_DBG_OBJ) + $(LD) -o $@ $^ $(COMMON_DBG_OBJ) $(LDFLAGS_DBG) -$(FWD_TOOL)-rel: $($(CPREFIX)2JSN_REL_OBJ) $(REL_TOOL_OBJ) - $(LD) -o $@ $^ $(COMMON_REL_OBJ) $(LDFLAGS) +$(FWD_TOOL)-rel: $($(CPREFIX)2JSN_REL_OBJ) + $(LD) -o $@ $^ $(COMMON_REL_OBJ) $(LDFLAGS_REL) -$(FWD_TOOL)-prof: $($(CPREFIX)2JSN_PROF_OBJ) $(PROF_TOOL_OBJ) +$(FWD_TOOL)-prof: $($(CPREFIX)2JSN_PROF_OBJ) $(LD) -o $@ $^ $(COMMON_PROF_OBJ) $(GPROFFLAGS) $(LDFLAGS) -$(REV_TOOL)-dbg: $(JSN2$(CPREFIX)_DBG_OBJ) $(DBG_TOOL_OBJ) - $(LD) -o $@ $^ $(COMMON_DBG_OBJ) $(LDFLAGS) +$(REV_TOOL)-dbg: $(JSN2$(CPREFIX)_DBG_OBJ) + $(LD) -o $@ $^ $(COMMON_DBG_OBJ) $(LDFLAGS_DBG) -$(REV_TOOL)-rel: $(JSN2$(CPREFIX)_REL_OBJ) $(REL_TOOL_OBJ) - $(LD) -o $@ $^ $(COMMON_REL_OBJ) $(LDFLAGS) +$(REV_TOOL)-rel: $(JSN2$(CPREFIX)_REL_OBJ) + $(LD) -o $@ $^ $(COMMON_REL_OBJ) $(LDFLAGS_REL) -$(REV_TOOL)-prof: $(JSN2$(CPREFIX)_PROF_OBJ) $(PROF_TOOL_OBJ) +$(REV_TOOL)-prof: $(JSN2$(CPREFIX)_PROF_OBJ) $(LD) -o $@ $^ $(COMMON_PROF_OBJ) $(GPROFFLAGS) $(LDFLAGS) $(PREFIX): mkdir $(FWD_TOOL)-dbg $(FWD_TOOL)-rel $(REV_TOOL)-dbg $(REV_TOOL)-rel @@ -98,24 +98,24 @@ $(PREFIX)_TEST_REVERSE_DBG_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.dbg.o,$($( $(PREFIX)_TEST_REVERSE_REL_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.rel.o,$($(PREFIX)_TEST_REVERSE_SRC))) $(BINDIR)/$(PREFIX)_test_parse-dbg: $($(PREFIX)_TEST_PARSE_DBG_OBJ) - $(LD) -o $@ $^ $(COMMON_DBG_OBJ) -lgtest $(LDFLAGS) + $(LD) -o $@ $^ $(COMMON_DBG_OBJ) -lgtest $(LDFLAGS_DBG) $(BINDIR)/$(PREFIX)_test_parse-rel: $($(PREFIX)_TEST_PARSE_REL_OBJ) - $(LD) -o $@ $^ $(COMMON_REL_OBJ) -lgtest $(LDFLAGS) + $(LD) -o $@ $^ $(COMMON_REL_OBJ) -lgtest $(LDFLAGS_REL) $(BINDIR)/$(PREFIX)_test_flex-dbg: $($(PREFIX)_TEST_FLEX_DBG_OBJ) - $(LD) -o $@ $^ -lgtest -lpthread + $(LD) -o $@ $^ -lgtest -lpthread $(LDFLAGS_DBG) $(BINDIR)/$(PREFIX)_test_flex-rel: $($(PREFIX)_TEST_FLEX_REL_OBJ) - $(LD) -o $@ $^ -lgtest -lpthread + $(LD) -o $@ $^ -lgtest -lpthread $(LDFLAGS_REL) #$(BINDIR)/$(PREFIX)_test_reverse-dbg: $(CPREFIX)_Interface.cpp $(BINDIR)/$(PREFIX)_test_reverse-dbg: $($(PREFIX)_TEST_REVERSE_DBG_OBJ) - $(LD) -o $@ $^ $(COMMON_DBG_OBJ) -lgtest $(LDFLAGS) + $(LD) -o $@ $^ $(COMMON_DBG_OBJ) -lgtest $(LDFLAGS_DBG) #$(BINDIR)/$(PREFIX)_test_reverse-rel: $(CPREFIX)_Interface.cpp $(BINDIR)/$(PREFIX)_test_reverse-rel: $($(PREFIX)_TEST_REVERSE_REL_OBJ) - $(LD) -o $@ $^ $(COMMON_REL_OBJ) -lgtest $(LDFLAGS) + $(LD) -o $@ $^ $(COMMON_REL_OBJ) -lgtest $(LDFLAGS_REL) unittest_parse: $(PREFIX) flex_test $(BINDIR)/$(PREFIX)_test_parse-dbg $(BINDIR)/$(PREFIX)_test_parse-rel $(BINDIR)/$(PREFIX)_test_parse-dbg diff --git a/parser/Makefile.set b/parser/Makefile.set index d033834..1e1ea2b 100644 --- a/parser/Makefile.set +++ b/parser/Makefile.set @@ -16,7 +16,8 @@ $(OBJDIR)/%.dbg.o: %.cpp $(CPP) $(CXXFLAGS) -g $< -o $(OBJDIR)/$(*F).dbg.o $(OBJDIR)/%.rel.o: %.cpp - $(CPP) $(CXXFLAGS) -O3 $< -o $(OBJDIR)/$(*F).rel.o + $(CPP) $(CXXFLAGS) -march=skylake -O3 $< -o $(OBJDIR)/$(*F).rel.o +# lmem12=skylake, lmem14=cascade lake $(OBJDIR)/%.prof.o: %.cpp $(CPP) $(CXXFLAGS) $(GPROFFLAGS) $< -o $(OBJDIR)/$(*F).prof.o @@ -34,11 +35,17 @@ $(OBJDIR)/%.prof.o: %.cpp ################################################# # Specify location of ncbi-vdb3/jwt-tool in JWT_TOOL if different -JWT_TOOL ?= ../../../ncbi-vdb3/jwt-tool +JWT_TOOL ?= $(abspath ../../../ncbi-vdb3/jwt-tool) -CXXFLAGS += -I$(JWT_TOOL)/inc -I$(JWT_TOOL)/utf8proc -I$(JWT_TOOL)/tool +GTEST ?= $(JWT_TOOL)/../external/googletest +GTESTLIB ?= $(JWT_TOOL)/../external/lib -LDFLAGS = -L$(JWT_TOOL)/lib -lncbi-json-dbg -lncbi-secure-dbg -lutf8proc -lmbedcrypto -lpthread +CXXFLAGS += -I$(JWT_TOOL)/inc -I$(JWT_TOOL)/utf8proc -I$(JWT_TOOL)/tool -I $(GTEST)/googletest/include + +LDFLAGS = -L$(JWT_TOOL)/lib -L$(GTESTLIB) -lutf8proc -lmbedcrypto -lpthread + +LDFLAGS_DBG = -lncbi-json-dbg -lncbi-secure-dbg $(LDFLAGS) +LDFLAGS_REL = -lncbi-json-rel -lncbi-secure-rel $(LDFLAGS) ################################################# diff --git a/parser/aws/AWS_Interface.hpp b/parser/aws/AWS_Interface.hpp index 78b528e..99f8736 100644 --- a/parser/aws/AWS_Interface.hpp +++ b/parser/aws/AWS_Interface.hpp @@ -4,6 +4,7 @@ #include "ParserInterface.hpp" #include +#include namespace NCBI { diff --git a/parser/cl/CL_Interface.hpp b/parser/cl/CL_Interface.hpp index 6dc9c32..2dd5fa9 100644 --- a/parser/cl/CL_Interface.hpp +++ b/parser/cl/CL_Interface.hpp @@ -4,6 +4,7 @@ #include "ParserInterface.hpp" #include +#include namespace NCBI { diff --git a/parser/common/Makefile b/parser/common/Makefile index 420424f..45d29cb 100644 --- a/parser/common/Makefile +++ b/parser/common/Makefile @@ -1,7 +1,7 @@ # COMMON_SRC include ../Makefile.set -all: cmd common +all: common test: cmn_test @@ -20,18 +20,23 @@ mkdir: CMD_SRC_LOCATION = $(JWT_TOOL)/tool CMD_SRC = $(CMD_SRC_LOCATION)/cmdline.cpp -CMD_DBG_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.dbg.o,$(notdir $(CMD_SRC)))) -CMD_REL_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.rel.o,$(notdir $(CMD_SRC)))) +CMD_DBG_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.dbg.o,$(notdir $(CMD_SRC)))) +CMD_REL_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.rel.o,$(notdir $(CMD_SRC)))) CMD_PROF_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.prof.o,$(notdir $(CMD_SRC)))) +$(info CMD_SRC_LOCATION=$(CMD_SRC_LOCATION)) +$(info $(OBJDIR)/%.dbg.o: $(CMD_SRC_LOCATION)/%.cpp) + $(OBJDIR)/%.dbg.o: $(CMD_SRC_LOCATION)/%.cpp $(CPP) $(CXXFLAGS) -g $< -o $(OBJDIR)/$(*F).dbg.o $(OBJDIR)/%.rel.o: $(CMD_SRC_LOCATION)/%.cpp - $(CPP) $(CXXFLAGS) -O3 $< -o $(OBJDIR)/$(*F).rel.o + $(CPP) $(CXXFLAGS) -O3 -march=skylake $< -o $(OBJDIR)/$(*F).rel.o $(OBJDIR)/%.prof.o: $(CMD_SRC_LOCATION)/%.cpp - $(CPP) $(CXXFLAGS) -O3 $< -o $(OBJDIR)/$(*F).prof.o + $(CPP) $(CXXFLAGS) -O3 -march=skylake $< -o $(OBJDIR)/$(*F).prof.o + +$(info cmd: mkdir $(CMD_DBG_OBJ) $(CMD_REL_OBJ) $(CMD_PROF_OBJ)) cmd: mkdir $(CMD_DBG_OBJ) $(CMD_REL_OBJ) $(CMD_PROF_OBJ) @@ -110,10 +115,10 @@ cmn_test: mkdir $(BINDIR)/test_common-dbg $(BINDIR)/test_common-rel cmn_prof: mkdir $(COMMON_PROF_OBJ) $(CMD_PROF_OBJ) $(BINDIR)/test_common-dbg: $(TEST_SUPPORT_DBG_OBJ) $(CMD_DBG_OBJ) - $(LD) -o $@ $^ -L$(JWT_TOOL)/lib -lncbi-json-dbg -lncbi-secure-dbg -lutf8proc -lmbedcrypto -lgtest -lpthread + $(LD) -o $@ $^ $(LDFLAGS_DBG) -lgtest $(BINDIR)/test_common-rel: $(TEST_SUPPORT_REL_OBJ) $(CMD_REL_OBJ) - $(LD) -o $@ $^ -L$(JWT_TOOL)/lib -lncbi-json-rel -lncbi-secure-rel -lutf8proc -lmbedcrypto -lgtest -lpthread + $(LD) -o $@ $^ $(LDFLAGS_DBG) -lgtest .PHONY: common cmn_clean cmn_test diff --git a/parser/common/ParserInterface.cpp b/parser/common/ParserInterface.cpp index 4af8bdc..1a7c62f 100644 --- a/parser/common/ParserInterface.cpp +++ b/parser/common/ParserInterface.cpp @@ -33,11 +33,24 @@ ParseBlockInterface :: receive_one_line( const char * line, size_t line_size, si if ( ! format_specific_parse( line, line_size ) ) { + if ( m_debug) + { + cout<<"ParseBlockInterface :: receive_one_line(): format_specific_parse failed " << endl; + } receiver . SetCategory( ReceiverInterface::cat_ugly ); } else if ( receiver . GetCategory() == ReceiverInterface::cat_good ) { - receiver . SetCategory( receiver.post_process() ); + if ( m_debug) + { + cout<<"ParseBlockInterface :: receive_one_line(): format_specific_parse succeeded " << endl; + } + ReceiverInterface::Category post_cat = receiver.post_process(); + if ( m_debug) + { + cout<<"ParseBlockInterface :: receive_one_line(): post_process = " << post_cat << endl; + } + receiver . SetCategory( post_cat ); } if ( receiver . GetCategory() != ReceiverInterface::cat_good ) diff --git a/parser/common/URL_Interface.cpp b/parser/common/URL_Interface.cpp index c74d674..9d673a4 100644 --- a/parser/common/URL_Interface.cpp +++ b/parser/common/URL_Interface.cpp @@ -52,7 +52,9 @@ URLParseBlock::format_specific_parse( const char * line, size_t line_size ) url__delete_buffer( bs, m_sc ); if ( ret != 0 ) + { m_receiver . SetCategory( ReceiverInterface::cat_ugly ); + } else if ( m_receiver .GetCategory() == ReceiverInterface::cat_unknown ) m_receiver . SetCategory( ReceiverInterface::cat_good ); return ret == 0; diff --git a/parser/common/test_url_parsing.cpp b/parser/common/test_url_parsing.cpp index 5084fab..d4ae736 100644 --- a/parser/common/test_url_parsing.cpp +++ b/parser/common/test_url_parsing.cpp @@ -439,4 +439,20 @@ TEST_F( URLTestFixture, LOGMON_217 ) ASSERT_EQ( "NC_000001", extract_value( res, "accession" ) ); ASSERT_EQ( "NC_000001", extract_value( res, "filename" ) ); ASSERT_EQ( ".10.1", extract_value( res, "extension" ) ); -} \ No newline at end of file +} + +TEST_F( URLTestFixture, LOGMON_240_1_query_starts_with_separator ) +{ // query starts with '&' + const std::string res = try_to_parse_good( "/entrez/eutils/esearch.fcgi?&retmode=json&version=2.0&db=taxonomy&term=&", true ); + ASSERT_EQ( "", extract_value( res, "accession" ) ); + ASSERT_EQ( "esearch", extract_value( res, "filename" ) ); + ASSERT_EQ( ".fcgi", extract_value( res, "extension" ) ); +} + +TEST_F( URLTestFixture, LOGMON_240_duplicated_query_separator ) +{ // '&&' in the query + const std::string res = try_to_parse_good( "/phpfm/index.php?&&path=&action=upload" ); + ASSERT_EQ( "", extract_value( res, "accession" ) ); + ASSERT_EQ( "index", extract_value( res, "filename" ) ); + ASSERT_EQ( ".php", extract_value( res, "extension" ) ); +} diff --git a/parser/common/url_parser.y b/parser/common/url_parser.y index 53c7b57..16e422a 100644 --- a/parser/common/url_parser.y +++ b/parser/common/url_parser.y @@ -288,6 +288,10 @@ query_list { $$ = $1; } + | query_list QUERY_SEP + { + $$ = $1; + } | query_list QUERY_SEP query_entry { // if $3 has more elements than $1, use $3 @@ -323,6 +327,8 @@ query_list query : QMARK query_list { $$ = $2; } + | QMARK QUERY_SEP query_list { $$ = $3; } + | QMARK { Init_Node( $$ ); } | %empty { Init_Node( $$ ); } ; diff --git a/parser/common/url_scanner.l b/parser/common/url_scanner.l index 4163681..236f38a 100644 --- a/parser/common/url_scanner.l +++ b/parser/common/url_scanner.l @@ -33,7 +33,7 @@ ACCESSION {SRA}|{REFSEQ1}|{REFSEQ2}|{WGS}|"hs37d5" SLASH (\/|%2F|%2f) QMARK "?" -QUERY_SEP [&;] +QUERY_SEP [&;]+ EQUAL "=" HASH "#" PERCENT "%" diff --git a/parser/err/ERR_Interface.hpp b/parser/err/ERR_Interface.hpp index c3e8276..218eea2 100644 --- a/parser/err/ERR_Interface.hpp +++ b/parser/err/ERR_Interface.hpp @@ -4,6 +4,7 @@ #include "ParserInterface.hpp" #include +#include namespace NCBI { diff --git a/parser/err/Makefile b/parser/err/Makefile index aab8e8f..94a183d 100644 --- a/parser/err/Makefile +++ b/parser/err/Makefile @@ -27,10 +27,10 @@ MSG_TEST_FLEX_DBG_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.dbg.o,$(MSG_TEST_FL MSG_TEST_FLEX_REL_OBJ = $(addprefix $(OBJDIR)/,$(subst .cpp,.rel.o,$(MSG_TEST_FLEX_SRC))) $(BINDIR)/msg_test_flex-dbg: $(MSG_TEST_FLEX_DBG_OBJ) - $(LD) -o $@ $^ -lgtest -lpthread + $(LD) -o $@ $^ -lgtest $(LDFLAGS_DBG) $(BINDIR)/msg_test_flex-rel: $(MSG_TEST_FLEX_REL_OBJ) - $(LD) -o $@ $^ -lgtest -lpthread + $(LD) -o $@ $^ -lgtest $(LDFLAGS_REL) flex_test: msg_flex_test diff --git a/parser/gcp/GCP_Interface.hpp b/parser/gcp/GCP_Interface.hpp index 6c9ec6f..30bdc28 100644 --- a/parser/gcp/GCP_Interface.hpp +++ b/parser/gcp/GCP_Interface.hpp @@ -4,6 +4,7 @@ #include "ParserInterface.hpp" #include +#include namespace NCBI { diff --git a/parser/gcp/gcp2jsn.cpp b/parser/gcp/gcp2jsn.cpp index 267273a..22f5a84 100644 --- a/parser/gcp/gcp2jsn.cpp +++ b/parser/gcp/gcp2jsn.cpp @@ -3,7 +3,7 @@ using namespace NCBI::Logging; -std::string tool_version( "1.1.0" ); +std::string tool_version( "1.1.3" ); int main ( int argc, char * argv [], const char * envp [] ) { diff --git a/parser/op/OP_Interface.cpp b/parser/op/OP_Interface.cpp index 90a018b..9278c3a 100644 --- a/parser/op/OP_Interface.cpp +++ b/parser/op/OP_Interface.cpp @@ -49,6 +49,7 @@ ReceiverInterface::Category OPReceiver::post_process( void ) { URLReceiver url( m_fmt ); URLParseBlock pb ( url ); + // pb.m_debug = true; pb.format_specific_parse( url_for_postprocess.c_str(), url_for_postprocess.size() ); url_for_postprocess.clear(); @@ -158,20 +159,34 @@ static void extract_and_set( const JSONObject &obj, FormatterInterface &formatter, const char * fieldname, QuoteSpaces quote_spaces = sp_auto, bool empty_is_dash = false ) { - const JSONValue &entry = obj . getValue ( fieldname ); - const String &S = entry . toString(); - if ( S . isEmpty() ) + if ( obj.exists( fieldname ) ) { - if ( empty_is_dash ) - formatter . addNameValue( fieldname, "-" ); - } - else - { - switch ( quote_spaces ) + const JSONValue &entry = obj . getValue ( fieldname ); + const String &S = entry . toString(); + if ( S . isEmpty() ) + { + if ( empty_is_dash ) + formatter . addNameValue( fieldname, "-" ); + } + else { - case sp_auto: - { - if ( S . find( ' ' ) != String::npos ) + switch ( quote_spaces ) + { + case sp_auto: + { + if ( S . find( ' ' ) != String::npos ) + { + std::stringstream ss; + ss . put ( '"' ); + ss . write( S . data(), S . size() ); + ss . put ( '"' ); + formatter . addNameValue( fieldname, ss . str() ); + } + else + formatter . addNameValue( fieldname, S . toSTLString() ); + } + break; + case sp_force: { std::stringstream ss; ss . put ( '"' ); @@ -179,22 +194,11 @@ extract_and_set( const JSONObject &obj, FormatterInterface &formatter, const cha ss . put ( '"' ); formatter . addNameValue( fieldname, ss . str() ); } - else - formatter . addNameValue( fieldname, S . toSTLString() ); - } - break; - case sp_force: - { - std::stringstream ss; - ss . put ( '"' ); - ss . write( S . data(), S . size() ); - ss . put ( '"' ); - formatter . addNameValue( fieldname, ss . str() ); - } - break; - case sp_off: - formatter . addNameValue( fieldname, S.toSTLString() ); - break; + break; + case sp_off: + formatter . addNameValue( fieldname, S.toSTLString() ); + break; + } } } } @@ -206,21 +210,30 @@ extract_and_set_request( const JSONObject &obj, FormatterInterface &formatter ) ss . put( '"' ); - const String & method = obj . getValue ( "method" ) . toString(); - ss . write( method . data(), method . size() ); + if ( obj.exists( "method" ) ) + { + const String & method = obj . getValue ( "method" ) . toString(); + ss . write( method . data(), method . size() ); + } - const String & path = obj . getValue ( "path" ) . toString(); - if ( ! path.isEmpty() ) + if ( obj.exists( "path" ) ) { - ss . put( ' ' ); - ss . write( path . data(), path . size() ); + const String & path = obj . getValue ( "path" ) . toString(); + if ( ! path.isEmpty() ) + { + ss . put( ' ' ); + ss . write( path . data(), path . size() ); + } } - const String & vers = obj . getValue ( "vers" ) . toString(); - if ( ! vers.isEmpty() ) + if ( obj.exists( "vers" ) ) { - ss . put( ' ' ); - ss . write( vers . data(), vers . size() ); + const String & vers = obj . getValue ( "vers" ) . toString(); + if ( ! vers.isEmpty() ) + { + ss . put( ' ' ); + ss . write( vers . data(), vers . size() ); + } } ss . put( '"' ); @@ -242,6 +255,11 @@ OPReverseBlock::format_specific_parse( const char * line, size_t line_size ) const JSONValueRef values = JSON::parse( src ); const JSONObject &obj = values -> toObject(); + if ( obj.isEmpty() ) + { + return false; + } + extract_and_set( obj, formatter, "ip" ); formatter . addNameValue( "", "-" ); extract_and_set( obj, formatter, "user", sp_auto, true ); @@ -255,12 +273,14 @@ OPReverseBlock::format_specific_parse( const char * line, size_t line_size ) extract_and_set( obj, formatter, "agent", sp_force ); extract_and_set( obj, formatter, "forwarded", sp_force ); + if ( obj.exists( "port" ) ) { stringstream ss; ss << "port=" << obj . getValue ( "port" ) . toString() . toSTLString(); formatter . addNameValue( "port", ss.str() ); } + if ( obj.exists( "req_len" ) ) { stringstream ss; ss << "rl=" << obj . getValue ( "req_len" ) . toString() . toSTLString(); diff --git a/parser/op/OP_Interface.hpp b/parser/op/OP_Interface.hpp index 742e7bc..cd5ebb1 100644 --- a/parser/op/OP_Interface.hpp +++ b/parser/op/OP_Interface.hpp @@ -4,6 +4,7 @@ #include "ParserInterface.hpp" #include +#include namespace NCBI { diff --git a/parser/op/op2jsn.cpp b/parser/op/op2jsn.cpp index a221c33..652002f 100644 --- a/parser/op/op2jsn.cpp +++ b/parser/op/op2jsn.cpp @@ -3,7 +3,7 @@ using namespace NCBI::Logging; -std::string tool_version( "1.1.0" ); +std::string tool_version( "1.1.7" ); int main ( int argc, char * argv [], const char * envp [] ) { diff --git a/parser/op/op_parser.y b/parser/op/op_parser.y index d376ae6..8de0b62 100644 --- a/parser/op/op_parser.y +++ b/parser/op/op_parser.y @@ -67,8 +67,7 @@ log_onprem referer SPACE { op_start_UserAgent( scanner ); } agent { op_pop_state( scanner ); } SPACE forwarded SPACE - port SPACE - req_len + tail { // in case the productions did not find vers/path, set them here to make them at least empty in the ouput SET_VALUE( OPReceiver::vers, EmptyTSTR ); @@ -76,6 +75,37 @@ log_onprem } ; +tail + : tail_elem + | tail tail_elem + ; + +tail_elem + : port + | req_len + | STR + | I64 + | DASH + | UNRECOGNIZED + | SPACE + | quoted_freeform + ; + +quoted_freeform + : QUOTE freeform QUOTE; + +freeform + : freeform_elem + | freeform freeform_elem + ; + +freeform_elem + : QSTR + | QSTR_ESC + | I64 + | SPACE + ; + log_onprem_err : ip error ; @@ -103,12 +133,22 @@ server | STR { SET_VALUE( OPReceiver::server, $1 ); } ; +path_token + : PATHSTR + | QSTR_ESC + ; + +path_list + : path_token + | path_list path_token { $$ = $1; MERGE_TSTR( $$, $2 ); } + ; + space_and_url - : SPACE { op_start_URL ( scanner ); } PATHSTR + : SPACE { op_start_URL ( scanner ); } path_list { lib -> url_for_postprocess = string( $3.p, $3.n ); SET_VALUE( OPReceiver::path, $3 ); - op_pop_state ( scanner ); + op_pop_state ( scanner ); // pop out of QUOTED } ; @@ -126,14 +166,15 @@ request_tail ; request - : QUOTE method space_and_url SPACE request_tail QUOTE { } - | QUOTE method space_and_url SPACE QUOTE { } - | QUOTE method space_and_url QUOTE { } - | QUOTE method QUOTE { } + : QUOTE method space_and_url SPACE request_tail QUOTE + | QUOTE method space_and_url SPACE QUOTE + | QUOTE method space_and_url QUOTE + | QUOTE method QSTR_ESC QSTR QUOTE + | QUOTE method QUOTE ; server_and_request - : server SPACE request { } + : server SPACE request | server SPACE quoted_list { lib->reportField( "Invalid request" ); @@ -153,13 +194,12 @@ quoted_list_elem : QSTR { $$ = $1; } | VERS { $$ = $1; } | QSTR_ESC { $$ = $1; } + | SPACE { $$ = $1; } ; quoted_list_body - : quoted_list_elem { $$ = $1; } - | quoted_list_body SPACE quoted_list_elem { $$ = $1; $$.n += 1 + $3.n; } - | quoted_list_body SPACE { $$ = $1; $$.n += 1; } - | quoted_list_body METHOD { $$ = $1; $$.n += $2.n; } + : quoted_list_elem { $$ = $1; } + | quoted_list_body quoted_list_elem { $$ = $1; $$.n += $2.n; } ; result_code @@ -209,8 +249,19 @@ agent | QUOTE QUOTE { lib -> set( ReceiverInterface::agent, EmptyTSTR ); } ; +forwarded_list + : forwarded_token + | forwarded_list forwarded_token { $$ = $1; MERGE_TSTR( $$, $2 ); } + ; + +forwarded_token + : QSTR + | QSTR_ESC + | SPACE + ; + forwarded - : QUOTE QSTR QUOTE { SET_VALUE( OPReceiver::forwarded, $2 ); } + : QUOTE forwarded_list QUOTE { SET_VALUE( OPReceiver::forwarded, $2 ); } ; port diff --git a/parser/op/op_scanner.l b/parser/op/op_scanner.l index 1309239..abaa2ad 100644 --- a/parser/op/op_scanner.l +++ b/parser/op/op_scanner.l @@ -37,11 +37,11 @@ I64 [-+]?[0-9]+ FLOAT [-+]?[0-9]+\.[0-9]+ CHARS [A-Za-z0-9\-_~!*'();:@&=+$,#\[\]<>|`{}^] -STR_CHARS [?\.\/\\%]|{CHARS}|[\x00-\x1f] +STR_CHARS [?\.\/%]|{CHARS}|[\x00-\x1f] QSTR {STR_CHARS}+ -QSTR_ESC (\\\"|{STR_CHARS})+ -PATHSTR {QSTR_ESC} +QSTR_ESC (\\\"|\\\\|\\) +PATHSTR {QSTR} SPACE " " STR [.\-a-zA-Z0-9_]+ @@ -72,16 +72,22 @@ TIMEFMT "["{INT2}"/"{MONTH}"/"{INT4}":"{INT2}":"{INT2}":"{INT2}" "{TIMEZ {SPACE} { MAKE_STR(); return SPACE; } {METHOD} { MAKE_STR(); return METHOD; } {VERS} { MAKE_STR(); return VERS; } -{QSTR} { MAKE_STR(); return QSTR; } -{QSTR_ESC} { MAKE_STR(); return QSTR; } +{QSTR_ESC} { MAKE_STR(); return QSTR_ESC; } +{QSTR} { MAKE_STR(); return QSTR; } . { MAKE_STR(); return UNRECOGNIZED; } + /* TIMEFMT is pushed by the parser (calls op_start_time) */ {TIMEFMT} { yy_pop_state( yyscanner ); MAKE_STR(); return TIME_FMT; } . { yy_pop_state( yyscanner ); MAKE_STR(); return UNRECOGNIZED; } + /* PATH is pushed and popped by the parser (calls op_start_URL, op_pop_state) */ +{QSTR_ESC} { MAKE_STR(); return QSTR_ESC; } {PATHSTR} { MAKE_STR(); return PATHSTR; } +{SPACE} { MAKE_STR(); return SPACE; } +\" { yy_pop_state( yyscanner ); return QUOTE; } . { MAKE_STR(); return UNRECOGNIZED; }; + /* AGENT is pushed and popped by the parser (calls op_start_UserAgent) */ \" { MAKE_STR(); return QUOTE; } {AGENTSTR} { MAKE_STR(); return QSTR; } . { MAKE_STR(); return QSTR; } diff --git a/parser/op/op_test_flex.cpp b/parser/op/op_test_flex.cpp index b93502d..b7b9779 100644 --- a/parser/op/op_test_flex.cpp +++ b/parser/op/op_test_flex.cpp @@ -109,7 +109,7 @@ TEST_F ( OP_TestFlexFixture, Embedded_CtlChars ) } TEST_F ( OP_TestFlexFixture, QuotedString ) { - #define str ".Bl0-_~!*'();:@&=+$,/%#[]?\\<>|`{}^" + #define str ".Bl0-_~!*'();:@&=+$,/%#[]?<>|`{}^" ASSERT_EQ( QUOTE, StartScan("\"" str "\"") ); ASSERT_EQ( QSTR, NextTokenType() ); ASSERT_EQ( str, TokenValue() ); #undef str @@ -127,11 +127,20 @@ TEST_F ( OP_TestFlexFixture, QuotedNonAscii ) } TEST_F ( OP_TestFlexFixture, QuotedEscapedQuote ) { - ASSERT_EQ( QUOTE, StartScan("\"\\\"\"") ); /* "\"" */ - ASSERT_EQ( QSTR, NextTokenType() ); - ASSERT_EQ( "\\\"", TokenValue() ); // needs to be unescaped later + ASSERT_EQ( QUOTE, StartScan( R"("\"")" ) ); + ASSERT_EQ( QSTR_ESC, NextTokenType() ); + ASSERT_EQ( R"(\")", TokenValue() ); // needs to be unescaped later + ASSERT_EQ( QUOTE, NextTokenType() ); +} + +TEST_F ( OP_TestFlexFixture, QuotedEscapedBackslash ) +{ + ASSERT_EQ( QUOTE, StartScan( R"("\\\"")" ) ); // an escaped backslash followed by an escaped quote + ASSERT_EQ( QSTR_ESC, NextTokenType() ); + ASSERT_EQ( "\\\\", TokenValue() ); // escaped backslash + ASSERT_EQ( QSTR_ESC, NextTokenType() ); + ASSERT_EQ( "\\\"", TokenValue() ); // escaped quote ASSERT_EQ( QUOTE, NextTokenType() ); - #undef str } TEST_F ( OP_TestFlexFixture, IPV4 ) diff --git a/parser/op/op_test_parse.cpp b/parser/op/op_test_parse.cpp index 78d319d..cc0d010 100644 --- a/parser/op/op_test_parse.cpp +++ b/parser/op/op_test_parse.cpp @@ -93,7 +93,7 @@ TEST_F( OPTestFixture, ErrorRecovery ) { try_to_parse( "line1 blah\n" - "18.207.254.142 - - [01/Jan/2020:02:50:24 -0500] \"sra-download.ncbi.nlm.nih.gov\" \"GET\" 1 2 3 \"-\" \"\" \"-\" port=4 rl=5" ); + "18.207.254.142 - - [01/Jan/2020:02:50:24 -0500] \"sra-download.ncbi.nlm.nih.gov\" \"GET\" 1 2 3 \"-\" \"\" \"-\" port=4 rl=5", true ); ASSERT_EQ( "{\"_line_nr\":1,\"_unparsed\":\"line1 blah\"}\n", s_outputs.get_ugly() ); ASSERT_EQ( "{\"accession\":\"\",\"agent\":\"\",\"extension\":\"\",\"filename\":\"\",\"forwarded\":\"-\",\"ip\":\"18.207.254.142\",\"method\":\"GET\",\"path\":\"\",\"port\":\"4\",\"referer\":\"-\",\"req_len\":\"5\",\"req_time\":\"3\",\"res_code\":\"1\",\"res_len\":\"2\",\"server\":\"sra-download.ncbi.nlm.nih.gov\",\"time\":\"[01/Jan/2020:02:50:24 -0500]\",\"user\":\"\",\"vdb_libc\":\"\",\"vdb_os\":\"\",\"vdb_phid_compute_env\":\"\",\"vdb_phid_guid\":\"\",\"vdb_phid_session_id\":\"\",\"vdb_release\":\"\",\"vdb_tool\":\"\",\"vers\":\"\"}\n", @@ -102,7 +102,7 @@ TEST_F( OPTestFixture, ErrorRecovery ) TEST_F( OPTestFixture, bad_path ) { - std::string res = try_to_parse_good( "13.59.252.14 - - [07/Jun/2020:01:09:44 -0400] \"sra-download.ncbi.nlm.nih.gov\" \"GET ..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5Cwindows\\x5Cwin.ini HTTP/1.1\" 400 150 0.012 \"-\" \"-\" \"-\" port=80 rl=0" ); + std::string res = try_to_parse_good( "13.59.252.14 - - [07/Jun/2020:01:09:44 -0400] \"sra-download.ncbi.nlm.nih.gov\" \"GET ..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5Cwindows\\x5Cwin.ini HTTP/1.1\" 400 150 0.012 \"-\" \"-\" \"-\" port=80 rl=0", true ); ASSERT_EQ( "..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5C..\\x5Cwindows\\x5Cwin.ini", extract_value( res, "path" ) ); ASSERT_EQ( "", extract_value( res, "accession" ) ); ASSERT_EQ( "", extract_value( res, "filename" ) ); @@ -399,11 +399,80 @@ TEST_F( OPTestFixture, parse_path_with_url_encoded_slash_in_extension ) ASSERT_EQ( "", extract_value( res, "extension" ) ); } -extern "C" +TEST_F( OPTestFixture, free_form_tail_VDB_5981 ) { - int main ( int argc, const char * argv [], const char * envp [] ) - { - testing :: InitGoogleTest ( & argc, ( char ** ) argv ); - return RUN_ALL_TESTS (); - } + std::string res = try_to_parse_good( "34.140.130.14 - - [07/May/2025:23:48:24 -0400] \"ftp.ncbi.nih.gov\" \"GET /1000genomes/ftp/phase3/data/HG02025/sequence_read/SRR821984_2.filt.fastq.gz HTTP/1.1\" 200 111014168 0 \"-\" \"Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0\" \"-\" -pct 134311 X \"NCBI-SID: -\" port=443 885 80688 application/x-gzip" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_noHttpMethod ) +{ // skipping unrecognized tokens in the tail + std::string res = try_to_parse_good( "2408:8421:1c0:a8c5:29ef:f6ef:630e:13f0 - - [15/May/2025:00:00:35 -0400] \"submit.ncbi.nlm.nih.gov\" \"POST /subs/bioproject/SUB15322657/general_info HTTP/2.0\" 200 12448 19 \"https://submit.ncbi.nlm.nih.gov/subs/bioproject/SUB15322657/general_info\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0\" \"-\" -pct 19219246 - \"NCBI-SID: -\" id=aCVm4xD4sBqr4p-crpVWXgANYQU port=443 6576 12994 text/html loc=\"-\" sslproto=TLSv1.3" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_URL_query_starts_and_ends_with_ampersand ) +{ // skipping unrecognized tokens in the tail + std::string res = try_to_parse_good( "210.218.220.52 - - [28/Apr/2025:00:02:06 -0400] \"eutils.ncbi.nlm.nih.gov\" \"GET /entrez/eutils/esearch.fcgi?&retmode=json&version=2.0&db=taxonomy&term=& HTTP/1.1\" 429 87 0 \"http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?&retmode=json&version=2.0&db=taxonomy&term="Haloterrigena+salinisoli"&usehistory=y\" \"-\" \"-\" -pct 3867 - \"NCBI-SID: -\" id=aA79vlj0szU_J3GQ1tT5vwAABps port=443 352 666 application/json loc=\"-\" sslproto=TLSv1.3" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_empty_query ) +{ + std::string res = try_to_parse_good( "14.139.215.100 - - [07/Jun/2025:00:10:16 -0400] \"submit.ncbi.nlm.nih.gov\" \"POST /subs/sra/? HTTP/2.0\" 302 - 0 \"https://submit.ncbi.nlm.nih.gov/subs/sra/\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\" \"-\" -pct 84316 - \"NCBI-SID: -\" id=aEO7qOwPKFXOLvzZm_-qFgAGIQA port=443 2055 251 text/html loc=\"/subs/sra/SUB15373223/submitter\" sslproto=TLSv1.3" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_multiple_forwarded ) +{ + std::string res = try_to_parse_good( "130.14.29.110 - - [09/Jun/2025:23:59:59 -0400] \"www.ncbi.nlm.nih.gov\" \"GET /redirect/pmc/articles/PMC4155689/ HTTP/1.1\" 301 257 0 \"-\" \"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.2; +https://openai.com/gptbot)\" \"20.171.207.158, 162.158.90.141\" -pct 474 + \"NCBI-SID: -\" id=aEetv43G411Wt01HtJrjmgAACYQ port=443 816 715 text/html loc=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC4155689/\" sslproto=TLSv1.3" ); + ASSERT_EQ( "20.171.207.158, 162.158.90.141", extract_value( res, "forwarded" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_duplicated_query_separator ) +{ + std::string res = try_to_parse_good( "10.154.26.16 - - [29/Jun/2025:20:52:48 -0400] \"web11.be-md.ncbi.nlm.nih.gov\" \"GET /phpfm/index.php?&&path=&action=upload HTTP/1.1\" 404 2127 0 \"-\" \"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)\" \"-\" -pct 1538 - \"NCBI-SID: -\" id=aGHf4Arhj6xPs5IcQIfgPQAACCM port=443 970 8761 text/html loc=\"-\" sslproto=TLSv1.3" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_multiple_escapes ) +{ + std::string res = try_to_parse_good( "2409:8a6c:580:4334:446b:203b:a97e:3180 - - [29/Jun/2025:07:55:48 -0400] \"www.ncbi.nlm.nih.gov\" \"GET\" 200 43 0 \"https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE183904\\\\\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0\" \"-\" -pct 14522 - \"NCBI-SID: -\" id=aGEpxDT9G0vKgfBpuVuKVwAJFj8 port=443 2524 399 image/gif loc=\"-\" sslproto=TLSv1.3" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, free_form_tail_VDB_5981_more_freeform_variation ) +{ + std::string res = try_to_parse_good( "130.14.23.73 - - [21/Jun/2025:00:02:40 -0400] \"blast.ncbi.nlm.nih.gov\" \"POST /DB_CUBBY_FORWARD/myblast_forward.cgi HTTP/1.0\" 200 118 1 \"-\" \"asnweb\" \"-\" -pct 1005082 - \"NCBI-SID: -\" id=aFYu4LzA0jG5AoyoRd8-5AAADBU port=443 786 6341 content-type: x-ncbi-data/x-unknown-urlencoded loc=\"-\" sslproto=TLSv1.2" ); + ASSERT_EQ( "-", extract_value( res, "forwarded" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_bad_request ) +{ + std::string res = try_to_parse_review( "34.200.223.129 - - [20/Sep/2018:13:32:54 -0400] \"sra-download.ncbi.nlm.nih.gov\" \"\\x16\\x03\\x01\\x00\\xF5\\x01\\x00\\x00\\xF1\\x03\\x03}\\xB0'\\xC8\\xCC\\xBA\\xF6h\\xE9Q^\\xBA\\xA3\\xF6\\xC5\\xD0\\xE6 U\\xE5\\xE5\\x81\\xC4?\\xA3M\\xD7[\\x00\\xA3;\\x00\\x00\\x82\\x003\\x009\\x005\\x00/\\xC0,\\xC00\\x00\\xA3\\x00\\x9F\\xCC\\xA9\\xCC\\xA8\\xCC\\xAA\\xC0\\xAF\\xC0\\xAD\\xC0\\xA3\\xC0\\x9F\\xC0+\\xC0/\\x00\\xA2\\x00\\x9E\\xC0\\xAE\\xC0\\xAC\\xC0\\xA2\\xC0\\x9E\\xC0$\\xC0(\\x00k\\x00j\\xC0s\\xC0w\\x00\\xC4\\x00\\xC3\\xC0#\\xC0'\\x00g\\x00@\\xC0r\\xC0v\\x00\\xBE\\x00\\xBD\\xC0\" 400 166 0.001 \"-\" \"-\" \"-\" port=80 rl=0" ); + ASSERT_EQ( "80", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, free_form_tail_VDB_5981_even_more_freeform_variation ) +{ + std::string res = try_to_parse_good( "103.75.46.202 - - [15/Jul/2025:00:30:02 -0400] \"www.ncbi.nlm.nih.gov\" \"GET /blast/Blast.cgi?PAGE_TYPE=Blast\\xef\\xbf\\xbd\\xef\\xbf\\xbd\\xef\\xbf\\xbd\\xef\\xbf\\xbd\\xd3\\xb0\\xef\\xbf\\xbd\\xef\\xbf\\xbd&PROG_DEF=blastn&BLAST_PROG_DEF=megaBlast&BLAST_SPEC=OGP__9606__9558 HTTP/1.1\" 302 1 0 \"https://www.ncbi.nlm.nih.gov/\" \"Baiduspider/2.0+(+http://www.baidu.com/search/spider.htm)\" \"-\" -pct 7174 + \"NCBI-SID: -\" id=aHXZSmLG8FzegxtAIRXP_wAAEJ0 port=443 989 6225 - loc=\"https://blast.ncbi.nlm.nih.gov/Blast.cgi?PAGE_TYPE=Blast\\xef\\xbf\\xbd\\xef\\xbf\\xbd\\xef\\xbf\\xbd\\xef\\xbf\\xbd\\xd3\\xb0\\xef\\xbf\\xbd\\xef\\xbf\\xbd&PROG_DEF=blastn&BLAST_PROG_DEF=megaBlast&BLAST_SPEC=OGP__9606__9558\" sslproto=TLSv1.2" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_eol_in_request ) +{ + std::string res = try_to_parse_good( "10.154.26.22 - - [20/Jul/2025:06:03:38 -0400] \"web11.ncbi.nlm.nih.gov\" \"GET\\n\" 400 135073 0 \"-\" \"-\" \"-\" -pct 428 - \"NCBI-SID: -\" id=aHy--n6WW8P5HlvS_vVqNwAACy8 port=80 22 135305 text/html loc=\"-\" sslproto=-" ); + ASSERT_EQ( "80", extract_value( res, "port" ) ); +} + +TEST_F( OPTestFixture, VDB_5981_escaped_quotes_in_forwarded ) +{ + std::string res = try_to_parse_good( "10.154.26.16 - - [27/Jul/2025:21:15:13 -0400] \"web11.be-md.ncbi.nlm.nih.gov\" \"GET /admbook/write.php?name=nessus&email=nessus@10.154.26.16&message=Nessus%20ran%20admbook_cmd_exec.nasl%20at%201753665313 HTTP/1.1\" 404 2127 0 \"-\" \"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)\" \"127.0.0.1 \\\";system(id);echo \\\"qziLpKkH\\\";echo\\\"\" -pct 15512 - \"NCBI-SID: -\" id=aIbPIY9TqwKu-4EM6O79UQAADZM port=443 1102 8761 text/html loc=\"-\" sslproto=TLSv1.3" ); + ASSERT_EQ( "443", extract_value( res, "port" ) ); +} + +int main ( int argc, const char * argv [], const char * envp [] ) +{ + testing :: InitGoogleTest ( & argc, ( char ** ) argv ); + return RUN_ALL_TESTS (); } diff --git a/parser/op/op_test_reverse.cpp b/parser/op/op_test_reverse.cpp index 7c501c6..fe85a89 100644 --- a/parser/op/op_test_reverse.cpp +++ b/parser/op/op_test_reverse.cpp @@ -28,6 +28,7 @@ TEST( OPReverseBlockTest, ReverseEmptyJson ) // if an empty json is given ( is in the json-source-file ) we want it to be in the unrecog-file OPReverseBlockFactory factory; auto pb = factory . MakeParseBlock(); + pb->SetDebug(true); string line( "{}" ); bool res = pb -> format_specific_parse( line.c_str(), line . size() ); ASSERT_FALSE( res ); diff --git a/parser/tw/TW_Interface.hpp b/parser/tw/TW_Interface.hpp index 2e00d69..824af03 100644 --- a/parser/tw/TW_Interface.hpp +++ b/parser/tw/TW_Interface.hpp @@ -4,6 +4,7 @@ #include "ParserInterface.hpp" #include +#include namespace NCBI {