// Copyright (c) 2021 Robin Davies // // 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 "HtmlHelper.hpp" #include #include #include using namespace pipedal; // non-localizable. RFC 7231 Date Format ; static const char*INVARIANT_WEEK_DAYS[] = { "Sun","Mon", "Tue", "Wed","Thu", "Fri","Sat"}; // non-localizable. RFC 7231 Date Format static const char*INVARIANT_MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep","Oct","Nov", "Dec"}; // non-localizable. RFC 7231 Date Format static const char*FORMAT = "%s, %02d %s %04d %02d:%02d:%02d GMT"; std::string HtmlHelper::timeToHttpDate() { return timeToHttpDate(time(nullptr)); } std::string HtmlHelper::timeToHttpDate(time_t time) { // RFC 7231, IMF-fixdate. struct tm gmTm; if (gmtime_r(&time,&gmTm) == nullptr) { return "Mon, 01 Jan 1970 00:00:00 GMT"; } char szBuffer[sizeof("Mon, 01 Jan 1970 00:00:00 GMT+")]; snprintf(szBuffer,sizeof(szBuffer), FORMAT, INVARIANT_MONTHS[gmTm.tm_wday], gmTm.tm_mday, INVARIANT_MONTHS[gmTm.tm_mon], gmTm.tm_year, gmTm.tm_hour, gmTm.tm_min, gmTm.tm_sec); return szBuffer; } static int hexN(char c) { if (c >= '0' && c <= '9') return c-'0'; if (c >= 'a' && c <= 'f') return 10+c-'a'; if (c >= 'A' && c <= 'F') return 10+c-'A'; throw std::invalid_argument("Malformed URL."); } static char hexChar(char c1, char c2) { return (char)((hexN(c1) << 4) + hexN(c2)); } class SafeCharacterMap { bool safeCharacter[256]; public: SafeCharacterMap() { for (int i = 0; i < 0x20; ++i) { safeCharacter[i] = false; } for (int i = 0x20; i < 0x80; ++i) { safeCharacter[i] = true; } for (int i = 0x80; i < 256; ++i) { safeCharacter[i] = false; } const char *unsafeCharacters = ":/+?=#%@ "; for (const char*p = unsafeCharacters; *p != 0; ++p) { safeCharacter[(uint8_t)*p] = false; } } bool isSafeCharacter(char c) { return safeCharacter[(uint8_t)c];} } g_safeCharacterMap; std::string HtmlHelper::encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment ) { std::stringstream s; encode_url_segment(s,pStart,pEnd,isQuerySegment); return s.str(); } static char hexChars[] = "0123456789ABCDEF"; void HtmlHelper::encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment) { while (pStart != pEnd) { char c = *pStart++; if (g_safeCharacterMap.isSafeCharacter(c)) { os << c; } else if (c == ' ' && isQuerySegment) { os << '+'; os << c; } else { os << '%' << hexChars[(c >> 4) & 0x0f] << hexChars[c & 0x0F]; } } } std::string HtmlHelper::decode_url_segment(const char*pStart, const char*pEnd, bool isQuery) { std::stringstream s; auto p = pStart; while (p != pEnd) { char c = *p; ++p; if (c == '+') { s << ' '; } else if (c != '%') { s << c; } else { if (pStart+2 < pEnd) { char c1 = *p; ++p; char c2 = *p; ++p; s << hexChar(c1,c2); } else { throw std::invalid_argument("Malformed URL."); } } } return s.str(); } std::string HtmlHelper::decode_url_segment(const char*text, bool isQuery) { return decode_url_segment(text, text + strlen(text),isQuery); } void HtmlHelper::utf32_to_utf8_stream(std::ostream &s, uint32_t uc) { if (uc < 0x80u) { s << (char)uc; } else if (uc < 0x800u) { s << (char)(0xC0 + (uc >> 6)); s << (char)(0x80 + (uc & 0x3F)); } else if (uc < 0x10000u) { s << (char)(0xE0 + (uc >> 12)); s << (char)(0x80 + ((uc >> 6) & 0x3F)); s << (char)(0x80 + (uc & 0x3F)); } else if (uc < 0x0110000) { s << (char)(0xF0 + (uc >> 18)); s << (char)(0x80 + ((uc >> 12) & 0x3F)); s << (char)(0x80 + ((uc >> 6) & 0x3F)); s << (char)(0x80 + (uc & 0x3F)); } else { throw std::range_error("Illegal UTF-32 character."); } } const std::string ESPECIALS = "()<>@,;:\"/[]?.="; #define MAX_FILENAME_LENGTH 96 #define MAX_SEGMENT_LENGTH 74 // 75 per RFC2047.2. But leave space for a ';' to allow easier breaking of lines. std::string HtmlHelper::Rfc5987EncodeFileName(const std::string&name) { // Encode all characters. Let the browser handle safe-ifying the actual name, since that's os dependent. std::stringstream s; std::string prefix = "UTF-8''"; std::string postfix = ""; s << prefix; for (char c: name) { uint8_t uc = (uint8_t)c; if (uc < 0x20 || uc >= 0x80 || ESPECIALS.find(c) != std::string::npos) { s << '%' << hexChars[uc >> 4] << hexChars[uc & 0x0F]; } else { s << c; } } s << postfix; return s.str(); } #define MAX_FILE_NAME_LENGHT 96 const std::string SF_SPECIALS = " <>@;:\"\'/[]?="; std::string HtmlHelper::SafeFileName(const std::string &name) { std::stringstream s; for (char c : name) { uint8_t uc = (uint8_t)c; if (uc < 0x20 || uc >= 0x80 || SF_SPECIALS.find(c) != std::string::npos) { s << '_'; } else { s << c; } } return s.str(); } std::string HtmlHelper::HtmlEncode(const std::string& text) { std::stringstream os; for (char c: text) { switch(c) { case '<': os << "<"; break; case '>': os << ">"; break; case '&': os << "&"; break; default: os << c; break; } } return os.str(); }