|
| 1 | +#include "twitch-timestamp.hpp" |
| 2 | + |
| 3 | +#include <log-helper.hpp> |
| 4 | + |
| 5 | +#include <chrono> |
| 6 | +#include <ctime> |
| 7 | +#include <regex> |
| 8 | +#include <string> |
| 9 | + |
| 10 | +using namespace std::chrono_literals; |
| 11 | + |
| 12 | +namespace advss { |
| 13 | + |
| 14 | +bool IsValidEventSubTimestamp(const std::string ×tamp) |
| 15 | +{ |
| 16 | + // Example input: 2023-07-19T14:56:51.634234626Z |
| 17 | + // or: 2023-07-19T14:56:51+05:30 |
| 18 | + static const std::regex pattern( |
| 19 | + R"(^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})$)"); |
| 20 | + |
| 21 | + std::smatch m; |
| 22 | + if (!std::regex_match(timestamp, m, pattern)) { |
| 23 | + blog(LOG_WARNING, "failed to parse timestamp %s", |
| 24 | + timestamp.c_str()); |
| 25 | + return false; |
| 26 | + } |
| 27 | + |
| 28 | + std::tm tm = {}; |
| 29 | + tm.tm_year = std::stoi(m[1]) - 1900; |
| 30 | + tm.tm_mon = std::stoi(m[2]) - 1; |
| 31 | + tm.tm_mday = std::stoi(m[3]); |
| 32 | + tm.tm_hour = std::stoi(m[4]); |
| 33 | + tm.tm_min = std::stoi(m[5]); |
| 34 | + tm.tm_sec = std::stoi(m[6]); |
| 35 | + |
| 36 | + // Range checks; timegm/_mkgmtime silently normalize out-of-range |
| 37 | + // fields instead of failing, so we need to catch that ourselves. |
| 38 | + if (tm.tm_mon < 0 || tm.tm_mon > 11 || tm.tm_mday < 1 || |
| 39 | + tm.tm_mday > 31 || tm.tm_hour > 23 || tm.tm_min > 59 || |
| 40 | + tm.tm_sec > 60 /* allow leap second */) { |
| 41 | + blog(LOG_WARNING, "timestamp field out of range %s", |
| 42 | + timestamp.c_str()); |
| 43 | + return false; |
| 44 | + } |
| 45 | + |
| 46 | +#ifdef _WIN32 |
| 47 | + time_t t = _mkgmtime(&tm); |
| 48 | +#else |
| 49 | + time_t t = timegm(&tm); |
| 50 | +#endif |
| 51 | + if (t == -1) { |
| 52 | + blog(LOG_WARNING, "failed to convert timestamp %s", |
| 53 | + timestamp.c_str()); |
| 54 | + return false; |
| 55 | + } |
| 56 | + |
| 57 | + // Reject dates that normalized to something else (e.g. Feb 30 -> Mar 2) |
| 58 | + if (tm.tm_mday != std::stoi(m[3]) || tm.tm_mon != std::stoi(m[2]) - 1) { |
| 59 | + blog(LOG_WARNING, |
| 60 | + "timestamp date invalid after normalization %s", |
| 61 | + timestamp.c_str()); |
| 62 | + return false; |
| 63 | + } |
| 64 | + |
| 65 | + const std::string &tz = m[7]; |
| 66 | + if (tz != "Z") { |
| 67 | + int offSign = (tz[0] == '+') ? 1 : -1; |
| 68 | + int offH = std::stoi(tz.substr(1, 2)); |
| 69 | + int offM = std::stoi(tz.substr(4, 2)); |
| 70 | + if (offH > 23 || offM > 59) { |
| 71 | + blog(LOG_WARNING, "invalid timestamp offset %s", |
| 72 | + timestamp.c_str()); |
| 73 | + return false; |
| 74 | + } |
| 75 | + time_t offsetSecs = (offH * 60 + offM) * 60; |
| 76 | + t += (offSign > 0) ? -offsetSecs : offsetSecs; |
| 77 | + } |
| 78 | + |
| 79 | + auto parsedTime = std::chrono::system_clock::from_time_t(t); |
| 80 | + auto now = std::chrono::system_clock::now(); |
| 81 | + auto duration = now - parsedTime; |
| 82 | + // Clocks might be off by a bit, so allow negative values also |
| 83 | + return duration <= 10min && duration >= -1min; |
| 84 | +} |
| 85 | + |
| 86 | +} // namespace advss |
0 commit comments