utils.h 20 KB

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