summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2016-12-14 03:15:42 +0100
committerGravatar Nikias Bassen2016-12-14 03:15:42 +0100
commitb2b56801b1342837d6d321a56e1f7d9b97015352 (patch)
tree259d10803d077c2f4fb65606309a199bcdfb0897 /src
parentd53d0412e014872b71dd9c91727234de4f08fb2f (diff)
downloadlibplist-b2b56801b1342837d6d321a56e1f7d9b97015352.tar.gz
libplist-b2b56801b1342837d6d321a56e1f7d9b97015352.tar.bz2
xplist: Improve text content parsing, reducing memory usage and unneeded copying
Diffstat (limited to 'src')
-rw-r--r--src/xplist.c431
1 files changed, 318 insertions, 113 deletions
diff --git a/src/xplist.c b/src/xplist.c
index 43b0422..9dd43fe 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -460,60 +460,153 @@ static void find_next(parse_ctx ctx, const char *nextchars, int skip_quotes)
460 } 460 }
461} 461}
462 462
463static char* get_text_content(parse_ctx ctx, const char* tag, int skip_ws, int unescape_entities) 463typedef struct {
464 const char *begin;
465 size_t length;
466 int is_cdata;
467 void *next;
468} text_part_t;
469
470static text_part_t* text_part_init(text_part_t* part, const char *begin, size_t length, int is_cdata)
471{
472 part->begin = begin;
473 part->length = length;
474 part->is_cdata = is_cdata;
475 part->next = NULL;
476 return part;
477}
478
479static void text_parts_free(text_part_t *tp)
480{
481 while (tp) {
482 text_part_t *tmp = tp;
483 tp = tp->next;
484 free(tmp);
485 }
486}
487
488static text_part_t* text_part_append(text_part_t* parts, const char *begin, size_t length, int is_cdata)
489{
490 text_part_t* newpart = malloc(sizeof(text_part_t));
491 assert(newpart);
492 parts->next = text_part_init(newpart, begin, length, is_cdata);
493 return newpart;
494}
495
496static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, int skip_ws, text_part_t *parts)
464{ 497{
465 const char *p = NULL; 498 const char *p = NULL;
466 const char *q = NULL; 499 const char *q = NULL;
467 int taglen = 0; 500 int taglen = 0;
468 char *str = NULL; 501 text_part_t *last = NULL;
469 int i = 0;
470 502
471 if (skip_ws) { 503 if (skip_ws) {
472 parse_skip_ws(ctx); 504 parse_skip_ws(ctx);
473 } 505 }
474 p = ctx->pos; 506 do {
475 if (strncmp(ctx->pos, "<![CDATA[", 9) == 0) {
476 ctx->pos+=9;
477 p = ctx->pos; 507 p = ctx->pos;
478 find_str(ctx, "]]>", 0); 508 find_char(ctx, '<', 0);
479 if (ctx->pos >= ctx->end || strncmp(ctx->pos, "]]>", 3) != 0) { 509 if (*ctx->pos != '<') {
480 PLIST_XML_ERR("EOF while looking for end of CDATA block\n"); 510 PLIST_XML_ERR("EOF while looking for closing tag\n");
511 ctx->err++;
481 return NULL; 512 return NULL;
482 } 513 }
483 q = ctx->pos; 514 q = ctx->pos;
484 ctx->pos+=3; 515 ctx->pos++;
485 unescape_entities = 0; 516 if (ctx->pos >= ctx->end) {
486 } 517 PLIST_XML_ERR("EOF while parsing '%s'\n", p);
487 find_char(ctx, '<', 0); 518 ctx->err++;
488 if (*ctx->pos != '<') { 519 return NULL;
489 PLIST_XML_ERR("EOF while looking for closing tag\n"); 520 }
490 return NULL; 521 if (*ctx->pos == '!') {
491 } 522 ctx->pos++;
492 if (!q) { 523 if (*ctx->pos == '-' && *(ctx->pos+1) == '-') {
493 q = ctx->pos; 524 if (last) {
494 } 525 last = text_part_append(last, p, q-p, 0);
495 ctx->pos++; 526 } else if (parts) {
496 if (ctx->pos >= ctx->end || *ctx->pos != '/') { PLIST_XML_ERR("EOF or empty tag while parsing '%s'\n",p); return NULL; } 527 last = text_part_init(parts, p, q-p, 0);
528 }
529 ctx->pos += 2;
530 find_str(ctx, "-->", 0);
531 if (ctx->pos >= ctx->end || strncmp(ctx->pos, "-->", 3) != 0) {
532 PLIST_XML_ERR("EOF while looking for end of comment\n");
533 ctx->err++;
534 return NULL;
535 }
536 ctx->pos += 3;
537 } else if (*ctx->pos == '[') {
538 ctx->pos++;
539 if (ctx->pos >= ctx->end - 8) {
540 PLIST_XML_ERR("EOF while parsing <[ tag\n");
541 ctx->err++;
542 return NULL;
543 }
544 if (strncmp(ctx->pos, "CDATA[", 6) == 0) {
545 if (q-p > 0) {
546 if (last) {
547 last = text_part_append(last, p, q-p, 0);
548 } else if (parts) {
549 last = text_part_init(parts, p, q-p, 0);
550 }
551 }
552 ctx->pos+=6;
553 p = ctx->pos;
554 find_str(ctx, "]]>", 0);
555 if (ctx->pos >= ctx->end || strncmp(ctx->pos, "]]>", 3) != 0) {
556 PLIST_XML_ERR("EOF while looking for end of CDATA block\n");
557 ctx->err++;
558 return NULL;
559 }
560 q = ctx->pos;
561 if (last) {
562 last = text_part_append(last, p, q-p, 1);
563 } else if (parts) {
564 last = text_part_init(parts, p, q-p, 1);
565 }
566 ctx->pos += 3;
567 } else {
568 PLIST_XML_ERR("Invalid special tag <[%.6s encountered\n", ctx->pos);
569 ctx->err++;
570 return NULL;
571 }
572 }
573 } else if (*ctx->pos == '/') {
574 break;
575 } else {
576 PLIST_XML_ERR("Invalid tag %.10s inside %s tag\n", ctx->pos, tag);
577 ctx->err++;
578 return NULL;
579 }
580 } while (1);
497 ctx->pos++; 581 ctx->pos++;
498 taglen = strlen(tag); 582 taglen = strlen(tag);
499 if (ctx->pos >= ctx->end-taglen || strncmp(ctx->pos, tag, taglen)) { PLIST_XML_ERR("EOF or end tag mismatch\n"); return NULL;} 583 if (ctx->pos >= ctx->end-taglen || strncmp(ctx->pos, tag, taglen)) {
584 PLIST_XML_ERR("EOF or end tag mismatch\n");
585 ctx->err++;
586 return NULL;
587 }
500 ctx->pos+=taglen; 588 ctx->pos+=taglen;
501 if (ctx->pos >= ctx->end || *ctx->pos != '>') { PLIST_XML_ERR("EOF or no '>' after tag name\n"); return NULL;} 589 if (ctx->pos >= ctx->end || *ctx->pos != '>') {
502 ctx->pos++; 590 PLIST_XML_ERR("EOF or no '>' after tag name\n");
503 int len = q - p; 591 ctx->err++;
504 if (len < 0) {
505 PLIST_XML_ERR("Couldn't find matching '%s' end tag\n", tag);
506 return NULL; 592 return NULL;
507 } 593 }
508 str = malloc(len+1); 594 ctx->pos++;
509 strncpy(str, p, len);
510 str[len] = 0;
511 595
512 if (!unescape_entities) { 596 if (q-p > 0) {
513 return str; 597 if (last) {
598 last = text_part_append(last, p, q-p, 0);
599 } else if (parts) {
600 last = text_part_init(parts, p, q-p, 0);
601 }
514 } 602 }
603 return parts;
604}
515 605
516 /* unescape entities */ 606static void unescape_entities(char *str, size_t *length)
607{
608 size_t i = 0;
609 size_t len = *length;
517 while (i < len-1) { 610 while (i < len-1) {
518 if (str[i] == '&') { 611 if (str[i] == '&') {
519 char *entp = str + i + 1; 612 char *entp = str + i + 1;
@@ -541,24 +634,24 @@ static char* get_text_content(parse_ctx ctx, const char* tag, int skip_ws, int u
541 char* ep = NULL; 634 char* ep = NULL;
542 if (entlen > 8) { 635 if (entlen > 8) {
543 PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", entlen, entp); 636 PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", entlen, entp);
544 return NULL; 637 return;
545 } 638 }
546 if (*(entp+1) == 'x' || *(entp+1) == 'X') { 639 if (*(entp+1) == 'x' || *(entp+1) == 'X') {
547 if (entlen < 3) { 640 if (entlen < 3) {
548 PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp); 641 PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp);
549 return NULL; 642 return;
550 } 643 }
551 val = strtoull(entp+2, &ep, 16); 644 val = strtoull(entp+2, &ep, 16);
552 } else { 645 } else {
553 if (entlen < 2) { 646 if (entlen < 2) {
554 PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp); 647 PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp);
555 return NULL; 648 return;
556 } 649 }
557 val = strtoull(entp+1, &ep, 10); 650 val = strtoull(entp+1, &ep, 10);
558 } 651 }
559 if (val == 0 || val > 0x10FFFF || ep-entp != entlen) { 652 if (val == 0 || val > 0x10FFFF || ep-entp != entlen) {
560 PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", entlen, entp); 653 PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", entlen, entp);
561 return NULL; 654 return;
562 } 655 }
563 /* convert to UTF8 */ 656 /* convert to UTF8 */
564 if (val >= 0x10000) { 657 if (val >= 0x10000) {
@@ -585,30 +678,63 @@ static char* get_text_content(parse_ctx ctx, const char* tag, int skip_ws, int u
585 } 678 }
586 } else { 679 } else {
587 PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", entlen, entp); 680 PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", entlen, entp);
588 return NULL; 681 return;
589 } 682 }
590 memmove(entp, str+i+1, len - i); 683 memmove(entp, str+i+1, len - i);
591 i -= entlen; 684 i -= entlen;
685 len -= (entlen+1);
592 continue; 686 continue;
593 } 687 }
594 } 688 }
595 i++; 689 i++;
596 } 690 }
597 691 *length = len;
598 return str;
599} 692}
600 693
601static void skip_text_content(parse_ctx ctx, const char* tag) 694static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t *length, int *requires_free)
602{ 695{
603 int taglen; 696 char *str = NULL;
604 find_char(ctx, '<', 1); 697 size_t total_length = 0;
605 if (*ctx->pos != '<') return; 698
606 ctx->pos++; 699 if (!tp) {
607 taglen = strlen(tag); 700 return NULL;
608 if (ctx->pos >= ctx->end-taglen || strncmp(ctx->pos, tag, taglen)) return; 701 }
609 ctx->pos+=taglen; 702 char *p;
610 if (ctx->pos >= ctx->end || *ctx->pos != '>') return; 703 if (requires_free && !tp->next) {
611 ctx->pos++; 704 if (tp->is_cdata || !unesc_entities) {
705 *requires_free = 0;
706 if (length) {
707 *length = tp->length;
708 }
709 return (char*)tp->begin;
710 }
711 }
712 text_part_t *tmp = tp;
713 while (tp && tp->begin) {
714 total_length += tp->length;
715 tp = tp->next;
716 }
717 str = malloc(total_length + 1);
718 assert(str);
719 p = str;
720 tp = tmp;
721 while (tp && tp->begin) {
722 size_t len = tp->length;
723 strncpy(p, tp->begin, len);
724 if (!tp->is_cdata && unesc_entities) {
725 unescape_entities(p, &len);
726 }
727 p += len;
728 tp = tp->next;
729 }
730 *p = '\0';
731 if (length) {
732 *length = p - str;
733 }
734 if (requires_free) {
735 *requires_free = 1;
736 }
737 return str;
612} 738}
613 739
614static void node_from_xml(parse_ctx ctx, plist_t *plist) 740static void node_from_xml(parse_ctx ctx, plist_t *plist)
@@ -749,80 +875,120 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
749 data->type = PLIST_ARRAY; 875 data->type = PLIST_ARRAY;
750 } else if (!strcmp(tag, XPLIST_INT)) { 876 } else if (!strcmp(tag, XPLIST_INT)) {
751 if (!is_empty) { 877 if (!is_empty) {
752 char *str_content = get_text_content(ctx, tag, 1, 0); 878 text_part_t first_part = { NULL, 0, 0, NULL };
753 if (!str_content) { 879 text_part_t *tp = get_text_parts(ctx, tag, 1, &first_part);
754 PLIST_XML_ERR("Couldn't find end tag for '%s'\n", tag); 880 if (!tp) {
755 ctx->pos = ctx->end; 881 PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
756 ctx->err++; 882 text_parts_free(first_part.next);
757 free(tag); 883 free(tag);
758 free(keyname); 884 free(keyname);
759 return; 885 break;
760 } 886 }
761 char *str = str_content; 887 if (tp->begin) {
762 int is_negative = 0; 888 int requires_free = 0;
763 if ((str[0] == '-') || (str[0] == '+')) { 889 char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
764 if (str[0] == '-') { 890 if (!str_content) {
765 is_negative = 1; 891 PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
892 text_parts_free(first_part.next);
893 ctx->err++;
894 free(tag);
895 free(keyname);
896 break;
766 } 897 }
767 str++; 898 char *str = str_content;
768 } 899 int is_negative = 0;
769 char* endp = NULL; 900 if ((str[0] == '-') || (str[0] == '+')) {
770 data->intval = strtoull((char*)str, &endp, 0); 901 if (str[0] == '-') {
771 if ((endp != NULL) && (strlen(endp) > 0)) { 902 is_negative = 1;
772 PLIST_XML_ERR("integer parse error: string contains invalid characters: '%s'\n", endp); 903 }
773 } 904 str++;
774 if (is_negative || (data->intval <= INT64_MAX)) { 905 }
775 int64_t v = data->intval; 906 data->intval = strtoull((char*)str, NULL, 0);
776 if (is_negative) { 907 if (is_negative || (data->intval <= INT64_MAX)) {
777 v = -v; 908 int64_t v = data->intval;
909 if (is_negative) {
910 v = -v;
911 }
912 data->intval = (uint64_t)v;
913 data->length = 8;
914 } else {
915 data->length = 16;
916 }
917 if (requires_free) {
918 free(str_content);
778 } 919 }
779 data->intval = (uint64_t)v;
780 data->length = 8;
781 } else { 920 } else {
782 data->length = 16; 921 is_empty = 1;
783 } 922 }
784 free(str_content); 923 text_parts_free(tp->next);
785 } else { 924 }
925 if (is_empty) {
786 data->intval = 0; 926 data->intval = 0;
787 data->length = 8; 927 data->length = 8;
788 } 928 }
789 data->type = PLIST_UINT; 929 data->type = PLIST_UINT;
790 } else if (!strcmp(tag, XPLIST_REAL)) { 930 } else if (!strcmp(tag, XPLIST_REAL)) {
791 if (!is_empty) { 931 if (!is_empty) {
792 char *strval = get_text_content(ctx, tag, 1, 0); 932 text_part_t first_part = { NULL, 0, 0, NULL };
793 if (!strval) { 933 text_part_t *tp = get_text_parts(ctx, tag, 1, &first_part);
794 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag); 934 if (!tp) {
795 ctx->pos = ctx->end; 935 PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
796 ctx->err++; 936 text_parts_free(first_part.next);
797 free(tag); 937 free(tag);
798 free(keyname); 938 free(keyname);
799 return; 939 break;
800 } 940 }
801 data->realval = atof((char *) strval); 941 if (tp->begin) {
802 free(strval); 942 int requires_free = 0;
943 char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
944 if (!str_content) {
945 PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
946 text_parts_free(first_part.next);
947 ctx->err++;
948 free(tag);
949 free(keyname);
950 break;
951 }
952 data->realval = atof(str_content);
953 if (requires_free) {
954 free(str_content);
955 }
956 }
957 text_parts_free(tp->next);
803 } 958 }
804 data->type = PLIST_REAL; 959 data->type = PLIST_REAL;
805 data->length = 8; 960 data->length = 8;
806 } else if (!strcmp(tag, XPLIST_TRUE)) { 961 } else if (!strcmp(tag, XPLIST_TRUE)) {
807 if (!is_empty) { 962 if (!is_empty) {
808 skip_text_content(ctx, tag); 963 get_text_parts(ctx, tag, 1, NULL);
809 } 964 }
810 data->type = PLIST_BOOLEAN; 965 data->type = PLIST_BOOLEAN;
811 data->boolval = 1; 966 data->boolval = 1;
812 data->length = 1; 967 data->length = 1;
813 } else if (!strcmp(tag, XPLIST_FALSE)) { 968 } else if (!strcmp(tag, XPLIST_FALSE)) {
814 if (!is_empty) { 969 if (!is_empty) {
815 skip_text_content(ctx, tag); 970 get_text_parts(ctx, tag, 1, NULL);
816 } 971 }
817 data->type = PLIST_BOOLEAN; 972 data->type = PLIST_BOOLEAN;
818 data->boolval = 0; 973 data->boolval = 0;
819 data->length = 1; 974 data->length = 1;
820 } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) { 975 } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) {
821 if (!is_empty) { 976 if (!is_empty) {
822 char *str = get_text_content(ctx, tag, 0, 1); 977 text_part_t first_part = { NULL, 0, 0, NULL };
978 text_part_t *tp = get_text_parts(ctx, tag, 0, &first_part);
979 char *str = NULL;
980 size_t length = 0;
981 if (!tp) {
982 PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
983 text_parts_free(first_part.next);
984 free(tag);
985 free(keyname);
986 break;
987 }
988 str = text_parts_get_content(tp, 1, &length, NULL);
989 text_parts_free(first_part.next);
823 if (!str) { 990 if (!str) {
824 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag); 991 PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
825 ctx->pos = ctx->end;
826 ctx->err++; 992 ctx->err++;
827 free(tag); 993 free(tag);
828 free(keyname); 994 free(keyname);
@@ -836,7 +1002,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
836 continue; 1002 continue;
837 } else { 1003 } else {
838 data->strval = str; 1004 data->strval = str;
839 data->length = strlen(str); 1005 data->length = length;
840 } 1006 }
841 } else { 1007 } else {
842 data->strval = strdup(""); 1008 data->strval = strdup("");
@@ -845,40 +1011,79 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
845 data->type = PLIST_STRING; 1011 data->type = PLIST_STRING;
846 } else if (!strcmp(tag, XPLIST_DATA)) { 1012 } else if (!strcmp(tag, XPLIST_DATA)) {
847 if (!is_empty) { 1013 if (!is_empty) {
848 char *strval = get_text_content(ctx, tag, 1, 0); 1014 text_part_t first_part = { NULL, 0, 0, NULL };
849 if (!strval) { 1015 text_part_t *tp = get_text_parts(ctx, tag, 1, &first_part);
850 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag); 1016 if (!tp) {
851 ctx->pos = ctx->end; 1017 PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
852 ctx->err++; 1018 text_parts_free(first_part.next);
853 free(tag); 1019 free(tag);
854 free(keyname); 1020 free(keyname);
855 return; 1021 break;
856 } 1022 }
857 size_t size = 0; 1023 if (tp->begin) {
858 data->buff = base64decode((char*)strval, &size); 1024 int requires_free = 0;
859 free(strval); 1025 char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
860 data->length = size; 1026 if (!str_content) {
1027 PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1028 text_parts_free(first_part.next);
1029 ctx->err++;
1030 free(tag);
1031 free(keyname);
1032 break;
1033 }
1034 size_t size = tp->length;
1035 data->buff = base64decode(str_content, &size);
1036 data->length = size;
1037
1038 if (requires_free) {
1039 free(str_content);
1040 }
1041 }
1042 text_parts_free(tp->next);
861 } 1043 }
862 data->type = PLIST_DATA; 1044 data->type = PLIST_DATA;
863 } else if (!strcmp(tag, XPLIST_DATE)) { 1045 } else if (!strcmp(tag, XPLIST_DATE)) {
864 if (!is_empty) { 1046 if (!is_empty) {
865 char *strval = get_text_content(ctx, tag, 1, 0); 1047 text_part_t first_part = { NULL, 0, 0, NULL };
866 if (!strval) { 1048 text_part_t *tp = get_text_parts(ctx, tag, 1, &first_part);
867 PLIST_XML_ERR("Couldn't get text content for '%s' node\n", tag); 1049 if (!tp) {
868 ctx->pos = ctx->end; 1050 PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
869 ctx->err++; 1051 text_parts_free(first_part.next);
870 free(tag); 1052 free(tag);
871 free(keyname); 1053 free(keyname);
872 return; 1054 break;
873 } 1055 }
874 Time64_T timev = 0; 1056 Time64_T timev = 0;
875 if (strlen((const char*)strval) >= 11) { 1057 if (tp->begin) {
876 struct TM btime; 1058 int requires_free = 0;
877 parse_date((const char*)strval, &btime); 1059 size_t length = 0;
878 timev = timegm64(&btime); 1060 char *str_content = text_parts_get_content(tp, 0, &length, &requires_free);
1061 if (!str_content) {
1062 PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
1063 text_parts_free(first_part.next);
1064 ctx->err++;
1065 free(tag);
1066 free(keyname);
1067 break;
1068 }
1069
1070 if ((length >= 11) && (length < 32)) {
1071 /* we need to copy here and 0-terminate because sscanf will read the entire string (whole rest of XML data) which can be huge */
1072 char strval[32];
1073 struct TM btime;
1074 strncpy(strval, str_content, length);
1075 strval[tp->length] = '\0';
1076 parse_date(strval, &btime);
1077 timev = timegm64(&btime);
1078 } else {
1079 PLIST_XML_ERR("Invalid text content in date node\n");
1080 }
1081 if (requires_free) {
1082 free(str_content);
1083 }
879 } 1084 }
1085 text_parts_free(tp->next);
880 data->realval = (double)(timev - MAC_EPOCH); 1086 data->realval = (double)(timev - MAC_EPOCH);
881 free(strval);
882 } 1087 }
883 data->length = sizeof(double); 1088 data->length = sizeof(double);
884 data->type = PLIST_DATE; 1089 data->type = PLIST_DATE;