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 18 using 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 36 static 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 55 BHttpTime::InvalidInput::InvalidInput(const char* origin, BString input) 56 : 57 BError(origin), 58 input(std::move(input)) 59 { 60 } 61 62 63 const char* 64 BHttpTime::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 73 BString 74 BHttpTime::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 86 BHttpTime::BHttpTime() noexcept 87 : 88 fDate(BDateTime::CurrentDateTime(B_GMT_TIME)), 89 fDateFormat(BHttpTimeFormat::RFC1123) 90 { 91 } 92 93 94 BHttpTime::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 104 BHttpTime::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 116 void 117 BHttpTime::SetTo(const BString& string) 118 { 119 _Parse(string); 120 } 121 122 123 void 124 BHttpTime::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 137 BDateTime 138 BHttpTime::DateTime() const noexcept 139 { 140 return fDate; 141 } 142 143 144 BHttpTimeFormat 145 BHttpTime::DateTimeFormat() const noexcept 146 { 147 return fDateFormat; 148 } 149 150 151 BString 152 BHttpTime::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 187 void 188 BHttpTime::_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 220 BDateTime 221 BPrivate::Network::parse_http_time(const BString& string) 222 { 223 BHttpTime httpTime(string); 224 return httpTime.DateTime(); 225 } 226 227 228 BString 229 BPrivate::Network::format_http_time(BDateTime timestamp, BHttpTimeFormat outputFormat) 230 { 231 BHttpTime httpTime(timestamp); 232 return httpTime.ToString(outputFormat); 233 } 234