Merge branch 'html-dataobj-mem-fix'
Fix problems flagged by ASAN in clipboard/data object code and improve wxMSW HTML data object. See #23661.
This commit is contained in:
commit
4cd8869218
3 changed files with 180 additions and 58 deletions
|
|
@ -429,6 +429,141 @@ bool wxTextDataObject::SetData(size_t len, const void *buf)
|
||||||
// wxHTMLDataObject
|
// wxHTMLDataObject
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef __WXMSW__
|
||||||
|
|
||||||
|
// Helper functions for MSW CF_HTML format, see MSDN for more information:
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
|
||||||
|
namespace wxMSWClip
|
||||||
|
{
|
||||||
|
|
||||||
|
const char* const VERSION_HEADER = "Version:";
|
||||||
|
const size_t VERSION_HEADER_LEN = strlen(VERSION_HEADER);
|
||||||
|
|
||||||
|
const char* const START_HTML_HEADER = "StartHTML:";
|
||||||
|
const size_t START_HTML_HEADER_LEN = strlen(START_HTML_HEADER);
|
||||||
|
|
||||||
|
const char* const END_HTML_HEADER = "EndHTML:";
|
||||||
|
const size_t END_HTML_HEADER_LEN = strlen(END_HTML_HEADER);
|
||||||
|
|
||||||
|
const char* const START_FRAGMENT_HEADER = "StartFragment:";
|
||||||
|
const size_t START_FRAGMENT_HEADER_LEN = strlen(START_FRAGMENT_HEADER);
|
||||||
|
|
||||||
|
const char* const END_FRAGMENT_HEADER = "EndFragment:";
|
||||||
|
const size_t END_FRAGMENT_HEADER_LEN = strlen(END_FRAGMENT_HEADER);
|
||||||
|
|
||||||
|
const char* const CF_HTML_PREAMBLE =
|
||||||
|
"Version:0.9\r\n"
|
||||||
|
"StartHTML:00000000\r\n"
|
||||||
|
"EndHTML:00000000\r\n"
|
||||||
|
"StartFragment:00000000\r\n"
|
||||||
|
"EndFragment:00000000\r\n"
|
||||||
|
;
|
||||||
|
const size_t CF_HTML_PREAMBLE_LEN = strlen(CF_HTML_PREAMBLE);
|
||||||
|
|
||||||
|
const char* const CF_HTML_WRAP_START =
|
||||||
|
"<html><body>\r\n"
|
||||||
|
"<!--StartFragment -->"
|
||||||
|
;
|
||||||
|
const size_t CF_HTML_WRAP_START_LEN = strlen(CF_HTML_WRAP_START);
|
||||||
|
|
||||||
|
const char* const CF_HTML_WRAP_END =
|
||||||
|
"<!--EndFragment-->\r\n"
|
||||||
|
"</body>\r\n"
|
||||||
|
"</html>"
|
||||||
|
;
|
||||||
|
const size_t CF_HTML_WRAP_END_LEN = strlen(CF_HTML_WRAP_END);
|
||||||
|
|
||||||
|
|
||||||
|
// Return the extra size needed by HTML data in addition to the length of the
|
||||||
|
// HTML fragment itself.
|
||||||
|
int GetExtraDataSize()
|
||||||
|
{
|
||||||
|
// +1 is for the trailing NUL.
|
||||||
|
return CF_HTML_PREAMBLE_LEN + CF_HTML_WRAP_START_LEN + CF_HTML_WRAP_END_LEN + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap HTML data with the extra information needed by CF_HTML and copy
|
||||||
|
// everything into the provided buffer assumed to be of sufficient size.
|
||||||
|
void FillFromHTML(char* buffer, const char* html, size_t lenHTML)
|
||||||
|
{
|
||||||
|
// add the extra info that the MSW clipboard format requires.
|
||||||
|
|
||||||
|
// Create a template string for the HTML header...
|
||||||
|
strcpy(buffer, CF_HTML_PREAMBLE);
|
||||||
|
const size_t startHTML = CF_HTML_PREAMBLE_LEN;
|
||||||
|
|
||||||
|
strcat(buffer, CF_HTML_WRAP_START);
|
||||||
|
const size_t startFragment = startHTML + CF_HTML_WRAP_START_LEN;
|
||||||
|
|
||||||
|
// Append the HTML...
|
||||||
|
strncat(buffer, html, lenHTML);
|
||||||
|
const size_t endFragment = startFragment + lenHTML;
|
||||||
|
|
||||||
|
// Finish up the HTML format...
|
||||||
|
strcat(buffer, CF_HTML_WRAP_END);
|
||||||
|
const size_t endHTML = endFragment + CF_HTML_WRAP_END_LEN;
|
||||||
|
|
||||||
|
// Now go back and write out the necessary header information.
|
||||||
|
//
|
||||||
|
// Note, wsprintf() truncates the string when you overwrite it so you
|
||||||
|
// follow up with code to replace the 0 appended at the end with a '\r'.
|
||||||
|
const size_t OFFSET_LEN = 8; // All offsets are formatted using 8 digits.
|
||||||
|
|
||||||
|
char *ptr = strstr(buffer, START_HTML_HEADER);
|
||||||
|
sprintf(ptr+START_HTML_HEADER_LEN, "%08zu", startHTML);
|
||||||
|
*(ptr+START_HTML_HEADER_LEN+OFFSET_LEN) = '\r';
|
||||||
|
|
||||||
|
ptr = strstr(buffer, END_HTML_HEADER);
|
||||||
|
sprintf(ptr+END_HTML_HEADER_LEN, "%08zu", endHTML);
|
||||||
|
*(ptr+END_HTML_HEADER_LEN+OFFSET_LEN) = '\r';
|
||||||
|
|
||||||
|
ptr = strstr(buffer, START_FRAGMENT_HEADER);
|
||||||
|
sprintf(ptr+START_FRAGMENT_HEADER_LEN, "%08zu", startFragment);
|
||||||
|
*(ptr+START_FRAGMENT_HEADER_LEN+OFFSET_LEN) = '\r';
|
||||||
|
|
||||||
|
ptr = strstr(buffer, END_FRAGMENT_HEADER);
|
||||||
|
sprintf(ptr+END_FRAGMENT_HEADER_LEN, "%08zu", endFragment);
|
||||||
|
*(ptr+END_FRAGMENT_HEADER_LEN+OFFSET_LEN) = '\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract just the HTML fragment part from CF_HTML data.
|
||||||
|
wxString ExtractHTML(const char* buffer, size_t len)
|
||||||
|
{
|
||||||
|
// Sanity check.
|
||||||
|
if ( len < VERSION_HEADER_LEN ||
|
||||||
|
wxCRT_StrnicmpA(buffer, VERSION_HEADER, VERSION_HEADER_LEN) != 0 )
|
||||||
|
{
|
||||||
|
// This doesn't look like CF_HTML at all, don't do anything.
|
||||||
|
return wxString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ptr = strstr(buffer, START_FRAGMENT_HEADER);
|
||||||
|
if ( !ptr )
|
||||||
|
return wxString();
|
||||||
|
|
||||||
|
ptr += START_FRAGMENT_HEADER_LEN;
|
||||||
|
|
||||||
|
const int start = atoi(ptr);
|
||||||
|
if ( start < 0 || (unsigned)start >= len )
|
||||||
|
return wxString();
|
||||||
|
|
||||||
|
ptr = strstr(ptr, END_FRAGMENT_HEADER);
|
||||||
|
if ( !ptr )
|
||||||
|
return wxString();
|
||||||
|
|
||||||
|
ptr += END_FRAGMENT_HEADER_LEN;
|
||||||
|
const int end = atoi(ptr);
|
||||||
|
if ( end < 0 || end < start || (unsigned)end >= len )
|
||||||
|
return wxString();
|
||||||
|
|
||||||
|
return wxString::FromUTF8(buffer + start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
#endif // __WXMSW__
|
||||||
|
|
||||||
size_t wxHTMLDataObject::GetDataSize() const
|
size_t wxHTMLDataObject::GetDataSize() const
|
||||||
{
|
{
|
||||||
// Ensure that the temporary string returned by GetHTML() is kept alive for
|
// Ensure that the temporary string returned by GetHTML() is kept alive for
|
||||||
|
|
@ -439,9 +574,7 @@ size_t wxHTMLDataObject::GetDataSize() const
|
||||||
size_t size = buffer.length();
|
size_t size = buffer.length();
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
// On Windows we need to add some stuff to the string to satisfy
|
size += wxMSWClip::GetExtraDataSize();
|
||||||
// its clipboard format requirements.
|
|
||||||
size += 400;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
|
|
@ -461,75 +594,27 @@ bool wxHTMLDataObject::GetDataHere(void *buf) const
|
||||||
char* const buffer = static_cast<char*>(buf);
|
char* const buffer = static_cast<char*>(buf);
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
// add the extra info that the MSW clipboard format requires.
|
wxMSWClip::FillFromHTML(buffer, html, html.length());
|
||||||
|
|
||||||
// Create a template string for the HTML header...
|
|
||||||
strcpy(buffer,
|
|
||||||
"Version:0.9\r\n"
|
|
||||||
"StartHTML:00000000\r\n"
|
|
||||||
"EndHTML:00000000\r\n"
|
|
||||||
"StartFragment:00000000\r\n"
|
|
||||||
"EndFragment:00000000\r\n"
|
|
||||||
"<html><body>\r\n"
|
|
||||||
"<!--StartFragment -->\r\n");
|
|
||||||
|
|
||||||
// Append the HTML...
|
|
||||||
strcat(buffer, html);
|
|
||||||
strcat(buffer, "\r\n");
|
|
||||||
// Finish up the HTML format...
|
|
||||||
strcat(buffer,
|
|
||||||
"<!--EndFragment-->\r\n"
|
|
||||||
"</body>\r\n"
|
|
||||||
"</html>");
|
|
||||||
|
|
||||||
// Now go back, calculate all the lengths, and write out the
|
|
||||||
// necessary header information. Note, wsprintf() truncates the
|
|
||||||
// string when you overwrite it so you follow up with code to replace
|
|
||||||
// the 0 appended at the end with a '\r'...
|
|
||||||
char *ptr = strstr(buffer, "StartHTML");
|
|
||||||
sprintf(ptr+10, "%08u", (unsigned)(strstr(buffer, "<html>") - buffer));
|
|
||||||
*(ptr+10+8) = '\r';
|
|
||||||
|
|
||||||
ptr = strstr(buffer, "EndHTML");
|
|
||||||
sprintf(ptr+8, "%08u", (unsigned)strlen(buffer));
|
|
||||||
*(ptr+8+8) = '\r';
|
|
||||||
|
|
||||||
ptr = strstr(buffer, "StartFragment");
|
|
||||||
sprintf(ptr+14, "%08u", (unsigned)(strstr(buffer, "<!--StartFrag") - buffer));
|
|
||||||
*(ptr+14+8) = '\r';
|
|
||||||
|
|
||||||
ptr = strstr(buffer, "EndFragment");
|
|
||||||
sprintf(ptr+12, "%08u", (unsigned)(strstr(buffer, "<!--EndFrag") - buffer));
|
|
||||||
*(ptr+12+8) = '\r';
|
|
||||||
#else
|
#else
|
||||||
strcpy(buffer, html);
|
memcpy(buffer, html, html.length());
|
||||||
#endif // __WXMSW__
|
#endif // __WXMSW__
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wxHTMLDataObject::SetData(size_t WXUNUSED(len), const void *buf)
|
bool wxHTMLDataObject::SetData(size_t len, const void *buf)
|
||||||
{
|
{
|
||||||
if ( buf == nullptr )
|
if ( buf == nullptr )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Windows and Mac always use UTF-8, and docs suggest GTK does as well.
|
const char* const buffer = static_cast<const char*>(buf);
|
||||||
wxString html = wxString::FromUTF8(static_cast<const char*>(buf));
|
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
// To be consistent with other platforms, we only add the Fragment part
|
// To be consistent with other platforms, we only add the Fragment part
|
||||||
// of the Windows HTML clipboard format to the data object.
|
// of the Windows HTML clipboard format to the data object.
|
||||||
int fragmentStart = html.rfind("StartFragment");
|
wxString html = wxMSWClip::ExtractHTML(buffer, len);
|
||||||
int fragmentEnd = html.rfind("EndFragment");
|
#else
|
||||||
|
wxString html = wxString::FromUTF8(buffer, len);
|
||||||
if (fragmentStart != wxNOT_FOUND && fragmentEnd != wxNOT_FOUND)
|
|
||||||
{
|
|
||||||
int startCommentEnd = html.find("-->", fragmentStart) + 3;
|
|
||||||
int endCommentStart = html.rfind("<!--", fragmentEnd);
|
|
||||||
|
|
||||||
if (startCommentEnd != wxNOT_FOUND && endCommentStart != wxNOT_FOUND)
|
|
||||||
html = html.Mid(startCommentEnd, endCommentStart - startCommentEnd);
|
|
||||||
}
|
|
||||||
#endif // __WXMSW__
|
#endif // __WXMSW__
|
||||||
|
|
||||||
SetHTML( html );
|
SetHTML( html );
|
||||||
|
|
|
||||||
|
|
@ -587,6 +587,15 @@ void wxClipboard::Clear()
|
||||||
// it will free our data
|
// it will free our data
|
||||||
SetSelectionOwner(false);
|
SetSelectionOwner(false);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We need to free our data directly to avoid leaking memory.
|
||||||
|
delete m_dataPrimary;
|
||||||
|
m_dataPrimary = nullptr;
|
||||||
|
|
||||||
|
delete m_dataClipboard;
|
||||||
|
m_dataClipboard = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
m_targetRequested = nullptr;
|
m_targetRequested = nullptr;
|
||||||
m_formatSupported = false;
|
m_formatSupported = false;
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,34 @@ TEST_CASE("GUI::URLDataObject", "[guifuncs][clipboard]")
|
||||||
CHECK( dobj2.GetURL() == url );
|
CHECK( dobj2.GetURL() == url );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("GUI::HTMLDataObject", "[guifuncs][clipboard]")
|
||||||
|
{
|
||||||
|
const wxString text("<h1>Hello clipboard!</h1>");
|
||||||
|
|
||||||
|
wxHTMLDataObject* const dobj = new wxHTMLDataObject(text);
|
||||||
|
CHECK( dobj->GetHTML() == text );
|
||||||
|
|
||||||
|
wxClipboardLocker lockClip;
|
||||||
|
CHECK( wxTheClipboard->SetData(dobj) );
|
||||||
|
wxTheClipboard->Flush();
|
||||||
|
|
||||||
|
wxHTMLDataObject dobj2;
|
||||||
|
REQUIRE( wxTheClipboard->GetData(dobj2) );
|
||||||
|
CHECK( dobj2.GetHTML() == text );
|
||||||
|
}
|
||||||
|
|
||||||
|
// This disabled by default test allows to check that we retrieve HTML data
|
||||||
|
// from the system clipboard correctly.
|
||||||
|
TEST_CASE("GUI::ShowHTML", "[.]")
|
||||||
|
{
|
||||||
|
wxClipboardLocker lockClip;
|
||||||
|
|
||||||
|
wxHTMLDataObject dobj;
|
||||||
|
REQUIRE( wxTheClipboard->GetData(dobj) );
|
||||||
|
|
||||||
|
WARN("Clipboard contents:\n---start---\n" << dobj.GetHTML() << "\n---end--");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("GUI::DataFormatCompare", "[guifuncs][dataformat]")
|
TEST_CASE("GUI::DataFormatCompare", "[guifuncs][dataformat]")
|
||||||
{
|
{
|
||||||
const wxDataFormat df(wxDF_TEXT);
|
const wxDataFormat df(wxDF_TEXT);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue