1/* 2 * Copyright 2010-2022 Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Christophe Huriaux, c.huriaux@gmail.com 7 * Adrien Destugues, pulkomandy@gmail.com 8 * Niels Sascha Reedijk, niels.reedijk@gmail.com 9 */ 10 11#include <HttpTime.h> 12 13#include <list> 14#include <new> 15 16#include <cstdio> 17 18using namespace BPrivate::Network; 19 20 21// The formats used should be, in order of preference (according to RFC2616, 22// section 3.3): 23// RFC1123 / RFC822: "Sun, 06 Nov 1994 08:49:37 GMT" 24// RFC850 : "Sunday, 06-Nov-94 08:49:37 GMT" (obsoleted by RFC 1036) 25// asctime : "Sun Nov 6 08:49:37 1994" 26// 27// RFC1123 is the preferred one because it has 4 digit years. 28// 29// But of course in real life, all possible mixes of the formats are used. 30// Believe it or not, it's even possible to find some website that gets this 31// right and use one of the 3 formats above. 32// Often seen variants are: 33// - RFC1036 but with 4 digit year, 34// - Missing or different timezone indicator 35// - Invalid weekday 36static const std::list<std::pair<BHttpTimeFormat, const char*>> kDateFormats = { 37 // RFC822 38 {BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S GMT"}, // canonical 39 {BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S"}, // without timezone 40 // Standard RFC850 41 {BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S GMT"}, // canonical 42 {BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S"}, // without timezone 43 // RFC 850 with 4 digit year 44 {BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S"}, // without timezone 45 {BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S GMT"}, // with 4-digit year 46 {BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S UTC"}, // "UTC" timezone 47 // asctime 48 {BHttpTimeFormat::AscTime, "%a %b %e %H:%M:%S %Y"}, 49}; 50 51 52// #pragma mark BHttpTime::InvalidInput 53 54 55BHttpTime::InvalidInput::InvalidInput(const char* origin, BString input) 56 : 57 BError(origin), 58 input(std::move(input)) 59{ 60} 61 62 63const char* 64BHttpTime::InvalidInput::Message() const noexcept 65{ 66 if (input.IsEmpty()) 67 return "A HTTP timestamp cannot be empty"; 68 else 69 return "The HTTP timestamp string does not match the expected format"; 70} 71 72 73BString 74BHttpTime::InvalidInput::DebugMessage() const 75{ 76 BString output = BError::DebugMessage(); 77 if (!input.IsEmpty()) 78 output << ":\t " << input << "\n"; 79 return output; 80} 81 82 83// #pragma mark BHttpTime 84 85 86BHttpTime::BHttpTime() noexcept 87 : 88 fDate(BDateTime::CurrentDateTime(B_GMT_TIME)), 89 fDateFormat(BHttpTimeFormat::RFC1123) 90{ 91} 92 93 94BHttpTime::BHttpTime(BDateTime date) 95 : 96 fDate(date), 97 fDateFormat(BHttpTimeFormat::RFC1123) 98{ 99 if (!fDate.IsValid()) 100 throw InvalidInput(__PRETTY_FUNCTION__, "Invalid BDateTime object"); 101} 102 103 104BHttpTime::BHttpTime(const BString& dateString) 105 : 106 fDate(0), 107 fDateFormat(BHttpTimeFormat::RFC1123) 108{ 109 _Parse(dateString); 110} 111 112 113// #pragma mark Date modification 114 115 116void 117BHttpTime::SetTo(const BString& string) 118{ 119 _Parse(string); 120} 121 122 123void 124BHttpTime::SetTo(BDateTime date) 125{ 126 if (!date.IsValid()) 127 throw InvalidInput(__PRETTY_FUNCTION__, "Invalid BDateTime object"); 128 129 fDate = date; 130 fDateFormat = BHttpTimeFormat::RFC1123; 131} 132 133 134// #pragma mark Date Access 135 136 137BDateTime 138BHttpTime::DateTime() const noexcept 139{ 140 return fDate; 141} 142 143 144BHttpTimeFormat 145BHttpTime::DateTimeFormat() const noexcept 146{ 147 return fDateFormat; 148} 149 150 151BString 152BHttpTime::ToString(BHttpTimeFormat outputFormat) const 153{ 154 BString expirationFinal; 155 struct tm expirationTm = {}; 156 expirationTm.tm_sec = fDate.Time().Second(); 157 expirationTm.tm_min = fDate.Time().Minute(); 158 expirationTm.tm_hour = fDate.Time().Hour(); 159 expirationTm.tm_mday = fDate.Date().Day(); 160 expirationTm.tm_mon = fDate.Date().Month() - 1; 161 expirationTm.tm_year = fDate.Date().Year() - 1900; 162 // strftime starts weekday count at 0 for Sunday, 163 // while DayOfWeek starts at 1 for Monday and thus uses 7 for Sunday 164 expirationTm.tm_wday = fDate.Date().DayOfWeek() % 7; 165 expirationTm.tm_yday = 0; 166 expirationTm.tm_isdst = 0; 167 168 for (auto& [format, formatString]: kDateFormats) { 169 if (format != outputFormat) 170 continue; 171 172 static const uint16 kTimetToStringMaxLength = 128; 173 char expirationString[kTimetToStringMaxLength + 1]; 174 size_t strLength; 175 176 strLength 177 = strftime(expirationString, kTimetToStringMaxLength, formatString, &expirationTm); 178 179 expirationFinal.SetTo(expirationString, strLength); 180 break; 181 } 182 183 return expirationFinal; 184} 185 186 187void 188BHttpTime::_Parse(const BString& dateString) 189{ 190 if (dateString.Length() < 4) 191 throw InvalidInput(__PRETTY_FUNCTION__, dateString); 192 193 struct tm expireTime = {}; 194 195 bool found = false; 196 for (auto& [format, formatString]: kDateFormats) { 197 const char* result = strptime(dateString.String(), formatString, &expireTime); 198 199 if (result == dateString.String() + dateString.Length()) { 200 fDateFormat = format; 201 found = true; 202 break; 203 } 204 } 205 206 // Did we identify some valid format? 207 if (!found) 208 throw InvalidInput(__PRETTY_FUNCTION__, dateString); 209 210 // Now convert the struct tm from strptime into a BDateTime. 211 BTime time(expireTime.tm_hour, expireTime.tm_min, expireTime.tm_sec); 212 BDate date(expireTime.tm_year + 1900, expireTime.tm_mon + 1, expireTime.tm_mday); 213 fDate = BDateTime(date, time); 214} 215 216 217// #pragma mark Convenience Functions 218 219 220BDateTime 221BPrivate::Network::parse_http_time(const BString& string) 222{ 223 BHttpTime httpTime(string); 224 return httpTime.DateTime(); 225} 226 227 228BString 229BPrivate::Network::format_http_time(BDateTime timestamp, BHttpTimeFormat outputFormat) 230{ 231 BHttpTime httpTime(timestamp); 232 return httpTime.ToString(outputFormat); 233} 234