-
Notifications
You must be signed in to change notification settings - Fork 396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #8542 - Escape HTML characters. #8544
Conversation
Added an optional boolean argument to ConvertToEscape to not convert " and ' like it would in the original routine which was meant for XML. This is to support HTML4 mostly, but might as well be correct.
Unit Test before at 0dcf25b
After it passes |
I added a MCVE on https://github.com/NREL/EnergyPlusDevSupport/blob/c8f21351be86526a2970cf009d2f2309f6578846/DefectFiles/8000s/8542/mcve.idf#L7 Before: After: HTML diff: Note: In both cases the CO2 table looks the same visually: |
Merged develop and resolved conflicts |
// " ' < > & degree-sign | ||
// If isXML=false, it does an HTML conversion, so only ` < > & ` and degree sign | ||
// Technically HTML4 doesn't support ", though most browsers would anyways. | ||
// Also, escaping single and double quotes is only needed inside attributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mod ConvertToEscaped
to add a bool isXML
so it can be used for HTML escaping too, which is quite similar
std::string ConvertToEscaped(std::string const &inString, // Input String | ||
bool isXML=true); // isXML if false assumes HTML and will not convert quotes and apostrophes, for HTML4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mod.
TEST_F(SQLiteFixture, OutputReportTabularTest_EscapeHTML) | ||
{ | ||
// Test for #8542 - Ensures strings are escaped before going to HTML | ||
EnergyPlus::sqlite->sqliteBegin(); | ||
EnergyPlus::sqlite->createSQLiteSimulationsRecord(1, "EnergyPlus Version", "Current Time"); | ||
|
||
auto &ort(state->dataOutRptTab); | ||
ort->numStyles = 1; | ||
ort->TableStyle(1) = OutputReportTabular::iTableStyle::HTML; | ||
ort->del(1) = DataStringGlobals::CharSpace; // space - this is not used much for HTML output | ||
|
||
ort->WriteTabularFiles = true; | ||
|
||
SetupUnitConversions(*state); | ||
ort->unitsStyle = OutputReportTabular::iUnitsStyle::JtoKWH; | ||
|
||
SetPredefinedTables(*state); | ||
std::string CompName = "My Coil <coil is DX>"; | ||
|
||
PreDefTableEntry(*state, state->dataOutRptPredefined->pdchDXCoolCoilType, CompName, "Coil:Cooling:DX:SingleSpeed"); | ||
// This would normally be called with numerics such as CompName, 0.006, 8, but I don't really care | ||
PreDefTableEntry(*state, state->dataOutRptPredefined->pdch2CoilLvgHumRatIdealPeak, CompName, "My Design Day where it's >= 8\u00B0"); | ||
PreDefTableEntry(*state, state->dataOutRptPredefined->pdst2CoilSummaryCoilSelection, CompName, "My Design Day where it's >= 8\u00B0"); // this is >= 8 degree sign | ||
|
||
// We enable the reports we care about, making sure we have the right ones | ||
EXPECT_EQ("HVACSizingSummary", state->dataOutRptPredefined->reportName(6).name); | ||
state->dataOutRptPredefined->reportName(6).show = true; | ||
|
||
OutputReportTabular::OpenOutputTabularFile(*state); | ||
|
||
WritePredefinedTables(*state); | ||
|
||
OutputReportTabular::CloseOutputTabularFile(*state); | ||
|
||
std::vector<std::string> lines = read_lines_in_file(DataStringGlobals::outputTblHtmFileName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setup the unit test. We write some stuff to the HTML with special characters. Ensures the HTML is closed. Then we read back the html lines.
// Lambda helper to locate a line in the html file, and compare that line with the expected html after trimming | ||
auto compare_html_output = [this, &lines](const std::string& lookup, const std::string& expectedHTMLString) { | ||
std::string found_cell; | ||
for (const auto& line: lines) { | ||
if (line.find(lookup) != std::string::npos) { | ||
found_cell = line; | ||
break; | ||
} | ||
} | ||
EXPECT_FALSE(found_cell.empty()) | ||
<< "Did not find the lookup string '" << lookup | ||
<< "' string in the html output at '" << DataStringGlobals::outputTblHtmFileName | ||
<< "'..." << '\n' << delimited_string(lines); | ||
|
||
// Trim leading and trailing spaces | ||
found_cell.erase(0, found_cell.find_first_not_of(' ')); // ltrim | ||
found_cell.erase(found_cell.find_last_not_of(' ') + 1); // rtrim | ||
|
||
EXPECT_EQ(expectedHTMLString, found_cell) << found_cell; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lambda to check we can actually find something in the HTML lines.
EXPECT_EQ(expectedHTMLString, found_cell) << found_cell; | ||
}; | ||
|
||
compare_html_output("My Coil", "<td align=\"right\">My Coil <coil is DX></td>"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First test. I can locate the line header for My Coil <coil is DX>
and it is properly escaped.
// Clean up | ||
FileSystem::removeFile(DataStringGlobals::outputTblHtmFileName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And we clean up to be a good citizen.
tbl_stream << "<p>Building: <b>" << ConvertToEscaped(BuildingName, false) << "</b></p>\n"; | ||
if (state.dataEnvrn->EnvironmentName == state.dataEnvrn->WeatherFileLocationTitle) { | ||
tbl_stream << "<p>Environment: <b>" << state.dataEnvrn->EnvironmentName << "</b></p>\n"; | ||
tbl_stream << "<p>Environment: <b>" << ConvertToEscaped(state.dataEnvrn->EnvironmentName, false) << "</b></p>\n"; | ||
} else { | ||
tbl_stream << "<p>Environment: <b>" << state.dataEnvrn->EnvironmentName << " ** " << state.dataEnvrn->WeatherFileLocationTitle << "</b></p>\n"; | ||
tbl_stream << "<p>Environment: <b>" << ConvertToEscaped(state.dataEnvrn->EnvironmentName, false) << " ** " << ConvertToEscaped(state.dataEnvrn->WeatherFileLocationTitle, false) << "</b></p>\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape at the very top of the HTML file.
@@ -15331,7 +15331,7 @@ namespace EnergyPlus::OutputReportTabular { | |||
for (iCol = 1; iCol <= colsColumnLabels; ++iCol) { | |||
outputLine = " <td align=\"right\">"; | |||
for (jRow = 1; jRow <= maxNumColLabelRows; ++jRow) { | |||
outputLine += colLabelMulti(iCol, jRow); | |||
outputLine += ConvertToEscaped(colLabelMulti(iCol, jRow), false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape column headers, isXML=False
@@ -15343,13 +15343,13 @@ namespace EnergyPlus::OutputReportTabular { | |||
for (jRow = 1; jRow <= rowsBody; ++jRow) { | |||
tbl_stream << " <tr>\n"; | |||
if (rowLabels(jRow) != "") { | |||
tbl_stream << " <td align=\"right\">" << InsertCurrencySymbol(state, rowLabels(jRow), true) << "</td>\n"; | |||
tbl_stream << " <td align=\"right\">" << ConvertToEscaped(InsertCurrencySymbol(state, rowLabels(jRow), true), false) << "</td>\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape row headers
} else { | ||
tbl_stream << " <td align=\"right\"> </td>\n"; | ||
} | ||
for (iCol = 1; iCol <= colsBody; ++iCol) { | ||
if (body(iCol, jRow) != "") { | ||
tbl_stream << " <td align=\"right\">" << InsertCurrencySymbol(state, body(iCol, jRow), true) << "</td>\n"; | ||
tbl_stream << " <td align=\"right\">" << ConvertToEscaped(InsertCurrencySymbol(state, body(iCol, jRow), true), false) << "</td>\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape every regular table cell.
// We ensure that SQL doesn't have the same escape | ||
for (const std::string reportName: {"HVACSizingSummary"}) { | ||
|
||
|
||
auto result = queryResult("SELECT RowName, Value From TabularDataWithStrings " | ||
"WHERE ReportName = \"" + reportName + "\"" | ||
" AND ColumnName = \"Coil Leaving Air Humidity Ratio at Ideal Loads Peak\"", | ||
"TabularDataWithStrings"); | ||
|
||
EnergyPlus::sqlite->sqliteCommit(); | ||
|
||
EXPECT_EQ(1u, result.size()); | ||
// Because the table has 8 cols | ||
EXPECT_EQ(8u, result[0].size()); | ||
|
||
// 0.006 is a ratio, so unitconv = 1 | ||
std::string s = result[0][0]; | ||
// Trim the string, it has leading spaces | ||
//s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end()); | ||
|
||
EXPECT_EQ("My Coil <coil is DX>", s); | ||
|
||
EXPECT_EQ("My Design Day where it's >= 8\u00B0", result[0][1]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
OK, pulled develop and verified the html is not sanitized, and indeed, brackets are treated as child html elements and hidden away. With this branch it looks great. Thanks for the fix @jmarrec , merging. |
Pull request overview
Pull Request Author
Add to this list or remove from it as applicable. This is a simple templated set of guidelines.
Reviewer
This will not be exhaustively relevant to every PR.