utils.h 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. #pragma once
  2. static const char threadTimestampFmt[] = "%Y/%m/%d %H:%M:%S %Z";
  3. static const char httpTimestampFmt[] = "%a, %d %b %Y %H:%M:%S GMT";
  4. static void *
  5. memmem_priv(const void *l, size_t l_len, const void *s, size_t s_len)
  6. {
  7. register char *cur, *last;
  8. const char *cl = (const char *)l;
  9. const char *cs = (const char *)s;
  10. /* we need something to compare */
  11. if (l_len == 0 || s_len == 0)
  12. return NULL;
  13. /* "s" must be smaller or equal to "l" */
  14. if (l_len < s_len)
  15. return NULL;
  16. /* special case where s_len == 1 */
  17. if (s_len == 1)
  18. return (void *)memchr(l, (int)*cs, l_len);
  19. /* the last position where its possible to find "s" in "l" */
  20. last = (char *)cl + l_len - s_len;
  21. for (cur = (char *)cl; cur <= last; cur++)
  22. if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
  23. return cur;
  24. return NULL;
  25. }
  26. static int decryptMail(unsigned char *decrypted, char *encrypted)
  27. {
  28. char current[5]="0x";
  29. unsigned char *ptr = decrypted;
  30. current[2] = encrypted[0];
  31. current[3] = encrypted[1];
  32. unsigned int r = strtol(current,NULL,16);
  33. int len = strlen(encrypted);
  34. int n = 2;
  35. for(;n<len;n+=2) {
  36. current[2] = encrypted[n];
  37. current[3] = encrypted[n+1];
  38. unsigned int i = strtol(current,NULL,16);
  39. *ptr++ = i^r;
  40. }
  41. *ptr = 0;
  42. //fprintf(stderr,"%s->%s\n",encrypted,decrypted);
  43. return ptr - decrypted;
  44. }
  45. static size_t header_callback_proxy(char *buffer, size_t size, size_t nitems, void *userdata)
  46. {
  47. BBS2chProxyConnection *conn = reinterpret_cast<BBS2chProxyConnection *>(userdata);
  48. if(conn->status) return size*nitems;
  49. if(!strncasecmp("Connection",buffer,10)) {
  50. conn->socketToClient->writeString("Connection: Close\r\n");
  51. return size*nitems;
  52. }
  53. else if(!strncasecmp("Transfer-Encoding",buffer,17)) {
  54. if(allow_chunked && strstr(buffer+19,"chunked")) {
  55. conn->chunked = true;
  56. //fprintf(stderr,"%s",buffer);
  57. size_t ret = conn->socketToClient->write(buffer, size*nitems);
  58. return ret;
  59. }
  60. return size*nitems;
  61. }
  62. else if(conn->force5ch && !strncasecmp("Set-Cookie:",buffer,11)) {
  63. char *ptr = (char *)memmem_priv(buffer,size*nitems,"domain=",7);
  64. if(ptr) {
  65. char *end = ptr;
  66. while(*end != ';' && *end != '\r') end++;
  67. ptr = (char *)memmem_priv(ptr,end-ptr,"5ch.net",7);
  68. if(ptr) {
  69. conn->socketToClient->write(buffer, ptr-buffer);
  70. conn->socketToClient->write("2", 1);
  71. conn->socketToClient->write(ptr+1, size*nitems-(ptr+1-buffer));
  72. return size*nitems;
  73. }
  74. }
  75. return conn->socketToClient->write(buffer, size*nitems);
  76. }
  77. else {
  78. if (conn->bbscgi) {
  79. if (!strncasecmp("X-Chx-Error:", buffer, 12)) {
  80. const char *ptr = buffer + 12;
  81. while (*ptr == ' ') ptr++;
  82. log_printf(0, "Posting to bbs.cgi has been cancelled. Reason: %s", ptr);
  83. if (*ptr == 'E') {
  84. int code = atoi(ptr+1);
  85. if ((code >= 3310 && code <= 3311) || /* inconsistent with User-Agent or something? */
  86. (code >= 3320 && code <= 3324) || /* key expiration? */
  87. (code >= 3390 && code <= 3392)) /* 3390: BAN, 3391-3392: key expiration? */
  88. conn->setMonaKey("", code);
  89. }
  90. }
  91. else if (!strncasecmp("X-MonaKey:", buffer, 10)) {
  92. const char *ptr = buffer + 10;
  93. while (*ptr == ' ') ptr++;
  94. const char *end = ptr;
  95. while (*end != '\n' && *end != '\r' && *end) end++;
  96. conn->setMonaKey(std::string(ptr, end-ptr), 0);
  97. }
  98. }
  99. size_t ret = conn->socketToClient->write(buffer, size*nitems);
  100. //fprintf(stderr,"%s",buffer);
  101. if(!memcmp(buffer,"\r\n",2)) {
  102. conn->status = 1;
  103. }
  104. return ret;
  105. }
  106. }
  107. static size_t header_callback_bbscgi(char *buffer, size_t size, size_t nitems, void *userdata)
  108. {
  109. BBS2chProxyConnection *conn = reinterpret_cast<BBS2chProxyConnection *>(userdata);
  110. if(conn->status) return size*nitems;
  111. if(!strncasecmp("Connection",buffer,10)) {
  112. conn->responseHeaders.append("Connection: Close\r\n");
  113. return size*nitems;
  114. }
  115. else if(!strncasecmp("Transfer-Encoding",buffer,17)) {
  116. if(allow_chunked && strstr(buffer+19,"chunked")) {
  117. conn->chunked = true;
  118. //fprintf(stderr,"%s",buffer);
  119. conn->responseHeaders.append(buffer, size*nitems);
  120. }
  121. return size*nitems;
  122. }
  123. else if(conn->force5ch && !strncasecmp("Set-Cookie:",buffer,11)) {
  124. char *ptr = (char *)memmem_priv(buffer,size*nitems,"domain=",7);
  125. if(ptr) {
  126. char *end = ptr;
  127. while(*end != ';' && *end != '\r') end++;
  128. ptr = (char *)memmem_priv(ptr,end-ptr,"5ch.net",7);
  129. if(ptr) {
  130. conn->responseHeaders.append(buffer, ptr-buffer);
  131. conn->responseHeaders.append(1, '2');
  132. conn->responseHeaders.append(ptr+1, size*nitems-(ptr+1-buffer));
  133. return size*nitems;
  134. }
  135. }
  136. conn->responseHeaders.append(buffer, size*nitems);
  137. return size*nitems;
  138. }
  139. else {
  140. if (!strncasecmp("X-Chx-Error:", buffer, 12)) {
  141. const char *ptr = buffer + 12;
  142. while (*ptr == ' ') ptr++;
  143. log_printf(0, "Posting to bbs.cgi has been cancelled. Reason: %s", ptr);
  144. if (*ptr == 'E') {
  145. int code = atoi(ptr+1);
  146. if ((code >= 3310 && code <= 3311) || /* inconsistent with User-Agent or something? */
  147. (code >= 3320 && code <= 3324) || /* key expiration? */
  148. (code >= 3390 && code <= 3392)) /* 3390: BAN, 3391-3392: key expiration? */
  149. {
  150. conn->setMonaKey("", code);
  151. conn->status = 2;
  152. return 0;
  153. }
  154. }
  155. }
  156. else if (!strncasecmp("X-MonaKey:", buffer, 10)) {
  157. const char *ptr = buffer + 10;
  158. while (*ptr == ' ') ptr++;
  159. const char *end = ptr;
  160. while (*end != '\n' && *end != '\r' && *end) end++;
  161. conn->setMonaKey(std::string(ptr, end-ptr), 0);
  162. }
  163. conn->responseHeaders.append(buffer, size*nitems);
  164. //fprintf(stderr,"%s",buffer);
  165. if(!memcmp(buffer,"\r\n",2)) {
  166. conn->socketToClient->write(conn->responseHeaders.data(), conn->responseHeaders.size());
  167. conn->status = 1;
  168. }
  169. return size*nitems;
  170. }
  171. }
  172. static size_t write_callback_proxy(char *buffer, size_t size, size_t nitems, void *userdata)
  173. {
  174. BBS2chProxyConnection *conn = reinterpret_cast<BBS2chProxyConnection *>(userdata);
  175. if(conn->chunked) {
  176. char buf[64];
  177. snprintf(buf, 64, "%lx\r\n", size*nitems);
  178. conn->socketToClient->write(buf, strlen(buf));
  179. }
  180. size_t ret = conn->socketToClient->write(buffer, size*nitems);
  181. if(conn->chunked) conn->socketToClient->writeString("\r\n");
  182. return ret;
  183. }
  184. static size_t header_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  185. {
  186. DataStorage *data = reinterpret_cast<DataStorage *>(userdata);
  187. if(!strncasecmp("Connection",buffer,10)) {
  188. data->appendBytes("Connection: Close\r\n",19);
  189. return size*nitems;
  190. }
  191. else if(!strncasecmp("Transfer-Encoding",buffer,17)) {
  192. return size*nitems;
  193. }
  194. else {
  195. data->appendBytes(buffer,size*nitems);
  196. return size*nitems;
  197. }
  198. }
  199. static size_t write_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  200. {
  201. DataStorage *data = reinterpret_cast<DataStorage *>(userdata);
  202. size_t downloaded = size*nitems;
  203. data->appendBytes(buffer, downloaded);
  204. return downloaded;
  205. }
  206. static size_t read_callback_proxy(char *buffer, size_t size, size_t nitems, void *userdata)
  207. {
  208. BBS2chProxyConnection *conn = reinterpret_cast<BBS2chProxyConnection *>(userdata);
  209. if(size*nitems < 1) return 0;
  210. if(conn->content_length) {
  211. size_t bytesToRead = conn->content_length;
  212. if(size*nitems < conn->content_length) bytesToRead = size*nitems;
  213. size_t ret = conn->socketToClient->read(buffer, bytesToRead);
  214. conn->content_length -= ret;
  215. return ret;
  216. }
  217. return 0;
  218. }
  219. static void sendBasicHeaders(int respCode, const char *respMsg, IBBS2chProxySocket *socket)
  220. {
  221. char date[256];
  222. time_t now = time(0);
  223. strftime(date, 256, httpTimestampFmt, gmtime(&now));
  224. std::ostringstream ss;
  225. ss << "HTTP/1.1 " << respCode << " " << respMsg << "\r\n";
  226. if(0 >= socket->writeString(ss.str())) return;
  227. if(0 >= socket->writeString("Connection: Close\r\n")) return;
  228. if(0 >= socket->writeString("Server: 2ch Proxy\r\n")) return;
  229. if(0 >= socket->writeString(std::string("Date: ") + date + "\r\n")) return;
  230. }
  231. static void sendResponse(int respCode, const char *respMsg, IBBS2chProxySocket *socket)
  232. {
  233. sendBasicHeaders(respCode, respMsg, socket);
  234. if(0 >= socket->writeString("Content-Type: text/plain; charset=UTF-8\r\n")) return;
  235. if(0 >= socket->writeString("\r\n")) return;
  236. if(respCode >= 400) {
  237. if(0 >= socket->writeString("   ∧_∧   / ̄ ̄ ̄ ̄ ̄\n")) return;
  238. if(0 >= socket->writeString(std::string("  ( ´∀`)< ") + respMsg + "\n")) return;
  239. if(0 >= socket->writeString("  (    ) \_____\n")) return;
  240. if(0 >= socket->writeString("   │ │ │\n")) return;
  241. if(0 >= socket->writeString("  (__)_)\n")) return;
  242. }
  243. }
  244. static double getCurrentTime(void)
  245. {
  246. #ifdef _WIN32
  247. SYSTEMTIME st;
  248. FILETIME ft;
  249. GetLocalTime(&st);
  250. SystemTimeToFileTime(&st, &ft);
  251. unsigned long long now = ft.dwHighDateTime;
  252. now <<= 32;
  253. now |= ft.dwLowDateTime;
  254. return (double)(now * 1e-7 - 11644473600.0);
  255. #else
  256. struct timeval tv;
  257. gettimeofday(&tv, NULL);
  258. return (double)(tv.tv_sec + tv.tv_usec * 1e-6);
  259. #endif
  260. }
  261. static std::string decodeURIComponent(const char *input, size_t inputLength, bool decodePlus)
  262. {
  263. std::string output;
  264. for (int i=0;i<inputLength;i++) {
  265. if (input[i] == '%') {
  266. if (i < inputLength - 2) {
  267. char from[3];
  268. char *end;
  269. from[0] = input[i+1];
  270. from[1] = input[i+2];
  271. from[2] = 0;
  272. unsigned long n = strtoul(from, &end, 16);
  273. if (n < 256 && end == from+2) {
  274. output.append(1, n);
  275. i += 2;
  276. continue;
  277. }
  278. }
  279. }
  280. else if (decodePlus && input[i] == '+') {
  281. output.append(" ");
  282. continue;
  283. }
  284. output.append(1, input[i]);
  285. }
  286. return output;
  287. }
  288. static std::string encodeURIComponent(const char *input, size_t inputLength, bool spaceAsPlus)
  289. {
  290. std::string output;
  291. for (int i=0;i<inputLength;i++) {
  292. unsigned char c = (unsigned char)input[i];
  293. if ((c >= '0' && c <= '9') ||
  294. (c >= 'A' && c <= 'Z') ||
  295. (c >= 'a' && c <= 'z') ||
  296. (c == '*') || (c == '-') || (c == '.') || (c == '_')) {
  297. output.append(1, c);
  298. }
  299. else if (c == ' ' && spaceAsPlus) {
  300. output.append("+");
  301. }
  302. else {
  303. char percentEncoded[4];
  304. snprintf(percentEncoded, 4, "%%%02X", c);
  305. output.append(percentEncoded);
  306. }
  307. }
  308. return output;
  309. }
  310. static void appendPostSignature(const char *body, const std::string &userAgent, const std::string &monaKey, curl_slist **headers)
  311. {
  312. std::map<std::string, std::string> fields;
  313. const char *ptr = body;
  314. while (1) {
  315. const char *tmp = ptr;
  316. while (*tmp != '=' && *tmp != 0) tmp++;
  317. if (*tmp == 0) break;
  318. std::string key(ptr, tmp-ptr);
  319. tmp++;
  320. ptr = tmp;
  321. while (*tmp != '&' && *tmp != 0) tmp++;
  322. fields.insert(std::make_pair(key, decodeURIComponent(ptr, tmp-ptr, true)));
  323. if (*tmp == 0) break;
  324. ptr = tmp + 1;
  325. }
  326. char nonce[32];
  327. snprintf(nonce, 32, "%.3f", getCurrentTime());
  328. std::string message;
  329. message.append(fields["bbs"]);
  330. message.append("<>");
  331. message.append(fields["key"]);
  332. message.append("<>");
  333. message.append(fields["time"]);
  334. message.append("<>");
  335. message.append(fields["FROM"]);
  336. message.append("<>");
  337. message.append(fields["mail"]);
  338. message.append("<>");
  339. message.append(fields["MESSAGE"]);
  340. message.append("<>");
  341. message.append(fields["subject"]);
  342. message.append("<>");
  343. message.append(userAgent);
  344. message.append("<>");
  345. message.append(monaKey);
  346. message.append("<>");
  347. message.append("<>");
  348. message.append(nonce);
  349. unsigned char digest[32];
  350. char digestStr[65];
  351. static const char *table = "0123456789abcdef";
  352. proxy2ch_HMAC_SHA256(hmacKey, strlen(hmacKey), message.data(), message.length(), digest);
  353. for (int i=0; i<32; i++) {
  354. unsigned char c = digest[i];
  355. unsigned char upper = (c >> 4) & 0xf;
  356. unsigned char lower = c & 0xf;
  357. digestStr[i*2] = table[upper];
  358. digestStr[i*2+1] = table[lower];
  359. }
  360. digestStr[64] = 0;
  361. char header[256];
  362. snprintf(header, 256, "X-APIKey: %s", appKey);
  363. *headers = curl_slist_append(*headers, header);
  364. log_printf(1, "Appended header \"%s\"\n", header);
  365. snprintf(header, 256, "X-PostSig: %s", digestStr);
  366. *headers = curl_slist_append(*headers, header);
  367. log_printf(1, "Appended header \"%s\"\n", header);
  368. snprintf(header, 256, "X-PostNonce: %s", nonce);
  369. *headers = curl_slist_append(*headers, header);
  370. log_printf(1, "Appended header \"%s\"\n", header);
  371. snprintf(header, 256, "X-MonaKey: %s", monaKey.c_str());
  372. *headers = curl_slist_append(*headers, header);
  373. log_printf(1, "Appended header \"%s\"\n", header);
  374. }
  375. #ifdef _WIN32
  376. const char * strp_weekdays[] =
  377. { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"};
  378. const char * strp_monthnames[] =
  379. { "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"};
  380. bool strp_atoi(const char * & s, int & result, int low, int high, int offset)
  381. {
  382. bool worked = false;
  383. char * end;
  384. unsigned long num = strtoul(s, & end, 10);
  385. if (num >= (unsigned long)low && num <= (unsigned long)high)
  386. {
  387. result = (int)(num + offset);
  388. s = end;
  389. worked = true;
  390. }
  391. return worked;
  392. }
  393. char * strptime(const char *s, const char *format, struct tm *tm)
  394. {
  395. bool working = true;
  396. while (working && *format && *s)
  397. {
  398. switch (*format)
  399. {
  400. case '%':
  401. {
  402. ++format;
  403. switch (*format)
  404. {
  405. case 'a':
  406. case 'A': // weekday name
  407. tm->tm_wday = -1;
  408. working = false;
  409. for (size_t i = 0; i < 7; ++ i)
  410. {
  411. size_t len = strlen(strp_weekdays[i]);
  412. if (!strnicmp(strp_weekdays[i], s, len))
  413. {
  414. tm->tm_wday = i;
  415. s += len;
  416. working = true;
  417. break;
  418. }
  419. else if (!strnicmp(strp_weekdays[i], s, 3))
  420. {
  421. tm->tm_wday = i;
  422. s += 3;
  423. working = true;
  424. break;
  425. }
  426. }
  427. break;
  428. case 'b':
  429. case 'B':
  430. case 'h': // month name
  431. tm->tm_mon = -1;
  432. working = false;
  433. for (size_t i = 0; i < 12; ++ i)
  434. {
  435. size_t len = strlen(strp_monthnames[i]);
  436. if (!strnicmp(strp_monthnames[i], s, len))
  437. {
  438. tm->tm_mon = i;
  439. s += len;
  440. working = true;
  441. break;
  442. }
  443. else if (!strnicmp(strp_monthnames[i], s, 3))
  444. {
  445. tm->tm_mon = i;
  446. s += 3;
  447. working = true;
  448. break;
  449. }
  450. }
  451. break;
  452. case 'd':
  453. case 'e': // day of month number
  454. working = strp_atoi(s, tm->tm_mday, 1, 31, 0);
  455. break;
  456. case 'D': // %m/%d/%y
  457. {
  458. const char * s_save = s;
  459. working = strp_atoi(s, tm->tm_mon, 1, 12, -1);
  460. if (working && *s == '/')
  461. {
  462. ++ s;
  463. working = strp_atoi(s, tm->tm_mday, 1, 31, 0);
  464. if (working && *s == '/')
  465. {
  466. ++ s;
  467. working = strp_atoi(s, tm->tm_year, 0, 99, 0);
  468. if (working && tm->tm_year < 69)
  469. tm->tm_year += 100;
  470. }
  471. }
  472. if (!working)
  473. s = s_save;
  474. }
  475. break;
  476. case 'H': // hour
  477. working = strp_atoi(s, tm->tm_hour, 0, 23, 0);
  478. break;
  479. case 'I': // hour 12-hour clock
  480. working = strp_atoi(s, tm->tm_hour, 1, 12, 0);
  481. break;
  482. case 'j': // day number of year
  483. working = strp_atoi(s, tm->tm_yday, 1, 366, -1);
  484. break;
  485. case 'm': // month number
  486. working = strp_atoi(s, tm->tm_mon, 1, 12, -1);
  487. break;
  488. case 'M': // minute
  489. working = strp_atoi(s, tm->tm_min, 0, 59, 0);
  490. break;
  491. case 'n': // arbitrary whitespace
  492. case 't':
  493. while (isspace((int)*s))
  494. ++s;
  495. break;
  496. case 'p': // am / pm
  497. if (!strnicmp(s, "am", 2))
  498. { // the hour will be 1 -> 12 maps to 12 am, 1 am .. 11 am, 12 noon 12 pm .. 11 pm
  499. if (tm->tm_hour == 12) // 12 am == 00 hours
  500. tm->tm_hour = 0;
  501. }
  502. else if (!strnicmp(s, "pm", 2))
  503. {
  504. if (tm->tm_hour < 12) // 12 pm == 12 hours
  505. tm->tm_hour += 12; // 1 pm -> 13 hours, 11 pm -> 23 hours
  506. }
  507. else
  508. working = false;
  509. break;
  510. case 'r': // 12 hour clock %I:%M:%S %p
  511. {
  512. const char * s_save = s;
  513. working = strp_atoi(s, tm->tm_hour, 1, 12, 0);
  514. if (working && *s == ':')
  515. {
  516. ++ s;
  517. working = strp_atoi(s, tm->tm_min, 0, 59, 0);
  518. if (working && *s == ':')
  519. {
  520. ++ s;
  521. working = strp_atoi(s, tm->tm_sec, 0, 60, 0);
  522. if (working && isspace((int)*s))
  523. {
  524. ++ s;
  525. while (isspace((int)*s))
  526. ++s;
  527. if (!strnicmp(s, "am", 2))
  528. { // the hour will be 1 -> 12 maps to 12 am, 1 am .. 11 am, 12 noon 12 pm .. 11 pm
  529. if (tm->tm_hour == 12) // 12 am == 00 hours
  530. tm->tm_hour = 0;
  531. }
  532. else if (!strnicmp(s, "pm", 2))
  533. {
  534. if (tm->tm_hour < 12) // 12 pm == 12 hours
  535. tm->tm_hour += 12; // 1 pm -> 13 hours, 11 pm -> 23 hours
  536. }
  537. else
  538. working = false;
  539. }
  540. }
  541. }
  542. if (!working)
  543. s = s_save;
  544. }
  545. break;
  546. case 'R': // %H:%M
  547. {
  548. const char * s_save = s;
  549. working = strp_atoi(s, tm->tm_hour, 0, 23, 0);
  550. if (working && *s == ':')
  551. {
  552. ++ s;
  553. working = strp_atoi(s, tm->tm_min, 0, 59, 0);
  554. }
  555. if (!working)
  556. s = s_save;
  557. }
  558. break;
  559. case 'S': // seconds
  560. working = strp_atoi(s, tm->tm_sec, 0, 60, 0);
  561. break;
  562. case 'T': // %H:%M:%S
  563. {
  564. const char * s_save = s;
  565. working = strp_atoi(s, tm->tm_hour, 0, 23, 0);
  566. if (working && *s == ':')
  567. {
  568. ++ s;
  569. working = strp_atoi(s, tm->tm_min, 0, 59, 0);
  570. if (working && *s == ':')
  571. {
  572. ++ s;
  573. working = strp_atoi(s, tm->tm_sec, 0, 60, 0);
  574. }
  575. }
  576. if (!working)
  577. s = s_save;
  578. }
  579. break;
  580. case 'w': // weekday number 0->6 sunday->saturday
  581. working = strp_atoi(s, tm->tm_wday, 0, 6, 0);
  582. break;
  583. case 'Y': // year
  584. working = strp_atoi(s, tm->tm_year, 1900, 65535, -1900);
  585. break;
  586. case 'y': // 2-digit year
  587. working = strp_atoi(s, tm->tm_year, 0, 99, 0);
  588. if (working && tm->tm_year < 69)
  589. tm->tm_year += 100;
  590. break;
  591. case '%': // escaped
  592. if (*s != '%')
  593. working = false;
  594. ++s;
  595. break;
  596. default:
  597. working = false;
  598. }
  599. }
  600. break;
  601. case ' ':
  602. case '\t':
  603. case '\r':
  604. case '\n':
  605. case '\f':
  606. case '\v':
  607. // zero or more whitespaces:
  608. while (isspace((int)*s))
  609. ++ s;
  610. break;
  611. default:
  612. // match character
  613. if (*s != *format)
  614. working = false;
  615. else
  616. ++s;
  617. break;
  618. }
  619. ++format;
  620. }
  621. return (working?(char *)s:0);
  622. }
  623. #endif