comparison libpurple/protocols/msn/msnutils.c @ 20400:ea9a5566a156

propagate from branch 'im.pidgin.rlaager.merging.msnp13-and-sf-1621854-4' (head 36b8a3e05397b5918f311a046fa580c5bb8846e0) to branch 'im.pidgin.cpw.khc.msnp14' (head 39ac2e1b7754245b292605b0e3055ebb1b954c5d)
author Richard Laager <rlaager@wiktel.com>
date Sun, 15 Apr 2007 05:00:56 +0000
parents
children 599b5a4aebab
comparison
equal deleted inserted replaced
20395:bb940f08c820 20400:ea9a5566a156
1 /**
2 * @file msnutils.c Utility functions
3 *
4 * purple
5 *
6 * Purple is the legal property of its developers, whose names are too numerous
7 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * source distribution.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24 #include "msn.h"
25 #include "msnutils.h"
26 #include "time.h"
27 //#include <openssl/md5.h>
28
29 char *rand_guid(void);
30
31 /**************************************************************************
32 * Util
33 **************************************************************************/
34 char *
35 rand_guid()
36 {
37 return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X",
38 rand() % 0xAAFF + 0x1111,
39 rand() % 0xAAFF + 0x1111,
40 rand() % 0xAAFF + 0x1111,
41 rand() % 0xAAFF + 0x1111,
42 rand() % 0xAAFF + 0x1111,
43 rand() % 0xAAFF + 0x1111,
44 rand() % 0xAAFF + 0x1111,
45 rand() % 0xAAFF + 0x1111);
46 }
47
48 void
49 msn_parse_format(const char *mime, char **pre_ret, char **post_ret)
50 {
51 char *cur;
52 GString *pre = g_string_new(NULL);
53 GString *post = g_string_new(NULL);
54 unsigned int colors[3];
55
56 if (pre_ret != NULL) *pre_ret = NULL;
57 if (post_ret != NULL) *post_ret = NULL;
58
59 cur = strstr(mime, "FN=");
60
61 if (cur && (*(cur = cur + 3) != ';'))
62 {
63 pre = g_string_append(pre, "<FONT FACE=\"");
64
65 while (*cur && *cur != ';')
66 {
67 pre = g_string_append_c(pre, *cur);
68 cur++;
69 }
70
71 pre = g_string_append(pre, "\">");
72 post = g_string_prepend(post, "</FONT>");
73 }
74
75 cur = strstr(mime, "EF=");
76
77 if (cur && (*(cur = cur + 3) != ';'))
78 {
79 while (*cur && *cur != ';')
80 {
81 pre = g_string_append_c(pre, '<');
82 pre = g_string_append_c(pre, *cur);
83 pre = g_string_append_c(pre, '>');
84 post = g_string_prepend_c(post, '>');
85 post = g_string_prepend_c(post, *cur);
86 post = g_string_prepend_c(post, '/');
87 post = g_string_prepend_c(post, '<');
88 cur++;
89 }
90 }
91
92 cur = strstr(mime, "CO=");
93
94 if (cur && (*(cur = cur + 3) != ';'))
95 {
96 int i;
97
98 i = sscanf(cur, "%02x%02x%02x;", &colors[0], &colors[1], &colors[2]);
99
100 if (i > 0)
101 {
102 char tag[64];
103
104 if (i == 1)
105 {
106 colors[1] = 0;
107 colors[2] = 0;
108 }
109 else if (i == 2)
110 {
111 unsigned int temp = colors[0];
112
113 colors[0] = colors[1];
114 colors[1] = temp;
115 colors[2] = 0;
116 }
117 else if (i == 3)
118 {
119 unsigned int temp = colors[2];
120
121 colors[2] = colors[0];
122 colors[0] = temp;
123 }
124
125 g_snprintf(tag, sizeof(tag),
126 "<FONT COLOR=\"#%02hhx%02hhx%02hhx\">",
127 colors[0], colors[1], colors[2]);
128
129 pre = g_string_append(pre, tag);
130 post = g_string_prepend(post, "</FONT>");
131 }
132 }
133
134 cur = strstr(mime, "RL=");
135
136 if (cur && (*(cur = cur + 3) != ';'))
137 {
138 if (*cur == '1')
139 {
140 /* RTL text was received */
141 pre = g_string_append(pre, "<SPAN style=\"direction:rtl;text-align:right;\">");
142 post = g_string_prepend(post, "</SPAN>");
143 }
144 }
145
146 cur = g_strdup(purple_url_decode(pre->str));
147 g_string_free(pre, TRUE);
148
149 if (pre_ret != NULL)
150 *pre_ret = cur;
151 else
152 g_free(cur);
153
154 cur = g_strdup(purple_url_decode(post->str));
155 g_string_free(post, TRUE);
156
157 if (post_ret != NULL)
158 *post_ret = cur;
159 else
160 g_free(cur);
161 }
162
163 /*encode the str to RFC2047 style
164 * Currently only support the UTF-8 and base64 encode
165 */
166 char *
167 msn_encode_mime(const char *str)
168 {
169 char *base64;
170
171 base64 = gaim_base64_encode((guchar *)str, strlen(str));
172 return g_strdup_printf("=?utf-8?B?%s?=", base64);
173 }
174
175 /*
176 * We need this because we're only supposed to encode spaces in the font
177 * names. purple_url_encode() isn't acceptable.
178 */
179 static const char *
180 encode_spaces(const char *str)
181 {
182 static char buf[BUF_LEN];
183 const char *c;
184 char *d;
185
186 g_return_val_if_fail(str != NULL, NULL);
187
188 for (c = str, d = buf; *c != '\0'; c++)
189 {
190 if (*c == ' ')
191 {
192 *d++ = '%';
193 *d++ = '2';
194 *d++ = '0';
195 }
196 else
197 *d++ = *c;
198 }
199
200 return buf;
201 }
202
203 /*
204 * Taken from the zephyr plugin.
205 * This parses HTML formatting (put out by one of the gtkimhtml widgets
206 * and converts it to msn formatting. It doesn't deal with the tag closing,
207 * but gtkimhtml widgets give valid html.
208 * It currently deals properly with <b>, <u>, <i>, <font face=...>,
209 * <font color=...>, <span dir=...>, <span style="direction: ...">.
210 * It ignores <font back=...> and <font size=...>
211 */
212 void
213 msn_import_html(const char *html, char **attributes, char **message)
214 {
215 int len, retcount = 0;
216 const char *c;
217 char *msg;
218 char *fontface = NULL;
219 char fonteffect[4];
220 char fontcolor[7];
221 char direction = '0';
222
223 gboolean has_bold = FALSE;
224 gboolean has_italic = FALSE;
225 gboolean has_underline = FALSE;
226 gboolean has_strikethrough = FALSE;
227
228 g_return_if_fail(html != NULL);
229 g_return_if_fail(attributes != NULL);
230 g_return_if_fail(message != NULL);
231
232 len = strlen(html);
233 msg = g_malloc0(len + 1);
234
235 memset(fontcolor, 0, sizeof(fontcolor));
236 strcat(fontcolor, "0");
237 memset(fonteffect, 0, sizeof(fonteffect));
238
239 for (c = html; *c != '\0';)
240 {
241 if (*c == '<')
242 {
243 if (!g_ascii_strncasecmp(c + 1, "br>", 3))
244 {
245 msg[retcount++] = '\r';
246 msg[retcount++] = '\n';
247 c += 4;
248 }
249 else if (!g_ascii_strncasecmp(c + 1, "i>", 2))
250 {
251 if (!has_italic)
252 {
253 strcat(fonteffect, "I");
254 has_italic = TRUE;
255 }
256 c += 3;
257 }
258 else if (!g_ascii_strncasecmp(c + 1, "b>", 2))
259 {
260 if (!has_bold)
261 {
262 strcat(fonteffect, "B");
263 has_bold = TRUE;
264 }
265 c += 3;
266 }
267 else if (!g_ascii_strncasecmp(c + 1, "u>", 2))
268 {
269 if (!has_underline)
270 {
271 strcat(fonteffect, "U");
272 has_underline = TRUE;
273 }
274 c += 3;
275 }
276 else if (!g_ascii_strncasecmp(c + 1, "s>", 2))
277 {
278 if (!has_strikethrough)
279 {
280 strcat(fonteffect, "S");
281 has_strikethrough = TRUE;
282 }
283 c += 3;
284 }
285 else if (!g_ascii_strncasecmp(c + 1, "a href=\"", 8))
286 {
287 c += 9;
288
289 if (!g_ascii_strncasecmp(c, "mailto:", 7))
290 c += 7;
291
292 while ((*c != '\0') && g_ascii_strncasecmp(c, "\">", 2))
293 msg[retcount++] = *c++;
294
295 if (*c != '\0')
296 c += 2;
297
298 /* ignore descriptive string */
299 while ((*c != '\0') && g_ascii_strncasecmp(c, "</a>", 4))
300 c++;
301
302 if (*c != '\0')
303 c += 4;
304 }
305 else if (!g_ascii_strncasecmp(c + 1, "span", 4))
306 {
307 /* Bi-directional text support using CSS properties in span tags */
308 c += 5;
309
310 while (*c != '\0' && *c != '>')
311 {
312 while (*c == ' ')
313 c++;
314 if (!g_ascii_strncasecmp(c, "dir=\"rtl\"", 9))
315 {
316 c += 9;
317 direction = '1';
318 }
319 else if (!g_ascii_strncasecmp(c, "style=\"", 7))
320 {
321 /* Parse inline CSS attributes */
322 char *attributes;
323 int attr_len = 0;
324 c += 7;
325 while (*(c + attr_len) != '\0' && *(c + attr_len) != '"')
326 attr_len++;
327 if (*(c + attr_len) == '"')
328 {
329 char *attr_dir;
330 attributes = g_strndup(c, attr_len);
331 attr_dir = purple_markup_get_css_property(attributes, "direction");
332 if (attr_dir && (!strncasecmp(attr_dir, "RTL", 3)))
333 direction = '1';
334 g_free(attr_dir);
335 g_free(attributes);
336 }
337
338 }
339 else
340 {
341 c++;
342 }
343 }
344 if (*c == '>')
345 c++;
346 }
347 else if (!g_ascii_strncasecmp(c + 1, "font", 4))
348 {
349 c += 5;
350
351 while ((*c != '\0') && !g_ascii_strncasecmp(c, " ", 1))
352 c++;
353
354 if (!g_ascii_strncasecmp(c, "color=\"#", 7))
355 {
356 c += 8;
357
358 fontcolor[0] = *(c + 4);
359 fontcolor[1] = *(c + 5);
360 fontcolor[2] = *(c + 2);
361 fontcolor[3] = *(c + 3);
362 fontcolor[4] = *c;
363 fontcolor[5] = *(c + 1);
364
365 c += 8;
366 }
367 else if (!g_ascii_strncasecmp(c, "face=\"", 6))
368 {
369 const char *end = NULL;
370 const char *comma = NULL;
371 unsigned int namelen = 0;
372
373 c += 6;
374 end = strchr(c, '\"');
375 comma = strchr(c, ',');
376
377 if (comma == NULL || comma > end)
378 namelen = (unsigned int)(end - c);
379 else
380 namelen = (unsigned int)(comma - c);
381
382 fontface = g_strndup(c, namelen);
383 c = end + 2;
384 }
385 else
386 {
387 /* Drop all unrecognized/misparsed font tags */
388 while ((*c != '\0') && g_ascii_strncasecmp(c, "\">", 2))
389 c++;
390
391 if (*c != '\0')
392 c += 2;
393 }
394 }
395 else
396 {
397 while ((*c != '\0') && (*c != '>'))
398 c++;
399 if (*c != '\0')
400 c++;
401 }
402 }
403 else if (*c == '&')
404 {
405 if (!g_ascii_strncasecmp(c, "&lt;", 4))
406 {
407 msg[retcount++] = '<';
408 c += 4;
409 }
410 else if (!g_ascii_strncasecmp(c, "&gt;", 4))
411 {
412 msg[retcount++] = '>';
413 c += 4;
414 }
415 else if (!g_ascii_strncasecmp(c, "&nbsp;", 6))
416 {
417 msg[retcount++] = ' ';
418 c += 6;
419 }
420 else if (!g_ascii_strncasecmp(c, "&quot;", 6))
421 {
422 msg[retcount++] = '"';
423 c += 6;
424 }
425 else if (!g_ascii_strncasecmp(c, "&amp;", 5))
426 {
427 msg[retcount++] = '&';
428 c += 5;
429 }
430 else if (!g_ascii_strncasecmp(c, "&apos;", 6))
431 {
432 msg[retcount++] = '\'';
433 c += 6;
434 }
435 else
436 msg[retcount++] = *c++;
437 }
438 else
439 msg[retcount++] = *c++;
440 }
441
442 if (fontface == NULL)
443 fontface = g_strdup("MS Sans Serif");
444
445 *attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c",
446 encode_spaces(fontface),
447 fonteffect, fontcolor, direction);
448 *message = g_strdup(msg);
449
450 g_free(fontface);
451 g_free(msg);
452 }
453
454 void
455 msn_parse_socket(const char *str, char **ret_host, int *ret_port)
456 {
457 char *host;
458 char *c;
459 int port;
460
461 host = g_strdup(str);
462
463 if ((c = strchr(host, ':')) != NULL){
464 *c = '\0';
465 port = atoi(c + 1);
466 }else{
467 port = 1863;
468 }
469
470 *ret_host = host;
471 *ret_port = port;
472 }
473 /***************************************************************************
474 * MSN Time Related Funciton
475 ***************************************************************************/
476 #if 0
477 int
478 msn_convert_iso8601(const char *timestr,struct tm tm_time)
479 {
480 char temp[64];
481 struct tm ctime;
482 time_t ts;
483
484 gaim_debug_info("MaYuan","convert string is{%s}\n",timestr);
485 tzset();
486 /*copy string first*/
487 memset(temp, 0, sizeof(temp));
488 strncpy(temp, timestr, strlen(timestr));
489
490 /*convert via strptime()*/
491 memset(&ctime, 0, sizeof(struct tm));
492 strptime(temp, "%d %b %Y %T %Z", &ctime);
493 ts = mktime(&ctime) - timezone;
494 localtime_r(&ts, tm_time);
495 }
496 #endif
497
498 /***************************************************************************
499 * MSN Challenge Computing Function
500 ***************************************************************************/
501 /*check the edian of system*/
502 int
503 isBigEndian(void)
504 {
505 short int word = 0x0100;
506 char *byte = (char *)&word;
507
508 return(byte[0]);
509 }
510
511 /*swap utility*/
512 unsigned int
513 swapInt(unsigned int dw)
514 {
515 unsigned int tmp;
516 tmp = (dw & 0x000000FF);
517 tmp = ((dw & 0x0000FF00) >> 0x08) | (tmp << 0x08);
518 tmp = ((dw & 0x00FF0000) >> 0x10) | (tmp << 0x08);
519 tmp = ((dw & 0xFF000000) >> 0x18) | (tmp << 0x08);
520 return(tmp);
521 }
522
523 /*
524 * Handle MSN Chanllege computation
525 *This algorithm reference with http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges
526 */
527 #define BUFSIZE 256
528 void
529 msn_handle_chl(char *input, char *output)
530 {
531 GaimCipher *cipher;
532 GaimCipherContext *context;
533 char *productKey = MSNP13_WLM_PRODUCT_KEY,
534 *productID = MSNP13_WLM_PRODUCT_ID,
535 *hexChars = "0123456789abcdef",
536 buf[BUFSIZE];
537 unsigned char md5Hash[16], *newHash;
538 unsigned int *md5Parts, *chlStringParts, newHashParts[5];
539
540 long long nHigh=0, nLow=0;
541
542 int i, bigEndian;
543
544 /* Determine our endianess */
545 bigEndian = isBigEndian();
546
547 /* Create the MD5 hash by using Gaim MD5 algorithm*/
548 cipher = gaim_ciphers_find_cipher("md5");
549 context = gaim_cipher_context_new(cipher, NULL);
550
551 gaim_cipher_context_append(context, (const guchar *)input,
552 strlen(input));
553 gaim_cipher_context_append(context, (const guchar *)productKey,
554 strlen(productKey));
555 gaim_cipher_context_digest(context, sizeof(md5Hash), md5Hash, NULL);
556 gaim_cipher_context_destroy(context);
557
558 /* Split it into four integers */
559 md5Parts = (unsigned int *)md5Hash;
560 for(i=0; i<4; i++){
561 /* check for endianess */
562 if(bigEndian)
563 md5Parts[i] = swapInt(md5Parts[i]);
564
565 /* & each integer with 0x7FFFFFFF */
566 /* and save one unmodified array for later */
567 newHashParts[i] = md5Parts[i];
568 md5Parts[i] &= 0x7FFFFFFF;
569 }
570
571 /* make a new string and pad with '0' */
572 snprintf(buf, BUFSIZE-5, "%s%s", input, productID);
573 i = strlen(buf);
574 memset(&buf[i], '0', 8 - (i % 8));
575 buf[i + (8 - (i % 8))]='\0';
576
577 /* split into integers */
578 chlStringParts = (unsigned int *)buf;
579
580 /* this is magic */
581 for (i=0; i<(strlen(buf)/4)-1; i+=2){
582 long long temp;
583
584 if(bigEndian){
585 chlStringParts[i] = swapInt(chlStringParts[i]);
586 chlStringParts[i+1] = swapInt(chlStringParts[i+1]);
587 }
588
589 temp=(md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
590 nHigh=(md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
591 nLow=nLow + nHigh + temp;
592 }
593 nHigh=(nHigh+md5Parts[1]) % 0x7FFFFFFF;
594 nLow=(nLow+md5Parts[3]) % 0x7FFFFFFF;
595
596 newHashParts[0]^=nHigh;
597 newHashParts[1]^=nLow;
598 newHashParts[2]^=nHigh;
599 newHashParts[3]^=nLow;
600
601 /* swap more bytes if big endian */
602 for(i=0; i<4 && bigEndian; i++)
603 newHashParts[i] = swapInt(newHashParts[i]);
604
605 /* make a string of the parts */
606 newHash = (unsigned char *)newHashParts;
607
608 /* convert to hexadecimal */
609 for (i=0; i<16; i++)
610 {
611 output[i*2]=hexChars[(newHash[i]>>4)&0xF];
612 output[(i*2)+1]=hexChars[newHash[i]&0xF];
613 }
614
615 output[32]='\0';
616
617 // gaim_debug_info("MaYuan","chl output{%s}\n",output);
618 }
619
620 #if (!defined(_XOPEN_SOURCE))||defined(_WIN32)
621
622 #ifndef __P
623 # if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
624 # define __P(args) args
625 # else
626 # define __P(args) ()
627 # endif /* GCC. */
628 #endif /* Not __P. */
629
630 #if defined(HAVE_LOCALTIME_R) && ! HAVE_LOCALTIME_R && ! defined localtime_r
631 # ifdef _LIBC
632 # define localtime_r __localtime_r
633 # else
634 /* Approximate localtime_r as best we can in its absence. */
635 # define localtime_r my_localtime_r
636 static struct tm *localtime_r __P ((const time_t *, struct tm *));
637 static struct tm *
638 localtime_r (t, tp)
639 const time_t *t;
640 struct tm *tp;
641 {
642 struct tm *l = localtime (t);
643 if (! l)
644 return 0;
645 *tp = *l;
646 return tp;
647 }
648 # endif /* ! _LIBC */
649 #endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */
650
651
652 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
653
654 #if defined __GNUC__ && __GNUC__ >= 2
655 # define match_string(cs1, s2) \
656 ({ size_t len = strlen (cs1); \
657 int result = strncasecmp ((cs1), (s2), len) == 0; \
658 if (result) (s2) += len; \
659 result; })
660 #else
661 /* Oh come on. Get a reasonable compiler. */
662 # define match_string(cs1, s2) \
663 (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
664 #endif
665
666 /* We intentionally do not use isdigit() for testing because this will
667 lead to problems with the wide character version. */
668 #define get_number(from, to, n) \
669 do { \
670 int __n = n; \
671 val = 0; \
672 while (*rp == ' ') \
673 ++rp; \
674 if ((*rp < '0') || (*rp > '9')) \
675 return NULL; \
676 do { \
677 val *= 10; \
678 val += *rp++ - '0'; \
679 } while ((--__n > 0) && (val * 10 <= to) && (*rp >= '0') && (*rp <= '9')); \
680 if ((val < from) || (val > to)) \
681 return NULL; \
682 } while (0)
683
684 #ifdef _NL_CURRENT
685 # define get_alt_number(from, to, n) \
686 ({ \
687 __label__ do_normal; \
688 if (*decided != raw) \
689 { \
690 const char *alts = _NL_CURRENT (LC_TIME, ALT_DIGITS); \
691 int __n = n; \
692 int any = 0; \
693 while (*rp == ' ') \
694 ++rp; \
695 val = 0; \
696 do { \
697 val *= 10; \
698 while (*alts != '\0') \
699 { \
700 size_t len = strlen (alts); \
701 if (strncasecmp (alts, rp, len) == 0) \
702 break; \
703 alts += len + 1; \
704 ++val; \
705 } \
706 if (*alts == '\0') \
707 { \
708 if (*decided == not && ! any) \
709 goto do_normal; \
710 /* If we haven't read anything it's an error. */ \
711 if (! any) \
712 return NULL; \
713 /* Correct the premature multiplication. */ \
714 val /= 10; \
715 break; \
716 } \
717 else \
718 *decided = loc; \
719 } while (--__n > 0 && val * 10 <= to); \
720 if (val < from || val > to) \
721 return NULL; \
722 } \
723 else \
724 { \
725 do_normal: \
726 get_number (from, to, n); \
727 } \
728 0; \
729 })
730 #else
731 # define get_alt_number(from, to, n) \
732 /* We don't have the alternate representation. */ \
733 get_number(from, to, n)
734 #endif
735
736 #define recursive(new_fmt) \
737 (*(new_fmt) != '\0' \
738 && (rp = strptime_internal (rp, (new_fmt), tm, decided, era_cnt)) != NULL)
739
740
741 #ifdef _LIBC
742 /* This is defined in locale/C-time.c in the GNU libc. */
743 extern const struct locale_data _nl_C_LC_TIME;
744 extern const unsigned short int __mon_yday[2][13];
745
746 # define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (DAY_1)].string)
747 # define ab_weekday_name \
748 (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string)
749 # define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string)
750 # define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string)
751 # define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string)
752 # define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_FMT)].string)
753 # define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (AM_STR)].string)
754 # define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (PM_STR)].string)
755 # define HERE_T_FMT_AMPM \
756 (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT_AMPM)].string)
757 # define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT)].string)
758
759 # define strncasecmp(s1, s2, n) __strncasecmp (s1, s2, n)
760 #else
761 static char const weekday_name[][10] =
762 {
763 "Sunday", "Monday", "Tuesday", "Wednesday",
764 "Thursday", "Friday", "Saturday"
765 };
766 static char const ab_weekday_name[][4] =
767 {
768 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
769 };
770 static char const month_name[][10] =
771 {
772 "January", "February", "March", "April", "May", "June",
773 "July", "August", "September", "October", "November", "December"
774 };
775 static char const ab_month_name[][4] =
776 {
777 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
778 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
779 };
780 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
781 # define HERE_D_FMT "%m/%d/%y"
782 # define HERE_AM_STR "AM"
783 # define HERE_PM_STR "PM"
784 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
785 # define HERE_T_FMT "%H:%M:%S"
786
787 const unsigned short int __mon_yday[2][13] =
788 {
789 /* Normal years. */
790 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
791 /* Leap years. */
792 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
793 };
794 #endif
795
796 /* Status of lookup: do we use the locale data or the raw data? */
797 enum locale_status { not, loc, raw };
798
799
800 #ifndef __isleap
801 /* Nonzero if YEAR is a leap year (every 4 years,
802 except every 100th isn't, and every 400th is). */
803 # define __isleap(year) \
804 ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
805 #endif
806
807 /* Compute the day of the week. */
808 static void
809 day_of_the_week (struct tm *tm)
810 {
811 /* We know that January 1st 1970 was a Thursday (= 4). Compute the
812 the difference between this data in the one on TM and so determine
813 the weekday. */
814 int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
815 int wday = (-473
816 + (365 * (tm->tm_year - 70))
817 + (corr_year / 4)
818 - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0)
819 + (((corr_year / 4) / 25) / 4)
820 + __mon_yday[0][tm->tm_mon]
821 + tm->tm_mday - 1);
822 tm->tm_wday = ((wday % 7) + 7) % 7;
823 }
824
825 /* Compute the day of the year. */
826 static void
827 day_of_the_year (struct tm *tm)
828 {
829 tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon]
830 + (tm->tm_mday - 1));
831 }
832
833 static char *
834 #ifdef _LIBC
835 internal_function
836 #endif
837 strptime_internal __P ((const char *rp, const char *fmt, struct tm *tm,
838 enum locale_status *decided, int era_cnt));
839
840 static char *
841 #ifdef _LIBC
842 internal_function
843 #endif
844 strptime_internal (rp, fmt, tm, decided, era_cnt)
845 const char *rp;
846 const char *fmt;
847 struct tm *tm;
848 enum locale_status *decided;
849 int era_cnt;
850 {
851 const char *rp_backup;
852 int cnt;
853 size_t val;
854 int have_I, is_pm;
855 int century, want_century;
856 int want_era;
857 int have_wday, want_xday;
858 int have_yday;
859 int have_mon, have_mday;
860 #ifdef _NL_CURRENT
861 size_t num_eras;
862 #endif
863 struct era_entry *era;
864
865 have_I = is_pm = 0;
866 century = -1;
867 want_century = 0;
868 want_era = 0;
869 era = NULL;
870
871 have_wday = want_xday = have_yday = have_mon = have_mday = 0;
872
873 while (*fmt != '\0')
874 {
875 /* A white space in the format string matches 0 more or white
876 space in the input string. */
877 if (isspace (*fmt))
878 {
879 while (isspace (*rp))
880 ++rp;
881 ++fmt;
882 continue;
883 }
884
885 /* Any character but `%' must be matched by the same character
886 in the iput string. */
887 if (*fmt != '%')
888 {
889 match_char (*fmt++, *rp++);
890 continue;
891 }
892
893 ++fmt;
894 #ifndef _NL_CURRENT
895 /* We need this for handling the `E' modifier. */
896 start_over:
897 #endif
898
899 /* Make back up of current processing pointer. */
900 rp_backup = rp;
901
902 switch (*fmt++)
903 {
904 case '%':
905 /* Match the `%' character itself. */
906 match_char ('%', *rp++);
907 break;
908 case 'a':
909 case 'A':
910 /* Match day of week. */
911 for (cnt = 0; cnt < 7; ++cnt)
912 {
913 #ifdef _NL_CURRENT
914 if (*decided !=raw)
915 {
916 if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp))
917 {
918 if (*decided == not
919 && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt),
920 weekday_name[cnt]))
921 *decided = loc;
922 break;
923 }
924 if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp))
925 {
926 if (*decided == not
927 && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt),
928 ab_weekday_name[cnt]))
929 *decided = loc;
930 break;
931 }
932 }
933 #endif
934 if (*decided != loc
935 && (match_string (weekday_name[cnt], rp)
936 || match_string (ab_weekday_name[cnt], rp)))
937 {
938 *decided = raw;
939 break;
940 }
941 }
942 if (cnt == 7)
943 /* Does not match a weekday name. */
944 return NULL;
945 tm->tm_wday = cnt;
946 have_wday = 1;
947 break;
948 case 'b':
949 case 'B':
950 case 'h':
951 /* Match month name. */
952 for (cnt = 0; cnt < 12; ++cnt)
953 {
954 #ifdef _NL_CURRENT
955 if (*decided !=raw)
956 {
957 if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp))
958 {
959 if (*decided == not
960 && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt),
961 month_name[cnt]))
962 *decided = loc;
963 break;
964 }
965 if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp))
966 {
967 if (*decided == not
968 && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt),
969 ab_month_name[cnt]))
970 *decided = loc;
971 break;
972 }
973 }
974 #endif
975 if (match_string (month_name[cnt], rp)
976 || match_string (ab_month_name[cnt], rp))
977 {
978 *decided = raw;
979 break;
980 }
981 }
982 if (cnt == 12)
983 /* Does not match a month name. */
984 return NULL;
985 tm->tm_mon = cnt;
986 want_xday = 1;
987 break;
988 case 'c':
989 /* Match locale's date and time format. */
990 #ifdef _NL_CURRENT
991 if (*decided != raw)
992 {
993 if (!recursive (_NL_CURRENT (LC_TIME, D_T_FMT)))
994 {
995 if (*decided == loc)
996 return NULL;
997 else
998 rp = rp_backup;
999 }
1000 else
1001 {
1002 if (*decided == not &&
1003 strcmp (_NL_CURRENT (LC_TIME, D_T_FMT), HERE_D_T_FMT))
1004 *decided = loc;
1005 want_xday = 1;
1006 break;
1007 }
1008 *decided = raw;
1009 }
1010 #endif
1011 if (!recursive (HERE_D_T_FMT))
1012 return NULL;
1013 want_xday = 1;
1014 break;
1015 case 'C':
1016 /* Match century number. */
1017 #ifdef _NL_CURRENT
1018 match_century:
1019 #endif
1020 get_number (0, 99, 2);
1021 century = val;
1022 want_xday = 1;
1023 break;
1024 case 'd':
1025 case 'e':
1026 /* Match day of month. */
1027 get_number (1, 31, 2);
1028 tm->tm_mday = val;
1029 have_mday = 1;
1030 want_xday = 1;
1031 break;
1032 case 'F':
1033 if (!recursive ("%Y-%m-%d"))
1034 return NULL;
1035 want_xday = 1;
1036 break;
1037 case 'x':
1038 #ifdef _NL_CURRENT
1039 if (*decided != raw)
1040 {
1041 if (!recursive (_NL_CURRENT (LC_TIME, D_FMT)))
1042 {
1043 if (*decided == loc)
1044 return NULL;
1045 else
1046 rp = rp_backup;
1047 }
1048 else
1049 {
1050 if (*decided == not
1051 && strcmp (_NL_CURRENT (LC_TIME, D_FMT), HERE_D_FMT))
1052 *decided = loc;
1053 want_xday = 1;
1054 break;
1055 }
1056 *decided = raw;
1057 }
1058 #endif
1059 /* Fall through. */
1060 case 'D':
1061 /* Match standard day format. */
1062 if (!recursive (HERE_D_FMT))
1063 return NULL;
1064 want_xday = 1;
1065 break;
1066 case 'k':
1067 case 'H':
1068 /* Match hour in 24-hour clock. */
1069 get_number (0, 23, 2);
1070 tm->tm_hour = val;
1071 have_I = 0;
1072 break;
1073 case 'I':
1074 /* Match hour in 12-hour clock. */
1075 get_number (1, 12, 2);
1076 tm->tm_hour = val % 12;
1077 have_I = 1;
1078 break;
1079 case 'j':
1080 /* Match day number of year. */
1081 get_number (1, 366, 3);
1082 tm->tm_yday = val - 1;
1083 have_yday = 1;
1084 break;
1085 case 'm':
1086 /* Match number of month. */
1087 get_number (1, 12, 2);
1088 tm->tm_mon = val - 1;
1089 have_mon = 1;
1090 want_xday = 1;
1091 break;
1092 case 'M':
1093 /* Match minute. */
1094 get_number (0, 59, 2);
1095 tm->tm_min = val;
1096 break;
1097 case 'n':
1098 case 't':
1099 /* Match any white space. */
1100 while (isspace (*rp))
1101 ++rp;
1102 break;
1103 case 'p':
1104 /* Match locale's equivalent of AM/PM. */
1105 #ifdef _NL_CURRENT
1106 if (*decided != raw)
1107 {
1108 if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp))
1109 {
1110 if (strcmp (_NL_CURRENT (LC_TIME, AM_STR), HERE_AM_STR))
1111 *decided = loc;
1112 break;
1113 }
1114 if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp))
1115 {
1116 if (strcmp (_NL_CURRENT (LC_TIME, PM_STR), HERE_PM_STR))
1117 *decided = loc;
1118 is_pm = 1;
1119 break;
1120 }
1121 *decided = raw;
1122 }
1123 #endif
1124 if (!match_string (HERE_AM_STR, rp))
1125 if (match_string (HERE_PM_STR, rp))
1126 is_pm = 1;
1127 else
1128 return NULL;
1129 break;
1130 case 'r':
1131 #ifdef _NL_CURRENT
1132 if (*decided != raw)
1133 {
1134 if (!recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM)))
1135 {
1136 if (*decided == loc)
1137 return NULL;
1138 else
1139 rp = rp_backup;
1140 }
1141 else
1142 {
1143 if (*decided == not &&
1144 strcmp (_NL_CURRENT (LC_TIME, T_FMT_AMPM),
1145 HERE_T_FMT_AMPM))
1146 *decided = loc;
1147 break;
1148 }
1149 *decided = raw;
1150 }
1151 #endif
1152 if (!recursive (HERE_T_FMT_AMPM))
1153 return NULL;
1154 break;
1155 case 'R':
1156 if (!recursive ("%H:%M"))
1157 return NULL;
1158 break;
1159 case 's':
1160 {
1161 /* The number of seconds may be very high so we cannot use
1162 the `get_number' macro. Instead read the number
1163 character for character and construct the result while
1164 doing this. */
1165 time_t secs = 0;
1166 if (*rp < '0' || *rp > '9')
1167 /* We need at least one digit. */
1168 return NULL;
1169
1170 do
1171 {
1172 secs *= 10;
1173 secs += *rp++ - '0';
1174 }
1175 while (*rp >= '0' && *rp <= '9');
1176
1177 if (localtime_r (&secs, tm) == NULL)
1178 /* Error in function. */
1179 return NULL;
1180 }
1181 break;
1182 case 'S':
1183 get_number (0, 61, 2);
1184 tm->tm_sec = val;
1185 break;
1186 case 'X':
1187 #ifdef _NL_CURRENT
1188 if (*decided != raw)
1189 {
1190 if (!recursive (_NL_CURRENT (LC_TIME, T_FMT)))
1191 {
1192 if (*decided == loc)
1193 return NULL;
1194 else
1195 rp = rp_backup;
1196 }
1197 else
1198 {
1199 if (strcmp (_NL_CURRENT (LC_TIME, T_FMT), HERE_T_FMT))
1200 *decided = loc;
1201 break;
1202 }
1203 *decided = raw;
1204 }
1205 #endif
1206 /* Fall through. */
1207 case 'T':
1208 if (!recursive (HERE_T_FMT))
1209 return NULL;
1210 break;
1211 case 'u':
1212 get_number (1, 7, 1);
1213 tm->tm_wday = val % 7;
1214 have_wday = 1;
1215 break;
1216 case 'g':
1217 get_number (0, 99, 2);
1218 /* XXX This cannot determine any field in TM. */
1219 break;
1220 case 'G':
1221 if (*rp < '0' || *rp > '9')
1222 return NULL;
1223 /* XXX Ignore the number since we would need some more
1224 information to compute a real date. */
1225 do
1226 ++rp;
1227 while (*rp >= '0' && *rp <= '9');
1228 break;
1229 case 'U':
1230 case 'V':
1231 case 'W':
1232 get_number (0, 53, 2);
1233 /* XXX This cannot determine any field in TM without some
1234 information. */
1235 break;
1236 case 'w':
1237 /* Match number of weekday. */
1238 get_number (0, 6, 1);
1239 tm->tm_wday = val;
1240 have_wday = 1;
1241 break;
1242 case 'y':
1243 #ifdef _NL_CURRENT
1244 match_year_in_century:
1245 #endif
1246 /* Match year within century. */
1247 get_number (0, 99, 2);
1248 /* The "Year 2000: The Millennium Rollover" paper suggests that
1249 values in the range 69-99 refer to the twentieth century. */
1250 tm->tm_year = val >= 69 ? val : val + 100;
1251 /* Indicate that we want to use the century, if specified. */
1252 want_century = 1;
1253 want_xday = 1;
1254 break;
1255 case 'Y':
1256 /* Match year including century number. */
1257 get_number (0, 9999, 4);
1258 tm->tm_year = val - 1900;
1259 want_century = 0;
1260 want_xday = 1;
1261 break;
1262 case 'Z':
1263 /* XXX How to handle this? */
1264 break;
1265 case 'E':
1266 #ifdef _NL_CURRENT
1267 switch (*fmt++)
1268 {
1269 case 'c':
1270 /* Match locale's alternate date and time format. */
1271 if (*decided != raw)
1272 {
1273 const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT);
1274
1275 if (*fmt == '\0')
1276 fmt = _NL_CURRENT (LC_TIME, D_T_FMT);
1277
1278 if (!recursive (fmt))
1279 {
1280 if (*decided == loc)
1281 return NULL;
1282 else
1283 rp = rp_backup;
1284 }
1285 else
1286 {
1287 if (strcmp (fmt, HERE_D_T_FMT))
1288 *decided = loc;
1289 want_xday = 1;
1290 break;
1291 }
1292 *decided = raw;
1293 }
1294 /* The C locale has no era information, so use the
1295 normal representation. */
1296 if (!recursive (HERE_D_T_FMT))
1297 return NULL;
1298 want_xday = 1;
1299 break;
1300 case 'C':
1301 if (*decided != raw)
1302 {
1303 if (era_cnt >= 0)
1304 {
1305 era = _nl_select_era_entry (era_cnt);
1306 if (match_string (era->era_name, rp))
1307 {
1308 *decided = loc;
1309 break;
1310 }
1311 else
1312 return NULL;
1313 }
1314 else
1315 {
1316 num_eras = _NL_CURRENT_WORD (LC_TIME,
1317 _NL_TIME_ERA_NUM_ENTRIES);
1318 for (era_cnt = 0; era_cnt < (int) num_eras;
1319 ++era_cnt, rp = rp_backup)
1320 {
1321 era = _nl_select_era_entry (era_cnt);
1322 if (match_string (era->era_name, rp))
1323 {
1324 *decided = loc;
1325 break;
1326 }
1327 }
1328 if (era_cnt == (int) num_eras)
1329 {
1330 era_cnt = -1;
1331 if (*decided == loc)
1332 return NULL;
1333 }
1334 else
1335 break;
1336 }
1337
1338 *decided = raw;
1339 }
1340 /* The C locale has no era information, so use the
1341 normal representation. */
1342 goto match_century;
1343 case 'y':
1344 if (*decided == raw)
1345 goto match_year_in_century;
1346
1347 get_number(0, 9999, 4);
1348 tm->tm_year = val;
1349 want_era = 1;
1350 want_xday = 1;
1351 break;
1352 case 'Y':
1353 if (*decided != raw)
1354 {
1355 num_eras = _NL_CURRENT_WORD (LC_TIME,
1356 _NL_TIME_ERA_NUM_ENTRIES);
1357 for (era_cnt = 0; era_cnt < (int) num_eras;
1358 ++era_cnt, rp = rp_backup)
1359 {
1360 era = _nl_select_era_entry (era_cnt);
1361 if (recursive (era->era_format))
1362 break;
1363 }
1364 if (era_cnt == (int) num_eras)
1365 {
1366 era_cnt = -1;
1367 if (*decided == loc)
1368 return NULL;
1369 else
1370 rp = rp_backup;
1371 }
1372 else
1373 {
1374 *decided = loc;
1375 era_cnt = -1;
1376 break;
1377 }
1378
1379 *decided = raw;
1380 }
1381 get_number (0, 9999, 4);
1382 tm->tm_year = val - 1900;
1383 want_century = 0;
1384 want_xday = 1;
1385 break;
1386 case 'x':
1387 if (*decided != raw)
1388 {
1389 const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT);
1390
1391 if (*fmt == '\0')
1392 fmt = _NL_CURRENT (LC_TIME, D_FMT);
1393
1394 if (!recursive (fmt))
1395 {
1396 if (*decided == loc)
1397 return NULL;
1398 else
1399 rp = rp_backup;
1400 }
1401 else
1402 {
1403 if (strcmp (fmt, HERE_D_FMT))
1404 *decided = loc;
1405 break;
1406 }
1407 *decided = raw;
1408 }
1409 if (!recursive (HERE_D_FMT))
1410 return NULL;
1411 break;
1412 case 'X':
1413 if (*decided != raw)
1414 {
1415 const char *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT);
1416
1417 if (*fmt == '\0')
1418 fmt = _NL_CURRENT (LC_TIME, T_FMT);
1419
1420 if (!recursive (fmt))
1421 {
1422 if (*decided == loc)
1423 return NULL;
1424 else
1425 rp = rp_backup;
1426 }
1427 else
1428 {
1429 if (strcmp (fmt, HERE_T_FMT))
1430 *decided = loc;
1431 break;
1432 }
1433 *decided = raw;
1434 }
1435 if (!recursive (HERE_T_FMT))
1436 return NULL;
1437 break;
1438 default:
1439 return NULL;
1440 }
1441 break;
1442 #else
1443 /* We have no information about the era format. Just use
1444 the normal format. */
1445 if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
1446 && *fmt != 'x' && *fmt != 'X')
1447 /* This is an illegal format. */
1448 return NULL;
1449
1450 goto start_over;
1451 #endif
1452 case 'O':
1453 switch (*fmt++)
1454 {
1455 case 'd':
1456 case 'e':
1457 /* Match day of month using alternate numeric symbols. */
1458 get_alt_number (1, 31, 2);
1459 tm->tm_mday = val;
1460 have_mday = 1;
1461 want_xday = 1;
1462 break;
1463 case 'H':
1464 /* Match hour in 24-hour clock using alternate numeric
1465 symbols. */
1466 get_alt_number (0, 23, 2);
1467 tm->tm_hour = val;
1468 have_I = 0;
1469 break;
1470 case 'I':
1471 /* Match hour in 12-hour clock using alternate numeric
1472 symbols. */
1473 get_alt_number (1, 12, 2);
1474 tm->tm_hour = val - 1;
1475 have_I = 1;
1476 break;
1477 case 'm':
1478 /* Match month using alternate numeric symbols. */
1479 get_alt_number (1, 12, 2);
1480 tm->tm_mon = val - 1;
1481 have_mon = 1;
1482 want_xday = 1;
1483 break;
1484 case 'M':
1485 /* Match minutes using alternate numeric symbols. */
1486 get_alt_number (0, 59, 2);
1487 tm->tm_min = val;
1488 break;
1489 case 'S':
1490 /* Match seconds using alternate numeric symbols. */
1491 get_alt_number (0, 61, 2);
1492 tm->tm_sec = val;
1493 break;
1494 case 'U':
1495 case 'V':
1496 case 'W':
1497 get_alt_number (0, 53, 2);
1498 /* XXX This cannot determine any field in TM without
1499 further information. */
1500 break;
1501 case 'w':
1502 /* Match number of weekday using alternate numeric symbols. */
1503 get_alt_number (0, 6, 1);
1504 tm->tm_wday = val;
1505 have_wday = 1;
1506 break;
1507 case 'y':
1508 /* Match year within century using alternate numeric symbols. */
1509 get_alt_number (0, 99, 2);
1510 tm->tm_year = val >= 69 ? val : val + 100;
1511 want_xday = 1;
1512 break;
1513 default:
1514 return NULL;
1515 }
1516 break;
1517 default:
1518 return NULL;
1519 }
1520 }
1521
1522 if (have_I && is_pm)
1523 tm->tm_hour += 12;
1524
1525 if (century != -1)
1526 {
1527 if (want_century)
1528 tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
1529 else
1530 /* Only the century, but not the year. Strange, but so be it. */
1531 tm->tm_year = (century - 19) * 100;
1532 }
1533
1534 #ifdef _NL_CURRENT
1535 if (era_cnt != -1)
1536 {
1537 era = _nl_select_era_entry(era_cnt);
1538 if (want_era)
1539 tm->tm_year = (era->start_date[0]
1540 + ((tm->tm_year - era->offset)
1541 * era->absolute_direction));
1542 else
1543 /* Era start year assumed. */
1544 tm->tm_year = era->start_date[0];
1545 }
1546 else
1547 #endif
1548 if (want_era)
1549 return NULL;
1550
1551 if (want_xday && !have_wday)
1552 {
1553 if ( !(have_mon && have_mday) && have_yday)
1554 {
1555 /* We don't have tm_mon and/or tm_mday, compute them. */
1556 int t_mon = 0;
1557 while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
1558 t_mon++;
1559 if (!have_mon)
1560 tm->tm_mon = t_mon - 1;
1561 if (!have_mday)
1562 tm->tm_mday =
1563 (tm->tm_yday
1564 - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
1565 }
1566 day_of_the_week (tm);
1567 }
1568 if (want_xday && !have_yday)
1569 day_of_the_year (tm);
1570
1571 return (char *) rp;
1572 }
1573
1574
1575 char *
1576 msn_strptime (buf, format, tm)
1577 const char *buf;
1578 const char *format;
1579 struct tm *tm;
1580 {
1581 enum locale_status decided;
1582
1583 #ifdef _NL_CURRENT
1584 decided = not;
1585 #else
1586 decided = raw;
1587 #endif
1588 return strptime_internal (buf, format, tm, &decided, -1);
1589 }
1590 #else
1591 #define msn_strptime strptime
1592 #endif