From 605608a574eeb7128823c04aa6e35246c63c37f7 Mon Sep 17 00:00:00 2001 From: Edwig Huisman Date: Sun, 18 Jun 2023 17:54:30 +0200 Subject: [PATCH] Update for VisualStudio 17.3 Update for StyleFramework Update for SQLComponents Update for BaseLibrary Update for Marlin Implemented DARK theme! --- BaseLibrary/BaseLibrary.vcxproj | 2 + BaseLibrary/BaseLibrary.vcxproj.filters | 12 +- BaseLibrary/LogAnalysis.cpp | 6 +- BaseLibrary/QueryReWriter.cpp | 21 +- BaseLibrary/QueryReWriter.h | 3 + BaseLibrary/Redirect.cpp | 85 ++- BaseLibrary/Redirect.h | 5 +- BaseLibrary/RunRedirect.cpp | 113 ++- BaseLibrary/RunRedirect.h | 15 +- BaseLibrary/StringUtilities.cpp | 130 ++++ BaseLibrary/StringUtilities.h | 38 + BaseLibrary/WinPing.cpp | 129 ++-- BaseLibrary/XMLRestriction.cpp | 68 +- BaseLibrary/XMLRestriction.h | 48 +- BaseLibrary/XSDSchema.cpp | 932 ++++++++++++++++++++++++ BaseLibrary/XSDSchema.h | 148 ++++ Kwatta/Kwatta.vcxproj | 4 +- Kwatta/KwattaAppDlg.cpp | 1 + Kwatta/resource.h | Bin 14186 -> 14280 bytes Marlin/HTTPClient.cpp | 91 ++- Marlin/HTTPClient.h | 3 +- Marlin/HTTPServer.cpp | 39 +- Marlin/HTTPServer.h | 5 + Marlin/HTTPServerIIS.cpp | 45 +- Marlin/HTTPServerMarlin.cpp | 2 +- Marlin/HTTPServerSync.cpp | 2 +- Marlin/ServerEventChannel.cpp | 107 ++- Marlin/ServerEventChannel.h | 2 + Marlin/ServerEventDriver.cpp | 8 +- Marlin/WebConfigIIS.cpp | 38 + Marlin/WebConfigIIS.h | 4 + Marlin/WebServiceServer.cpp | 5 +- Marlin/WebSocketServerIIS.cpp | 66 +- ResultViewer/ResultViewer.vcxproj | 4 +- SQLComponents/DDLCreateTable.cpp | 4 +- SQLComponents/SQLComponents.vcxproj | 2 +- SQLComponents/SQLComponentsVersion.h | 6 +- SQLComponents/SQLFilter.cpp | 14 + SQLComponents/SQLFilter.h | 6 - SQLComponents/SQLInfo.cpp | 6 +- SQLComponents/SQLInfoFirebird.cpp | 2 + SQLComponents/SQLInfoMariaDB.cpp | 5 + SQLComponents/SQLInfoMySQL.cpp | 5 + SQLComponents/SQLInfoOracle.cpp | 5 + SQLComponents/SQLInfoSQLServer.cpp | 13 +- SQLComponents/SQLQuery.cpp | 84 ++- SQLComponents/SQLQuery.h | 26 + SQLComponents/SQLVariant.cpp | 4 +- StepEditor/StepDatabaseDlg.cpp | 1 + StepEditor/StepEditor.vcxproj | 4 +- StepEditor/StepEditorDlg.cpp | Bin 31432 -> 31536 bytes StepEditor/StepInternetDlg.cpp | 1 + StepEditor/resource.h | Bin 20668 -> 20762 bytes StyleFramework/StyleButton.cpp | 5 + StyleFramework/StyleComboBox.cpp | 7 +- StyleFramework/StyleDialog.cpp | 91 ++- StyleFramework/StyleDialog.h | 35 +- StyleFramework/StyleDialogCA.h | 1 + StyleFramework/StyleFonts.h | 2 + StyleFramework/StyleFrameWndEx.cpp | 28 +- StyleFramework/StyleListBox.cpp | 147 ++-- StyleFramework/StyleListCtrl.cpp | 223 +++++- StyleFramework/StyleListCtrl.h | 35 +- StyleFramework/StyleMDIFrameWnd.cpp | 26 +- StyleFramework/StyleMessageBox.cpp | 15 +- StyleFramework/StyleMessageBox.h | 2 +- StyleFramework/StyleTab.cpp | 32 + StyleFramework/StyleTab.h | 2 + StyleFramework/WinToastLib.cpp | 74 +- StyleFramework/WinToastLib.h | 45 +- SuiteLibrary/SuiteLibrary.rc | Bin 12234 -> 12368 bytes SuiteLibrary/resource.h | 7 +- TestEditor/TestEditor.vcxproj | 4 +- TestEditor/TestEditorDlg.cpp | Bin 74662 -> 74766 bytes TestEditor/resource.h | Bin 9290 -> 9384 bytes TestRunner/ConsoleDlg.cpp | 2 +- TestRunner/TestRunner.vcxproj | 4 +- ValidateEditor/ValidateDatabaseDlg.cpp | 1 + ValidateEditor/ValidateEditor.vcxproj | 4 +- ValidateEditor/ValidateEditorDlg.cpp | Bin 20334 -> 20438 bytes ValidateEditor/ValidateInternetDlg.cpp | 1 + ValidateEditor/resource.h | Bin 12146 -> 12240 bytes 82 files changed, 2592 insertions(+), 570 deletions(-) create mode 100644 BaseLibrary/XSDSchema.cpp create mode 100644 BaseLibrary/XSDSchema.h diff --git a/BaseLibrary/BaseLibrary.vcxproj b/BaseLibrary/BaseLibrary.vcxproj index bd92f21..af3c0a0 100644 --- a/BaseLibrary/BaseLibrary.vcxproj +++ b/BaseLibrary/BaseLibrary.vcxproj @@ -153,6 +153,7 @@ + @@ -233,6 +234,7 @@ + diff --git a/BaseLibrary/BaseLibrary.vcxproj.filters b/BaseLibrary/BaseLibrary.vcxproj.filters index b265a57..cc2ebe6 100644 --- a/BaseLibrary/BaseLibrary.vcxproj.filters +++ b/BaseLibrary/BaseLibrary.vcxproj.filters @@ -276,11 +276,11 @@ Header Files + + Header Files\XML_SOAP + - - Source Files - Source Files @@ -485,5 +485,11 @@ Source Files + + Source Files\XML_SOAP + + + Configuration + \ No newline at end of file diff --git a/BaseLibrary/LogAnalysis.cpp b/BaseLibrary/LogAnalysis.cpp index e8ecf92..5c7449e 100644 --- a/BaseLibrary/LogAnalysis.cpp +++ b/BaseLibrary/LogAnalysis.cpp @@ -690,7 +690,7 @@ LogAnalysis::ReadConfig() else break; } // Look for a comment - if(buffer[0] == ';' || buffer[0] == '#') + if(buffer[0] == ';' || buffer[0] == '#' || !buffer[0]) { continue; } @@ -699,9 +699,9 @@ LogAnalysis::ReadConfig() m_logFileName = &buffer[8]; continue; } - if(_strnicmp(buffer,"loglevel=",8) == 0) + if(_strnicmp(buffer,"loglevel=",9) == 0) { - int logLevel = atoi(&buffer[8]) > 0; + int logLevel = atoi(&buffer[9]); SetLogLevel(logLevel); continue; } diff --git a/BaseLibrary/QueryReWriter.cpp b/BaseLibrary/QueryReWriter.cpp index 3b4efe9..0bc12f1 100644 --- a/BaseLibrary/QueryReWriter.cpp +++ b/BaseLibrary/QueryReWriter.cpp @@ -73,6 +73,8 @@ static const char* all_tokens[] = ,"HAVING" ,"INTO" ,"UNION" + ,"STATISTICS" + ,"FOR" }; // All registered SQL words including tokens and special registrations @@ -334,6 +336,17 @@ QueryReWriter::ParseStatement(bool p_closingEscape /*= false*/) odbc = true; } + if(m_token == Token::TK_STATISTICS) + { + m_inStatement = m_token; + PrintToken(); + continue; + } + if(m_token == Token::TK_FOR) + { + m_nextTable = false; + } + // Append schema if(m_nextTable) { @@ -341,7 +354,7 @@ QueryReWriter::ParseStatement(bool p_closingEscape /*= false*/) } // Find next table for appending a schema - if(m_inStatement == Token::TK_SELECT && (m_token == Token::TK_FROM || m_token == Token::TK_JOIN)) + if(m_inStatement == Token::TK_SELECT && (m_token == Token::TK_FROM || m_token == Token::TK_JOIN )) { m_nextTable = true; if(m_token == Token::TK_FROM) @@ -349,6 +362,10 @@ QueryReWriter::ParseStatement(bool p_closingEscape /*= false*/) m_inFrom = true; } } + if(m_inStatement == Token::TK_STATISTICS && m_token == Token::TK_FOR) + { + m_nextTable = true; + } if(m_inStatement == Token::TK_SELECT && m_inFrom && m_token == Token::TK_COMMA) { m_nextTable = true; @@ -454,6 +471,8 @@ QueryReWriter::PrintToken() case Token::TK_ORDER: [[fallthrough]]; case Token::TK_HAVING: [[fallthrough]]; case Token::TK_INTO: [[fallthrough]]; + case Token::TK_STATISTICS:[[fallthrough]]; + case Token::TK_FOR: [[fallthrough]]; case Token::TK_UNION: m_output += all_tokens[(int)m_token]; break; case Token::TK_EOS: break; diff --git a/BaseLibrary/QueryReWriter.h b/BaseLibrary/QueryReWriter.h index 632cb45..998284d 100644 --- a/BaseLibrary/QueryReWriter.h +++ b/BaseLibrary/QueryReWriter.h @@ -64,6 +64,8 @@ enum class Token ,TK_HAVING ,TK_INTO ,TK_UNION + ,TK_STATISTICS + ,TK_FOR }; enum class SROption @@ -137,6 +139,7 @@ class QueryReWriter void PrintOuterJoin(); Token FindToken(); void AppendSchema(); + void SkipSpaceAndComment(); Token CommentSQL(); Token CommentCPP(); diff --git a/BaseLibrary/Redirect.cpp b/BaseLibrary/Redirect.cpp index f7d6275..a95a486 100644 --- a/BaseLibrary/Redirect.cpp +++ b/BaseLibrary/Redirect.cpp @@ -31,6 +31,8 @@ #include #include #include +#include +#include #ifdef _DEBUG #define new DEBUG_NEW @@ -67,9 +69,11 @@ Redirect::Redirect() m_bRunThread = FALSE; m_exitCode = 0; m_eof_input = 0; + m_eof_error = 0; m_timeoutChild = INFINITE; m_timeoutIdle = MAXWAIT_FOR_INPUT_IDLE; m_terminated = false; + m_bRunThread = TRUE; InitializeCriticalSection((LPCRITICAL_SECTION)&m_critical); } @@ -94,6 +98,7 @@ Redirect::StartChildProcess(LPCSTR lpszCmdLine,UINT uShowChildWindow /*=SW_HIDE* HANDLE hProcess = ::GetCurrentProcess(); m_eof_input = 0; + m_eof_error = 0; m_exitCode = 0; // Set up the security attributes struct. @@ -155,6 +160,7 @@ Redirect::StartChildProcess(LPCSTR lpszCmdLine,UINT uShowChildWindow /*=SW_HIDE* m_terminated = 1; m_eof_input = 1; + m_eof_error = 1; return FALSE; } @@ -220,7 +226,7 @@ Redirect::TerminateChildProcess() m_bRunThread = FALSE; ::SetEvent(m_hExitEvent); - WaitForSingleObject(m_hProcessThread,3000); + WaitForSingleObject(m_hProcessThread,1000); m_hProcessThread = NULL; } @@ -265,11 +271,23 @@ Redirect::TerminateChildProcess() int maxWaittime = DRAIN_STDOUT_MAXWAIT; while(maxWaittime >= 0) { - Sleep(DRAIN_STDOUT_INTERVAL); if(!m_hStdOutRead) { break; } + Sleep(DRAIN_STDOUT_INTERVAL); + maxWaittime -= DRAIN_STDOUT_INTERVAL; + } + + // Wait for the stderr to drain + maxWaittime = DRAIN_STDOUT_MAXWAIT; + while(maxWaittime >= 0) + { + if(!m_hStdErrRead) + { + break; + } + Sleep(DRAIN_STDOUT_INTERVAL); maxWaittime -= DRAIN_STDOUT_INTERVAL; } @@ -396,34 +414,31 @@ Redirect::PrepAndLaunchRedirectedChild(LPCSTR lpszCmdLine return pi.hProcess; } -BOOL Redirect::m_bRunThread = TRUE; - // Thread to read the child stdout. int Redirect::StdOutThread(HANDLE hStdOutRead) { DWORD nBytesRead; CHAR lpszBuffer[10]; - CHAR lineBuffer[BUFFER_SIZE+10]; + CHAR lineBuffer[BUFFER_SIZE + 10] = {0} ; char* linePointer = lineBuffer; + // FOR DEBUGGING: See below + // int i = 0; while(true) { nBytesRead = 0; - if(!::ReadFile(hStdOutRead, lpszBuffer, 1, &nBytesRead, NULL) || !nBytesRead) + if(!::ReadFile(hStdOutRead, lpszBuffer, 1, &nBytesRead, NULL) || !nBytesRead || lpszBuffer[0] == EOT) { - if(::GetLastError() != ERROR_IO_PENDING) + // pipe done - normal exit path. + // Partial input line left hanging? + if(linePointer != lineBuffer) { - // pipe done - normal exit path. - // Partial input line left hanging? - if(linePointer != lineBuffer) - { - *linePointer = 0; - OnChildStdOutWrite(lineBuffer); - } - m_eof_input = 1; - break; + *linePointer = 0; + OnChildStdOutWrite(lineBuffer); } + m_eof_input = 1; + break; } // Add to line *linePointer++ = lpszBuffer[0]; @@ -431,6 +446,13 @@ Redirect::StdOutThread(HANDLE hStdOutRead) // Add end-of-line or line overflow, write to listener if(lpszBuffer[0] == '\n' || ((linePointer - lineBuffer) > BUFFER_SIZE)) { + // USED FOR DEBUGGING PURPOSES ONLY!! + // So we can detect the draining of the standard output from the process +// if(i++ % 20 == 0) +// { +// Sleep(1); +// } + // Virtual function to notify derived class that // characters are written to stdout. *linePointer = 0; @@ -449,24 +471,24 @@ Redirect::StdErrThread(HANDLE hStdErrRead) { DWORD nBytesRead; CHAR lpszBuffer[10]; - CHAR lineBuffer[BUFFER_SIZE + 1]; + CHAR lineBuffer[BUFFER_SIZE + 10] = { 0 }; char* linePointer = lineBuffer; + // FOR DEBUGGING: See below + // int i = 0; while(m_bRunThread) { - if(!::ReadFile(hStdErrRead,lpszBuffer,1,&nBytesRead,NULL) || !nBytesRead) + if(!::ReadFile(hStdErrRead,lpszBuffer,1,&nBytesRead,NULL) || !nBytesRead || lpszBuffer[0] == EOT) { - if(::GetLastError() != ERROR_IO_PENDING) + // pipe done - normal exit path. + // Partial input line left hanging? + if(linePointer != lineBuffer) { - // pipe done - normal exit path. - // Partial input line left hanging? - if(linePointer != lineBuffer) - { - *linePointer = 0; - OnChildStdErrWrite(lineBuffer); - } - break; + *linePointer = 0; + OnChildStdErrWrite(lineBuffer); } + m_eof_error = 1; + break; } // Add to line: caller sees stdout AND stderr alike *linePointer++ = lpszBuffer[0]; @@ -474,10 +496,17 @@ Redirect::StdErrThread(HANDLE hStdErrRead) // Add end-of-line or line overflow, write to listener if(lpszBuffer[0] == '\n' || ((linePointer - lineBuffer) > BUFFER_SIZE)) { + // USED FOR DEBUGGING PURPOSES ONLY!! + // So we can detect the draining of the standard output from the process +// if(i++ % 20 == 0) +// { +// Sleep(1); +// } + // Virtual function to notify derived class that // characters are written to stdout. *linePointer = 0; - OnChildStdErrWrite(lpszBuffer); + OnChildStdErrWrite(lineBuffer); linePointer = lineBuffer; } } diff --git a/BaseLibrary/Redirect.h b/BaseLibrary/Redirect.h index d425557..d5bad45 100644 --- a/BaseLibrary/Redirect.h +++ b/BaseLibrary/Redirect.h @@ -35,6 +35,8 @@ // After the process we must wait for the stdout to be completely read #define DRAIN_STDOUT_MAXWAIT 10000 #define DRAIN_STDOUT_INTERVAL 50 +// END-OF-TRANSMISSION is ASCII 4 +#define EOT '\x4' ///////////////////////////////////////////////////////////////////////////// // Redirect class @@ -62,6 +64,7 @@ class Redirect mutable int m_exitCode; mutable int m_eof_input; + mutable int m_eof_error; mutable int m_terminated; protected: HANDLE m_hExitEvent; @@ -87,7 +90,7 @@ class Redirect ,UINT uShowChildWindow = SW_HIDE ,BOOL bWaitForInputIdle = FALSE); - static BOOL m_bRunThread; + BOOL m_bRunThread; static unsigned int WINAPI staticStdOutThread(void* pRedirect) { Redirect* redir = reinterpret_cast(pRedirect); diff --git a/BaseLibrary/RunRedirect.cpp b/BaseLibrary/RunRedirect.cpp index 79eb9e4..2707615 100644 --- a/BaseLibrary/RunRedirect.cpp +++ b/BaseLibrary/RunRedirect.cpp @@ -52,18 +52,18 @@ RunRedirect::~RunRedirect() } void -RunRedirect::RunCommand(LPCSTR p_commandLine) +RunRedirect::RunCommand(LPCSTR p_commandLine,bool p_show) { AutoCritSec lock((LPCRITICAL_SECTION)&m_critical); - StartChildProcess(p_commandLine,FALSE); + m_ready = StartChildProcess(p_commandLine,p_show ? SW_SHOW : SW_HIDE) == FALSE; } void -RunRedirect::RunCommand(LPCSTR p_commandLine,LPCSTR p_stdInput) +RunRedirect::RunCommand(LPCSTR p_commandLine,LPCSTR p_stdInput,bool p_show) { AutoCritSec lock(&m_critical); m_input = p_stdInput; - StartChildProcess(p_commandLine,FALSE,TRUE); + m_ready = StartChildProcess(p_commandLine,p_show ? SW_SHOW : SW_HIDE,TRUE) == FALSE; } void @@ -71,11 +71,7 @@ RunRedirect::RunCommand(LPCSTR p_commandLine,HWND p_console,UINT p_showWindow,BO { AutoCritSec lock(&m_critical); m_console = p_console; - if(StartChildProcess(p_commandLine,p_showWindow,p_waitForInputIdle) == FALSE) - { - // Do not continue waiting on the process - m_ready = true; - } + m_ready = StartChildProcess(p_commandLine,p_showWindow,p_waitForInputIdle) == FALSE; } void RunRedirect::OnChildStarted(LPCSTR /*lpszCmdLine*/) @@ -112,21 +108,45 @@ void RunRedirect::OnChildTerminate() { AutoCritSec lock(&m_critical); m_ready = true; + + // Write an END-OF-TRANSMISSION after the output, so the + // Redirect scanner can stop reading + if(m_hStdOut != NULL) + { + char buf[1] = { EOT }; + ::WriteFile(m_hStdOut,&buf,1,NULL,NULL); + } + if(m_hStdErr != NULL) + { + char buf[1] = { EOT }; + ::WriteFile(m_hStdErr,&buf,1,NULL,NULL); + } } bool RunRedirect::IsReady() { AutoCritSec lock(&m_critical); - bool res = m_ready; - return res; + return m_ready; } bool RunRedirect::IsEOF() { AutoCritSec lock(&m_critical); - bool eof = m_eof_input > 0; - return eof; + return m_eof_input > 0; +} + +bool RunRedirect::IsErrorEOF() +{ + AutoCritSec lock(&m_critical); + return m_eof_error > 0; +} + +bool RunRedirect::IsReadyAndEOF() +{ + AutoCritSec lock(&m_critical); + + return (m_ready && (m_eof_input > 0) && (m_eof_error > 0)); } // Write to the STDIN after starting the program @@ -140,6 +160,8 @@ RunRedirect::FlushStdIn() { // Error. Stop as soon as possible m_ready = true; + m_eof_input = 1; + m_eof_error = 1; } // Ready with the input channel CloseChildStdIn(); @@ -148,7 +170,7 @@ RunRedirect::FlushStdIn() } int -CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result) +CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,bool p_show /*= false*/) { #ifdef _ATL AFX_MANAGE_STATE(AfxGetStaticModuleState()); @@ -163,8 +185,8 @@ CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result) // Create a new command line commandLine.Format("\"%s\" %s",p_program,p_commandLine); - run.RunCommand(commandLine.GetString()); - while((run.IsEOF() == false) && (run.IsReady() == false)) + run.RunCommand(commandLine.GetString(),p_show); + while(!run.IsReadyAndEOF()) { Sleep(WAITTIME_STATUS); } @@ -175,7 +197,7 @@ CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result) } int -CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,XString& p_result,int p_waittime) +CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,XString& p_result,int p_waittime,bool p_show /*= false*/) { #ifdef _ATL AFX_MANAGE_STATE(AfxGetStaticModuleState()); @@ -191,8 +213,8 @@ CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,X commandLine.Format("\"%s\" %s",p_program,p_commandLine); clock_t start = clock(); - run.RunCommand(commandLine.GetString(),p_stdInput); - while((run.IsEOF() == false) && (run.IsReady() == false)) + run.RunCommand(commandLine.GetString(),p_stdInput,p_show); + while(!run.IsReadyAndEOF()) { Sleep(WAITTIME_STATUS); @@ -208,8 +230,43 @@ CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,X return run.m_exitCode; } -int -CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,int p_waittime) +int +CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,XString& p_result,XString& p_errors,int p_waittime,bool p_show /*= false*/) +{ +#ifdef _ATL + AFX_MANAGE_STATE(AfxGetStaticModuleState()); +#endif + RunRedirect run; + + XString commandLine; + + // Result is initially empty + p_result = ""; + + // Create a new command line + commandLine.Format("\"%s\" %s",p_program,p_commandLine); + + clock_t start = clock(); + run.RunCommand(commandLine.GetString(),p_stdInput,p_show); + while(!run.IsReadyAndEOF()) + { + Sleep(WAITTIME_STATUS); + + // Check if we are out of waittime + clock_t now = clock(); + if((now - start) > p_waittime) + { + break; + } + } + run.TerminateChildProcess(); + p_result = run.m_output; + p_errors = run.m_error; + return run.m_exitCode; +} + +int +CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,int p_waittime,bool p_show /*= false*/) { #ifdef _ATL AFX_MANAGE_STATE(AfxGetStaticModuleState()); @@ -225,8 +282,8 @@ CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,i commandLine.Format("\"%s\" %s",p_program,p_commandLine); clock_t start = clock(); - run.RunCommand(commandLine.GetString()); - while((run.IsEOF() == false) && (run.IsReady() == false)) + run.RunCommand(commandLine.GetString(),p_show); + while(!run.IsReadyAndEOF()) { Sleep(WAITTIME_STATUS); @@ -243,7 +300,7 @@ CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,i } int -CallProgram(LPCSTR p_program, LPCSTR p_commandLine) +CallProgram(LPCSTR p_program, LPCSTR p_commandLine,bool p_show /*= false*/) { #ifdef _ATL AFX_MANAGE_STATE(AfxGetStaticModuleState()); @@ -254,8 +311,8 @@ CallProgram(LPCSTR p_program, LPCSTR p_commandLine) // Create a new command line commandLine.Format("\"%s\" %s",p_program,p_commandLine); - run.RunCommand(commandLine.GetString()); - while ((run.IsEOF() == false) && (run.IsReady() == false)) + run.RunCommand(commandLine.GetString(),p_show); + while(!run.IsReadyAndEOF()) { Sleep(WAITTIME_STATUS); } @@ -316,14 +373,14 @@ PosixCallProgram(XString p_directory } // Wait for the standard output/standard error to drain - while((run.IsEOF() == false) && (run.IsReady() == false)) + while(!run.IsReadyAndEOF()) { Sleep(WAITTIME_STATUS); } run.TerminateChildProcess(); // Reset console title - if (p_console) + if(p_console) { SendMessage(p_console,WM_CONSOLE_TITLE,0,(LPARAM)""); } diff --git a/BaseLibrary/RunRedirect.h b/BaseLibrary/RunRedirect.h index cbefe24..1a4304b 100644 --- a/BaseLibrary/RunRedirect.h +++ b/BaseLibrary/RunRedirect.h @@ -37,10 +37,11 @@ class RunRedirect; // All global 'CallProgram' variants -int CallProgram (LPCSTR p_program,LPCSTR p_commandLine); -int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result); -int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,int p_waittime); -int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,XString& p_result,int p_waittime); +int CallProgram (LPCSTR p_program,LPCSTR p_commandLine,bool p_show = false); +int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,bool p_show = false); +int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,XString& p_result,int p_waittime,bool p_show = false); +int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,XString& p_result,int p_waittime,bool p_show = false); +int CallProgram_For_String(LPCSTR p_program,LPCSTR p_commandLine,LPCSTR p_stdInput,XString& p_result,XString& p_errors,int p_waittime,bool p_show = false); int PosixCallProgram(XString p_directory ,XString p_programma @@ -60,8 +61,8 @@ class RunRedirect : public Redirect RunRedirect(ULONG p_maxTime = INFINITE); ~RunRedirect(); - void RunCommand(LPCSTR p_commandLine); - void RunCommand(LPCSTR p_commandLine,LPCSTR p_stdInput); + void RunCommand(LPCSTR p_commandLine,bool p_show); + void RunCommand(LPCSTR p_commandLine,LPCSTR p_stdInput,bool p_show); void RunCommand(LPCSTR p_commandLine,HWND p_console,UINT p_showWindow,BOOL p_waitForInputIdle); // Virtual interface. Derived class must implement this!! @@ -71,6 +72,8 @@ class RunRedirect : public Redirect virtual void OnChildTerminate() override; bool IsReady(); bool IsEOF(); + bool IsErrorEOF(); + bool IsReadyAndEOF(); HWND m_console { NULL }; bool m_ready; diff --git a/BaseLibrary/StringUtilities.cpp b/BaseLibrary/StringUtilities.cpp index 91cdb2b..d3635de 100644 --- a/BaseLibrary/StringUtilities.cpp +++ b/BaseLibrary/StringUtilities.cpp @@ -1,3 +1,30 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// SourceFile: StringUtilities.cpp +// +// BaseLibrary: Indispensable general objects and functions +// +// Copyright (c) 2014-2022 ir. W.E. Huisman +// All rights reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// #include "pch.h" #include "StringUtilities.h" @@ -96,3 +123,106 @@ void NormalizeLineEndings(XString& p_string) } p_string = tempStr.c_str(); } + +// Find the position of the matching bracket +// starting at the bracket in the parameters bracketPos +// +int FindMatchingBracket(const CString& p_string,int p_bracketPos) +{ + char bracket = p_string[p_bracketPos]; + char match = char(0); + bool reverse = false; + + switch(bracket) + { + case '(': match = ')'; break; + case '{': match = '}'; break; + case '[': match = ']'; break; + case '<': match = '>'; break; + case ')': reverse = true; + match = '('; + break; + case '}': reverse = true; + match = '{'; + break; + case ']': reverse = true; + match = '['; + break; + case '>': reverse = true; + match = '<'; + break; + default: // Nothing to match + return -1; + } + + if(reverse) + { + for(int pos = p_bracketPos - 1,nest = 1; pos >= 0; --pos) + { + char c = p_string[pos]; + if(c == bracket) + { + ++nest; + } + else if(c == match) + { + if(--nest == 0) + { + return pos; + } + } + } + } + else + { + for(int pos = p_bracketPos + 1,nest = 1,len = p_string.GetLength(); pos < len; ++pos) + { + char c = p_string[pos]; + if(c == bracket) + { + ++nest; + } + else if(c == match) + { + if(--nest == 0) + { + return pos; + } + } + } + } + return -1; +} + +// Split arguments with p_splitter not within brackets +// p_pos must be 0 initially +bool SplitArgument(int& p_pos,const CString& p_data,char p_splitter,CString& p_argument) +{ + int len = p_data.GetLength(); + if(p_pos >= len) + { + return false; + } + for(int pos = p_pos,nest = 0; pos < len; ++pos) + { + switch(char c = p_data[pos]) + { + case '(': ++nest; + break; + case ')': if(--nest < 0) + { + pos = len; + } + break; + default: if(nest == 0 && c == p_splitter) + { + p_argument = p_data.Mid(p_pos,pos - p_pos).Trim(); + p_pos = pos + 1; + return true; + } + } + } + p_argument = p_data.Mid(p_pos).Trim(); + p_pos = len; + return true; +} diff --git a/BaseLibrary/StringUtilities.h b/BaseLibrary/StringUtilities.h index 0bc0cc6..b0ce177 100644 --- a/BaseLibrary/StringUtilities.h +++ b/BaseLibrary/StringUtilities.h @@ -1,12 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// SourceFile: StringUtilities.h +// +// BaseLibrary: Indispensable general objects and functions +// +// Copyright (c) 2014-2022 ir. W.E. Huisman +// All rights reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// #pragma once #include +// Get a number (int,double) as a string XString AsString(int p_number,int p_radix = 10); XString AsString(double p_number); +// Convert a string to int,double,bcd int AsInteger(XString p_string); double AsDouble(XString p_string); bcd AsBcd(XString p_string); int SplitString(XString p_string,std::vector& p_vector,char p_separator = ',',bool p_trim = false); void NormalizeLineEndings(XString& p_string); + +// Find the position of the matching bracket +// starting at the bracket in the parameters bracketPos +// +int FindMatchingBracket(const CString& p_string,int p_bracketPos); + +// Split arguments with p_splitter not within brackets +// p_pos must be 0 initially +bool SplitArgument(int& p_pos,const CString& p_data,char p_splitter,CString& p_argument); diff --git a/BaseLibrary/WinPing.cpp b/BaseLibrary/WinPing.cpp index 7100713..6951202 100644 --- a/BaseLibrary/WinPing.cpp +++ b/BaseLibrary/WinPing.cpp @@ -55,7 +55,7 @@ static char THIS_FILE[] = __FILE__; // converts it to a string representation. This string is printed // to the console via stdout. // -static int PrintAddress(SOCKADDR* sa,int salen) +static int PrintAddress(SOCKADDR* sa,int salen,char* p_errorbuffer) { char host[NI_MAXHOST]; char serv[NI_MAXSERV]; @@ -72,7 +72,7 @@ static int PrintAddress(SOCKADDR* sa,int salen) rc = getnameinfo(sa,salen,host,hostlen,serv,servlen,NI_NUMERICHOST | NI_NUMERICSERV); if(rc != 0) { - fprintf(stderr,"%s: getnameinfo failed: %d\n",__FILE__,rc); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"%s: getnameinfo failed: %d\n",__FILE__,rc); return rc; } @@ -103,7 +103,7 @@ static int PrintAddress(SOCKADDR* sa,int salen) // printing the string address to the console, it is formatted into // the supplied string buffer. // -static int FormatAddress(SOCKADDR* sa,int salen,char* addrbuf,int addrbuflen) +static int FormatAddress(SOCKADDR* sa,int salen,char* addrbuf,int addrbuflen,char* p_errorbuffer) { char host[NI_MAXHOST]; char serv[NI_MAXSERV]; @@ -114,14 +114,14 @@ static int FormatAddress(SOCKADDR* sa,int salen,char* addrbuf,int addrbuflen) if(!sa || !addrbuf || salen == 0 || addrbuflen == 0) { - fprintf(stderr,"\n"); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"\n"); return ERROR_INVALID_FUNCTION; } rc = getnameinfo(sa,salen,host,hostlen,serv,servlen,NI_NUMERICHOST | NI_NUMERICSERV); if(rc != 0) { - fprintf(stderr,"%s: getnameinfo failed: %d\n",__FILE__,rc); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"%s: getnameinfo failed: %d\n",__FILE__,rc); return rc; } @@ -135,7 +135,7 @@ static int FormatAddress(SOCKADDR* sa,int salen,char* addrbuf,int addrbuflen) { if(FAILED(hRet = StringCchPrintf(addrbuf,addrbuflen,"%s:%s",host,serv))) { - fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet); return (int)hRet; } } @@ -143,7 +143,7 @@ static int FormatAddress(SOCKADDR* sa,int salen,char* addrbuf,int addrbuflen) { if(FAILED(hRet = StringCchPrintf(addrbuf,addrbuflen,"[%s]:%s",host,serv))) { - fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet); return (int)hRet; } } @@ -157,7 +157,7 @@ static int FormatAddress(SOCKADDR* sa,int salen,char* addrbuf,int addrbuflen) // This routine resolves the specified address and returns a list of addrinfo // structure containing SOCKADDR structures representing the resolved addresses. // Note that if 'addr' is non-NULL, then getaddrinfo will resolve it whether -// it is a string listeral address or a hostname. +// it is a string literal address or a host name. // static struct addrinfo* ResolveAddress(char* addr,char* port,int af,int type,int proto,char* p_errorbuffer) { @@ -187,7 +187,7 @@ static struct addrinfo* ResolveAddress(char* addr,char* port,int af,int type,int // This routine takes a SOCKADDR and does a reverse lookup for the name // corresponding to that address. // -static int ReverseLookup(SOCKADDR* sa,int salen,char* buf,int buflen) +static int ReverseLookup(SOCKADDR* sa,int salen,char* buf,int buflen,char* p_errorbuffer) { char host[NI_MAXHOST]; int hostlen = NI_MAXHOST; @@ -197,14 +197,14 @@ static int ReverseLookup(SOCKADDR* sa,int salen,char* buf,int buflen) rc = getnameinfo(sa,salen,host,hostlen,NULL,0,0); if(rc != 0) { - fprintf(stderr,"getnameinfo failed: %d\n",rc); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"getnameinfo failed: %d\n",rc); return rc; } buf[0] = '\0'; if(FAILED(hRet = StringCchCopy(buf,buflen,host))) { - fprintf(stderr,"StringCchCopy failed: 0x%x\n",hRet); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"StringCchCopy failed: 0x%x\n",hRet); return (int)hRet; } return NO_ERROR; @@ -216,14 +216,14 @@ static int ReverseLookup(SOCKADDR* sa,int salen,char* buf,int buflen) // Description: // Helper function to fill in various stuff in our ICMP request. // -static void InitIcmpHeader(char* buf,int datasize) +static void InitIcmpHeader(char* buf,int datasize,char* p_errorbuffer) { ICMP_HDR* icmp_hdr = NULL; char* datapart = NULL; if(!buf || datasize <= 0) { - fprintf(stderr,"\n"); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"\n"); return; } @@ -299,13 +299,13 @@ static USHORT ICMPChecksum(USHORT* buffer,int size) // Description: // This routine sets the sequence number of the ICMP request packet. // -static void SetIcmpSequence(char* buf,int p_family) +static void SetIcmpSequence(char* buf,int p_family,char* p_errorbuffer) { ULONGLONG sequence = 0; if(!buf) { - fprintf(stderr,"\n"); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"\n"); return; } @@ -344,7 +344,7 @@ static void SetIcmpSequence(char* buf,int p_family) // To do this we call the SIO_ROUTING_INTERFACE_QUERY ioctl to find which // local interface for the outgoing packet. // -static USHORT ComputeIcmp6PseudoHeaderChecksum(SOCKET s,char* icmppacket,int icmplen,struct addrinfo* dest) +static USHORT ComputeIcmp6PseudoHeaderChecksum(SOCKET s,char* icmppacket,int icmplen,struct addrinfo* dest,char* p_errorbuffer) { SOCKADDR_STORAGE localif; DWORD bytes; @@ -353,7 +353,7 @@ static USHORT ComputeIcmp6PseudoHeaderChecksum(SOCKET s,char* icmppacket,int icm if(!s || !icmppacket || !dest) { - fprintf(stderr,"\n"); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"\n"); return 0xFFFF; } @@ -369,7 +369,7 @@ static USHORT ComputeIcmp6PseudoHeaderChecksum(SOCKET s,char* icmppacket,int icm NULL); if(rc == SOCKET_ERROR) { - fprintf(stderr,"WSAIoctl failed: %d\n",WSAGetLastError()); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"WSAIoctl failed: %d\n",WSAGetLastError()); return 0xFFFF; } @@ -451,11 +451,11 @@ static USHORT ComputeIcmp6PseudoHeaderChecksum(SOCKET s,char* icmppacket,int icm // header which is difficult since we aren't building our own IPv6 // header. // -static void ComputeIcmpChecksum(SOCKET s,char* buf,int packetlen,struct addrinfo* dest,int p_family) +static void ComputeIcmpChecksum(SOCKET s,char* buf,int packetlen,struct addrinfo* dest,int p_family,char* p_errorbuffer) { if(!buf) { - fprintf(stderr,"\n"); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"\n"); return; } @@ -473,7 +473,7 @@ static void ComputeIcmpChecksum(SOCKET s,char* buf,int packetlen,struct addrinfo icmpv6 = (ICMPV6_HDR*)buf; icmpv6->icmp6_checksum = 0; - icmpv6->icmp6_checksum = ComputeIcmp6PseudoHeaderChecksum(s,buf,packetlen,dest); + icmpv6->icmp6_checksum = ComputeIcmp6PseudoHeaderChecksum(s,buf,packetlen,dest,p_errorbuffer); } } @@ -483,7 +483,7 @@ static void ComputeIcmpChecksum(SOCKET s,char* buf,int packetlen,struct addrinfo // Description: // This routine posts an overlapped WSARecvFrom on the raw socket. // -static int PostRecvfrom(SOCKET s,char* buf,int buflen,SOCKADDR* from,int* fromlen,WSAOVERLAPPED* ol) +static int PostRecvfrom(SOCKET s,char* buf,int buflen,SOCKADDR* from,int* fromlen,WSAOVERLAPPED* ol,char* p_errorbuffer) { WSABUF wbuf; DWORD flags; @@ -500,7 +500,7 @@ static int PostRecvfrom(SOCKET s,char* buf,int buflen,SOCKADDR* from,int* fromle { if(WSAGetLastError() != WSA_IO_PENDING) { - fprintf(stderr,"WSARecvFrom failed: %d\n",WSAGetLastError()); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"WSARecvFrom failed: %d\n",WSAGetLastError()); return SOCKET_ERROR; } } @@ -515,7 +515,7 @@ static int PostRecvfrom(SOCKET s,char* buf,int buflen,SOCKADDR* from,int* fromle // present (by seeing if the IP header length is greater than 20 bytes) and // if so it prints the IP record route options. // -static void PrintPayload(char* buf,int bytes,int p_family) +static void PrintPayload(char* buf,int bytes,int p_family,char* p_errorbuffer) { int hdrlen = 0; int routes = 0; @@ -534,7 +534,7 @@ static void PrintPayload(char* buf,int bytes,int p_family) if(!buf) { - fprintf(stderr,"\n"); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"\n"); return; } @@ -555,7 +555,7 @@ static void PrintPayload(char* buf,int bytes,int p_family) if(i == 0) printf(" Route: "); else printf(" "); - PrintAddress((SOCKADDR*)&hop,sizeof(hop)); + PrintAddress((SOCKADDR*)&hop,sizeof(hop),p_errorbuffer); if(i < routes - 1) printf(" ->\n"); else printf("\n"); @@ -571,7 +571,7 @@ static void PrintPayload(char* buf,int bytes,int p_family) // Description: // Sets the TTL on the socket. // -static int SetTTL(SOCKET p_socket,int p_ttl,int p_family) +static int SetTTL(SOCKET p_socket,int p_ttl,int p_family,char* p_errorbuffer) { int optlevel = 0; int option = 0; @@ -598,7 +598,7 @@ static int SetTTL(SOCKET p_socket,int p_ttl,int p_family) } if(rc == SOCKET_ERROR) { - fprintf(stderr,"SetTTL: setsockopt failed: %d\n",WSAGetLastError()); + sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"SetTTL: setsockopt failed: %d\n",WSAGetLastError()); } return rc; } @@ -619,12 +619,11 @@ double WinPing(char* p_destination // internet address to ping ,int p_sendCount /*= DEFAULT_SEND_COUNT */ ,int p_family /*= AF_UNSPEC */ ,int p_timeToLive /*= DEFAULT_TTL */ - ,int p_dataSize /*= DEFAULT_DATA_SIZE */ - ,bool p_recordRoute /*= false */) + ,int p_dataSize /*= DEFAULT_DATA_SIZE */) { WSADATA wsd; WSAOVERLAPPED recvol; - int protocol = IPPROTO_ICMP; // Protocol value + int protocol = IPPROTO_ICMP; // Protocol value SOCKET sock = INVALID_SOCKET; int recvbuflen = MAX_RECV_BUF_LEN; // Length of received packets. char* recvbuf = nullptr; @@ -632,7 +631,7 @@ double WinPing(char* p_destination // internet address to ping struct addrinfo* dest = nullptr; struct addrinfo* local = nullptr; double* all_times = nullptr; - IPV4_OPTION_HDR ipopt; +//IPV4_OPTION_HDR ipopt; // Record route SOCKADDR_STORAGE from; double time = 0.0; DWORD bytes = 0; @@ -640,7 +639,6 @@ double WinPing(char* p_destination // internet address to ping int packetlen = 0; int fromlen = 0; int rc = 0; - int index = 0; int status = 0; HPFCounter counter; @@ -697,7 +695,7 @@ double WinPing(char* p_destination // internet address to ping goto CLEANUP; } - SetTTL(sock,p_timeToLive,p_family); + SetTTL(sock,p_timeToLive,p_family,p_errorbuffer); // Figure out the size of the ICMP header and payload if(p_family == AF_INET) @@ -723,23 +721,23 @@ double WinPing(char* p_destination // internet address to ping // Initialize the ICMP headers if(p_family == AF_INET) { - if(p_recordRoute) - { - // Setup the IP option header to go out on every ICMP packet - ZeroMemory(&ipopt,sizeof(ipopt)); - ipopt.opt_code = IP_RECORD_ROUTE; // record route option - ipopt.opt_ptr = 4; // point to the first addr offset - ipopt.opt_len = 39; // length of option header - - rc = setsockopt(sock,IPPROTO_IP,IP_OPTIONS,(char*)&ipopt,sizeof(ipopt)); - if(rc == SOCKET_ERROR) - { - sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"setsockopt(IP_OPTIONS) failed: %d\n",WSAGetLastError()); - status = -6; - goto CLEANUP; - } - } - InitIcmpHeader(icmpbuf,p_dataSize); +// if(p_recordRoute) +// { +// // Setup the IP option header to go out on every ICMP packet +// ZeroMemory(&ipopt,sizeof(ipopt)); +// ipopt.opt_code = IP_RECORD_ROUTE; // record route option +// ipopt.opt_ptr = 4; // point to the first addr offset +// ipopt.opt_len = 39; // length of option header +// +// rc = setsockopt(sock,IPPROTO_IP,IP_OPTIONS,(char*)&ipopt,sizeof(ipopt)); +// if(rc == SOCKET_ERROR) +// { +// sprintf_s(p_errorbuffer,ERROR_BUFFER_SIZE,"setsockopt(IP_OPTIONS) failed: %d\n",WSAGetLastError()); +// status = -6; +// goto CLEANUP; +// } +// } + InitIcmpHeader(icmpbuf,p_dataSize,p_errorbuffer); } else if(p_family == AF_INET6) { @@ -770,23 +768,23 @@ double WinPing(char* p_destination // internet address to ping // Post the first overlapped receive fromlen = sizeof(from); - PostRecvfrom(sock,recvbuf,recvbuflen,(SOCKADDR*)&from,&fromlen,&recvol); + PostRecvfrom(sock,recvbuf,recvbuflen,(SOCKADDR*)&from,&fromlen,&recvol,p_errorbuffer); if(p_print) { printf("\nPinging %s [",p_destination); - PrintAddress(dest->ai_addr,(int)dest->ai_addrlen); + PrintAddress(dest->ai_addr,(int)dest->ai_addrlen,p_errorbuffer); printf("] with %d bytes of data\n\n",p_dataSize); } // Allocate mean-times array all_times = (double*) calloc(p_sendCount,sizeof(double)); // Start sending the ICMP requests - for(index = 0; index < p_sendCount;index++) + for(int index = 0; index < p_sendCount;++index) { - // Set the sequence number and compute the checksum - SetIcmpSequence(icmpbuf,p_family); - ComputeIcmpChecksum(sock,icmpbuf,packetlen,dest,p_family); + // Set the sequence number and compute the checksum + SetIcmpSequence(icmpbuf,p_family,p_errorbuffer); + ComputeIcmpChecksum(sock,icmpbuf,packetlen,dest,p_family,p_errorbuffer); all_times[index] = 0.0; counter.Reset(); @@ -827,19 +825,19 @@ double WinPing(char* p_destination // internet address to ping if(p_print) { printf("Reply from "); - PrintAddress((SOCKADDR*)&from,fromlen); - printf(": bytes=%d time=%.4fms TTL=%d\n",p_dataSize,time,p_timeToLive); - PrintPayload(recvbuf,bytes,p_family); + PrintAddress((SOCKADDR*)&from,fromlen,p_errorbuffer); + printf(": bytes=%d TTL=%d time=%.4fms\n",p_dataSize,p_timeToLive,time); + PrintPayload(recvbuf,bytes,p_family,p_errorbuffer); } if(index < p_sendCount - 1) { fromlen = sizeof(from); - PostRecvfrom(sock,recvbuf,recvbuflen,(SOCKADDR*)&from,&fromlen,&recvol); + PostRecvfrom(sock,recvbuf,recvbuflen,(SOCKADDR*)&from,&fromlen,&recvol,p_errorbuffer); } } if(index < (p_sendCount - 1)) { - Sleep(1000); + Sleep(500); } } @@ -885,7 +883,7 @@ double WinPing(char* p_destination // internet address to ping if(all_times) { - for(index = 0; index < p_sendCount; ++index) + for(int index = 0; index < p_sendCount; ++index) { // Received packets if(all_times[index] != 0.0) @@ -903,7 +901,10 @@ double WinPing(char* p_destination // internet address to ping // Account for total totalTime += all_times[index]; } - // Dispose of timing array + } + // Dispose of timing array + if(all_times) + { free(all_times); } diff --git a/BaseLibrary/XMLRestriction.cpp b/BaseLibrary/XMLRestriction.cpp index b1a440a..2c6f3bf 100644 --- a/BaseLibrary/XMLRestriction.cpp +++ b/BaseLibrary/XMLRestriction.cpp @@ -84,6 +84,31 @@ XMLRestriction::AddMinInclusive(XString p_max) m_minInclusiveInteger = _atoi64(p_max); } +void +XMLRestriction::AddMinOccurs(XString p_min) +{ + m_minOccurs = (unsigned) atoll(p_min); + if(m_minOccurs > m_maxOccurs) + { + m_minOccurs = m_maxOccurs; + } +} + +void +XMLRestriction::AddMaxOccurs(XString p_max) +{ + if(p_max.Compare("unbounded") == 0) + { + m_maxOccurs = UINT_MAX; + return; + } + m_maxOccurs = (unsigned) atoll(p_max); + if(m_maxOccurs < m_minOccurs) + { + m_maxOccurs = m_minOccurs; + } +} + bool XMLRestriction::HasEnumeration(XString p_enum) { @@ -742,24 +767,37 @@ XString XMLRestriction::CheckDate(XString p_value) { XString result; - int pos1 = p_value.Find('Z'); - int pos2 = p_value.Find('+'); - int pos3 = p_value.Find('-'); + int dash1 = p_value.Find('-'); + int dash2 = p_value.Find('-',dash1 + 1); - if(pos1 > 0) + // Must have a datepart + if(dash1 < 0 || dash2 < 0) { - result += CheckDatePart(p_value.Left(pos1)); - result += CheckTimeZone(p_value.Mid(pos1 + 1)); + return "Not a date: " + p_value; } - else if(pos2 > 0 || pos3 > 0) + + int tz1 = p_value.Find('Z',dash2 + 1); + int tz2 = p_value.Find('+',dash2 + 1); + int tz3 = p_value.Find('-',dash2 + 1); + + if(tz1 < 0 && tz2 < 0 && tz3 < 0) { - pos1 = max(pos2,pos3); - result += CheckDatePart(p_value.Left(pos1)); - result += CheckTimeZone(p_value.Mid(pos1 + 1)); + result += CheckDatePart(p_value); + } + else if(tz1 > 0) + { + result += CheckDatePart(p_value.Left(tz1)); + result += CheckTimeZone(p_value.Mid(tz1 + 1)); + } + else if(tz2 > 0 || tz3 > 0) + { + tz1 = max(tz2,tz3); + result += CheckDatePart(p_value.Left(tz1)); + result += CheckTimeZone(p_value.Mid(tz1 + 1)); } else { - result = CheckDatePart(p_value); + result = "Invalid date: " + p_value; } return result; } @@ -920,7 +958,7 @@ XMLRestriction::CheckGregYear(XString p_value) int num = atoi(p_value); XString result; - if(num < 01 || num > 99) + if(num < 01 || num > 9999) { result = "Not a Gregorian XML year: " + p_value; return result; @@ -1570,7 +1608,7 @@ XMLRestriction::CheckFractionDigits(XString p_value) int pos = p_value.Find('.'); if(pos >= 0) { - // Take fractin part + // Take fraction part p_value = p_value.Mid(pos + 1); int count = 0; @@ -1646,7 +1684,7 @@ XMLRestriction::CheckRestriction(XmlDataType p_type,XString p_value) { if(m_enums.empty()) { - result = "NOTATION must declare an enumarator list of QNames"; + result = "NOTATION must declare an enumerator list of QNames"; return result; } for(auto& value : m_enums) @@ -1672,7 +1710,7 @@ XMLRestriction::CheckRestriction(XmlDataType p_type,XString p_value) } } - // If no enums, then we are done + // If no enumerations, then we are done if(m_enums.empty()) { return result; diff --git a/BaseLibrary/XMLRestriction.h b/BaseLibrary/XMLRestriction.h index 1822172..e53e80c 100644 --- a/BaseLibrary/XMLRestriction.h +++ b/BaseLibrary/XMLRestriction.h @@ -40,34 +40,38 @@ class XMLRestriction // Set restrictions void AddEnumeration(XString p_enum,XString p_displayValue = ""); - void AddBaseType(XString p_type) { m_baseType = p_type; }; - void AddLength(int p_length) { m_length = p_length; }; - void AddMinLength(int p_length) { m_minLength = p_length; }; - void AddMaxLength(int p_length) { m_maxLength = p_length; }; - void AddTotalDigits(int p_digits) { m_totalDigits = p_digits; }; - void AddFractionDigits(int p_digits) { m_fractionDigits = p_digits; }; - void AddPattern(XString p_pattern) { m_pattern = p_pattern;}; - void AddWhitespace(int p_white) { m_whiteSpace = p_white; }; + void AddBaseType(XString p_type) { m_baseType = p_type; } + void AddLength(int p_length) { m_length = p_length; } + void AddMinLength(int p_length) { m_minLength = p_length; } + void AddMaxLength(int p_length) { m_maxLength = p_length; } + void AddTotalDigits(int p_digits) { m_totalDigits = p_digits; } + void AddFractionDigits(int p_digits) { m_fractionDigits = p_digits; } + void AddPattern(XString p_pattern) { m_pattern = p_pattern;} + void AddWhitespace(int p_white) { m_whiteSpace = p_white; } void AddMaxExclusive(XString p_max); void AddMaxInclusive(XString p_max); void AddMinExclusive(XString p_max); void AddMinInclusive(XString p_max); + void AddMinOccurs(XString p_min); + void AddMaxOccurs(XString p_max); // Get restrictions bool HasEnumeration (XString p_enum); XString GiveDisplayValue(XString p_enum); - XString HasBaseType() { return m_baseType; }; - int HasLength() { return m_length; }; - int HasMaxLength() { return m_maxLength; }; - int HasMinLength() { return m_minLength; }; - int HasTotalDigits() { return m_totalDigits; }; - int HasFractionDigits() { return m_fractionDigits; }; - XString HasMaxExclusive() { return m_maxExclusive; }; - XString HasMaxInclusive() { return m_maxInclusive; }; - XString HasMinExclusive() { return m_minExclusive; }; - XString HasMinInclusive() { return m_minInclusive; }; - XString HasPattern() { return m_pattern; }; - int HasWhitespace() { return m_whiteSpace; }; + XString HasBaseType() { return m_baseType; } + int HasLength() { return m_length; } + int HasMaxLength() { return m_maxLength; } + int HasMinLength() { return m_minLength; } + int HasTotalDigits() { return m_totalDigits; } + int HasFractionDigits() { return m_fractionDigits; } + XString HasMaxExclusive() { return m_maxExclusive; } + XString HasMaxInclusive() { return m_maxInclusive; } + XString HasMinExclusive() { return m_minExclusive; } + XString HasMinInclusive() { return m_minInclusive; } + XString HasPattern() { return m_pattern; } + int HasWhitespace() { return m_whiteSpace; } + unsigned HasMinOccurs() { return m_minOccurs; } + unsigned HasMaxOccurs() { return m_maxOccurs; } // GETTERS XString GetName() { return m_name; }; @@ -142,12 +146,14 @@ class XMLRestriction int m_totalDigits { 0 }; // Exact number of digits allowed int m_fractionDigits { 0 }; // Number of decimal places int m_whiteSpace { 0 }; // 1=preserve, 2=replace, 3=collapse + unsigned m_minOccurs { 1 }; // Minimum number of child elements + unsigned m_maxOccurs { 1 }; // Maximum number of child elements XString m_pattern; // Pattern for pattern matching XString m_maxExclusive; // Max value up-to this value XString m_maxInclusive; // Max value including this value XString m_minExclusive; // Min value down-to this value XString m_minInclusive; // Min value including this value - // Redundant optimalisation + // Redundant optimalization bcd m_maxExclusiveDouble; bcd m_maxInclusiveDouble; bcd m_minExclusiveDouble; diff --git a/BaseLibrary/XSDSchema.cpp b/BaseLibrary/XSDSchema.cpp new file mode 100644 index 0000000..3bc0e22 --- /dev/null +++ b/BaseLibrary/XSDSchema.cpp @@ -0,0 +1,932 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// SourceFile: XSDSchema.cpp +// +// Copyright (c) 2014-2022 ir. W.E. Huisman +// All rights reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +////////////////////////////////////////////////////////////////////////// +// +// Partial implementation of XSD Schema as defined by W3C +// See: https://www.w3.org/TR/xmlschema11-1 +// +////////////////////////////////////////////////////////////////////////// + +#include "pch.h" +#include "XSDSchema.h" +#include "XMLRestriction.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +////////////////////////////////////////////////////////////////////////// +// +// XSDComplexType +// +////////////////////////////////////////////////////////////////////////// + +XSDComplexType::XSDComplexType(XString p_name) + :m_name(p_name) +{ +} + +XSDComplexType::~XSDComplexType() +{ + for(auto& elem : m_elements) + { + delete elem; + } + m_elements.clear(); +} + +////////////////////////////////////////////////////////////////////////// +// +// XSDSchema +// +////////////////////////////////////////////////////////////////////////// + +XSDSchema::XSDSchema() + :m_qualified(false) +{ +} + +XSDSchema::~XSDSchema() +{ + // Delete all elements + for(auto& elem : m_elements) + { + delete elem; + } + m_elements.clear(); + + // Delete all complex types + for(auto& type : m_types) + { + if(type.second) + { + delete type.second; + } + } + m_types.clear(); + + // Remove all restrictions + for(auto& restrict : m_restrictions) + { + delete restrict; + } + m_restrictions.clear(); +} + +XsdError +XSDSchema::ReadXSDSchema(XString p_fileName) +{ + XMLMessage doc; + if(!doc.LoadFile(p_fileName)) + { + return XsdError::XSDE_Cannot_load_xsd_file; + } + if(doc.GetInternalError() != XmlError::XE_NoError) + { + return XsdError::XSDE_No_valid_xml_definition; + } + // Read the schema root and validate it! + XsdError result = ReadXSDSchemaRoot(doc); + if(result != XsdError::XSDE_NoError) + { + return result; + } + // Read all complex types first + result = ReadXSDComplexTypes(doc); + if(result != XsdError::XSDE_NoError) + { + return result; + } + // Read all elements + result = ReadXSDElements(doc); + if(result != XsdError::XSDE_NoError) + { + return result; + } + // Reached the end with success + return XsdError::XSDE_NoError; +} + +bool +XSDSchema::WriteXSDSchema(XString p_fileName) +{ + XMLMessage doc; + + // Set the root + WriteXSDSchemaRoot(doc); + // Write all elements + WriteXSDElements(doc,doc.GetRoot(),m_elements); + // Write all complex types + WriteXSDComplexTypes(doc); + + // Save as human readable file + doc.SetCondensed(false); + doc.SetEncoding(StringEncoding::ENC_UTF8); + return doc.SaveFile(p_fileName,true); +} + +// Validate an XML document, with optional starting point +// Typically SOAP messages will present the first child node after their +// to this function to validate the functional SOAPMessage +// +XsdError +XSDSchema::ValidateXML(XMLMessage& p_document + ,XString& p_error + ,XMLElement* p_start /*= nullptr*/) +{ + XsdError result = XsdError::XSDE_NoError; + + // Find our starting element + XMLElement* starting = p_start ? p_start : p_document.GetRoot(); + + if(m_elements.empty()) + { + result = XsdError::XSDE_Schema_has_no_starting_element; + p_error = "XSDSchema has no starting element"; + } + else + { + // Begin of validation loop + for(int index = 0;index < (int)m_elements.size(); ++index) + { + result = ValidateElement(p_document,starting,m_elements[index],p_error); + starting = p_document.GetElementSibling(starting); + + if(result != XsdError::XSDE_NoError) + { + break; + } + if(starting == nullptr && index < (int)m_elements.size() - 1) + { + XsdError::XSDE_Missing_elements_in_xml_at_level_0; + p_error.AppendFormat("Missing elements after index: %d",index + 1); + break; + } + } + if(starting) + { + XsdError::XSDE_Extra_elements_in_xml_at_level_0; + p_error = "More elements in XMLMessage than in XSDSchema."; + } + } + return result; +} + +////////////////////////////////////////////////////////////////////////// +// +// PRIVATE METHODS +// +////////////////////////////////////////////////////////////////////////// + +// + +// Read the schema root node +XsdError +XSDSchema::ReadXSDSchemaRoot(XMLMessage& p_doc) +{ + XMLElement* root = p_doc.GetRoot(); + if(root->GetName().Compare("schema")) + { + return XsdError::XSDE_schema_missing; + } + m_xs = root->GetNamespace(); + XMLAttribute* xmlns = p_doc.FindAttribute(root,"xmlns"); + if(xmlns) + { + m_namespace = xmlns->m_value; + if(!xmlns->m_namespace.IsEmpty()) + { + return XsdError::XSDE_primary_namespace_not_qualified; + } + } + else return XsdError::XSDE_primary_namespace_missing; + + XMLAttribute* xs = p_doc.FindAttribute(root,"xs"); + if(xs) + { + m_xs = "xs"; + if(xs->m_namespace.Compare("xmlns")) + { + return XsdError::XSDE_xml_schema_must_be_xml_namespace; + } + } + else return XsdError::XSDE_xml_schema_missing; + + XMLAttribute* target = p_doc.FindAttribute(root,"targetNamespace"); + if(target) + { + m_targetNamespace = target->m_value; + if(!target->m_namespace.IsEmpty()) + { + return XsdError::XSDE_targetnamespace_must_not_be_qualified; + } + } + else return XsdError::XSDE_missing_targetnamespace; + + XMLAttribute* formdef = p_doc.FindAttribute(root,"elementFormDefault"); + if(formdef) + { + if(formdef->m_value.Compare("qualified") == 0) + { + m_qualified = true; + } + } + + // Reached the end with success + return XsdError::XSDE_NoError; +} + +// Read the list of complex types +XsdError +XSDSchema::ReadXSDComplexTypes(XMLMessage& p_doc) +{ + XMLElement* complex = p_doc.FindElement("complexType",false); + while(complex) + { + // Skip past elements + if(complex->GetName().CompareNoCase("complexType") == 0) + { + XString name = p_doc.GetAttribute(complex,"name"); + if(name.IsEmpty()) + { + return XsdError::XSDE_complex_type_without_a_name; + } + XSDComplexType* type = AddComplexType(name); + + XsdError result = ReadXSDComplexType(p_doc,complex,type); + if(result != XsdError::XSDE_NoError) + { + return result; + } + } + // Read next node to scan for more complex types + complex = p_doc.GetElementSibling(complex); + } + // Reached the end with success + return XsdError::XSDE_NoError; +} + +// Read the content of one ComplexType +XsdError +XSDSchema::ReadXSDComplexType(XMLMessage& p_doc + ,XMLElement* p_elem + ,XSDComplexType* p_type) +{ + XsdError result = XsdError::XSDE_NoError; + XMLElement* selector = p_doc.GetElementFirstChild(p_elem); + if(selector->GetName().Compare("sequence") == 0) + { + p_type->m_order = WsdlOrder::WS_Sequence; + } + if(selector->GetName().Compare("all") == 0) + { + p_type->m_order = WsdlOrder::WS_All; + } + if(selector->GetName().Compare("choice") == 0) + { + p_type->m_order = WsdlOrder::WS_Choice; + } + + XMLElement* elem = p_doc.GetElementFirstChild(selector); + while(elem) + { + XMLElement* next = nullptr; + result = ReadElementDefinition(p_doc,elem,&next); + p_type->m_elements.push_back(next); + + if(result != XsdError::XSDE_NoError) + { + break; + } + // read next element + elem = p_doc.GetElementSibling(elem); + } + return result; +} + +// Read all elements in the schema +XsdError +XSDSchema::ReadXSDElements(XMLMessage& p_doc) +{ + XsdError result = XsdError::XSDE_NoError; + + XMLElement* elem = p_doc.FindElement("element",false); + while(elem) + { + // Skip past complex types + if(elem->GetName().Compare("element") == 0) + { + XMLElement* next = nullptr; + result = ReadElementDefinition(p_doc,elem,&next); + m_elements.push_back(next); + + if(result != XsdError::XSDE_NoError) + { + break; + } + } + // Next element + elem = p_doc.GetElementSibling(elem); + } + // Reached the end with success + return result; +} + +XsdError +XSDSchema::ReadElementDefinition(XMLMessage& p_doc + ,XMLElement* p_elem + ,XMLElement** p_next) +{ + XsdError result = XsdError::XSDE_NoError; + XString name = p_doc.GetAttribute(p_elem,"name"); + XString type = p_doc.GetAttribute(p_elem,"type"); + XString minoccurs = p_doc.GetAttribute(p_elem,"minOccurs"); + XString maxoccurs = p_doc.GetAttribute(p_elem,"maxOccurs"); + + XmlDataType xmlType = 0; + XMLRestriction* restrict = new XMLRestriction(type); + m_restrictions.push_back(restrict); + + if(minoccurs.GetLength()) + { + restrict->AddMinOccurs(minoccurs); + } + if(maxoccurs.GetLength()) + { + restrict->AddMaxOccurs(maxoccurs); + } + + if(StripSchemaNS(type)) + { + xmlType = StringToXmlDataType(type); + restrict->AddBaseType(type); + } + else if(type.IsEmpty()) + { + // Simple type + XMLElement* simple = p_doc.GetElementFirstChild(p_elem); + if(simple && simple->GetName().Compare("simpleType") == 0) + { + XMLElement* rest = p_doc.GetElementFirstChild(simple); + if(rest && rest->GetName().Compare("restriction") == 0) + { + XString base = p_doc.GetAttribute(rest,"base"); + StripSchemaNS(base); + restrict->AddBaseType(base); + + // Scan all parts + XMLElement* part = p_doc.GetElementFirstChild(rest); + while(part) + { + XString rname = part->GetName(); + XString value = p_doc.GetAttribute(part,"value"); + + if(rname.Compare("length") == 0) restrict->AddLength(atoi(value)); + else if(rname.Compare("minLength") == 0) restrict->AddMinLength(atoi(value)); + else if(rname.Compare("maxLength") == 0) restrict->AddMaxLength(atoi(value)); + else if(rname.Compare("totalDigits") == 0) restrict->AddTotalDigits(atoi(value)); + else if(rname.Compare("fractionDigits") == 0) restrict->AddFractionDigits(atoi(value)); + else if(rname.Compare("minExclusive") == 0) restrict->AddMinExclusive(value); + else if(rname.Compare("maxExclusive") == 0) restrict->AddMaxExclusive(value); + else if(rname.Compare("minInclusive") == 0) restrict->AddMinInclusive(value); + else if(rname.Compare("maxInclusive") == 0) restrict->AddMaxInclusive(value); + else if(rname.Compare("pattern") == 0) restrict->AddPattern(value); + else if(rname.Compare("enumeration") == 0) + { + XString documentation; + XMLElement* anno = p_doc.GetElementFirstChild(part); + if(anno && anno->GetName().Compare("annotation") == 0) + { + XMLElement* doc = p_doc.GetElementFirstChild(anno); + if(doc) + { + documentation = doc->GetValue(); + } + } + restrict->AddEnumeration(value,documentation); + } + else if(rname.Compare("whitespace") == 0) + { + int ws = 0; + if(value.Compare("preserve") == 0) ws = 1; + else if(value.Compare("replace") == 0) ws = 2; + else if(value.Compare("collapse") == 0) ws = 3; + else + { + result = XsdError::XSDE_unknown_simple_type_restriction; + } + restrict->AddWhitespace(ws); + } + else + { + result = XsdError::XSDE_unknown_simple_type_restriction; + } + // Next part + part = p_doc.GetElementSibling(part); + } + } + } + } + else + { + // Complex type reference + } + XMLElement* next = new XMLElement(); + next->SetName(name); + next->SetType(xmlType); + next->SetRestriction(restrict); + + // Tell it our caller + *p_next = next; + return result; +} + +////////////////////////////////////////////////////////////////////////// + +// + +// Set the root +void +XSDSchema::WriteXSDSchemaRoot(XMLMessage& p_doc) +{ + XMLElement* root = p_doc.GetRoot(); + root->SetName("schema"); + root->SetNamespace("ns"); + p_doc.SetAttribute(root,"xmlns",m_namespace); + p_doc.SetAttribute(root,"xmlns:xs",XSDSCHEMA); + p_doc.SetAttribute(root,"targetNamespace",m_targetNamespace); + p_doc.SetAttribute(root,"elementFormDefault",m_qualified ? "qualified" : "unqualified"); +} + +// Write all elements +void +XSDSchema::WriteXSDElements(XMLMessage& p_doc,XMLElement* p_base,ElementMap& p_elements) +{ + for(int index = 0;index < (int) p_elements.size();++index) + { + XMLElement* elem = p_doc.AddElement(p_base,"xs:element",XDT_String,""); + p_doc.SetAttribute(elem,"name",p_elements[index]->GetName()); + XMLRestriction* restrict = p_elements[index]->GetRestriction(); + if(restrict) + { + p_doc.SetAttribute(elem,"type",restrict->GetName()); + } + if(restrict->HasMinOccurs() != 1) + { + p_doc.SetAttribute(elem,"minOccurs",(int)restrict->HasMinOccurs()); + } + if(restrict->HasMaxOccurs() != 1) + { + if(restrict->HasMaxOccurs() == UINT_MAX) + { + p_doc.SetAttribute(elem,"maxOccurs","unbounded"); + } + else + { + p_doc.SetAttribute(elem,"maxOccurs",(int) restrict->HasMaxOccurs()); + } + } + if(FindComplexType(restrict->GetName()) == nullptr) + { + if(XMLRestrictionMoreThanBase(restrict)) + { + // Must be a simple type + XMLElement* simple = p_doc.AddElement(elem, "xs:simpleType", XDT_String,""); + XMLElement* restrt = p_doc.AddElement(simple,"xs:restriction",XDT_String,""); + p_doc.SetAttribute(restrt,"base","xs:" + restrict->HasBaseType()); + + WriteXSDRestrictions(p_doc,restrt,restrict); + } + } + } +} + +// Write all complex types +void +XSDSchema::WriteXSDComplexTypes(XMLMessage& p_doc) +{ + for(auto& comp : m_types) + { + XSDComplexType* complex = comp.second; + XMLElement* type = p_doc.AddElement(nullptr,"xs:complexType",XDT_String,""); + p_doc.SetAttribute(type,"name",complex->m_name); + + XMLElement* order(nullptr); + switch(complex->m_order) + { + case WsdlOrder::WS_All: order = p_doc.AddElement(type,"xs:all", XDT_String,""); break; + case WsdlOrder::WS_Choice: order = p_doc.AddElement(type,"xs:choice", XDT_String,""); break; + case WsdlOrder::WS_Sequence: order = p_doc.AddElement(type,"xs:sequence",XDT_String,""); break; + default: return; + } + // Write all parts of the complex type + WriteXSDElements(p_doc,order,complex->m_elements); + } +} + +bool +XSDSchema::XMLRestrictionMoreThanBase(XMLRestriction* p_restrict) +{ + if(p_restrict->HasLength() > 0 || + p_restrict->HasMaxLength() > 0 || + p_restrict->HasMinLength() > 0 || + p_restrict->HasTotalDigits() > 0 || + p_restrict->HasFractionDigits() > 0 || + p_restrict->HasWhitespace() > 0 || + !p_restrict->HasPattern().IsEmpty() || + !p_restrict->HasMaxExclusive().IsEmpty() || + !p_restrict->HasMaxInclusive().IsEmpty() || + !p_restrict->HasMinExclusive().IsEmpty() || + !p_restrict->HasMinInclusive().IsEmpty() || + !p_restrict->GetEnumerations().empty()) + { + return true; + } + return false; +} + +void +XSDSchema::WriteXSDRestrictions(XMLMessage& p_doc,XMLElement* p_elem,XMLRestriction* p_restrict) +{ + XMLElement* extra(nullptr); + + if(p_restrict->HasLength() > 0) + { + extra = p_doc.AddElement(p_elem,"xs:length",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasLength()); + } + if(p_restrict->HasMaxLength() > 0) + { + extra = p_doc.AddElement(p_elem,"xs:maxLength",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasMaxLength()); + } + if(p_restrict->HasMinLength() > 0) + { + extra = p_doc.AddElement(p_elem,"xs:minLength",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasMinLength()); + } + if(p_restrict->HasTotalDigits()) + { + extra = p_doc.AddElement(p_elem,"xs:totalDigits",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasTotalDigits()); + } + if(p_restrict->HasFractionDigits()) + { + extra = p_doc.AddElement(p_elem,"xs:fractionDigits",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasFractionDigits()); + } + if(!p_restrict->HasMaxExclusive().IsEmpty()) + { + extra = p_doc.AddElement(p_elem,"xs:maxExclusive",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasMaxExclusive()); + } + if(!p_restrict->HasMaxInclusive().IsEmpty()) + { + extra = p_doc.AddElement(p_elem,"xs:maxInclusive",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasMaxInclusive()); + } + if(!p_restrict->HasMinExclusive().IsEmpty()) + { + extra = p_doc.AddElement(p_elem,"xs:minExclusive",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasMinExclusive()); + } + if(!p_restrict->HasMinInclusive().IsEmpty()) + { + extra = p_doc.AddElement(p_elem,"xs:minInclusive",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasMinInclusive()); + } + if(!p_restrict->HasPattern().IsEmpty()) + { + extra = p_doc.AddElement(p_elem,"xs:pattern",XDT_String,""); + p_doc.SetAttribute(extra,"value",p_restrict->HasPattern()); + } + if(p_restrict->HasWhitespace()) + { + extra = p_doc.AddElement(p_elem,"xs:whiteSpace",XDT_String,""); + // 1=preserve, 2=replace, 3=collapse + switch(p_restrict->HasWhitespace()) + { + case 1: p_doc.SetAttribute(extra,"value","preserve"); break; + case 2: p_doc.SetAttribute(extra,"value","replace"); break; + case 3: p_doc.SetAttribute(extra,"value","collapse"); break; + } + } + XmlEnums& enums = p_restrict->GetEnumerations(); + if(!enums.empty()) + { + for(auto& num : enums) + { + extra = p_doc.AddElement(p_elem,"xs:enumeration",XDT_String,""); + p_doc.SetAttribute(extra,"value",num.first); + if(!num.second.IsEmpty()) + { + XMLElement* anno = p_doc.AddElement(extra,"annotation",XDT_String,""); + p_doc.AddElement(anno,"documentation",XDT_String,num.second); + } + } + } +} + +////////////////////////////////////////////////////////////////////////// +// +// Helper methods +// +////////////////////////////////////////////////////////////////////////// + +// Handle ComplexType objects +XSDComplexType* +XSDSchema::FindComplexType(XString p_name) +{ + ComplexMap::iterator it = m_types.find(p_name); + if(it != m_types.end()) + { + return it->second; + } + return nullptr; +} + +XSDComplexType* +XSDSchema::AddComplexType(XString p_name) +{ + XSDComplexType* complex = FindComplexType(p_name); + if(!complex) + { + complex = new XSDComplexType(p_name); + m_types[p_name] = complex; + } + return complex; +} + +// Helper functions +bool +XSDSchema::StripSchemaNS(XString& p_name) +{ + int pos = p_name.Find(':'); + if(pos < 0) + { + return false; + } + if(p_name.Left(pos).Compare(m_xs)) + { + return false; + } + p_name = p_name.Mid(pos + 1); + return true; +} + +XMLElement* +XSDSchema::FindElement(ElementMap& p_map,XString p_name) +{ + for(auto& elem : p_map) + { + if(elem->GetName().Compare(p_name) == 0) + { + return elem; + } + } + return nullptr; +} + +////////////////////////////////////////////////////////////////////////// +// +// VALIDATION +// +////////////////////////////////////////////////////////////////////////// + +XsdError +XSDSchema::ValidateElement(XMLMessage& p_doc + ,XMLElement* p_compare + ,XMLElement* p_schemaElement + ,XString& p_error) +{ + XsdError result = XsdError::XSDE_NoError; + + // 1) Find restriction and data type + XMLRestriction* rest = p_schemaElement->GetRestriction(); + XString type = rest->GetName(); + + // 2) Check for qualified element name + // if(m_qualified && p_compare->GetNamespace().IsEmpty()) + // { + // p_error = "Unqualified element: " + p_compare->GetName(); + // return XsdError::XSDE_element_not_qualified; + // } + + // 3) Complex type -> more steps -> Find XSDComplexType + XSDComplexType* complex = FindComplexType(type); + + // 4) Simple type -> Validate the element value + if(complex == nullptr || p_schemaElement->GetType() > 0) + { + return ValidateValue(rest,p_compare,p_schemaElement->GetType(),type,p_error); + } + + // 5) Validate Wsdl Order in the XSDComplexType + result = ValidateOrder(p_doc,p_compare,complex,p_error); + if(result != XsdError::XSDE_NoError) + { + return result; + } + + // 6) Validate Elements (recursive calling ourselves) + XMLElement* child = p_doc.GetElementFirstChild(p_compare); + while(child) + { + XMLElement* valAgainst = FindElement(complex->m_elements,child->GetName()); + if(valAgainst == nullptr) + { + XsdError::XSDE_Element_not_in_xsd; + p_error.AppendFormat("Unknown element [%s] in: %s",child->GetName().GetString(),complex->m_name.GetString()); + break; + } + result = ValidateElement(p_doc,child,valAgainst,p_error); + if(result != XsdError::XSDE_NoError) + { + break; + } + // Getting next element to validate + child = p_doc.GetElementSibling(child); + } + // Come to the end + return result; +} + +XsdError +XSDSchema::ValidateOrder(XMLMessage& p_doc + ,XMLElement* p_compare + ,XSDComplexType* p_complex + ,XString& p_error) +{ + XsdError result = XsdError::XSDE_NoError; + switch(p_complex->m_order) + { + case WsdlOrder::WS_Sequence: result = ValidateOrderSequence(p_doc,p_compare,p_complex->m_elements,p_error); + break; + case WsdlOrder::WS_Choice: result = ValidateOrderChoice (p_doc,p_compare,p_complex->m_elements,p_error); + break; + case WsdlOrder::WS_All: result = ValidateOrderAll (p_doc,p_compare,p_complex->m_elements,p_error); + break; + default: XsdError::XSDE_Unknown_ComplexType_ordering; + p_error += "Unknown ComplexType ordering found (NOT sequence,choice,all)"; + break; + } + return result; +} + +XsdError +XSDSchema::ValidateOrderSequence(XMLMessage& p_doc,XMLElement* p_compare,ElementMap& p_elements,XString& p_error) +{ + XsdError result = XsdError::XSDE_NoError; + + // Elements must occur in the same order, or must have "minOccur = 0" + int findpos = 0; + + XMLElement* tocheck = p_doc.GetElementFirstChild(p_compare); + while(tocheck) + { + if(findpos >= (int)p_elements.size()) + { + // error: Element not found in definition + break; + } + XMLElement* defelem = p_elements[findpos]; + if(tocheck->GetName().Compare(defelem->GetName()) != 0) + { + // Not found: see if it has 'minOccur' + XMLRestriction* restrict = defelem->GetRestriction(); + if(!restrict || restrict->HasMinOccurs() > 0) + { + p_error = "Missing element in message: " + defelem->GetName(); + return XsdError::XSDE_Missing_element_in_xml; + } + } + else + { + // Next element to check + tocheck = p_doc.GetElementSibling(tocheck); + } + ++findpos; + } + if(tocheck) + { + p_error = "Element not found in XSD type definition: " + tocheck->GetName(); + result = XsdError::XSDE_Element_not_in_xsd; + } + return result; +} + +XsdError +XSDSchema::ValidateOrderChoice(XMLMessage& p_doc,XMLElement* p_compare,ElementMap& p_elements,XString& p_error) +{ + XsdError result = XsdError::XSDE_NoError; + + XMLElement* found(nullptr); + XMLElement* elem = p_doc.GetElementFirstChild(p_compare); + if(elem) + { + found = FindElement(p_elements,elem->GetName()); + } + if(found == nullptr) + { + p_error = "Element by not defined in complex type: " + p_compare->GetName(); + result = XsdError::XSDE_Missing_element_in_xml; + } + else if(p_doc.GetElementSibling(p_compare)) + { + p_error = " selection can have only ONE (1) element"; + result = XsdError::XSDE_only_one_choice_element; + } + return result; +} + +XsdError +XSDSchema::ValidateOrderAll(XMLMessage& p_doc,XMLElement* p_compare,ElementMap& p_elements,XString& p_error) +{ + XsdError result = XsdError::XSDE_NoError; + + XMLElement* elem = p_doc.GetElementFirstChild(p_compare); + while(elem) + { + XMLElement* found = FindElement(p_elements,elem->GetName()); + if(found == nullptr) + { + p_error = "Element not found in type definition: " + elem->GetName(); + result = XsdError::XSDE_Missing_element_in_xml; + break; + } + } + return result; +} + +XsdError +XSDSchema::ValidateValue(XMLRestriction* p_restrict + ,XMLElement* p_compare + ,XmlDataType p_datatype + ,XString p_type + ,XString& p_error) +{ + XsdError result = XsdError::XSDE_NoError; + + XmlDataType basetype = p_datatype; + if(!basetype && !p_type.IsEmpty()) + { + basetype = StringToXmlDataType(p_type); + } + else + { + basetype = StringToXmlDataType(p_restrict->HasBaseType()); + } + + // Check datatype contents + XString errors = p_restrict->CheckDatatype(basetype,p_compare->GetValue()); + if(!errors.IsEmpty()) + { + result = XsdError::XSDE_base_datatype_violation; + p_error += "Field: " + p_compare->GetName(); + p_error += " : " + errors; + } + + // Check extra restrictions on datatype value + errors = p_restrict->CheckRestriction(basetype,p_compare->GetValue()); + if(!errors.IsEmpty()) + { + result = XsdError::XSDE_datatype_restriction_violation; + p_error += "Field: " + p_compare->GetName(); + p_error += " : " + errors; + } + + return result; +} + diff --git a/BaseLibrary/XSDSchema.h b/BaseLibrary/XSDSchema.h new file mode 100644 index 0000000..b29a90a --- /dev/null +++ b/BaseLibrary/XSDSchema.h @@ -0,0 +1,148 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// SourceFile: XSDSchema.h +// +// Copyright (c) 2014-2022 ir. W.E. Huisman +// All rights reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +////////////////////////////////////////////////////////////////////////// +// +// Partial implementation of XSD Schema as defined by W3C +// See: https://www.w3.org/TR/xmlschema11-1 +// +////////////////////////////////////////////////////////////////////////// + +#pragma once +#include "XMLMessage.h" +#include +#include + +enum class XsdError +{ + XSDE_NoError + ,XSDE_Cannot_load_xsd_file + ,XSDE_No_valid_xml_definition + ,XSDE_schema_root_invalid + ,XSDE_schema_missing + ,XSDE_primary_namespace_not_qualified + ,XSDE_primary_namespace_missing + ,XSDE_xml_schema_missing + ,XSDE_xml_schema_must_be_xml_namespace + ,XSDE_missing_targetnamespace + ,XSDE_targetnamespace_must_not_be_qualified + ,XSDE_complex_type_without_a_name + ,XSDE_unknown_simple_type_restriction + ,XSDE_Schema_has_no_starting_element + ,XSDE_Missing_elements_in_xml_at_level_0 + ,XSDE_Extra_elements_in_xml_at_level_0 + ,XSDE_Missing_elements_in_xml + ,XSDE_Extra_elements_in_xml // no more + ,XSDE_Element_not_in_xsd + ,XSDE_Unknown_ComplexType_ordering + ,XSDE_base_datatype_violation + ,XSDE_datatype_restriction_violation + ,XSDE_Missing_element_in_xml + ,XSDE_only_one_choice_element + ,XSDE_element_not_qualified +}; + +#define XSDSCHEMA "http://www.w3.org/2001/XMLSchema" + +using ElementMap = std::vector; +using RestrictMap = std::vector; + +class XSDComplexType: public XMLElement +{ +public: + XSDComplexType(XString p_name); + ~XSDComplexType(); + + XString m_name; + WsdlOrder m_order; + ElementMap m_elements; +}; + +using ComplexMap = std::map; + +////////////////////////////////////////////////////////////////////////// + +class XSDSchema +{ +public: + XSDSchema(); + virtual ~XSDSchema(); + + // Reading and writing + XsdError ReadXSDSchema(XString p_fileName); + bool WriteXSDSchema(XString p_fileName); + + // Validate an XML document, with optional starting point + XsdError ValidateXML(XMLMessage& p_document,XString& p_error,XMLElement* p_start = nullptr); + +private: + // Read the schema root node + XsdError ReadXSDSchemaRoot(XMLMessage& p_doc); + // Read the list of complex types + XsdError ReadXSDComplexTypes(XMLMessage& p_doc); + // Read all elements in the schema + XsdError ReadXSDElements(XMLMessage& p_doc); + // Read the content of one ComplexType + XsdError ReadXSDComplexType (XMLMessage& p_doc,XMLElement* p_elem,XSDComplexType* p_type); + XsdError ReadElementDefinition(XMLMessage& p_doc,XMLElement* p_elem,XMLElement** p_next); + + // Writing the root of the XSD schema + void WriteXSDSchemaRoot(XMLMessage& p_doc); + // Write all XSD elements + void WriteXSDElements(XMLMessage& doc,XMLElement* p_base,ElementMap& p_elements); + // Write all XSD complex types + void WriteXSDComplexTypes(XMLMessage& p_doc); + // Write all XSD restrictions to a node + void WriteXSDRestrictions(XMLMessage& p_doc,XMLElement* p_elem,XMLRestriction* p_restrict); + + // Handle ComplexType objects and helper functions + XSDComplexType* FindComplexType(XString p_name); + XSDComplexType* AddComplexType (XString p_name); + bool StripSchemaNS (XString& p_name); + XMLElement* FindElement(ElementMap& p_map,XString p_name); + bool XMLRestrictionMoreThanBase(XMLRestriction* p_restrict); + + // Validation + XsdError ValidateElement(XMLMessage& p_doc,XMLElement* p_compare,XMLElement* p_schemaElement,XString& p_error); + XsdError ValidateOrder (XMLMessage& p_doc,XMLElement* p_compare,XSDComplexType* p_complex,XString& p_error); + XsdError ValidateValue (XMLRestriction* p_restrict,XMLElement* p_compare,XmlDataType p_datatype,XString p_type,XString& p_error); + + XsdError ValidateOrderSequence(XMLMessage& p_doc,XMLElement* p_compare,ElementMap& p_elements,XString& p_error); + XsdError ValidateOrderChoice (XMLMessage& p_doc,XMLElement* p_compare,ElementMap& p_elements,XString& p_error); + XsdError ValidateOrderAll (XMLMessage& p_doc,XMLElement* p_compare,ElementMap& p_elements,XString& p_error); + + // Schema names + XString m_namespace; // Namespace of the schema (xmlns) + XString m_xmlNamespace; // XML Schema ("http://www.w3.org/2001/XMLSchema") + XString m_targetNamespace; // Target namespace of the schema (targetNamespace) + XString m_xs; // XML Schema qualifier (xs:) + bool m_qualified; // true or false + // Contents of the schema + ElementMap m_elements; // All elements in the schema + ComplexMap m_types; // All complex types + RestrictMap m_restrictions; // Keep track of all restrictions +}; + diff --git a/Kwatta/Kwatta.vcxproj b/Kwatta/Kwatta.vcxproj index ea7ea9f..3c465e8 100644 --- a/Kwatta/Kwatta.vcxproj +++ b/Kwatta/Kwatta.vcxproj @@ -62,7 +62,7 @@ _WINDOWS;_DEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFrameWork\;$(SolutionDir)StyleFrameWork\Grid\;$(SolutionDir)Marlin\;%(AdditionalIncludeDirectories) Async - stdcpplatest + stdcpp20 stdc17 true false @@ -99,7 +99,7 @@ true false _WINDOWS;NDEBUG;%(PreprocessorDefinitions) - stdcpplatest + stdcpp20 stdc17 $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFrameWork\;$(SolutionDir)StyleFrameWork\Grid\;$(SolutionDir)Marlin\;%(AdditionalIncludeDirectories) Async diff --git a/Kwatta/KwattaAppDlg.cpp b/Kwatta/KwattaAppDlg.cpp index a0ea4d7..4438f5f 100644 --- a/Kwatta/KwattaAppDlg.cpp +++ b/Kwatta/KwattaAppDlg.cpp @@ -143,6 +143,7 @@ BEGIN_MESSAGE_MAP(KwattaAppDlg, StyleDialog) ON_COMMAND(ID_MENU_THEMEMODERATE, OnStyleModerateGray) ON_COMMAND(ID_MENU_THEMEPURE, OnStylePureGray) ON_COMMAND(ID_MENU_THEMEBLACK, OnStyleBlackWhite) + ON_COMMAND(ID_MENU_THEMEDARK, OnStyleDark) ON_COMMAND(ID_MENU_ABOUT, OnAbout) ON_COMMAND(ID_MENU_EXIT, OnExit) diff --git a/Kwatta/resource.h b/Kwatta/resource.h index a3c272e728e26f7365d13a82011333e4da5ec1fc..11103c97cb87eafeae320ab91a038a390a3f3f7d 100644 GIT binary patch delta 68 zcmV-K0K5O{Zpd%2YBZBjGYSVp06_p!085eaACrJH2$P~TCX;G3IFdC6lX@T&k~9pH a$TT>Ujy49f1~rBOlQ0$-k~b8SxHhst&=-gR delta 42 zcmV+_0M-A #include #include @@ -121,7 +122,7 @@ HTTPClient::Reset() m_proxyPassword.Empty(); m_enc_password.Empty(); - m_agent = "HTTPClient/1.0"; + m_agent = "HTTPClient/" MARLIN_VERSION_NUMBER; m_scheme = "http"; m_retries = 0; m_useProxy = ProxyType::PROXY_IEPROXY; @@ -1047,7 +1048,7 @@ HTTPClient::AddExtraHeaders() } if(m_httpCompression) { - AddHeader("Accept-Encoding","gzip"); + AddHeader("Accept-Encoding","chunked, gzip"); } if(m_terminalServices) { @@ -1828,6 +1829,7 @@ HTTPClient::ReceivePushEvents() { DWORD dwSize = 0; DWORD dwRead = 0; + DWORD status = 0; if(::WinHttpQueryDataAvailable(m_request,&dwSize)) { @@ -1919,8 +1921,33 @@ HTTPClient::ReceivePushEvents() } else { + // Already closing status found? + if(m_request == NULL) + { + m_status = HTTP_STATUS_NO_CONTENT; + } + else if(::WinHttpQueryHeaders(m_request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + NULL, // WINHTTP_HEADER_NAME_BY_INDEX, + &status, + &dwSize, + WINHTTP_NO_HEADER_INDEX)) + { + m_status = status; + } + if(m_status == HTTP_STATUS_NO_CONTENT) + { + DETAILLOG("Server close event-stream properly with HTTP 204."); + if(m_eventSource->GetReadyState() == OPEN) + { + ServerEvent* event = new ServerEvent("close"); + m_eventSource->OnClose(event); + } + return; + } // Error in polling HTTP Status DWORD er = GetLastError(); + CString message = GetLastErrorAsString(er); switch(er) { case ERROR_WINHTTP_CONNECTION_ERROR: m_status = HTTP_STATUS_SERVICE_UNAVAIL; break; @@ -1942,7 +1969,7 @@ HTTPClient::ReceivePushEvents() // Make error event and dispatch it ServerEvent* event = new ServerEvent("error"); - event->m_data.Format("OS Error: %lu, HTTP Error: %u",er,m_status); + event->m_data.Format("OS Error [%lu:%s] HTTP Status [%u] %s",er,message.GetString(),m_status,GetHTTPStatusText(m_status)); ERRORLOG(event->m_data); m_eventSource->OnError(event); @@ -2375,20 +2402,23 @@ HTTPClient::Send(SOAPMessage* p_msg) } else { - XString response; - if(m_response) - { - response = m_response; - response += ". "; - } - if(m_lastError >= WINHTTP_ERROR_BASE && m_lastError <= WINHTTP_ERROR_LAST) + // No specific SOAPAction header + p_msg->DelHeader("SOAPAction"); + + // In case of an error and NO soap XML with a fault in the error body + if(p_msg->GetFaultCode().IsEmpty() && p_msg->GetFaultActor().IsEmpty()) { - response.AppendFormat("%s. Error number [%d]",GetHTTPErrorText(m_lastError).GetString(),m_lastError); + XString response; + if(m_lastError >= WINHTTP_ERROR_BASE && m_lastError <= WINHTTP_ERROR_LAST) + { + response.Format("Error number [%d] %s\n",m_lastError,GetHTTPErrorText(m_lastError).GetString()); + } + if(m_response) + { + response += XString(m_response); + } + ReCreateAsSOAPFault(p_msg,oldVersion,response); } - // In case of an error - p_msg->SetSoapVersion(oldVersion); - p_msg->Reset(); - p_msg->SetFault("Client","Send error","HTTPClient send result",response); } // Freeing Unicode UTF-16 buffer @@ -3122,9 +3152,10 @@ HTTPClient::Send() } DETAILLOG("Returned HTTP status: %d:%s",m_status,GetHTTPStatusText(m_status)); - // See if we must do 'gzip' decompression + // See if we must do 'gzip' decompression. + // Header can contain other information (e.g. "chunked") XString compress = ReadHeaderField(WINHTTP_QUERY_CONTENT_ENCODING); - if(compress.CompareNoCase("gzip") == 0) + if(compress.Find("gzip") >= 0) { // DECompress in-memory with ZLib from a 'gzip' HTTP buffer size_t before = m_responseLength; @@ -3727,6 +3758,31 @@ HTTPClient::SetCORSPreFlight(XString p_method,XString p_headers) return false; } +void +HTTPClient::ReCreateAsSOAPFault(SOAPMessage* p_msg,SoapVersion p_version,XString p_response) +{ + p_msg->SetSoapVersion(p_version); + p_msg->Reset(); + + XMLElement* fault = p_msg->AddElement(p_msg->GetXMLBodyPart(),"Fault",XDT_String,""); + if(p_version == SoapVersion::SOAP_12) + { + // SOAP 1.2 + XMLElement* fcode = p_msg->AddElement(fault,"Code", XDT_String,""); + p_msg->AddElement(fcode,"Value", XDT_String,"Client"); + XMLElement* reasn = p_msg->AddElement(fault,"Reason",XDT_String,""); + p_msg->AddElement(reasn,"Text", XDT_String,p_response); + } + else + { + // SOAP 1.1 or less + p_msg->AddElement(fault,"faultcode", XDT_String,"Client"); + p_msg->AddElement(fault,"faultstring",XDT_String,"Send result"); + p_msg->AddElement(fault,"detail", XDT_String,p_response); + } + p_msg->SetFault("Client","Client","Send error",p_response); +} + ////////////////////////////////////////////////////////////////////////// // // WS-Security answer @@ -4321,7 +4377,6 @@ HTTPClient::StopClient() DETAILLOG("Stopping the EventSource"); if(m_request) { - ERRORLOG("Stopping of the HTTP event-source channel"); OnCloseSeen(); } for (int i = 0; i < 10; ++i) diff --git a/Marlin/HTTPClient.h b/Marlin/HTTPClient.h index 180dd5f..8dd0ebe 100644 --- a/Marlin/HTTPClient.h +++ b/Marlin/HTTPClient.h @@ -372,6 +372,7 @@ class HTTPClient bool DoRedirectionAfterSend(); void ProcessChunkedEncoding(); uchar* GetChunkSize(uchar* p_reading,unsigned& p_size); + void ReCreateAsSOAPFault(SOAPMessage* p_msg,SoapVersion p_version,XString p_response); // Methods for WS-Security void CheckAnswerSecurity (SOAPMessage* p_msg,XString p_answer,XMLEncryption p_security,XString p_password); void CheckBodySigning (XString p_password,SOAPMessage* p_msg); @@ -398,7 +399,7 @@ class HTTPClient // Connection specials bool m_initialized { false }; // Initialisation done unsigned m_retries { 0 }; // Number of sending retries - XString m_agent { "HTTPClient/7.0" }; // User agents name (spoofing!!) + XString m_agent { "HTTPClient/8.1" }; // User agents name (spoofing!!) ProxyType m_useProxy { ProxyType::PROXY_IEPROXY }; // Which proxy to use XString m_proxy; // Use proxy XString m_proxyBypass; // Do not use these proxies diff --git a/Marlin/HTTPServer.cpp b/Marlin/HTTPServer.cpp index 5f26542..7c6690b 100644 --- a/Marlin/HTTPServer.cpp +++ b/Marlin/HTTPServer.cpp @@ -90,6 +90,9 @@ constexpr const char* global_client_error = "\n" "\n"; +// Error state is retained in TLS (Thread-Local-Storage) on a per-call basis for the server +__declspec(thread) ULONG tls_lastError = 0; + // Static globals for the server as a whole // Can be set through the Marlin.config reading of the HTTPServer // unsigned long g_streaming_limit = STREAMING_LIMIT; @@ -128,6 +131,7 @@ HTTPServer::HTTPServer(XString p_name) InitializeCriticalSection(&m_eventLock); InitializeCriticalSection(&m_sitesLock); + InitializeCriticalSection(&m_socketLock); // Initially the counter is stopped m_counter.Stop(); @@ -159,6 +163,7 @@ HTTPServer::~HTTPServer() // Free CS to the OS DeleteCriticalSection(&m_eventLock); DeleteCriticalSection(&m_sitesLock); + DeleteCriticalSection(&m_socketLock); // Resetting the signal handlers ResetProcessAfterSEH(); @@ -282,6 +287,19 @@ HTTPServer::SetQueueLength(ULONG p_length) m_queueLength = p_length; } +// Setting the error in TLS, so no locking needed. +void +HTTPServer::SetError(int p_error) +{ + tls_lastError = p_error; +} + +ULONG +HTTPServer::GetLastError() +{ + return tls_lastError; +} + void HTTPServer::SetLogLevel(int p_logLevel) { @@ -353,9 +371,12 @@ HTTPServer::ErrorLog(const char* p_function,DWORD p_code,XString p_text) { bool result = false; + // Record error for the current thread + SetError(p_code); + if(m_log) { - p_text.AppendFormat(" Error [%d] %s",p_code,GetLastErrorAsString(p_code).GetString()); + p_text.AppendFormat(" Error [%08lX] %s",p_code,GetLastErrorAsString(p_code).GetString()); result = m_log->AnalysisLog(p_function, LogType::LOG_ERROR,false,p_text); } @@ -363,8 +384,8 @@ HTTPServer::ErrorLog(const char* p_function,DWORD p_code,XString p_text) // nothing logged if(!result) { - // What can we do? As a last result: print to stdout - printf(MARLIN_SERVER_VERSION " Error [%d] %s\n",p_code,(LPCTSTR)p_text); + // What can we do? As a last result: print to trace output + TRACE("Marlin " MARLIN_SERVER_VERSION " Error [%08lX] %s\n",p_code,(LPCTSTR) p_text); } #endif } @@ -384,8 +405,8 @@ HTTPServer::HTTPError(const char* p_function,int p_status,XString p_text) // nothing logged if(!result) { - // What can we do? As a last result: print to stdout - printf(MARLIN_SERVER_VERSION " Status [%d] %s\n",p_status,(LPCTSTR)p_text); + // What can we do? As a last result: print to trace output + TRACE("Marlin " MARLIN_SERVER_VERSION " Status [%d] %s\n",p_status,(LPCTSTR) p_text); } #endif } @@ -897,7 +918,7 @@ HTTPServer::SendResponse(JSONMessage* p_message) HTTPMessage* answer = new HTTPMessage(HTTPCommand::http_response,p_message); if(answer->GetContentType().Find("json") < 0) { - answer->SetContentType("application/text+json"); + answer->SetContentType("application/json"); } // Send the HTTP Message as response SendResponse(answer); @@ -921,6 +942,7 @@ HTTPServer::RespondWithServerError(HTTPMessage* p_message p_message->GetFileBuffer()->Reset(); p_message->GetFileBuffer()->SetBuffer((uchar*)page.GetString(),page.GetLength()); p_message->SetStatus(p_error); + p_message->SetContentType("text/html"); SendResponse(p_message); } @@ -948,6 +970,7 @@ HTTPServer::RespondWithClientError(HTTPMessage* p_message p_message->GetFileBuffer()->Reset(); p_message->GetFileBuffer()->SetBuffer((uchar*)page.GetString(),page.GetLength()); p_message->SetStatus(p_error); + p_message->SetContentType("text/html"); XString challenge = BuildAuthenticationChallenge(p_authScheme,p_realm); if(!challenge.IsEmpty()) @@ -1830,6 +1853,7 @@ HTTPServer::RegisterSocket(WebSocket* p_socket) DETAILLOGV("Register websocket [%s] at the server",key.GetString()); key.MakeLower(); + AutoCritSec lock(&m_socketLock); SocketMap::iterator it = m_sockets.find(key); if(it != m_sockets.end()) { @@ -1848,6 +1872,7 @@ HTTPServer::UnRegisterWebSocket(WebSocket* p_socket) DETAILLOGV("Unregistering websocket [%s] from the server",key.GetString()); key.MakeLower(); + AutoCritSec lock(&m_socketLock); SocketMap::iterator it = m_sockets.find(key); if(it != m_sockets.end()) { @@ -1856,6 +1881,7 @@ HTTPServer::UnRegisterWebSocket(WebSocket* p_socket) return true; } // We don't have it + ERRORLOG(ERROR_FILE_NOT_FOUND,"Websocket to unregister NOT FOUND! : " + key); return false; } @@ -1865,6 +1891,7 @@ HTTPServer::FindWebSocket(XString p_key) { p_key.MakeLower(); + AutoCritSec lock(&m_socketLock); SocketMap::iterator it = m_sockets.find(p_key); if(it != m_sockets.end()) { diff --git a/Marlin/HTTPServer.h b/Marlin/HTTPServer.h index 0364c5b..5d9aba0 100644 --- a/Marlin/HTTPServer.h +++ b/Marlin/HTTPServer.h @@ -240,6 +240,8 @@ class HTTPServer XString GetWebroot(); // Get host name of the server's machine XString GetHostname(); + // Last error encountered + ULONG GetLastError(); // Is the server still running bool GetIsRunning(); // Get High Performance counter @@ -422,6 +424,8 @@ class HTTPServer void TryStartEventHeartbeat(); // Check all event streams for the heartbeat monitor UINT CheckEventStreams(); + // Set the error status + void SetError(int p_error); // For the handling of the event streams: implement this function virtual bool SendResponseEventBuffer(HTTP_OPAQUE_ID p_response ,CRITICAL_SECTION* p_lock @@ -476,6 +480,7 @@ class HTTPServer CRITICAL_SECTION m_eventLock; // Pulsing events or accessing streams // WebSocket SocketMap m_sockets; // Registered WebSockets + CRITICAL_SECTION m_socketLock; // Lock to register, find, remove WebSockets // Registered DDOS Attacks DDOSMap m_attacks; // Registration of DDOS attacks }; diff --git a/Marlin/HTTPServerIIS.cpp b/Marlin/HTTPServerIIS.cpp index 9039414..45a3478 100644 --- a/Marlin/HTTPServerIIS.cpp +++ b/Marlin/HTTPServerIIS.cpp @@ -611,7 +611,7 @@ HTTPServerIIS::ReadEntityChunks(HTTPMessage* p_message,PHTTP_REQUEST p_request) case HttpDataChunkFromFileHandle: // Should not happen upon receive [[fallthrough]]; case HttpDataChunkFromFragmentCache: [[fallthrough]]; - case HttpDataChunkFromFragmentCacheEx: ERRORLOG(87,"Unhandled HTTP chunk type from IIS"); + case HttpDataChunkFromFragmentCacheEx: ERRORLOG(ERROR_INVALID_PARAMETER,"Unhandled HTTP chunk type from IIS"); break; } } @@ -645,7 +645,7 @@ HTTPServerIIS::ReceiveIncomingRequest(HTTPMessage* p_message) } if(!SUCCEEDED(hr) && HRESULT_CODE(hr) != ERROR_MORE_DATA) { - ERRORLOG(HRESULT_CODE(hr),"Cannot read incoming HTTP buffer"); + ERRORLOG(HRESULT_FROM_WIN32(hr),"Cannot read incoming HTTP buffer"); return false; } // Add to filebuffer @@ -714,7 +714,7 @@ HTTPServerIIS::FlushSocket(HTTP_OPAQUE_ID p_request,XString /*p_prefix*/) HRESULT hr = response->Flush(FALSE,TRUE,&bytesSent,&completion); if(hr != S_OK) { - ERRORLOG(GetLastError(),"Flushing WebSocket failed!"); + ERRORLOG(HRESULT_FROM_WIN32(hr),"Flushing WebSocket failed!"); CancelRequestStream(p_request); return false; } @@ -781,7 +781,7 @@ HTTPServerIIS::InitEventStream(EventStream& p_stream) HRESULT hr = response->WriteEntityChunks(&dataChunk,1,FALSE,true,&bytesSent); if(hr != S_OK) { - ERRORLOG(GetLastError(),"HttpSendResponseEntityBody failed initialisation of an event stream"); + ERRORLOG(HRESULT_FROM_WIN32(hr),"HttpSendResponseEntityBody failed initialisation of an event stream"); } else { @@ -800,26 +800,24 @@ HTTPServerIIS::InitEventStream(EventStream& p_stream) void HTTPServerIIS::SetResponseHeader(IHttpResponse* p_response,XString p_name,XString p_value,bool p_replace) { - if(p_response->SetHeader(p_name,p_value,(USHORT)p_value.GetLength(),p_replace) != S_OK) + HRESULT hr = p_response->SetHeader(p_name,p_value,(USHORT)p_value.GetLength(),p_replace); + if(hr != S_OK) { - DWORD val = GetLastError(); - XString error = GetLastErrorAsString(val); XString bark; - bark.Format("Cannot set HTTP response header [%s] to value [%s] : %s",p_name.GetString(),p_value.GetString(),error.GetString()); - ERRORLOG(val,bark); + bark.Format("Cannot set HTTP response header [%s] to value [%s] : %s",p_name.GetString(),p_value.GetString()); + ERRORLOG(HRESULT_FROM_WIN32(hr),bark); } } void HTTPServerIIS::SetResponseHeader(IHttpResponse* p_response,HTTP_HEADER_ID p_id,XString p_value,bool p_replace) { - if(p_response->SetHeader(p_id,p_value,(USHORT)p_value.GetLength(),p_replace) != S_OK) + HRESULT hr = p_response->SetHeader(p_id,p_value,(USHORT)p_value.GetLength(),p_replace); + if(hr != S_OK) { - DWORD val = GetLastError(); - XString error = GetLastErrorAsString(val); XString bark; - bark.Format("Cannot set HTTP response header [%d] to value [%s] : %s",p_id,p_value.GetString(),error.GetString()); - ERRORLOG(val,bark); + bark.Format("Cannot set HTTP response header [%d] to value [%s] : %s",p_id,p_value.GetString()); + ERRORLOG(HRESULT_FROM_WIN32(hr),bark); } } @@ -1138,6 +1136,7 @@ HTTPServerIIS::SendResponse(HTTPMessage* p_message) // Error handler XString message = GetLastErrorAsString(); m_log->AnalysisLog(__FUNCTION__, LogType::LOG_ERROR,true,"HTTP Answer [%d:%s]",::GetLastError(),message.GetString()); + SetLastError(NO_ERROR); } // Possibly log and trace what we just sent @@ -1185,7 +1184,7 @@ HTTPServerIIS::SendResponseBuffer(IHttpResponse* p_response } else { - result = GetLastError(); + result = HRESULT_FROM_WIN32(hr); ERRORLOG(result,"ResponseBuffer"); } } @@ -1231,7 +1230,7 @@ HTTPServerIIS::SendResponseBufferParts(IHttpResponse* p_response } else { - DWORD result = GetLastError(); + DWORD result = HRESULT_FROM_WIN32(hr); ERRORLOG(result,"HTTP ResponseBufferPart error"); break; } @@ -1252,7 +1251,7 @@ HTTPServerIIS::SendResponseFileHandle(IHttpResponse* p_response,FileBuffer* p_bu // File to transmit if(p_buffer->OpenFile() == false) { - ERRORLOG(GetLastError(),"OpenFile for SendHttpResponse"); + ERRORLOG(::GetLastError(),"OpenFile for SendHttpResponse"); return; } // Get the file handle from buffer @@ -1289,7 +1288,7 @@ HTTPServerIIS::SendResponseFileHandle(IHttpResponse* p_response,FileBuffer* p_bu } else { - result = GetLastError(); + result = HRESULT_FROM_WIN32(hr); ERRORLOG(result,"SendResponseEntityBody for file"); } // Now close our file handle @@ -1332,7 +1331,7 @@ HTTPServerIIS::SendResponseError(IHttpResponse* p_response } else { - result = GetLastError(); + result = HRESULT_FROM_WIN32(hr); ERRORLOG(result,"SendResponseEntityBody for file"); } } @@ -1373,7 +1372,7 @@ HTTPServerIIS::SendResponseEventBuffer(HTTP_OPAQUE_ID p_response HRESULT hr = response->WriteEntityChunks(&dataChunk,1,FALSE,p_continue,&bytesSent); if(hr != S_OK) { - ERRORLOG(GetLastError(),"WriteEntityChunks failed for SendEvent"); + ERRORLOG(HRESULT_FROM_WIN32(hr),"WriteEntityChunks failed for SendEvent"); } else { @@ -1381,7 +1380,7 @@ HTTPServerIIS::SendResponseEventBuffer(HTTP_OPAQUE_ID p_response hr = response->Flush(false,p_continue,&bytesSent); if(hr != S_OK && p_continue) { - ERRORLOG(GetLastError(),"Flushing event stream failed!"); + ERRORLOG(HRESULT_FROM_WIN32(hr),"Flushing event stream failed!"); } else { @@ -1401,9 +1400,9 @@ HTTPServerIIS::SendResponseEventBuffer(HTTP_OPAQUE_ID p_response void HTTPServerIIS::CancelRequestStream(HTTP_OPAQUE_ID p_response,bool p_doReset /*=false*/) { - IHttpContext* context = (IHttpContext*)p_response; + IHttpContext* context = reinterpret_cast(p_response); IHttpResponse* response = context->GetResponse(); - + try { // Set disconnection diff --git a/Marlin/HTTPServerMarlin.cpp b/Marlin/HTTPServerMarlin.cpp index 16e93ca..7662109 100644 --- a/Marlin/HTTPServerMarlin.cpp +++ b/Marlin/HTTPServerMarlin.cpp @@ -808,7 +808,7 @@ HTTPServerMarlin::FlushSocket(HTTP_OPAQUE_ID p_request,XString p_prefix) if(result != NO_ERROR) { - ERRORLOG(GetLastError(),"Flushing HTTP request for WebSocket failed!"); + ERRORLOG(result,"Flushing HTTP request for WebSocket failed!"); CancelRequestStream(p_request); return false; } diff --git a/Marlin/HTTPServerSync.cpp b/Marlin/HTTPServerSync.cpp index 5469e0a..092151d 100644 --- a/Marlin/HTTPServerSync.cpp +++ b/Marlin/HTTPServerSync.cpp @@ -1360,7 +1360,7 @@ HTTPServerSync::SendResponseFileHandle(PHTTP_RESPONSE p_response // File to transmit if(p_buffer->OpenFile() == false) { - ERRORLOG(GetLastError(),"OpenFile for SendHttpResponse"); + ERRORLOG(::GetLastError(),"OpenFile for SendHttpResponse"); return; } // Get the filehandle from buffer diff --git a/Marlin/ServerEventChannel.cpp b/Marlin/ServerEventChannel.cpp index 885ca74..0fc7bc9 100644 --- a/Marlin/ServerEventChannel.cpp +++ b/Marlin/ServerEventChannel.cpp @@ -233,6 +233,7 @@ ServerEventChannel::GetQueueCount() // Count all 'opened' sockets and all SSE-streams // Giving the fact that a channel is 'connected' to a number of clients +// Only looks at the reading state! int ServerEventChannel::GetClientCount() { @@ -241,7 +242,10 @@ ServerEventChannel::GetClientCount() for(auto& socket : m_sockets) { - count += socket.m_open == true ? 1 : 0; + if(socket.m_open && socket.m_socket->IsOpenForReading()) + { + ++count; + } } count += (int) m_streams.size(); @@ -251,7 +255,18 @@ ServerEventChannel::GetClientCount() bool ServerEventChannel::RegisterNewSocket(HTTPMessage* p_message,WebSocket* p_socket,bool p_check /*=false*/) { - AutoCritSec lock(&m_lock); + // Getting the senders URL + Citrix desktop + XString url; + XString sender = SocketToServer(p_message->GetSender()); + XString desktop = p_message->GetHeader("desktop").Trim(); + url.Format("http://%s:c%s", sender.GetString(),desktop.GetString()); + url.MakeLower(); + + // Check for a brute-force attack. If so, already logged to the server + if(m_driver->CheckBruteForceAttack(url)) + { + return false; + } if(p_check) { @@ -283,21 +298,10 @@ ServerEventChannel::RegisterNewSocket(HTTPMessage* p_message,WebSocket* p_socket p_socket->SetOnError (EventChannelOnError); p_socket->SetOnClose (EventChannelOnClose); - // Getting the senders URL + Citrix desktop - XString url; - XString sender = SocketToServer(p_message->GetSender()); - XString desktop = p_message->GetHeader("desktop").Trim(); - url.Format("http://%s:c%s", sender.GetString(),desktop.GetString()); - url.MakeLower(); - - // Check for a brute-force attack. If so, already logged to the server - if (m_driver->CheckBruteForceAttack(url)) - { - return false; - } - // Register our socket + AutoCritSec lock(&m_lock); bool found = false; + for(auto& sock : m_sockets) { if(sock.m_open && sock.m_url.Compare(url) == 0) @@ -338,14 +342,14 @@ ServerEventChannel::OnOpenSocket(WebSocket* p_socket) void ServerEventChannel::OnCloseSocket(WebSocket* p_socket) { - AutoCritSec lock(&m_lock); - - for(auto& sock : m_sockets) + // NO LOCK HERE ON m_sockets + // Just register the new state + for(AllSockets::iterator it = m_sockets.begin(); it != m_sockets.end();++it) { - if(sock.m_socket == p_socket) + if(it->m_socket == p_socket) { - sock.m_open = false; m_closeSeen = true; + return; } } } @@ -543,6 +547,17 @@ ServerEventChannel::SendQueueToSocket() { if(it->m_open == true && (ltevent->m_sent == 0 || ltevent->m_sent == it->m_sender)) { + // See if it was closed by the client side + if(!it->m_socket->IsOpenForWriting()) + { + CloseSocket(it->m_socket); + + AutoCritSec lock(&m_lock); + it = m_sockets.erase(it); + OnClose(""); + continue; + } + // Make sure channel is now open on the server side and 'in-use' if(!m_openSeen) { @@ -552,6 +567,8 @@ ServerEventChannel::SendQueueToSocket() { allok = false; CloseSocket(it->m_socket); + + AutoCritSec lock(&m_lock); it = m_sockets.erase(it); OnClose(""); continue; @@ -677,18 +694,19 @@ ServerEventChannel::CloseChannel() // event channels to the client possibly still open. if(m_closeSeen == false) { - LTEvent* event = new LTEvent(EvtType::EV_Close); - event->m_payload = "Channel closed"; - event->m_number = ++m_maxNumber; - event->m_sent = m_appData; - // Do not come here again m_closeSeen = true; - // Tell it the server + + // Tell it the server application if(m_application) { try { + LTEvent* event = new LTEvent(EvtType::EV_Close); + event->m_payload = "Channel closed"; + event->m_number = ++m_maxNumber; + event->m_sent = m_appData; + (*m_application)(event); } catch(StdException& ex) @@ -731,8 +749,9 @@ void ServerEventChannel::CloseSocket(WebSocket* p_socket) { DETAILLOGV("Closing WebSocket for event channel [%s] Queue size: %d",m_name.GetString(),(int)m_outQueue.size()); - p_socket->SendCloseSocket(WS_CLOSE_NORMAL,"ServerEventDriver is closing channel"); - Sleep(200); // Wait for close before deleting the socket + p_socket->SendCloseSocket(WS_CLOSE_NORMAL,"ServerEventDriver is closing channel"); + Sleep(200); // Wait for close to be sent before deleting the socket + p_socket->CloseSocket(); m_server->UnRegisterWebSocket(p_socket); } @@ -821,7 +840,8 @@ ServerEventChannel::OnError(XString p_message) void ServerEventChannel::OnClose(XString p_message) { - AutoCritSec lock(&m_lock); + // Already locked by ServerEventChannel::CloseChannel() + // AutoCritSec lock(&m_lock); LTEvent* event = new LTEvent; event->m_payload = p_message; @@ -890,3 +910,32 @@ ServerEventChannel::Receiving() } return received; } + +// Sanity check on channel +void +ServerEventChannel::CheckChannel() +{ + // Only check if we do sockets + if(m_current != EventDriverType::EDT_Sockets) + { + return; + } + + // See if we must close sockets + AllSockets::iterator it = m_sockets.begin(); + while(it != m_sockets.end()) + { + // See if it was closed by the client side + if(it->m_open == false || !it->m_socket->IsOpenForWriting()) + { + CloseSocket(it->m_socket); + + AutoCritSec lock(&m_lock); + it = m_sockets.erase(it); + OnClose(""); + continue; + } + // Next socket + ++it; + } +} diff --git a/Marlin/ServerEventChannel.h b/Marlin/ServerEventChannel.h index 9e8e1cb..962d80c 100644 --- a/Marlin/ServerEventChannel.h +++ b/Marlin/ServerEventChannel.h @@ -83,6 +83,8 @@ class ServerEventChannel int SendChannel(); // Process the receiving part of the queue int Receiving(); + // Sanity check on channel + void CheckChannel(); // Post a new event, giving a new event numerator int PostEvent(XString p_payload,XString p_sender,EvtType p_type = EvtType::EV_Message,XString p_typeName = ""); // Flushing a channel directly diff --git a/Marlin/ServerEventDriver.cpp b/Marlin/ServerEventDriver.cpp index 624121e..3306352 100644 --- a/Marlin/ServerEventDriver.cpp +++ b/Marlin/ServerEventDriver.cpp @@ -832,7 +832,7 @@ ServerEventDriver::RecalculateInterval(int p_sent) void ServerEventDriver::SendChannels() { - DETAILLOG1("ServerEventDriver monitor waking up. Sending to client channels."); + DETAILLOG1("ServerEventDriver monitor waking up. Sending/Receiving client channels."); int sent = 0; try @@ -845,6 +845,12 @@ ServerEventDriver::SendChannels() channels = m_channels; } + // Check all channels + for(auto& channel : channels) + { + channel.second->CheckChannel(); + } + // All outbound traffic for(auto& channel : channels) { diff --git a/Marlin/WebConfigIIS.cpp b/Marlin/WebConfigIIS.cpp index fcec073..1ee8516 100644 --- a/Marlin/WebConfigIIS.cpp +++ b/Marlin/WebConfigIIS.cpp @@ -251,6 +251,12 @@ WebConfigIIS::GetSiteError(XString p_site) return IISER_NoError; } +XString +WebConfigIIS::GetWebConfig() +{ + return m_webconfig; +} + XString WebConfigIIS::GetPoolStartMode(XString p_pool) { @@ -570,6 +576,32 @@ WebConfigIIS::ReadSettings(XMLMessage& p_msg) } } +void +WebConfigIIS::ReadWebConfigHandlers(XMLMessage& p_msg) +{ + XMLElement* handlers = p_msg.FindElement("handlers"); + if(handlers) + { + XMLElement* add = p_msg.FindElement(handlers,"add"); + while(add) + { + IISHandler handler; + + XString name = p_msg.GetAttribute(add,"name"); + handler.m_path = p_msg.GetAttribute(add,"path"); + handler.m_verb = p_msg.GetAttribute(add,"verb"); + handler.m_modules = p_msg.GetAttribute(add,"modules"); + handler.m_resourceType = p_msg.GetAttribute(add,"resourceType"); + handler.m_precondition = p_msg.GetAttribute(add,"preCondition"); + + m_webConfigHandlers.insert(std::make_pair(name,handler)); + + // Next handler mapping + add = p_msg.GetElementSibling(add); + } + } +} + void WebConfigIIS::ReadStreamingLimit(XMLMessage& p_msg,XMLElement* p_elem) { @@ -792,6 +824,12 @@ WebConfigIIS::GetHandler(XString p_site,XString p_handler) return nullptr; } +IISHandlers* +WebConfigIIS::GetWebConfigHandlers() +{ + return &m_webConfigHandlers; +} + // Finding a site registration // Site/Subsite -> Finds "site" // Site -> FInds "site" diff --git a/Marlin/WebConfigIIS.h b/Marlin/WebConfigIIS.h index 3dff408..6feb74d 100644 --- a/Marlin/WebConfigIIS.h +++ b/Marlin/WebConfigIIS.h @@ -122,9 +122,11 @@ class WebConfigIIS bool GetSiteNTLMCache(XString p_site,bool p_default); bool GetSitePreload (XString p_site); IISError GetSiteError (XString p_site); + XString GetWebConfig(); IISHandlers* GetAllHandlers (XString p_site); IISHandler* GetHandler (XString p_site,XString p_handler); + IISHandlers* GetWebConfigHandlers(); // Getting information of a application pool XString GetPoolStartMode (XString p_pool); @@ -135,6 +137,7 @@ class WebConfigIIS // Read one config file bool ReadConfig(XString p_configFile,IISSite* p_site); + void ReadWebConfigHandlers(XMLMessage& p_msg); private: // Replace environment variables in a string @@ -159,6 +162,7 @@ class WebConfigIIS // Files already read in WCFiles m_files; + IISHandlers m_webConfigHandlers; AppSettings m_settings; IISPools m_pools; IISSites m_sites; diff --git a/Marlin/WebServiceServer.cpp b/Marlin/WebServiceServer.cpp index dbdae37..0c3f303 100644 --- a/Marlin/WebServiceServer.cpp +++ b/Marlin/WebServiceServer.cpp @@ -612,8 +612,9 @@ WebServiceServer::RunService() return false; } - // Only starting the server if no errors found - if(::GetLastError() == NO_ERROR) + // Only starting the server if no (real) errors found + if(::GetLastError() == NO_ERROR || + ::GetLastError() == ERROR_ALREADY_EXISTS) { // Go run our service!! if(m_httpServer->GetIsRunning() == false) diff --git a/Marlin/WebSocketServerIIS.cpp b/Marlin/WebSocketServerIIS.cpp index 7153f5b..7aab92c 100644 --- a/Marlin/WebSocketServerIIS.cpp +++ b/Marlin/WebSocketServerIIS.cpp @@ -120,7 +120,7 @@ ServerWriteCompletion(HRESULT p_error, // Duties to perform after writing of a fragment is completed void WebSocketServerIIS::SocketWriter(HRESULT p_error - ,DWORD p_bytes + ,DWORD /*p_bytes*/ ,BOOL /*p_utf8*/ ,BOOL /*p_final*/ ,BOOL p_close) @@ -135,7 +135,7 @@ WebSocketServerIIS::SocketWriter(HRESULT p_error m_writing.pop_front(); } } - TRACE("WS BLOCK WRITTEN: %d\n",p_bytes); + // TRACE("WS BLOCK WRITTEN: %d\n",p_bytes); // Handle any error (if any) if(p_error != (HRESULT)0) @@ -258,8 +258,11 @@ ServerReadCompletionIIS(HRESULT p_error, _set_se_translator(SeTranslator); try { + if(!IsBadReadPtr(socket,sizeof(WebSocketServerIIS))) + { socket->SocketReader(p_error,p_bytes,p_utf8,p_final,p_close); } + } catch(StdException& ex) { SvcReportErrorEvent(0,false,__FUNCTION__,"Error reading websocket (%lX,%d,%d,%d,%d) %s",p_error,p_bytes,p_utf8,p_final,p_close,ex.GetErrorMessage().GetString()); @@ -414,7 +417,7 @@ WebSocketServerIIS::SendCloseSocket(USHORT p_code,XString p_reason) LPCWSTR pointer = L""; // Check if already closed - if(m_iis_socket == nullptr) + if(m_iis_socket == nullptr || !m_openWriting) { return true; } @@ -427,7 +430,7 @@ WebSocketServerIIS::SendCloseSocket(USHORT p_code,XString p_reason) // Still other parameters and reason to do BOOL expected = FALSE; XString message; - HRESULT hr = S_FALSE; + HRESULT hr = E_FAIL; try { hr = m_iis_socket->SendConnectionClose(TRUE @@ -440,11 +443,11 @@ WebSocketServerIIS::SendCloseSocket(USHORT p_code,XString p_reason) catch (StdException& ex) { UNREFERENCED_PARAMETER(ex); - hr = S_FALSE; + hr = E_FAIL; } - delete[] buffer; if(FAILED(hr)) { + delete[] buffer; ERRORLOG(ERROR_INVALID_OPERATION,"Cannot send a 'close' message on the WebSocket [" + m_key + "] on [" + m_uri + "] " + message); return false; } @@ -452,6 +455,7 @@ WebSocketServerIIS::SendCloseSocket(USHORT p_code,XString p_reason) { SocketWriter(hr,length,true,true,false); } + delete[] buffer; DETAILLOGV("Sent a 'close' message [%d:%s] on WebSocket [%s] on [%s]",p_code,p_reason.GetString(),m_key.GetString(),m_uri.GetString()); return true; } @@ -585,54 +589,42 @@ WebSocketServerIIS::PostCompletion(DWORD dwErrorCode, DWORD /*dwNumberOfBytes*/) bool WebSocketServerIIS::CloseSocket() { - if(m_iis_socket) + bool closed(false); + + if(m_iis_socket && (m_openReading || m_openWriting)) { - // Make local copies, so we are non-reentrant + // Make local copy, so we are non-reentrant // Canceling I/O can make read/write closing end up here!! - HTTPServer* server = m_server; IWebSocketContext* context = m_iis_socket; m_iis_socket = nullptr; + DETAILLOGV("Closing WebSocket [%s] on [%s]",m_key.GetString(),m_uri.GetString()); + // Try to gracefully close the WebSocket try { context->CancelOutstandingIO(); - context->CloseTcpConnection(); - Yield(); - Sleep(100); - Yield(); - } -#ifndef MARLIN_USE_ATL_ONLY - catch(CException& er) - { - ERRORLOG(12102,MessageFromException(er).GetString()); + Sleep(0); // Yield the thread to IIS } -#endif catch(StdException& ex) { ReThrowSafeException(ex); ERRORLOG(12102,ex.GetErrorMessage().GetString()); } - + // We are no longer open + m_openReading = false; + m_openWriting = false; + closed = true; + } // Cancel the outstanding request altogether - server->CancelRequestStream(m_request,false); - - DETAILLOGV("Closed WebSocket [%s] on [%s]",m_key.GetString(),m_uri.GetString()); - - // Reduce memory, removing reading/writing stacks - Reset(); - - // Now find the IWebSocketContext -// IHttpContext* contextIIS = reinterpret_cast(m_request); -// IHttpContext3* context3 = nullptr; -// HRESULT hr = HttpGetExtendedInterface(g_iisServer,contextIIS,&context3); -// if (SUCCEEDED(hr)) -// { -// context3->PostCompletion(0,OverlappedCompletion,this); -// } - return true; + // Otherwise the global application pool cannot close + if(m_request) + { + m_server->CancelRequestStream(m_request,false); + m_request = NULL; + Sleep(0); // Yield the thread to IIS } - return false; + return closed; } diff --git a/ResultViewer/ResultViewer.vcxproj b/ResultViewer/ResultViewer.vcxproj index fe04b15..d75a75f 100644 --- a/ResultViewer/ResultViewer.vcxproj +++ b/ResultViewer/ResultViewer.vcxproj @@ -61,7 +61,7 @@ _WINDOWS;_DEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFramework\;$(SolutionDir)StyleFramework\Grid\;$(SolutionDir)Marlin\ Async - stdcpplatest + stdcpp20 stdc17 true false @@ -100,7 +100,7 @@ _WINDOWS;NDEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFramework\;$(SolutionDir)StyleFramework\Grid\;$(SolutionDir)Marlin\ Async - stdcpplatest + stdcpp20 stdc17 true false diff --git a/SQLComponents/DDLCreateTable.cpp b/SQLComponents/DDLCreateTable.cpp index 1be5b53..833609d 100644 --- a/SQLComponents/DDLCreateTable.cpp +++ b/SQLComponents/DDLCreateTable.cpp @@ -728,6 +728,7 @@ DDLCreateTable::ReplaceLengthPrecScale(TypeInfo* p_type ,int p_precision ,int p_scale) { + bool max = false; XString params = p_type->m_create_params; // Set in lowercase for replacing @@ -762,6 +763,7 @@ DDLCreateTable::ReplaceLengthPrecScale(TypeInfo* p_type { // SQL-Server does this as a replacement for CLOB params.Replace("max length","max"); + max = true; } if(p_precision > 0) @@ -769,7 +771,7 @@ DDLCreateTable::ReplaceLengthPrecScale(TypeInfo* p_type params.Replace("precision", precision); params.Replace("scale", scale); } - else + else if(!max) { params.Empty(); } diff --git a/SQLComponents/SQLComponents.vcxproj b/SQLComponents/SQLComponents.vcxproj index 63c891b..e99ab66 100644 --- a/SQLComponents/SQLComponents.vcxproj +++ b/SQLComponents/SQLComponents.vcxproj @@ -167,7 +167,7 @@ _MBCS;%(PreprocessorDefinitions) true $(IntDir)$(TargetName).pdb - stdcpplatest + stdcpp20 stdc17 $(SolutionDir)BaseLibrary\ diff --git a/SQLComponents/SQLComponentsVersion.h b/SQLComponents/SQLComponentsVersion.h index 382cf09..85fba23 100644 --- a/SQLComponents/SQLComponentsVersion.h +++ b/SQLComponents/SQLComponentsVersion.h @@ -26,7 +26,7 @@ // General version, date and copyright // of the SQLComponents library -#define SQL_COMPONENTS_VERSION "2.2.0" -#define SQL_COMPONENTS_BINVERSION 2,2,0,0 -#define SQL_COMPONENTS_DATE "19-03-2023" +#define SQL_COMPONENTS_VERSION "2.2.1" +#define SQL_COMPONENTS_BINVERSION 2,2,1,0 +#define SQL_COMPONENTS_DATE "23-04-2023" #define SQL_COMPONENTS_COPYRIGHT "Copyright (c) 2002-2023 ir. W.E. Huisman" diff --git a/SQLComponents/SQLFilter.cpp b/SQLComponents/SQLFilter.cpp index d87f31d..727c656 100644 --- a/SQLComponents/SQLFilter.cpp +++ b/SQLComponents/SQLFilter.cpp @@ -143,6 +143,20 @@ SQLFilter::SetField(XString p_field) return false; } +void +SQLFilter::SetField2(XString p_field2) +{ + int pos = p_field2.Find('@'); + if(pos > 0) + { + m_field2 = p_field2.Mid(pos + 1); + } + else + { + m_field2 = p_field2; + } +} + // Adding an operator (if not yet set) bool SQLFilter::SetOperator(SQLOperator p_oper) diff --git a/SQLComponents/SQLFilter.h b/SQLComponents/SQLFilter.h index 060afbf..973d855 100644 --- a/SQLComponents/SQLFilter.h +++ b/SQLComponents/SQLFilter.h @@ -273,12 +273,6 @@ SQLFilter::GetTimestampPart() return m_extract.m_calcpart; } -inline void -SQLFilter::SetField2(XString p_field2) -{ - m_field2 = p_field2; -} - inline XString SQLFilter::GetField2() { diff --git a/SQLComponents/SQLInfo.cpp b/SQLComponents/SQLInfo.cpp index 64debfe..078d680 100644 --- a/SQLComponents/SQLInfo.cpp +++ b/SQLComponents/SQLInfo.cpp @@ -808,10 +808,10 @@ SQLInfo::GetAttributeInteger(LPCTSTR description,SQLINTEGER attrib) SQLINTEGER cbMax = 0; SQLRETURN nRetCode = SQLGetConnectAttr(m_hdbc - ,attrib - ,(SQLPOINTER)&value + ,attrib + ,(SQLPOINTER)&value ,sizeof(value) - ,&cbMax); + ,&cbMax); if(!m_database->Check(nRetCode)) { XString error; diff --git a/SQLComponents/SQLInfoFirebird.cpp b/SQLComponents/SQLInfoFirebird.cpp index 0480e11..5a3a701 100644 --- a/SQLComponents/SQLInfoFirebird.cpp +++ b/SQLComponents/SQLInfoFirebird.cpp @@ -1949,6 +1949,7 @@ SQLInfoFirebird::GetPSMProcedureList(XString& p_schema) const XString sql1("SELECT '' as catalog_name\n" " ,trim(rdb$owner_name) as schema_name\n" " ,trim(rdb$procedure_name)\n" + " ,1\n" " FROM rdb$procedures pro\n"); if(!p_schema.IsEmpty()) { @@ -1958,6 +1959,7 @@ SQLInfoFirebird::GetPSMProcedureList(XString& p_schema) const XString sql2("SELECT '' as catalog_name\n" " ,trim(rdb$owner_name) as schema_name\n" " ,trim(rdb$function_name)\n" + " ,2\n" " FROM rdb$functions fun\n"); if(!p_schema.IsEmpty()) diff --git a/SQLComponents/SQLInfoMariaDB.cpp b/SQLComponents/SQLInfoMariaDB.cpp index 6dd973e..4291a2c 100644 --- a/SQLComponents/SQLInfoMariaDB.cpp +++ b/SQLComponents/SQLInfoMariaDB.cpp @@ -1756,6 +1756,11 @@ SQLInfoMariaDB::GetPSMProcedureList(XString& p_schema) const sql = "SELECT routine_catalog\n" " ,routine_schema\n" " ,routine_name\n" + " ,CASE routine_type\n" + " WHEN 'PROCEDURE' THEN 1\n" + " WHEN 'FUNCTION' THEN 2\n" + " ELSE 3\n" + " end\n" " FROM information_schema.routines fun\n"; if (!p_schema.IsEmpty()) { diff --git a/SQLComponents/SQLInfoMySQL.cpp b/SQLComponents/SQLInfoMySQL.cpp index ef04d46..17561c5 100644 --- a/SQLComponents/SQLInfoMySQL.cpp +++ b/SQLComponents/SQLInfoMySQL.cpp @@ -1411,6 +1411,11 @@ SQLInfoMySQL::GetPSMProcedureList(XString& p_schema) const sql = "SELECT routine_catalog\n" " ,routine_schema\n" " ,routine_name\n" + " ,CASE routine_type\n" + " WHEN 'PROCEDURE' THEN 1\n" + " WHEN 'FUNCTION' THEN 2\n" + " ELSE 3\n" + " end\n" " FROM information_schema.routines fun\n"; if (!p_schema.IsEmpty()) { diff --git a/SQLComponents/SQLInfoOracle.cpp b/SQLComponents/SQLInfoOracle.cpp index 9fe29c3..def9916 100644 --- a/SQLComponents/SQLInfoOracle.cpp +++ b/SQLComponents/SQLInfoOracle.cpp @@ -2233,6 +2233,11 @@ SQLInfoOracle::GetPSMProcedureList(XString& p_schema) const sql = "SELECT sys_context('USERENV','DB_NAME') AS procedure_catalog\n" " ,owner AS procedure_schema\n" " ,object_name AS procedure_name\n" + " ,CASE object_type \n" + " WHEN 'PROCEDURE' THEN 1\n" + " WHEN 'FUNCTION' THEN 2\n" + " ELSE 3\n" + " END AS procedure_type\n" " FROM all_procedures\n"; if(!p_schema.IsEmpty()) { diff --git a/SQLComponents/SQLInfoSQLServer.cpp b/SQLComponents/SQLInfoSQLServer.cpp index 7d74f76..1ab0f92 100644 --- a/SQLComponents/SQLInfoSQLServer.cpp +++ b/SQLComponents/SQLInfoSQLServer.cpp @@ -727,7 +727,7 @@ SQLInfoSQLServer::GetCATALOGTableAttributes(XString& p_schema,XString& p_tablena " ELSE 'UNKNOWN'\n" " END AS table_type\n" " ,CASE e.name\n" - " WHEN N'MS_Description' THEN CAST (e.value AS VARCHAR(Max))\n" + " WHEN N'MS_Description' THEN CAST (e.value AS VARCHAR(4000))\n" " ELSE ''\n" " END AS remarks\n" " ,null AS tablespace\n" @@ -2312,7 +2312,7 @@ SQLInfoSQLServer::GetCATALOGSequencePrivilege(XString& p_schema,XString& p_seque "SELECT db_name() AS sequence_catalog\n" " ,s.name AS sequence_schema\n" " ,q.name AS sequence_name\n" - " ,pg.name AS grantor\n" + " ,p.permission_name AS privilege\n" " ,pe.name AS grantee\n" " ,CASE p.state\n" " WHEN 'G' THEN 'NO'\n" @@ -2479,11 +2479,10 @@ SQLInfoSQLServer::GetPSMProcedureList(XString& p_schema) const "SELECT db_name() as catalog_name\n" " ,s.name as schema_name\n" " ,o.name as procedure_name\n" - " ,CASE type\n" - " WHEN 'P' THEN 1\n" - " WHEN 'FN' THEN 2\n" - " ELSE 3\n" - " END as procedure_type\n" + " ,CASE o.type\n" + " WHEN 'P' THEN 1\n" + " ELSE 2\n" + " END as procedure_type\n" " FROM sys.objects o\n" " INNER JOIN sys.schemas s ON o.schema_id = s.schema_id\n" " WHERE type IN ('P','FN')\n"; diff --git a/SQLComponents/SQLQuery.cpp b/SQLComponents/SQLQuery.cpp index bf6fe87..1f6f53c 100644 --- a/SQLComponents/SQLQuery.cpp +++ b/SQLComponents/SQLQuery.cpp @@ -102,6 +102,7 @@ SQLQuery::Init(SQLDatabase* p_database) m_speedThreshold = QUERY_TOO_LONG; m_connection = NULL; m_concurrency = SQL_CONCUR_READ_ONLY; + m_lengthOption = LOption::LO_LEN_ZERO; } void @@ -646,8 +647,16 @@ SQLQuery::DoSQLStatement(const XString& p_statement) // by a missing NULL-Terminator. By changing the length of the statement // _including_ the terminating NUL, it won't crash at all // NOTE: This also means we cannot use the SQL_NTS terminator - SQLINTEGER lengthStatement = statement.GetLength() + 1; - + SQLINTEGER lengthStatement(SQL_NTS); + switch(m_lengthOption) + { + case LOption::LO_NTS: lengthStatement = SQL_NTS; + break; + case LOption::LO_LENGTH: lengthStatement = statement.GetLength(); + break; + case LOption::LO_LEN_ZERO:lengthStatement = statement.GetLength() + 1; + break; + } // GO DO IT RIGHT AWAY m_retCode = SqlExecDirect(m_hstmt,(SQLCHAR*)statement.GetString(),lengthStatement); @@ -861,8 +870,16 @@ SQLQuery::DoSQLPrepare(const XString& p_statement) // in the processing of the query-strings which crashes it in CharNexW // by a missing NUL-Terminator. By changing the length of the statement // _including_ the terminating NUL, it won't crash at all - SQLINTEGER lengthStatement = statement.GetLength() + 1; - + SQLINTEGER lengthStatement(SQL_NTS); + switch(m_lengthOption) + { + case LOption::LO_NTS: lengthStatement = SQL_NTS; + break; + case LOption::LO_LENGTH: lengthStatement = statement.GetLength(); + break; + case LOption::LO_LEN_ZERO:lengthStatement = statement.GetLength() + 1; + break; + } // GO DO THE PREPARE m_retCode = SqlPrepare(m_hstmt,(SQLCHAR*)(LPCSTR)statement,lengthStatement); if(SQL_SUCCEEDED(m_retCode)) @@ -1206,15 +1223,9 @@ SQLQuery::BindColumns() // NOW WE HAVE ALL INFORMATION // TO BEGIN THE BINDING PROCES - for(auto& column : m_numMap) { - // Bind columns up to the first 'long' column - if(m_hasLongColumns && column.first >= m_hasLongColumns) - { - break; - } - SQLVariant* var = column.second; + SQLVariant* var = column.second; SQLUSMALLINT bcol = (SQLUSMALLINT) var->GetColumnNumber(); SQLSMALLINT type = (SQLSMALLINT) var->GetDataType(); SQLLEN size = var->GetDataSize(); @@ -1222,24 +1233,41 @@ SQLQuery::BindColumns() // Rebind the column datatype type = RebindColumn(type); - m_retCode = SQLBindCol(m_hstmt // statement handle - ,bcol // Column number - ,type // Data type - ,var->GetDataPointer() // Data pointer - ,size // Buffer length - ,var->GetIndicatorPointer() // Indicator address - ); - if(!SQL_SUCCEEDED(m_retCode)) + // Bind columns up to the first long column + if(m_hasLongColumns == 0 || bcol < m_hasLongColumns) { - GetLastError("Cannot bind to column. Error: "); - m_lastError.AppendFormat(" Column number: %d",icol); - throw StdException(m_lastError); + m_retCode = SQLBindCol(m_hstmt // statement handle + ,bcol // Column number + ,type // Data type + ,var->GetDataPointer() // Data pointer + ,size // Buffer length + ,var->GetIndicatorPointer() // Indicator address + ); + if(!SQL_SUCCEEDED(m_retCode)) + { + GetLastError("Cannot bind to column. Error: "); + m_lastError.AppendFormat(" Column number: %d",icol); + throw StdException(m_lastError); + } + // Now do the SQL_NUMERIC precision/scale binding + if(type == SQL_C_NUMERIC) + { + BindColumnNumeric((SQLSMALLINT)bcol,var,SQL_RESULT_COL); + } } - - // Now do the SQL_NUMERIC precision/scale binding - if(type == SQL_C_NUMERIC) + else { - BindColumnNumeric((SQLSMALLINT)bcol,var,SQL_RESULT_COL); + if(type == SQL_C_NUMERIC && + m_database && ((m_database->GetSQLInfoDB()->GetGetDataExtensions() & SQL_GD_BOUND) == 0) && + var->GetNumericScale() > 0 && + !(var->GetNumericPrecision() == 38 && var->GetNumericScale() == 16)) + { + // Cannot get a NUMERIC with decimals after a At-Exec column, + // because we cannot bind the precision and scale + m_lastError = "Cannot retrieve a NUMERIC after a (binary)large object."; + m_lastError.AppendFormat(" Column: %d",bcol); + throw StdException(m_lastError); + } } } } @@ -1587,8 +1615,8 @@ SQLQuery::RetrieveAtExecData() ,(SQLLEN*) var->GetIndicatorPointer()); if(!SQL_SUCCEEDED(m_retCode)) { - // SQL_ERROR / SQL_NO_DATA / SQL_STILL_EXECUTING / SQL_INVALID_HANDLE - return m_retCode; + // SQL_ERROR / SQL_NO_DATA / SQL_STILL_EXECUTING / SQL_INVALID_HANDLE + return m_retCode; } } return SQL_SUCCESS; diff --git a/SQLComponents/SQLQuery.h b/SQLComponents/SQLQuery.h index 5c70d95..eb2a562 100644 --- a/SQLComponents/SQLQuery.h +++ b/SQLComponents/SQLQuery.h @@ -54,6 +54,14 @@ typedef std::map ColNameMap; typedef std::map VarMap; typedef std::map MaxSizeMap; +// Length option for SQLPrepare SQLExecDirect +enum class LOption +{ + LO_NTS = 1 + ,LO_LENGTH = 2 + ,LO_LEN_ZERO = 3 +}; + class SQLQuery { public: @@ -89,6 +97,8 @@ class SQLQuery void SetNoScan(bool p_noscan = false); // Setting the fetching policy void SetFetchPolicy(bool p_policy); + // Setting the length option + void SetLengthOption(LOption p_option = LOption::LO_LEN_ZERO); // Set parameters for statement void SetParameter (int p_num,SQLVariant* p_param,SQLParamType p_type = P_SQL_PARAM_INPUT); @@ -197,6 +207,8 @@ class SQLQuery HSTMT GetStatementHandle(); // Getting the 'noscan' setting bool GetNoScan(); + // LengthOption for SQLPrepare/SQLExecDirect + LOption GetLengthOption(); // Getting the results of the query as a SQLVariant reference SQLVariant& operator[](int p_index); @@ -262,6 +274,7 @@ class SQLQuery SQLDatabase* m_database; // Database HDBC m_connection; // In CTOR connection handle. HSTMT m_hstmt; // Statement handle + LOption m_lengthOption; // Statementlength at SQLPrepare/SQLExecDirect RETCODE m_retCode; // last SQL (error)code XString m_lastError; // last error string int m_maxColumnLength; // Max length @@ -376,5 +389,18 @@ SQLQuery::SetFetchPolicy(bool p_policy) } } +inline LOption +SQLQuery::GetLengthOption() +{ + return m_lengthOption; +} + +// Setting the length option +inline void +SQLQuery::SetLengthOption(LOption p_option /*= LOption::LO_LEN_ZERO*/) +{ + m_lengthOption = p_option; +} + // End of namespace } \ No newline at end of file diff --git a/SQLComponents/SQLVariant.cpp b/SQLComponents/SQLVariant.cpp index a203ffd..94051f8 100644 --- a/SQLComponents/SQLVariant.cpp +++ b/SQLComponents/SQLVariant.cpp @@ -2288,12 +2288,12 @@ SQLVariant::SetSizeIndicator(bool p_realSize,bool p_binary) { if(p_binary) { - // Special ODBC macro to set the data size + // Special ODBC macro to set the data size // SQL_BINARY // SQL_LONGVARCHAR // SQL_LONGVARBINARY // SQL_WLONGVARCHAR - m_indicator = (SQLLEN) SQL_LEN_DATA_AT_EXEC(size); + m_indicator = (SQLLEN) SQL_LEN_DATA_AT_EXEC(size); } else { diff --git a/StepEditor/StepDatabaseDlg.cpp b/StepEditor/StepDatabaseDlg.cpp index f1f3f8b..7b62ce3 100644 --- a/StepEditor/StepDatabaseDlg.cpp +++ b/StepEditor/StepDatabaseDlg.cpp @@ -107,6 +107,7 @@ BEGIN_MESSAGE_MAP(StepDatabaseDlg, StyleDialog) ON_COMMAND(ID_MENU_THEMEMODERATE, OnStyleModerateGray) ON_COMMAND(ID_MENU_THEMEPURE, OnStylePureGray) ON_COMMAND(ID_MENU_THEMEBLACK, OnStyleBlackWhite) + ON_COMMAND(ID_MENU_THEMEDARK, OnStyleDark) ON_COMMAND(ID_MENU_ABOUT, OnAbout) ON_COMMAND(ID_MENU_EXIT, OnExit) END_MESSAGE_MAP() diff --git a/StepEditor/StepEditor.vcxproj b/StepEditor/StepEditor.vcxproj index 1ef1220..7104b38 100644 --- a/StepEditor/StepEditor.vcxproj +++ b/StepEditor/StepEditor.vcxproj @@ -62,7 +62,7 @@ _WINDOWS;_DEBUG;%(PreprocessorDefinitions) Async $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFrameWork\;$(SolutionDir)StyleFrameWork\Grid\;$(SolutionDir)Marlin\;$(SolutionDir)SQLComponents\;%(AdditionalIncludeDirectories) - stdcpplatest + stdcpp20 stdc17 true false @@ -101,7 +101,7 @@ _WINDOWS;NDEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFrameWork\;$(SolutionDir)StyleFrameWork\Grid\;$(SolutionDir)Marlin\;$(SolutionDir)SQLComponents\;%(AdditionalIncludeDirectories) Async - stdcpplatest + stdcpp20 stdc17 true false diff --git a/StepEditor/StepEditorDlg.cpp b/StepEditor/StepEditorDlg.cpp index 7460ea15410e1dde9bc1fd082655edbeb4c85107..fc944e0e2160844ada56c046b86c2dbcafd94ec3 100644 GIT binary patch delta 42 xcmX@{m2ty2#tl~TlkdrLvAZxhG6XSrPhKk{4`e4Y6ftB^c9b>TY$e|p4FEmM4NCw3 delta 14 Wcmdn+jq$`+#tl~Tn-|IVMFRjh76!}! diff --git a/StepEditor/StepInternetDlg.cpp b/StepEditor/StepInternetDlg.cpp index 3b53008..ceea55b 100644 --- a/StepEditor/StepInternetDlg.cpp +++ b/StepEditor/StepInternetDlg.cpp @@ -124,6 +124,7 @@ BEGIN_MESSAGE_MAP(StepInternetDlg, StyleDialog) ON_COMMAND(ID_MENU_THEMEMODERATE, OnStyleModerateGray) ON_COMMAND(ID_MENU_THEMEPURE, OnStylePureGray) ON_COMMAND(ID_MENU_THEMEBLACK, OnStyleBlackWhite) + ON_COMMAND(ID_MENU_THEMEDARK, OnStyleDark) ON_COMMAND(ID_MENU_ABOUT, OnAbout) ON_COMMAND(ID_MENU_EXIT, OnExit) END_MESSAGE_MAP() diff --git a/StepEditor/resource.h b/StepEditor/resource.h index 9f22b01c50e30b071c17df323d4a52ead653ef69..f150433cc13fc45fe8d5858b9957af3400cce2cc 100644 GIT binary patch delta 67 zcmV-J0KEUap#hqr0iaEj%t{IeL;yhmQUFVl@gI{gO)ip6IFqIv8k4|E43l0>IFsN{ Z7Lzb65VO2ZA_9}t0T`1$PZX01P_j8)7xMrB delta 45 zcmbQWh;h$C#s$8U<9sb9TllI?-UFnU$;eGMQ0Lez<)_3n`IE22SetMultiSelect(m_multiselect = true); } - m_listControl->CreateEx(styleEx,"LISTBOX","",style,rect,GetDesktopWindow(),0); + m_listControl->CreateEx(styleEx,"LISTBOX","",style,rect,CWnd::FromHandle(::GetDesktopWindow()),0); m_listControl->InitSkin(); if(m_listControl->GetSkin()) { @@ -1047,7 +1047,10 @@ StyleComboBox::OnSetFocus(CWnd* pOldWnd) if(!rect.PtInRect(here)) { // We are not on the combobox button - m_itemControl->SetFocus(); + if(m_itemControl) + { + m_itemControl->SetFocus(); + } } } diff --git a/StyleFramework/StyleDialog.cpp b/StyleFramework/StyleDialog.cpp index cd71ef6..3753647 100644 --- a/StyleFramework/StyleDialog.cpp +++ b/StyleFramework/StyleDialog.cpp @@ -53,6 +53,9 @@ StyleDialog::StyleDialog(UINT p_IDTemplate { LoadStyleTheme(); } + // Load the default icon + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + // Set default background brush m_defaultBrush.DeleteObject(); m_defaultBrush.CreateSolidBrush(ThemeColor::GetColor(Colors::ColorWindowFrame)); } @@ -75,6 +78,7 @@ BEGIN_MESSAGE_MAP(StyleDialog,CDialog) ON_WM_ACTIVATE() ON_WM_ACTIVATEAPP() ON_WM_SETTINGCHANGE() + ON_WM_QUERYDRAGICON() ON_REGISTERED_MESSAGE(g_msg_changed,OnStyleChanged) ON_NOTIFY_EX(TTN_NEEDTEXT,0, OnToolTipNotify) ON_MESSAGE(WM_CTLCOLORSTATIC, OnCtlColorStatic) @@ -537,6 +541,8 @@ StyleDialog::LoadStyleTheme() ThemeColor::Themes theme = (ThemeColor::Themes)th; ThemeColor::SetTheme(theme); + SendMessageToAllChildWindows(g_msg_changed,0,0); + // Needed for coloring backgrounds of the controls m_defaultBrush.DeleteObject(); m_defaultBrush.CreateSolidBrush(ThemeColor::GetColor(Colors::ColorWindowFrame)); @@ -551,6 +557,24 @@ StyleDialog::LoadStyleTheme() } } +BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam) +{ + PSMessage psMessage = (PSMessage) lParam; + SendMessage(hwnd,psMessage->MessageId,psMessage->wParam,psMessage->lParam); + return TRUE; +} + +void +StyleDialog::SendMessageToAllChildWindows(UINT MessageId,WPARAM wParam,LPARAM lParam) +{ + SMessage sMessage; + sMessage.MessageId = MessageId; + sMessage.wParam = wParam; + sMessage.lParam = lParam; + + EnumChildWindows(GetSafeHwnd(),EnumChildProc,(LPARAM) &sMessage); +} + // After setting of a theme, // we invalidate all themed controls void @@ -650,10 +674,13 @@ StyleDialog::OnCtlColorListBox(WPARAM wParam, LPARAM lParam) void StyleDialog::OnActivate(UINT nState,CWnd* pWndOther,BOOL bMinimized) { - CDialog::OnActivate(nState,pWndOther,bMinimized); - if(nState == WA_ACTIVE || nState == WA_CLICKACTIVE) + if(m_canActivate) { - ReDrawFrame(); + if(nState == WA_ACTIVE || nState == WA_CLICKACTIVE) + { + CDialog::OnActivate(nState,pWndOther,bMinimized); + ReDrawFrame(); + } } } @@ -742,6 +769,10 @@ StyleDialog::OnNcLButtonDown(UINT nFlags, CPoint point) CDialog::OnNcLButtonDown(nFlags, point); return; } + + // Remove gripper. We could resize the dialog + EraseGripper(); + switch (nFlags) { case HTCLOSE: @@ -816,10 +847,11 @@ StyleDialog::OnNcLButtonDown(UINT nFlags, CPoint point) SetWindowPos(&CWnd::wndTop,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW|SWP_NOSENDCHANGING|SWP_DRAWFRAME); CPoint lastpoint(point); + CPoint moved(point); CRect window; GetWindowRect(window); - point.x -= window.left; - point.y -= window.top; + moved.x -= window.left; + moved.y -= window.top; while (LBUTTONDOWN) { @@ -827,10 +859,11 @@ StyleDialog::OnNcLButtonDown(UINT nFlags, CPoint point) GetCursorPos(&cursor); if (cursor != lastpoint) { - SetWindowPos(NULL, cursor.x - point.x, cursor.y - point.y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); + SetWindowPos(NULL, cursor.x - moved.x, cursor.y - moved.y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); lastpoint = cursor; } } + return; } break; @@ -883,7 +916,6 @@ StyleDialog::OnNcLButtonDown(UINT nFlags, CPoint point) if(window.Width() >= m_originalSize.Width() && window.Height() >= m_originalSize.Height()) { - SetWindowPos(nullptr, window.left, window.top, window.Width(), window.Height(), SWP_NOZORDER | SWP_DRAWFRAME); } lastpoint = cursor; @@ -1423,9 +1455,28 @@ StyleDialog::Button(CDC* pDC, CRect rect, LRESULT type,StyleDialog::BUTTONSTATE void StyleDialog::OnPaint() { + if(IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND,reinterpret_cast(dc.GetSafeHdc()),0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x,y,m_hIcon); + return; + } + CDialog::OnPaint(); - if(m_canResize && (GetParent() == nullptr)) + if(m_canResize && (GetParent() == nullptr) && (GetStyle() & WS_MAXIMIZE) == 0) { CDC* pDC = GetDC(); CRect rect; @@ -1437,6 +1488,30 @@ StyleDialog::OnPaint() } } +// Remove gripper before resizing the dialog +void +StyleDialog::EraseGripper() +{ + if(m_canResize && (GetParent() == nullptr) && (GetStyle() & WS_MAXIMIZE) == 0) + { + CDC* pDC = GetDC(); + CRect rect; + GetClientRect(rect); + rect.left = rect.right - ::GetSystemMetrics(SM_CXHSCROLL); + rect.top = rect.bottom - ::GetSystemMetrics(SM_CYVSCROLL); + pDC->FillRect(rect,&m_defaultBrush); + ReleaseDC(pDC); + } +} + +// The system calls this function to obtain the cursor to display while the user drags the minimized window. +// Normally generated by the MFC Wizard +HCURSOR +StyleDialog::OnQueryDragIcon() +{ + return static_cast(m_hIcon); +} + // Try overridden OnClosing before canceling the dialog void StyleDialog::OnCancel() diff --git a/StyleFramework/StyleDialog.h b/StyleFramework/StyleDialog.h index fade17b..8e2c821 100644 --- a/StyleFramework/StyleDialog.h +++ b/StyleFramework/StyleDialog.h @@ -20,14 +20,21 @@ #include class StyleComboBox; +class AutoBlockActivation; using ToolTips = std::map; +typedef struct +{ + UINT MessageId; + WPARAM wParam; + LPARAM lParam; +} +SMessage, *PSMessage; + class StyleDialog : public CDialog { DECLARE_DYNAMIC(StyleDialog) - DECLARE_MESSAGE_MAP(); - public: enum BUTTONSTATE { @@ -69,6 +76,8 @@ class StyleDialog : public CDialog void RegisterTooltip(StyleComboBox& p_wnd,const char* p_text); protected: + friend AutoBlockActivation; + virtual BOOL PreTranslateMessage(MSG* p_msg) override; virtual INT_PTR OnToolHitTest(CPoint point,TOOLINFO* pTI) const override; @@ -78,8 +87,12 @@ class StyleDialog : public CDialog void DrawButton(CDC* pDC,CRect rect,LRESULT type); void PositionButtons(); void Button(CDC* pDC, CRect rect, LRESULT type, BUTTONSTATE state = BS_NORMAL, bool max = true); + void SendMessageToAllChildWindows(UINT MessageId,WPARAM wParam,LPARAM lParam); void PerformMenu(); void InitStatusBar(); + void EraseGripper(); + + DECLARE_MESSAGE_MAP(); // Message handlers afx_msg int OnCreate(LPCREATESTRUCT p_create); @@ -103,6 +116,7 @@ class StyleDialog : public CDialog afx_msg LRESULT OnStyleChanged(WPARAM wParam,LPARAM lParam); afx_msg void OnSettingChange(UINT uFlags,LPCTSTR lpszSection); afx_msg BOOL OnToolTipNotify(UINT id,NMHDR* pNMHDR,LRESULT* pResult); + afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnPaint(); afx_msg void OnOK() override; @@ -136,6 +150,7 @@ class StyleDialog : public CDialog bool m_maxButton { false }; bool m_canResize { false }; bool m_hasStatus { false }; + bool m_canActivate { true }; LRESULT m_curhit { HTNOWHERE }; UINT m_sysmenu { NULL }; // Objects @@ -145,3 +160,19 @@ class StyleDialog : public CDialog CBrush m_defaultBrush; ToolTips m_tooltips; }; + +class AutoBlockActivation +{ +public: + AutoBlockActivation(StyleDialog* p_dialog) : m_dialog(p_dialog) + { + m_dialog->m_canActivate = false; + } + ~AutoBlockActivation() + { + m_dialog->m_canActivate = true; + } + +private: + StyleDialog* m_dialog; +}; diff --git a/StyleFramework/StyleDialogCA.h b/StyleFramework/StyleDialogCA.h index 972be2e..6dce21e 100644 --- a/StyleFramework/StyleDialogCA.h +++ b/StyleFramework/StyleDialogCA.h @@ -21,6 +21,7 @@ // #pragma once #include +#include "GrayWindow.h" class StyleDialogCA: public CDialogEx { diff --git a/StyleFramework/StyleFonts.h b/StyleFramework/StyleFonts.h index 3422db1..da8cce5 100644 --- a/StyleFramework/StyleFonts.h +++ b/StyleFramework/StyleFonts.h @@ -30,6 +30,8 @@ #define ERRORFONTSIZE 12 // MulDiv(12, 96, StyleFonts::logpixelsy()) #define CAPTIONTEXTSIZE 18 // MulDiv(18, 96, StyleFonts::logpixelsy()) +#define LISTBOX_ITEMHEIGTH 16 // For StyleListBox + #define CaptionFontString StyleFontName + CString(";") + IntegerToString(CAPTIONTEXTSIZE) + CString(";") + IntegerToString(FW_BOLD) #define DialogFontString StyleFontName + CString(";") + IntegerToString(STANDARDFONTSIZE) + CString(";") + IntegerToString(FW_NORMAL) #define DialogFontBoldString StyleFontName + CString(";") + IntegerToString(STANDARDFONTSIZE) + CString(";") + IntegerToString(FW_HEAVY) diff --git a/StyleFramework/StyleFrameWndEx.cpp b/StyleFramework/StyleFrameWndEx.cpp index 438a5b8..9d0e103 100644 --- a/StyleFramework/StyleFrameWndEx.cpp +++ b/StyleFramework/StyleFrameWndEx.cpp @@ -508,7 +508,7 @@ void StyleFrameWndEx::OnSize(UINT nType, int cx, int cy) int border = 0; if ((GetStyle() & WS_MAXIMIZE) != 0) { - // Bij een volledig scherm gebruikt het OS deze marge buiten beeld!! + // On a full screen the OS uses this margin outside of view CSize marge = afxGlobalUtils.GetSystemBorders(GetStyle()); m_windowRectLocal.left += marge.cx; @@ -605,30 +605,28 @@ StyleFrameWndEx::MenuFromPoint(CPoint p_point) void StyleFrameWndEx::OnNcCalcSize(BOOL calcValidRects,NCCALCSIZE_PARAMS* p_params) { - int baseMargin = MARGIN; - p_params->rgrc[0].top += CAPTIONHEIGHT; - if(GetStyle() & WS_MAXIMIZE) { - // In full-screen mode, the MS-Windows OS uses this extra margin - CSize marge = afxGlobalUtils.GetSystemBorders(GetStyle()); - - p_params->rgrc[0].left += marge.cx; - p_params->rgrc[0].top += marge.cy; - p_params->rgrc[0].right -= marge.cx; - p_params->rgrc[0].bottom -= marge.cy; + CRect area; + StyleGetWorkArea(this,area); + area.top += CAPTIONHEIGHT; + p_params->rgrc[0] = area; } else { // The baseMargin is needed for two things: // 1) To activate the left/right/bottom mouse pulling action // 2) To provide space for a painted border around the window - p_params->rgrc[0].left += baseMargin; - p_params->rgrc[0].right -= baseMargin; - p_params->rgrc[0].bottom -= baseMargin; + p_params->rgrc[0].top += CAPTIONHEIGHT; + p_params->rgrc[0].left += MARGIN; + p_params->rgrc[0].right -= MARGIN; + p_params->rgrc[0].bottom -= MARGIN; } // Use same rectangle for displacement (so hide it) - p_params->rgrc[2] = p_params->rgrc[0]; + if(calcValidRects) + { + p_params->rgrc[2] = p_params->rgrc[0]; + } } // Avoid flicker of the titlebar on activate diff --git a/StyleFramework/StyleListBox.cpp b/StyleFramework/StyleListBox.cpp index 91bb8ed..052d389 100644 --- a/StyleFramework/StyleListBox.cpp +++ b/StyleFramework/StyleListBox.cpp @@ -45,6 +45,7 @@ StyleListBox::StyleListBox() StyleListBox::~StyleListBox() { + RemoveLineInfo(); ResetSkin(); OnNcDestroy(); } @@ -83,7 +84,7 @@ StyleListBox::InitSkin(int p_borderSize /*=1*/,int p_clientBias /*=0*/) if(m_skin == nullptr) { SetFont(&STYLEFONTS.DialogTextFont); - int height = GetItemHeight(0); + int height = LISTBOX_ITEMHEIGTH; SetItemHeight(0,(height * GetSFXSizeFactor()) / 100); m_skin = SkinWndScroll(this,p_borderSize,p_clientBias); @@ -277,7 +278,7 @@ StyleListBox::DrawItem(LPDRAWITEMSTRUCT p_drawItemStruct) COLORREF foreground(FRAME_DEFAULT_COLOR); COLORREF background(FRAME_DEFAULT_COLOR); // Getting our foreground/background colors - ListBoxColorLine* line = reinterpret_cast(GetItemDataPtr(p_drawItemStruct->itemID)); + ListBoxColorLine* line = reinterpret_cast(p_drawItemStruct->itemData); if(!line || (line == (ListBoxColorLine*)LB_ERR) || line->m_magic != LIST_MAGIC) { return; @@ -424,8 +425,8 @@ StyleListBox::AdjustHorizontalExtent() { CClientDC dc(this); - CFont* f = CListBox::GetFont(); - dc.SelectObject(f); + CFont* f = GetFont(); + CFont* o = dc.SelectObject(f); m_width = 0; for(int i = 0; i < CListBox::GetCount(); i++) @@ -443,6 +444,7 @@ StyleListBox::AdjustHorizontalExtent() } CListBox::SetHorizontalExtent(m_width); AdjustScroll(); + dc.SelectObject(o); } void @@ -763,7 +765,7 @@ StyleListBox::UpdateWidth(LPCTSTR p_string) { CClientDC dc(this); - CFont* font = CListBox::GetFont(); + CFont* font = GetFont(); CFont* oldfont = dc.SelectObject(font); // Guard against really long strings like SOAP messages @@ -822,8 +824,18 @@ StyleListBox::RemoveLineNumber(CString& p_text) void StyleListBox::RemoveLineInfo() { + // See if we have lines left + int nCount = 0; + if(::IsWindow(GetSafeHwnd())) + { + nCount = GetCount(); + } + else + { + // Nothing to do + return; + } // Remove our text and color content - int nCount = GetCount(); for(int index = 0;index < nCount;index++) { ListBoxColorLine* line = reinterpret_cast(GetItemDataPtr(index)); @@ -897,7 +909,7 @@ StyleListBox::Internal_Paint(CDC* p_cdc) int top_item = GetTopIndex(); int focus_item = GetCaretIndex(); - HFONT oldFont = (HFONT) p_cdc->SelectObject(&STYLEFONTS.DialogTextFont); + HFONT oldFont = (HFONT) p_cdc->SelectObject(GetFont()); for(int index = top_item; index < items; index++) { @@ -947,106 +959,61 @@ StyleListBox::Internal_Paint(CDC* p_cdc) void StyleListBox::Internal_PaintItem(CDC* p_cdc,const RECT* rect,INT index,UINT action,BOOL ignoreFocus) { - const char* item_str = NULL; - int nb_items = GetCount(); - int style = GetStyle(); + int nb_items = GetCount(); + bool selected = GetSel(index); + bool focused = GetCaretIndex() == index; + bool enabled = IsWindowEnabled(); - ListBoxColorLine* line = reinterpret_cast(GetItemDataPtr(index)); - if(line && (line != (ListBoxColorLine*) LB_ERR) && line->m_magic == LIST_MAGIC) - { - item_str = line->m_text.GetString(); - } - else - { - TRACE("called with an out of bounds index %d (Total: %d) in owner draw, Not good.\n",index,GetCount()); - return; - } - BOOL selected = GetSel(index); - BOOL focused = GetCaretIndex() == index; + ListBoxColorLine line; + ListBoxColorLine* line_ptr = &line; + DRAWITEMSTRUCT dis; if(GetStyle() & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE)) { - DRAWITEMSTRUCT dis; - RECT r; - if (index >= nb_items) + line_ptr = reinterpret_cast(GetItemDataPtr(index)); + if(!line_ptr || (line_ptr == (ListBoxColorLine*) LB_ERR) || line_ptr->m_magic != LIST_MAGIC) { - if(action == ODA_FOCUS) - { - p_cdc->DrawFocusRect(rect); - } - else - { - TRACE("called with an out of bounds index %d(%d) in owner draw, Not good.\n", index, nb_items); - } + TRACE("called with an out of bounds index %d (Total: %d) in owner draw, Not good.\n",index,GetCount()); return; } - - GetClientRect(&r); - - dis.CtlType = ODT_LISTBOX; - dis.CtlID = (UINT)GetWindowLongPtrW(GetSafeHwnd(), GWLP_ID); - dis.hwndItem = GetSafeHwnd(); - dis.itemAction = action; - dis.hDC = p_cdc->GetSafeHdc(); - dis.itemID = index; - dis.itemState = 0; - if (selected) dis.itemState |= ODS_SELECTED; - if (focused) dis.itemState |= ODS_FOCUS; - if (!IsWindowEnabled()) dis.itemState |= ODS_DISABLED; - dis.itemData = (ULONG_PTR) GetItemDataPtr(index); - dis.rcItem = *rect; - // TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",GetSafeHwnd(),index,item_str, action, dis.itemState, DebugRect(rect)); - - // This is the reason we are here! - // Standard MS-Windows sends this to the parent of the control so CMenu can have a go at it - // But the standard desktop does NOT reflect the MEASUREITEM/DRAWITEM messages - // So we send it directly to ourselves!!!!!! - // - // SendMessage(GetParent()->GetSafeHwnd(),WM_DRAWITEM,(WPARAM)dis.CtlID,(LPARAM)&dis); - SendMessage(WM_DRAWITEM,(WPARAM)dis.CtlID,(LPARAM)&dis); } else { - COLORREF oldText = 0, oldBk = 0; + // Get the text from the control + CListBox::GetText(index,line.m_text); + } - if (action == ODA_FOCUS) + if (index >= nb_items) + { + if(action == ODA_FOCUS) { p_cdc->DrawFocusRect(rect); - return; - } - if (selected) - { - oldBk = p_cdc->SetBkColor (GetSysColor(COLOR_HIGHLIGHT)); - oldText = p_cdc->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT)); - } - - TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",GetSafeHwnd(),index,item_str,action,DebugRect(rect)); - if(!item_str) - { - p_cdc->ExtTextOut(rect->left + 1, rect->top, ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL); - } - else if(style & LBS_USETABSTOPS) - { - int nb_tabs = 0; - int* tabs = nullptr; - - /* Output empty string to paint background in the full width. */ - p_cdc->ExtTextOut (rect->left + 1, rect->top,ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL); - p_cdc->TabbedTextOut(rect->left + 1, rect->top, item_str, lstrlen(item_str),nb_tabs,tabs,0); } else { - p_cdc->ExtTextOut(rect->left + 1, rect->top, ETO_OPAQUE | ETO_CLIPPED, rect, item_str, (UINT)strlen(item_str), NULL); - } - if(selected) - { - p_cdc->SetBkColor(oldBk); - p_cdc->SetTextColor(oldText); - } - if(focused) - { - p_cdc->DrawFocusRect(rect); + TRACE("called with an out of bounds index %d(%d) in owner draw, Not good.\n", index, nb_items); } + return; } + dis.CtlType = ODT_LISTBOX; + dis.CtlID = (UINT)GetWindowLongPtrW(GetSafeHwnd(), GWLP_ID); + dis.hwndItem = GetSafeHwnd(); + dis.itemAction = action; + dis.hDC = p_cdc->GetSafeHdc(); + dis.itemID = index; + dis.itemState = 0; + if (selected) dis.itemState |= ODS_SELECTED; + if (focused) dis.itemState |= ODS_FOCUS; + if (!enabled) dis.itemState |= ODS_DISABLED; + dis.itemData = (ULONG_PTR) line_ptr; + dis.rcItem = *rect; + // TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",GetSafeHwnd(),index,item_str, action, dis.itemState, DebugRect(rect)); + + // This is the reason we are here! + // Standard MS-Windows sends this to the parent of the control so CMenu can have a go at it + // But the standard desktop does NOT reflect the MEASUREITEM/DRAWITEM messages + // So we send it directly to ourselves!!!!!! + // + SendMessage(WM_DRAWITEM,(WPARAM)dis.CtlID,(LPARAM)&dis); } diff --git a/StyleFramework/StyleListCtrl.cpp b/StyleFramework/StyleListCtrl.cpp index 31223ff..3998722 100644 --- a/StyleFramework/StyleListCtrl.cpp +++ b/StyleFramework/StyleListCtrl.cpp @@ -29,8 +29,171 @@ static char THIS_FILE[] = __FILE__; using namespace ThemeColor; -///////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// +// StyleHeaderCtrl +// +IMPLEMENT_DYNAMIC(StyleHeaderCtrl,CMFCHeaderCtrl); + +StyleHeaderCtrl::StyleHeaderCtrl() +{ +} + +StyleHeaderCtrl::~StyleHeaderCtrl() +{ +} + +void +StyleHeaderCtrl::OnDrawItem(CDC* pDC,int iItem,CRect rect,BOOL bIsPressed,BOOL bIsHighlighted) +{ + int bgColor = ThemeColor::GetColor(Colors::ColorButtonBackground); + int txColor = ThemeColor::GetColor(Colors::ColorButtonText); + if(bIsHighlighted) + { + bgColor = ThemeColor::GetColor(Colors::ColorControlHover); + txColor = ThemeColor::GetColor(Colors::ColorControlTextHover); + } + else if(bIsPressed) + { + bgColor = ThemeColor::GetColor(Colors::ColorControlPressed); + txColor = RGB(0,0,0); + } + + pDC->FillSolidRect(rect,bgColor); + // CMFCHeaderCtrl::OnDrawItem(pDC,iItem,rect,bIsPressed,bIsHighlighted); + + ASSERT_VALID(this); + ASSERT_VALID(pDC); + + const int nTextMargin = 5; + + if(iItem < 0) + { + return; + } + + int nSortVal = 0; + if(m_mapColumnsStatus.Lookup(iItem,nSortVal) && nSortVal != 0) + { + // Draw sort arrow: + CRect rectArrow = rect; + rectArrow.DeflateRect(5,5); + rectArrow.left = rectArrow.right - rectArrow.Height(); + + if(bIsPressed) + { + rectArrow.right++; + rectArrow.bottom++; + } + + rect.right = rectArrow.left - 1; + + int dy2 = (int) (.134 * rectArrow.Width()); + rectArrow.DeflateRect(0,dy2); + + m_bAscending = nSortVal > 0; + OnDrawSortArrow(pDC,rectArrow); + } + + HD_ITEM hdItem; + memset(&hdItem,0,sizeof(hdItem)); + hdItem.mask = HDI_FORMAT | HDI_BITMAP | HDI_TEXT | HDI_IMAGE; + + TCHAR szText[256]; + hdItem.pszText = szText; + hdItem.cchTextMax = 255; + + if(!GetItem(iItem,&hdItem)) + { + return; + } + + // Draw bitmap and image: + if((hdItem.fmt & HDF_IMAGE) && hdItem.iImage >= 0) + { + // The column has a image from imagelist: + CImageList* pImageList = GetImageList(); + if(pImageList != NULL) + { + int cx = 0; + int cy = 0; + + VERIFY(::ImageList_GetIconSize(*pImageList,&cx,&cy)); + + CPoint pt = rect.TopLeft(); + pt.x++; + pt.y = (rect.top + rect.bottom - cy) / 2; + + VERIFY(pImageList->Draw(pDC,hdItem.iImage,pt,ILD_NORMAL)); + + rect.left += cx; + } + } + + if((hdItem.fmt & (HDF_BITMAP | HDF_BITMAP_ON_RIGHT)) && hdItem.hbm != NULL) + { + CBitmap* pBmp = CBitmap::FromHandle(hdItem.hbm); + ASSERT_VALID(pBmp); + + BITMAP bmp; + pBmp->GetBitmap(&bmp); + + CRect rectBitmap = rect; + if(hdItem.fmt & HDF_BITMAP_ON_RIGHT) + { + rectBitmap.right--; + rect.right = rectBitmap.left = rectBitmap.right - bmp.bmWidth; + } + else + { + rectBitmap.left++; + rect.left = rectBitmap.right = rectBitmap.left + bmp.bmWidth; + } + + rectBitmap.top += max(0,(rectBitmap.Height() - bmp.bmHeight) / 2); + rectBitmap.bottom = rectBitmap.top + bmp.bmHeight; + + pDC->DrawState(rectBitmap.TopLeft(),rectBitmap.Size(),pBmp,DSS_NORMAL); + } + + // Draw text: + if((hdItem.fmt & HDF_STRING) && hdItem.pszText != NULL) + { + CRect rectLabel = rect; + rectLabel.DeflateRect(nTextMargin,0); + + CString strLabel = hdItem.pszText; + + UINT uiTextFlags = DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX; + if(hdItem.fmt & HDF_CENTER) + { + uiTextFlags |= DT_CENTER; + } + else if(hdItem.fmt & HDF_RIGHT) + { + uiTextFlags |= DT_RIGHT; + } + + pDC->SetTextColor(txColor); + pDC->DrawText(strLabel,rectLabel,uiTextFlags); + } +} + +void +StyleHeaderCtrl::OnFillBackground(CDC* pDC) +{ + CRect client; + GetClientRect(client); + int bkcolor = RGB(0,0,0); // ThemeColor::GetColor(Colors::ColorButtonBackground); + pDC->SetBkColor(bkcolor); + pDC->FillSolidRect(client,bkcolor); +} + +////////////////////////////////////////////////////////////////////////// +// // StyleListCtrl +// +IMPLEMENT_DYNAMIC(StyleListCtrl,CMFCListCtrl); StyleListCtrl::StyleListCtrl() { @@ -46,26 +209,57 @@ BEGIN_MESSAGE_MAP(StyleListCtrl,CMFCListCtrl) ON_WM_CREATE() ON_WM_PAINT() ON_WM_SIZE() + ON_WM_ERASEBKGND() ON_WM_SHOWWINDOW() END_MESSAGE_MAP() void StyleListCtrl::PreSubclassWindow() { + CMFCListCtrl::PreSubclassWindow(); ScaleControl(this); - if(m_directInit) { InitSkin(); } } +CMFCHeaderCtrl& +StyleListCtrl::GetHeaderCtrl() +{ + return m_styleHeader; +} + void StyleListCtrl::SetDirectInit(bool p_init) { m_directInit = p_init; } +// Default string comparison sort +int +StyleListCtrl::OnCompareItems(LPARAM lParam1,LPARAM lParam2,int iColumn) +{ + int lower = (int)(lParam1 < lParam2 ? lParam1 : lParam2); + int upper = (int)(lParam1 < lParam2 ? lParam2 : lParam1); + CString first = GetItemText(lower,iColumn); + CString second = GetItemText(upper,iColumn); + + return first.CompareNoCase(second); +} + +COLORREF +StyleListCtrl::OnGetCellTextColor(int p_row,int p_colum) +{ + return ThemeColor::GetColor(Colors::ColorEditText); +} + +COLORREF +StyleListCtrl::OnGetCellBkColor(int p_row,int p_colum) +{ + return ThemeColor::GetColor(Colors::ColorEditBkgnd); +} + ///////////////////////////////////////////////////////////////////////////// // StyleListCtrl message handlers @@ -155,24 +349,13 @@ StyleListCtrl::DrawFrame() } } -void -StyleListCtrl::CheckColors() +BOOL +StyleListCtrl::OnEraseBkgnd(CDC* pDC) { - int background = ThemeColor::GetColor(Colors::ColorCtrlBackground); - int textcolor = ThemeColor::GetColor(Colors::ColorEditText); - - if(GetBkColor() != background) - { - SetBkColor(background); - } - if(GetTextBkColor() != background) - { - SetTextBkColor(background); - } - if(GetTextColor() != textcolor) - { - SetTextColor(textcolor); - } + CRect client; + GetClientRect(client); + pDC->FillSolidRect(client,ThemeColor::GetColor(Colors::ColorCtrlBackground)); + return TRUE; } void @@ -181,7 +364,6 @@ StyleListCtrl::OnPaint() if(!m_inPaint) { m_inPaint = true; - CheckColors(); CMFCListCtrl::OnPaint(); DrawFrame(); m_inPaint = false; @@ -210,4 +392,3 @@ StyleListCtrl::OnShowWindow(BOOL bShow, UINT nStatus) CMFCListCtrl::OnShowWindow(bShow, nStatus); } } - diff --git a/StyleFramework/StyleListCtrl.h b/StyleFramework/StyleListCtrl.h index a4d715d..185ee45 100644 --- a/StyleFramework/StyleListCtrl.h +++ b/StyleFramework/StyleListCtrl.h @@ -20,9 +20,30 @@ #include "SkinScrollWnd.h" #include +////////////////////////////////////////////////////////////////////////// +// +// StyleHeaderCtrl +// +class StyleHeaderCtrl: public CMFCHeaderCtrl +{ + DECLARE_DYNAMIC(StyleHeaderCtrl) +public: + StyleHeaderCtrl(); + virtual ~StyleHeaderCtrl(); + + virtual void OnDrawItem(CDC* pDC,int iItem,CRect rect,BOOL bIsPressed,BOOL bIsHighlighted) override; + virtual void OnFillBackground(CDC* pDC) override; +}; + +////////////////////////////////////////////////////////////////////////// +// +// StyleListCtrl +// class StyleListCtrl : public CMFCListCtrl { -// Construction + DECLARE_DYNAMIC(StyleListCtrl) + + // Construction public: StyleListCtrl(); virtual ~StyleListCtrl(); @@ -33,16 +54,26 @@ class StyleListCtrl : public CMFCListCtrl SkinScrollWnd* GetSkin(); void SetDirectInit(bool p_init); + // Override if you want anything other than string comparisons! + // Beware: You must use the "SetItemData(item,)" to trigger this function + virtual int OnCompareItems(LPARAM lParam1,LPARAM lParam2,int iColumn) override; + + virtual CMFCHeaderCtrl& GetHeaderCtrl() override; + virtual COLORREF OnGetCellTextColor(int p_row,int p_colum) override; + virtual COLORREF OnGetCellBkColor (int p_row,int p_colum) override; + protected: + StyleHeaderCtrl m_styleHeader; + bool m_inPaint { false }; bool m_directInit { true }; virtual LRESULT WindowProc(UINT message,WPARAM wParam,LPARAM lParam) override; virtual void PreSubclassWindow() override; - void CheckColors(); afx_msg void OnPaint(); afx_msg void OnSize(UINT nType,int cx,int cy); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnShowWindow(BOOL bShow, UINT nStatus); DECLARE_MESSAGE_MAP() }; diff --git a/StyleFramework/StyleMDIFrameWnd.cpp b/StyleFramework/StyleMDIFrameWnd.cpp index a760fa7..f290ce4 100644 --- a/StyleFramework/StyleMDIFrameWnd.cpp +++ b/StyleFramework/StyleMDIFrameWnd.cpp @@ -515,30 +515,28 @@ void StyleMDIFrameWnd::OnSize(UINT nType, int cx, int cy) void StyleMDIFrameWnd::OnNcCalcSize(BOOL calcValidRects,NCCALCSIZE_PARAMS* p_params) { - int baseMargin = MARGIN; - p_params->rgrc[0].top += WINCAPTIONHEIGHT; - if(GetStyle() & WS_MAXIMIZE) { - // In full-screen mode, the MS-Windows OS uses this extra margin - CSize margin = afxGlobalUtils.GetSystemBorders(GetStyle()); - - p_params->rgrc[0].left += margin.cx; - p_params->rgrc[0].top += margin.cy; - p_params->rgrc[0].right -= margin.cx; - p_params->rgrc[0].bottom -= margin.cy; + CRect area; + StyleGetWorkArea(this,area); + area.top += WINCAPTIONHEIGHT; + p_params->rgrc[0] = area; } else { // The baseMargin is needed for two things: // 1) To activate the left/right/bottom mouse pulling action // 2) To provide space for a painted border around the window - p_params->rgrc[0].left += baseMargin; - p_params->rgrc[0].right -= baseMargin; - p_params->rgrc[0].bottom -= baseMargin; + p_params->rgrc[0].top += WINCAPTIONHEIGHT; + p_params->rgrc[0].left += MARGIN; + p_params->rgrc[0].right -= MARGIN; + p_params->rgrc[0].bottom -= MARGIN; } // Use same rectangle for displacement (so hide it) - p_params->rgrc[2] = p_params->rgrc[0]; + if(calcValidRects) + { + p_params->rgrc[2] = p_params->rgrc[0]; + } } // Avoid flicker of the title bar on activate diff --git a/StyleFramework/StyleMessageBox.cpp b/StyleFramework/StyleMessageBox.cpp index 34df3d6..9824a8f 100644 --- a/StyleFramework/StyleMessageBox.cpp +++ b/StyleFramework/StyleMessageBox.cpp @@ -41,10 +41,15 @@ INT_PTR StyleMessageBox(CWnd* p_parent ,LPCSTR p_message ,LPCSTR p_title - ,long p_styles /*= MB_OK*/ - ,bool* p_doNotShowAgain /*= nullptr*/) + ,long p_styles /*= MB_OK */ + ,bool* p_doNotShowAgain /*= nullptr */ + ,bool p_foreground /*= false */) { MessageDialog dlg(p_parent,p_title,p_message,p_styles,p_doNotShowAgain); + if(p_foreground) + { + dlg.SetForeground(); + } return dlg.GetResultID(dlg.DoModal()); } @@ -52,9 +57,9 @@ INT_PTR StyleMessageBox(CWnd* p_parent ,LPCSTR p_message ,LPCSTR p_title - ,LPCSTR p_labels /*= "ok"*/ - ,bool* p_doNotShowAgain /*= nullptr*/ - ,bool p_foreground /*= false*/) + ,LPCSTR p_labels /*= "ok" */ + ,bool* p_doNotShowAgain /*= nullptr */ + ,bool p_foreground /*= false */) { MessageDialog dlg(p_parent,p_title,p_message,p_labels,p_doNotShowAgain); if(p_foreground) diff --git a/StyleFramework/StyleMessageBox.h b/StyleFramework/StyleMessageBox.h index ebee3c9..f5a7a07 100644 --- a/StyleFramework/StyleMessageBox.h +++ b/StyleFramework/StyleMessageBox.h @@ -31,7 +31,7 @@ // OUTSIDE WORLD INTERFACE -INT_PTR StyleMessageBox(CWnd* p_parent,LPCSTR p_message,LPCSTR p_title,long p_styles = MB_OK,bool* p_doNotShowAgain = nullptr); +INT_PTR StyleMessageBox(CWnd* p_parent,LPCSTR p_message,LPCSTR p_title,long p_styles = MB_OK,bool* p_doNotShowAgain = nullptr,bool p_foreground = false); INT_PTR StyleMessageBox(CWnd* p_parent,LPCSTR p_message,LPCSTR p_title,LPCSTR p_labels , bool* p_doNotShowAgain = nullptr,bool p_foreground = false); diff --git a/StyleFramework/StyleTab.cpp b/StyleFramework/StyleTab.cpp index 44ae969..b8c261c 100644 --- a/StyleFramework/StyleTab.cpp +++ b/StyleFramework/StyleTab.cpp @@ -18,6 +18,7 @@ // #include "stdafx.h" #include "StyleTab.h" +#include "RegistryManager.h" #ifdef _DEBUG #define new DEBUG_NEW @@ -32,6 +33,7 @@ IMPLEMENT_DYNAMIC(StyleTab,CDialog); BEGIN_MESSAGE_MAP(StyleTab,CDialog) ON_WM_ERASEBKGND() ON_WM_CTLCOLOR() + ON_REGISTERED_MESSAGE(g_msg_changed,OnStyleChanged) ON_NOTIFY_EX(TTN_NEEDTEXT,0, OnToolTipNotify) ON_MESSAGE(WM_CTLCOLORSTATIC, OnCtlColorStatic) ON_MESSAGE(WM_CTLCOLORLISTBOX,OnCtlColorListBox) @@ -48,6 +50,14 @@ StyleTab::~StyleTab() { } +BOOL +StyleTab::OnInitDialog() +{ + CDialog::OnInitDialog(); + OnStyleChanged(0,0); + return TRUE; +} + void StyleTab::SetCanResize(bool p_resize) { @@ -86,6 +96,28 @@ StyleTab::SetupDynamicLayout() } } +// Another program has changed our styling +LRESULT +StyleTab::OnStyleChanged(WPARAM,LPARAM) +{ + RegistryManager manager; + int th = manager.GetRegistryInteger(STYLECOLORS_KEY,STYLECOLORS_THEME,(int) ThemeColor::Themes::ThemeSkyblue); + ThemeColor::Themes theme = (ThemeColor::Themes) th; + ThemeColor::SetTheme(theme); + + // Needed for coloring backgrounds of the controls + m_defaultBrush.DeleteObject(); + m_defaultBrush.CreateSolidBrush(ThemeColor::GetColor(Colors::ColorWindowFrame)); + + // Now repaint ourselves and all of our children + if(GetSafeHwnd()) + { + Invalidate(); + RedrawWindow(NULL,NULL,RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + } + return 0; // AOK +} + // Catch the ESC key, so the tab will always stay visible BOOL StyleTab::PreTranslateMessage(MSG* p_msg) diff --git a/StyleFramework/StyleTab.h b/StyleFramework/StyleTab.h index 09fce37..d4f8c0f 100644 --- a/StyleFramework/StyleTab.h +++ b/StyleFramework/StyleTab.h @@ -30,6 +30,7 @@ class StyleTab: public CDialog protected: virtual BOOL PreTranslateMessage(MSG* p_msg) override; + virtual BOOL OnInitDialog() override; virtual void SetupDynamicLayout(); void RegisterTooltip(int p_ID,const char* p_text); @@ -51,6 +52,7 @@ class StyleTab: public CDialog afx_msg LPARAM OnCtlColorStatic (WPARAM wParam,LPARAM lParam); afx_msg LPARAM OnCtlColorListBox(WPARAM wParam,LPARAM lParam); afx_msg HBRUSH OnCtlColor(CDC* pDC,CWnd* pWnd,UINT nCtlColor); + afx_msg LRESULT OnStyleChanged(WPARAM,LPARAM); afx_msg void OnCancel(); afx_msg void OnOK(); }; \ No newline at end of file diff --git a/StyleFramework/WinToastLib.cpp b/StyleFramework/WinToastLib.cpp index df53558..608fc05 100644 --- a/StyleFramework/WinToastLib.cpp +++ b/StyleFramework/WinToastLib.cpp @@ -740,7 +740,7 @@ HRESULT WinToast::createShellLinkHelper() return hr; } -INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error) +INT64 WinToast::ShowToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error) { setError(error, WinToastError::NoError); INT64 id = -1; @@ -807,12 +807,12 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan { toast_type = ToastTemplateType(toast.type()); } - HRESULT hr = notificationManager->GetTemplateContent(toast_type, &xmlDocument); + hr = notificationManager->GetTemplateContent(toast_type, &xmlDocument); if (SUCCEEDED(hr) && toast.isToastGeneric()) hr = setBindToastGenericHelper(xmlDocument.Get()); if (SUCCEEDED(hr)) { - for (std::size_t i = 0, fieldsCount = toast.textFieldsCount(); i < fieldsCount && SUCCEEDED(hr); i++) + for (std::size_t i = 0, fieldsCount = toast.TextFieldsCount(); i < fieldsCount && SUCCEEDED(hr); i++) { hr = setTextFieldHelper(xmlDocument.Get(), toast.textField(WinToastTemplate::TextField(i)), (UINT32)i); } @@ -829,7 +829,7 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan } std::array buf; - for (std::size_t i = 0, actionsCount = toast.actionsCount(); i < actionsCount && SUCCEEDED(hr); i++) + for (std::size_t i = 0, actionsCount = toast.ActionsCount(); i < actionsCount && SUCCEEDED(hr); i++) { _snwprintf_s(buf.data(), buf.size(), _TRUNCATE, L"%zd", i); hr = addActionHelper(xmlDocument.Get(), toast.actionLabel(i), buf.data()); @@ -857,8 +857,8 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan { bool isWin10AnniversaryOrAbove = WinToast::isWin10AnniversaryOrHigher(); bool isCircleCropHint = isWin10AnniversaryOrAbove ? toast.isCropHintCircle() : false; - hr = toast.hasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath(), toast.isToastGeneric(), isCircleCropHint) : hr; - if (SUCCEEDED(hr) && isWin10AnniversaryOrAbove && toast.hasHeroImage()) + hr = toast.HasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath(), toast.isToastGeneric(), isCircleCropHint) : hr; + if (SUCCEEDED(hr) && isWin10AnniversaryOrAbove && toast.HasHeroImage()) hr = setHeroImageHelper(xmlDocument.Get(), toast.heroImagePath(), toast.isInlineHeroImage()); if (SUCCEEDED(hr)) { @@ -925,7 +925,7 @@ ComPtr WinToast::notifier(_In_ bool* succeded) const return notifier; } -bool WinToast::hideToast(_In_ INT64 id) +bool WinToast::HideToast(_In_ INT64 id) { if (!isInitialized()) { @@ -1331,31 +1331,35 @@ WinToastTemplate::~WinToastTemplate() _textFields.clear(); } -void WinToastTemplate::setTextField(_In_ const std::wstring& txt, _In_ WinToastTemplate::TextField pos) +void WinToastTemplate::SetTextField(_In_ const std::string& txt, _In_ WinToastTemplate::TextField pos) { + std::wstring text = m_converter.from_bytes(txt.c_str()); const auto position = static_cast(pos); assert(position < _textFields.size()); - _textFields[position] = txt; + _textFields[position] = text; } -void WinToastTemplate::setImagePath(_In_ const std::wstring& imgPath, _In_ CropHint cropHint) +void WinToastTemplate::SetImagePath(_In_ const std::string& imgPath, _In_ CropHint cropHint) { - _imagePath = imgPath; - _cropHint = cropHint; + std::wstring path = m_converter.from_bytes(imgPath.c_str()); + _imagePath = path; + _cropHint = cropHint; } -void WinToastTemplate::setHeroImagePath(_In_ const std::wstring& imgPath, bool inlineImage) +void WinToastTemplate::SetHeroImagePath(_In_ const std::string& imgPath, bool inlineImage) { - _heroImagePath = imgPath; + std::wstring path = m_converter.from_bytes(imgPath.c_str()); + _heroImagePath = path; _inlineHeroImage = inlineImage; } -void WinToastTemplate::setAudioPath(_In_ const std::wstring& audioPath) +void WinToastTemplate::SetAudioPath(_In_ const std::string& audioPath) { - _audioPath = audioPath; + std::wstring path = m_converter.from_bytes(audioPath.c_str()); + _audioPath = path; } -void WinToastTemplate::setAudioPath(_In_ AudioSystemFile file) +void WinToastTemplate::SetAudioPath(_In_ AudioSystemFile file) { static const std::unordered_map Files = { @@ -1391,57 +1395,59 @@ void WinToastTemplate::setAudioPath(_In_ AudioSystemFile file) _audioPath = iter->second; } -void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOption) +void WinToastTemplate::SetAudioOption(_In_ WinToastTemplate::AudioOption audioOption) { _audioOption = audioOption; } -void WinToastTemplate::setFirstLine(const std::wstring &text) +void WinToastTemplate::SetFirstLine(const std::string &text) { - setTextField(text, WinToastTemplate::FirstLine); + SetTextField(text, WinToastTemplate::FirstLine); } -void WinToastTemplate::setSecondLine(const std::wstring &text) +void WinToastTemplate::SetSecondLine(const std::string &text) { - setTextField(text, WinToastTemplate::SecondLine); + SetTextField(text, WinToastTemplate::SecondLine); } -void WinToastTemplate::setThirdLine(const std::wstring &text) +void WinToastTemplate::SetThirdLine(const std::string &text) { - setTextField(text, WinToastTemplate::ThirdLine); + SetTextField(text, WinToastTemplate::ThirdLine); } -void WinToastTemplate::setDuration(_In_ Duration duration) +void WinToastTemplate::SetDuration(_In_ Duration duration) { _duration = duration; } -void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) +void WinToastTemplate::SetExpiration(_In_ INT64 millisecondsFromNow) { _expiration = millisecondsFromNow; } -void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) +void WinToastTemplate::SetAttributionText(_In_ const std::string& attributionText) { - _attributionText = attributionText; + std::wstring text = m_converter.from_bytes(attributionText.c_str()); + _attributionText = text; } -void WinToastTemplate::addAction(_In_ const std::wstring & label) +void WinToastTemplate::AddAction(_In_ const std::string & label) { - _actions.push_back(label); + std::wstring lbl = m_converter.from_bytes(label.c_str()); + _actions.push_back(lbl); } -std::size_t WinToastTemplate::textFieldsCount() const +std::size_t WinToastTemplate::TextFieldsCount() const { return _textFields.size(); } -std::size_t WinToastTemplate::actionsCount() const +std::size_t WinToastTemplate::ActionsCount() const { return _actions.size(); } -bool WinToastTemplate::hasImage() const +bool WinToastTemplate::HasImage() const { return (_type < WinToastTemplateType::Text01 || _type == WinToastTemplate::HeroImageAndImageAndText01 @@ -1450,7 +1456,7 @@ bool WinToastTemplate::hasImage() const || _type == WinToastTemplate::HeroImageAndImageAndText04); } -bool WinToastTemplate::hasHeroImage() const +bool WinToastTemplate::HasHeroImage() const { return (_type == WinToastTemplate::HeroImageAndText01 || _type == WinToastTemplate::HeroImageAndText02 diff --git a/StyleFramework/WinToastLib.h b/StyleFramework/WinToastLib.h index 0711349..987239e 100644 --- a/StyleFramework/WinToastLib.h +++ b/StyleFramework/WinToastLib.h @@ -36,16 +36,18 @@ #include #include #include +#include #include #include +#pragma warning(disable: 4996) + using namespace Microsoft::WRL; using namespace ABI::Windows::Data::Xml::Dom; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Notifications; using namespace Windows::Foundation; - namespace WinToastLib { @@ -128,24 +130,24 @@ class WinToastTemplate WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02); ~WinToastTemplate(); - void setFirstLine (_In_ const std::wstring& text); - void setSecondLine (_In_ const std::wstring& text); - void setThirdLine (_In_ const std::wstring& text); - void setTextField (_In_ const std::wstring& txt, _In_ TextField pos); - void setAttributionText (_In_ const std::wstring & attributionText); - void setImagePath (_In_ const std::wstring& imgPath, _In_ CropHint cropHint = CropHint::Square); - void setHeroImagePath (_In_ const std::wstring& imgPath, bool inlineImage); - void setAudioPath (_In_ WinToastTemplate::AudioSystemFile audio); - void setAudioPath (_In_ const std::wstring& audioPath); - void setAudioOption (_In_ WinToastTemplate::AudioOption audioOption); - void setDuration (_In_ Duration duration); - void setExpiration (_In_ INT64 millisecondsFromNow); - void addAction (_In_ const std::wstring& label); + void SetFirstLine (_In_ const std::string& text); + void SetSecondLine (_In_ const std::string& text); + void SetThirdLine (_In_ const std::string& text); + void SetTextField (_In_ const std::string& txt, _In_ TextField pos); + void SetAttributionText (_In_ const std::string & attributionText); + void SetImagePath (_In_ const std::string& imgPath, _In_ CropHint cropHint = CropHint::Square); + void SetHeroImagePath (_In_ const std::string& imgPath, bool inlineImage); + void SetAudioPath (_In_ WinToastTemplate::AudioSystemFile audio); + void SetAudioPath (_In_ const std::string& audioPath); + void SetAudioOption (_In_ WinToastTemplate::AudioOption audioOption); + void SetDuration (_In_ Duration duration); + void SetExpiration (_In_ INT64 millisecondsFromNow); + void AddAction (_In_ const std::string& label); - std::size_t textFieldsCount() const; - std::size_t actionsCount() const; - bool hasImage() const; - bool hasHeroImage() const; + std::size_t TextFieldsCount() const; + std::size_t ActionsCount() const; + bool HasImage() const; + bool HasHeroImage() const; const std::vector& textFields() const; const std::wstring& textField(_In_ TextField pos) const; const std::wstring& actionLabel(_In_ std::size_t pos) const; @@ -173,6 +175,9 @@ class WinToastTemplate WinToastTemplateType _type{WinToastTemplateType::Text01}; Duration _duration{Duration::System}; CropHint _cropHint{CropHint::Square}; + + // UTF-8 to UTF-16 converter + std::wstring_convert> m_converter; }; class WinToast @@ -216,8 +221,8 @@ class WinToast static const std::wstring& strerror(_In_ WinToastError error); virtual bool initialize(_Out_ WinToastError* error = nullptr); virtual bool isInitialized() const; - virtual bool hideToast(_In_ INT64 id); - virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error = nullptr); + virtual bool HideToast(_In_ INT64 id); + virtual INT64 ShowToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error = nullptr); virtual void clear(); virtual enum ShortcutResult createShortcut(); diff --git a/SuiteLibrary/SuiteLibrary.rc b/SuiteLibrary/SuiteLibrary.rc index bc131ed533eb4865190b1e45ae49f43524daa909..5766d2d5081d001c462c79a793344aeb85393e4c 100644 GIT binary patch delta 46 zcmX>Ve<5MRBh|?U%6yzI42cXy4A~4ylOL+7PtH@8Vs~M1WC&vLo?NJwxLHPR9VY;5 Chz=0| delta 16 YcmcbRa4LSoBh|@r>Q_WINDOWS;_DEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFrameWork\;$(SolutionDir)StyleFrameWork\Grid\;$(SolutionDir)Marlin\;%(AdditionalIncludeDirectories) Async - stdcpplatest + stdcpp20 stdc17 true false @@ -92,7 +92,7 @@ _WINDOWS;NDEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFrameWork\;$(SolutionDir)StyleFrameWork\Grid\;$(SolutionDir)Marlin\;%(AdditionalIncludeDirectories) Async - stdcpplatest + stdcpp20 stdc17 true false diff --git a/TestEditor/TestEditorDlg.cpp b/TestEditor/TestEditorDlg.cpp index 248d5533360999828e7b977c77e544e69fe993bb..7b01ec079ce9960078136f9595c469a8dd51bd4a 100644 GIT binary patch delta 48 zcmV-00MGxX#{`bZ1hDWRlMEmV1w;Tr08*2Y9TStl83dC~9|{LV0AT=f0BeK4Ah*9D G0p|jViVtc4 delta 18 acmeA>!LsZ)%Z3jM%@K;*BNQ38F#!NjZwGMz diff --git a/TestEditor/resource.h b/TestEditor/resource.h index 66f1e411e93c06614b310e4bfb62ab015469e211..37a96b32703f05bb1e620d657cff1e4288d09d52 100644 GIT binary patch delta 71 zcmV-N0J#6kNT^A$N+FXlAPNUW06_p!085eaACq7p2$O;#CX-4bIFsNZ29vl26qDc} d43nrKIFoK82D9cNh5?h*0T`3uBNUUEBeGu$8A$*D delta 44 zcmV+{0Mq}dNy_WINDOWS;_DEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML;$(SolutionDir)StyleFramework\;$(SolutionDir)StyleFramework\Grid\;$(SolutionDir)Marlin\;$(SolutionDir)SQLComponents\;%(AdditionalIncludeDirectories) Async - stdcpplatest + stdcpp20 stdc17 true false @@ -90,7 +90,7 @@ true false _WINDOWS;NDEBUG;%(PreprocessorDefinitions) - stdcpplatest + stdcpp20 stdc17 $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary\;$(SolutionDir)SuiteLibrary\XML;$(SolutionDir)StyleFramework\;$(SolutionDir)StyleFramework\Grid\;$(SolutionDir)Marlin\;$(SolutionDir)SQLComponents\;%(AdditionalIncludeDirectories) Async diff --git a/ValidateEditor/ValidateDatabaseDlg.cpp b/ValidateEditor/ValidateDatabaseDlg.cpp index 21b28ea..e78fdf6 100644 --- a/ValidateEditor/ValidateDatabaseDlg.cpp +++ b/ValidateEditor/ValidateDatabaseDlg.cpp @@ -95,6 +95,7 @@ BEGIN_MESSAGE_MAP(ValidateDatabaseDlg,StyleDialog) ON_COMMAND(ID_MENU_THEMEMODERATE, OnStyleModerateGray) ON_COMMAND(ID_MENU_THEMEPURE, OnStylePureGray) ON_COMMAND(ID_MENU_THEMEBLACK, OnStyleBlackWhite) + ON_COMMAND(ID_MENU_THEMEDARK, OnStyleDark) ON_COMMAND(ID_MENU_ABOUT, OnAbout) ON_COMMAND(ID_MENU_EXIT, OnExit) END_MESSAGE_MAP() diff --git a/ValidateEditor/ValidateEditor.vcxproj b/ValidateEditor/ValidateEditor.vcxproj index d66579a..17559d4 100644 --- a/ValidateEditor/ValidateEditor.vcxproj +++ b/ValidateEditor/ValidateEditor.vcxproj @@ -62,7 +62,7 @@ _WINDOWS;_DEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFramework;$(SolutionDir)StyleFramework\Grid\;$(SolutionDir)Marlin\ Async - stdcpplatest + stdcpp20 stdc17 true false @@ -101,7 +101,7 @@ _WINDOWS;NDEBUG;%(PreprocessorDefinitions) $(SolutionDir)BaseLibrary\;$(SolutionDir)SuiteLibrary;$(SolutionDir)SuiteLibrary\XML\;$(SolutionDir)StyleFramework;$(SolutionDir)StyleFramework\Grid\;$(SolutionDir)Marlin\ Async - stdcpplatest + stdcpp20 stdc17 true false diff --git a/ValidateEditor/ValidateEditorDlg.cpp b/ValidateEditor/ValidateEditorDlg.cpp index 7e2d649b172d5ef0391f6a9f9d23fee1e0810f89..789422703294df7a1ba41aaa18791e6973626c0f 100644 GIT binary patch delta 44 zcmaDikMY`k#tjh?lP8FAvAQrgG6YQ)6cC<#N0g78MTC)3so<*qpB0%{ZA&S7P!SJ&DOm`s)BUUJgqD