diff options
| author | 2026-01-29 17:45:01 +0100 | |
|---|---|---|
| committer | 2026-01-29 17:45:01 +0100 | |
| commit | b7f09ccdddc75d82ccaed867eb60e6997a7cad40 (patch) | |
| tree | 261c3184ac15aebc953d91bc6cb097b5419d320a | |
| parent | ebf24567ea5e4f72bd5acbc22f085e9d0b208e05 (diff) | |
| download | libplist-b7f09ccdddc75d82ccaed867eb60e6997a7cad40.tar.gz libplist-b7f09ccdddc75d82ccaed867eb60e6997a7cad40.tar.bz2 | |
xplist: Improve robustness of XML text parsing and value conversion
This change adds stricter validation for numeric and date nodes,
including full-input consumption, overflow/range checks, and rejection
of invalid floating-point values. Whitespace handling is clarified by
explicitly trimming trailing XML whitespace for value nodes.
| -rw-r--r-- | src/xplist.c | 221 | ||||
| -rw-r--r-- | test/data/1.plist | 6 |
2 files changed, 155 insertions, 72 deletions
diff --git a/src/xplist.c b/src/xplist.c index 2c21272..6100afc 100644 --- a/src/xplist.c +++ b/src/xplist.c | |||
| @@ -39,6 +39,7 @@ | |||
| 39 | #include <float.h> | 39 | #include <float.h> |
| 40 | #include <math.h> | 40 | #include <math.h> |
| 41 | #include <limits.h> | 41 | #include <limits.h> |
| 42 | #include <errno.h> | ||
| 42 | 43 | ||
| 43 | #include <node.h> | 44 | #include <node.h> |
| 44 | 45 | ||
| @@ -381,24 +382,44 @@ static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth | |||
| 381 | return PLIST_ERR_SUCCESS; | 382 | return PLIST_ERR_SUCCESS; |
| 382 | } | 383 | } |
| 383 | 384 | ||
| 384 | static void parse_date(const char *strval, struct TM *btime) | 385 | static int parse_date(const char *strval, struct TM *btime) |
| 385 | { | 386 | { |
| 386 | if (!btime) return; | 387 | if (!btime) return -1; |
| 387 | memset(btime, 0, sizeof(struct tm)); | 388 | memset(btime, 0, sizeof(*btime)); |
| 388 | if (!strval) return; | 389 | if (!strval) return -1; |
| 389 | #ifdef HAVE_STRPTIME | 390 | #ifdef HAVE_STRPTIME |
| 390 | strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime); | 391 | #ifdef USE_TM64 |
| 392 | struct tm t = { 0 }; | ||
| 393 | char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", &t); | ||
| 394 | if (!r || *r != '\0') { | ||
| 395 | return -1; | ||
| 396 | } | ||
| 397 | copy_tm_to_TM64(&t, btime); | ||
| 398 | #else | ||
| 399 | char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime); | ||
| 400 | if (!r || *r != '\0') { | ||
| 401 | return -1; | ||
| 402 | } | ||
| 403 | #endif | ||
| 391 | #else | 404 | #else |
| 392 | #ifdef USE_TM64 | 405 | #ifdef USE_TM64 |
| 393 | #define PLIST_SSCANF_FORMAT "%lld-%d-%dT%d:%d:%dZ" | 406 | #define PLIST_SSCANF_FORMAT "%lld-%d-%dT%d:%d:%dZ" |
| 394 | #else | 407 | #else |
| 395 | #define PLIST_SSCANF_FORMAT "%d-%d-%dT%d:%d:%dZ" | 408 | #define PLIST_SSCANF_FORMAT "%d-%d-%dT%d:%d:%dZ" |
| 396 | #endif | 409 | #endif |
| 397 | sscanf(strval, PLIST_SSCANF_FORMAT, &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec); | 410 | int n = 0; |
| 411 | if (sscanf(strval, PLIST_SSCANF_FORMAT "%n", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec, &n) != 6) return -1; | ||
| 412 | if (strval[n] != '\0') return -1; | ||
| 413 | if (btime->tm_mon < 1 || btime->tm_mon > 12) return -1; | ||
| 414 | if (btime->tm_mday < 1 || btime->tm_mday > 31) return -1; | ||
| 415 | if (btime->tm_hour < 0 || btime->tm_hour > 23) return -1; | ||
| 416 | if (btime->tm_min < 0 || btime->tm_min > 59) return -1; | ||
| 417 | if (btime->tm_sec < 0 || btime->tm_sec > 59) return -1; | ||
| 398 | btime->tm_year-=1900; | 418 | btime->tm_year-=1900; |
| 399 | btime->tm_mon--; | 419 | btime->tm_mon--; |
| 400 | #endif | 420 | #endif |
| 401 | btime->tm_isdst=0; | 421 | btime->tm_isdst=0; |
| 422 | return 0; | ||
| 402 | } | 423 | } |
| 403 | 424 | ||
| 404 | #define PO10i_LIMIT (INT64_MAX/10) | 425 | #define PO10i_LIMIT (INT64_MAX/10) |
| @@ -599,9 +620,14 @@ struct _parse_ctx { | |||
| 599 | }; | 620 | }; |
| 600 | typedef struct _parse_ctx* parse_ctx; | 621 | typedef struct _parse_ctx* parse_ctx; |
| 601 | 622 | ||
| 623 | static inline int is_xml_ws(unsigned char c) | ||
| 624 | { | ||
| 625 | return (c == ' ' || c == '\t' || c == '\r' || c == '\n'); | ||
| 626 | } | ||
| 627 | |||
| 602 | static void parse_skip_ws(parse_ctx ctx) | 628 | static void parse_skip_ws(parse_ctx ctx) |
| 603 | { | 629 | { |
| 604 | while (ctx->pos < ctx->end && ((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n'))) { | 630 | while (ctx->pos < ctx->end && is_xml_ws(*(ctx->pos))) { |
| 605 | ctx->pos++; | 631 | ctx->pos++; |
| 606 | } | 632 | } |
| 607 | } | 633 | } |
| @@ -822,8 +848,7 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le | |||
| 822 | return NULL; | 848 | return NULL; |
| 823 | } | 849 | } |
| 824 | ctx->pos++; | 850 | ctx->pos++; |
| 825 | 851 | if (q > p) { | |
| 826 | if (q-p > 0) { | ||
| 827 | if (last) { | 852 | if (last) { |
| 828 | last = text_part_append(last, p, q-p, 0); | 853 | last = text_part_append(last, p, q-p, 0); |
| 829 | } else if (parts) { | 854 | } else if (parts) { |
| @@ -944,7 +969,7 @@ static int unescape_entities(char *str, size_t *length) | |||
| 944 | return 0; | 969 | return 0; |
| 945 | } | 970 | } |
| 946 | 971 | ||
| 947 | static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t *length, int *requires_free) | 972 | static char* text_parts_get_content(text_part_t *tp, int unesc_entities, int trim_ws, size_t *length, int *requires_free) |
| 948 | { | 973 | { |
| 949 | char *str = NULL; | 974 | char *str = NULL; |
| 950 | size_t total_length = 0; | 975 | size_t total_length = 0; |
| @@ -953,14 +978,12 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t | |||
| 953 | return NULL; | 978 | return NULL; |
| 954 | } | 979 | } |
| 955 | char *p; | 980 | char *p; |
| 956 | if (requires_free && !tp->next) { | 981 | if (requires_free && !tp->next && !unesc_entities && !trim_ws) { |
| 957 | if (tp->is_cdata || !unesc_entities) { | 982 | *requires_free = 0; |
| 958 | *requires_free = 0; | 983 | if (length) { |
| 959 | if (length) { | 984 | *length = tp->length; |
| 960 | *length = tp->length; | ||
| 961 | } | ||
| 962 | return (char*)tp->begin; | ||
| 963 | } | 985 | } |
| 986 | return (char*)tp->begin; | ||
| 964 | } | 987 | } |
| 965 | text_part_t *tmp = tp; | 988 | text_part_t *tmp = tp; |
| 966 | while (tp && tp->begin) { | 989 | while (tp && tp->begin) { |
| @@ -985,6 +1008,21 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t | |||
| 985 | tp = (text_part_t*)tp->next; | 1008 | tp = (text_part_t*)tp->next; |
| 986 | } | 1009 | } |
| 987 | *p = '\0'; | 1010 | *p = '\0'; |
| 1011 | if (trim_ws) { | ||
| 1012 | char* start = str; | ||
| 1013 | char* end = p; | ||
| 1014 | while (start < end && is_xml_ws((unsigned char)start[0])) start++; | ||
| 1015 | while (end > start && is_xml_ws((unsigned char)end[-1])) end--; | ||
| 1016 | if (start != str) { | ||
| 1017 | size_t newlen = (size_t)(end - start); | ||
| 1018 | memmove(str, start, newlen); | ||
| 1019 | str[newlen] = '\0'; | ||
| 1020 | p = str + newlen; | ||
| 1021 | } else { | ||
| 1022 | *end = '\0'; | ||
| 1023 | p = end; | ||
| 1024 | } | ||
| 1025 | } | ||
| 988 | if (length) { | 1026 | if (length) { |
| 989 | *length = p - str; | 1027 | *length = p - str; |
| 990 | } | 1028 | } |
| @@ -1001,11 +1039,10 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1001 | plist_t subnode = NULL; | 1039 | plist_t subnode = NULL; |
| 1002 | const char *p = NULL; | 1040 | const char *p = NULL; |
| 1003 | plist_t parent = NULL; | 1041 | plist_t parent = NULL; |
| 1004 | int has_content = 0; | ||
| 1005 | 1042 | ||
| 1006 | struct node_path_item { | 1043 | struct node_path_item { |
| 1007 | const char *type; | 1044 | const char *type; |
| 1008 | void *prev; | 1045 | struct node_path_item *prev; |
| 1009 | }; | 1046 | }; |
| 1010 | struct node_path_item* node_path = NULL; | 1047 | struct node_path_item* node_path = NULL; |
| 1011 | int depth = 0; | 1048 | int depth = 0; |
| @@ -1047,7 +1084,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1047 | /* comment or DTD */ | 1084 | /* comment or DTD */ |
| 1048 | if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) { | 1085 | if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) { |
| 1049 | ctx->pos += 3; | 1086 | ctx->pos += 3; |
| 1050 | find_str(ctx,"-->", 3, 0); | 1087 | find_str(ctx, "-->", 3, 0); |
| 1051 | if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) { | 1088 | if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) { |
| 1052 | PLIST_XML_ERR("Couldn't find end of comment\n"); | 1089 | PLIST_XML_ERR("Couldn't find end of comment\n"); |
| 1053 | ctx->err = PLIST_ERR_PARSE; | 1090 | ctx->err = PLIST_ERR_PARSE; |
| @@ -1096,7 +1133,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1096 | int is_empty = 0; | 1133 | int is_empty = 0; |
| 1097 | int closing_tag = 0; | 1134 | int closing_tag = 0; |
| 1098 | p = ctx->pos; | 1135 | p = ctx->pos; |
| 1099 | find_next(ctx," \r\n\t<>", 6, 0); | 1136 | find_next(ctx, " \r\n\t<>", 6, 0); |
| 1100 | if (ctx->pos >= ctx->end) { | 1137 | if (ctx->pos >= ctx->end) { |
| 1101 | PLIST_XML_ERR("Unexpected EOF while parsing XML\n"); | 1138 | PLIST_XML_ERR("Unexpected EOF while parsing XML\n"); |
| 1102 | ctx->err = PLIST_ERR_PARSE; | 1139 | ctx->err = PLIST_ERR_PARSE; |
| @@ -1132,8 +1169,6 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1132 | } | 1169 | } |
| 1133 | ctx->pos++; | 1170 | ctx->pos++; |
| 1134 | if (!strcmp(tag, "plist")) { | 1171 | if (!strcmp(tag, "plist")) { |
| 1135 | has_content = 0; | ||
| 1136 | |||
| 1137 | if (!node_path && *plist) { | 1172 | if (!node_path && *plist) { |
| 1138 | /* we don't allow another top-level <plist> */ | 1173 | /* we don't allow another top-level <plist> */ |
| 1139 | break; | 1174 | break; |
| @@ -1156,7 +1191,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1156 | 1191 | ||
| 1157 | continue; | 1192 | continue; |
| 1158 | } else if (!strcmp(tag, "/plist")) { | 1193 | } else if (!strcmp(tag, "/plist")) { |
| 1159 | if (!has_content) { | 1194 | if (!*plist) { |
| 1160 | PLIST_XML_ERR("encountered empty plist tag\n"); | 1195 | PLIST_XML_ERR("encountered empty plist tag\n"); |
| 1161 | ctx->err = PLIST_ERR_PARSE; | 1196 | ctx->err = PLIST_ERR_PARSE; |
| 1162 | goto err_out; | 1197 | goto err_out; |
| @@ -1176,10 +1211,12 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1176 | free(path_item); | 1211 | free(path_item); |
| 1177 | continue; | 1212 | continue; |
| 1178 | } | 1213 | } |
| 1179 | 1214 | if (tag[0] == '/') { | |
| 1215 | closing_tag = 1; | ||
| 1216 | goto handle_closing; | ||
| 1217 | } | ||
| 1180 | plist_data_t data = plist_new_plist_data(); | 1218 | plist_data_t data = plist_new_plist_data(); |
| 1181 | subnode = plist_new_node(data); | 1219 | subnode = plist_new_node(data); |
| 1182 | has_content = 1; | ||
| 1183 | 1220 | ||
| 1184 | if (!strcmp(tag, XPLIST_DICT)) { | 1221 | if (!strcmp(tag, XPLIST_DICT)) { |
| 1185 | data->type = PLIST_DICT; | 1222 | data->type = PLIST_DICT; |
| @@ -1196,8 +1233,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1196 | goto err_out; | 1233 | goto err_out; |
| 1197 | } | 1234 | } |
| 1198 | if (tp->begin) { | 1235 | if (tp->begin) { |
| 1199 | int requires_free = 0; | 1236 | char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL); |
| 1200 | char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free); | ||
| 1201 | if (!str_content) { | 1237 | if (!str_content) { |
| 1202 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); | 1238 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); |
| 1203 | text_parts_free((text_part_t*)first_part.next); | 1239 | text_parts_free((text_part_t*)first_part.next); |
| @@ -1212,7 +1248,30 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1212 | } | 1248 | } |
| 1213 | str++; | 1249 | str++; |
| 1214 | } | 1250 | } |
| 1215 | data->intval = strtoull(str, NULL, 0); | 1251 | errno = 0; |
| 1252 | char* endp = NULL; | ||
| 1253 | data->intval = strtoull(str, &endp, 0); | ||
| 1254 | if (errno == ERANGE) { | ||
| 1255 | PLIST_XML_ERR("Integer overflow detected while parsing '%.20s'\n", str_content); | ||
| 1256 | text_parts_free((text_part_t*)first_part.next); | ||
| 1257 | ctx->err = PLIST_ERR_PARSE; | ||
| 1258 | free(str_content); | ||
| 1259 | goto err_out; | ||
| 1260 | } | ||
| 1261 | if (endp == str || *endp != '\0') { | ||
| 1262 | PLIST_XML_ERR("Invalid characters while parsing integer value '%.20s'\n", str_content); | ||
| 1263 | text_parts_free((text_part_t*)first_part.next); | ||
| 1264 | ctx->err = PLIST_ERR_PARSE; | ||
| 1265 | free(str_content); | ||
| 1266 | goto err_out; | ||
| 1267 | } | ||
| 1268 | if (is_negative && data->intval > ((uint64_t)INT64_MAX + 1)) { | ||
| 1269 | PLIST_XML_ERR("Signed integer value out of range while parsing '%.20s'\n", str_content); | ||
| 1270 | text_parts_free((text_part_t*)first_part.next); | ||
| 1271 | ctx->err = PLIST_ERR_PARSE; | ||
| 1272 | free(str_content); | ||
| 1273 | goto err_out; | ||
| 1274 | } | ||
| 1216 | if (is_negative || (data->intval <= INT64_MAX)) { | 1275 | if (is_negative || (data->intval <= INT64_MAX)) { |
| 1217 | uint64_t v = data->intval; | 1276 | uint64_t v = data->intval; |
| 1218 | if (is_negative) { | 1277 | if (is_negative) { |
| @@ -1223,17 +1282,16 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1223 | } else { | 1282 | } else { |
| 1224 | data->length = 16; | 1283 | data->length = 16; |
| 1225 | } | 1284 | } |
| 1226 | if (requires_free) { | 1285 | free(str_content); |
| 1227 | free(str_content); | ||
| 1228 | } | ||
| 1229 | } else { | 1286 | } else { |
| 1230 | is_empty = 1; | 1287 | is_empty = 1; |
| 1231 | } | 1288 | } |
| 1232 | text_parts_free((text_part_t*)tp->next); | 1289 | text_parts_free((text_part_t*)first_part.next); |
| 1233 | } | 1290 | } |
| 1234 | if (is_empty) { | 1291 | if (is_empty) { |
| 1235 | data->intval = 0; | 1292 | PLIST_XML_ERR("Encountered empty " XPLIST_INT " tag\n"); |
| 1236 | data->length = 8; | 1293 | ctx->err = PLIST_ERR_PARSE; |
| 1294 | goto err_out; | ||
| 1237 | } | 1295 | } |
| 1238 | data->type = PLIST_INT; | 1296 | data->type = PLIST_INT; |
| 1239 | } else if (!strcmp(tag, XPLIST_REAL)) { | 1297 | } else if (!strcmp(tag, XPLIST_REAL)) { |
| @@ -1247,20 +1305,48 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1247 | goto err_out; | 1305 | goto err_out; |
| 1248 | } | 1306 | } |
| 1249 | if (tp->begin) { | 1307 | if (tp->begin) { |
| 1250 | int requires_free = 0; | 1308 | char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL); |
| 1251 | char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free); | ||
| 1252 | if (!str_content) { | 1309 | if (!str_content) { |
| 1253 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); | 1310 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); |
| 1254 | text_parts_free((text_part_t*)first_part.next); | 1311 | text_parts_free((text_part_t*)first_part.next); |
| 1255 | ctx->err = PLIST_ERR_PARSE; | 1312 | ctx->err = PLIST_ERR_PARSE; |
| 1256 | goto err_out; | 1313 | goto err_out; |
| 1257 | } | 1314 | } |
| 1258 | data->realval = atof(str_content); | 1315 | errno = 0; |
| 1259 | if (requires_free) { | 1316 | char *endp = NULL; |
| 1317 | data->realval = strtod(str_content, &endp); | ||
| 1318 | if (errno == ERANGE) { | ||
| 1319 | PLIST_XML_ERR("Invalid range while parsing value for '%s' node\n", tag); | ||
| 1320 | text_parts_free((text_part_t*)first_part.next); | ||
| 1321 | ctx->err = PLIST_ERR_PARSE; | ||
| 1260 | free(str_content); | 1322 | free(str_content); |
| 1323 | goto err_out; | ||
| 1261 | } | 1324 | } |
| 1325 | if (endp == str_content || *endp != '\0') { | ||
| 1326 | PLIST_XML_ERR("Could not parse value for '%s' node\n", tag); | ||
| 1327 | text_parts_free((text_part_t*)first_part.next); | ||
| 1328 | ctx->err = PLIST_ERR_PARSE; | ||
| 1329 | free(str_content); | ||
| 1330 | goto err_out; | ||
| 1331 | |||
| 1332 | } | ||
| 1333 | if (!isfinite(data->realval)) { | ||
| 1334 | PLIST_XML_ERR("Invalid real value while parsing '%.20s'\n", str_content); | ||
| 1335 | text_parts_free((text_part_t*)first_part.next); | ||
| 1336 | ctx->err = PLIST_ERR_PARSE; | ||
| 1337 | free(str_content); | ||
| 1338 | goto err_out; | ||
| 1339 | } | ||
| 1340 | free(str_content); | ||
| 1341 | } else { | ||
| 1342 | is_empty = 1; | ||
| 1262 | } | 1343 | } |
| 1263 | text_parts_free((text_part_t*)tp->next); | 1344 | text_parts_free((text_part_t*)first_part.next); |
| 1345 | } | ||
| 1346 | if (is_empty) { | ||
| 1347 | PLIST_XML_ERR("Encountered empty " XPLIST_REAL " tag\n"); | ||
| 1348 | ctx->err = PLIST_ERR_PARSE; | ||
| 1349 | goto err_out; | ||
| 1264 | } | 1350 | } |
| 1265 | data->type = PLIST_REAL; | 1351 | data->type = PLIST_REAL; |
| 1266 | data->length = 8; | 1352 | data->length = 8; |
| @@ -1290,7 +1376,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1290 | ctx->err = PLIST_ERR_PARSE; | 1376 | ctx->err = PLIST_ERR_PARSE; |
| 1291 | goto err_out; | 1377 | goto err_out; |
| 1292 | } | 1378 | } |
| 1293 | str = text_parts_get_content(tp, 1, &length, NULL); | 1379 | str = text_parts_get_content(tp, 1, 0, &length, NULL); |
| 1294 | text_parts_free((text_part_t*)first_part.next); | 1380 | text_parts_free((text_part_t*)first_part.next); |
| 1295 | if (!str) { | 1381 | if (!str) { |
| 1296 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); | 1382 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); |
| @@ -1329,7 +1415,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1329 | } | 1415 | } |
| 1330 | if (tp->begin) { | 1416 | if (tp->begin) { |
| 1331 | int requires_free = 0; | 1417 | int requires_free = 0; |
| 1332 | char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free); | 1418 | char *str_content = text_parts_get_content(tp, 0, 0, NULL, &requires_free); |
| 1333 | if (!str_content) { | 1419 | if (!str_content) { |
| 1334 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); | 1420 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); |
| 1335 | text_parts_free((text_part_t*)first_part.next); | 1421 | text_parts_free((text_part_t*)first_part.next); |
| @@ -1346,7 +1432,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1346 | free(str_content); | 1432 | free(str_content); |
| 1347 | } | 1433 | } |
| 1348 | } | 1434 | } |
| 1349 | text_parts_free((text_part_t*)tp->next); | 1435 | text_parts_free((text_part_t*)first_part.next); |
| 1350 | } | 1436 | } |
| 1351 | data->type = PLIST_DATA; | 1437 | data->type = PLIST_DATA; |
| 1352 | } else if (!strcmp(tag, XPLIST_DATE)) { | 1438 | } else if (!strcmp(tag, XPLIST_DATE)) { |
| @@ -1361,38 +1447,36 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1361 | } | 1447 | } |
| 1362 | Time64_T timev = 0; | 1448 | Time64_T timev = 0; |
| 1363 | if (tp->begin) { | 1449 | if (tp->begin) { |
| 1364 | int requires_free = 0; | 1450 | char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL); |
| 1365 | size_t length = 0; | ||
| 1366 | char *str_content = text_parts_get_content(tp, 0, &length, &requires_free); | ||
| 1367 | if (!str_content) { | 1451 | if (!str_content) { |
| 1368 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); | 1452 | PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); |
| 1369 | text_parts_free((text_part_t*)first_part.next); | 1453 | text_parts_free((text_part_t*)first_part.next); |
| 1370 | ctx->err = PLIST_ERR_PARSE; | 1454 | ctx->err = PLIST_ERR_PARSE; |
| 1371 | goto err_out; | 1455 | goto err_out; |
| 1372 | } | 1456 | } |
| 1373 | 1457 | struct TM btime; | |
| 1374 | if ((length >= 11) && (length < 32)) { | 1458 | if (parse_date(str_content, &btime) < 0) { |
| 1375 | /* we need to copy here and 0-terminate because sscanf will read the entire string (whole rest of XML data) which can be huge */ | 1459 | PLIST_XML_ERR("Failed to parse date node\n"); |
| 1376 | char strval[32]; | 1460 | text_parts_free((text_part_t*)first_part.next); |
| 1377 | struct TM btime; | 1461 | ctx->err = PLIST_ERR_PARSE; |
| 1378 | memcpy(strval, str_content, length); | ||
| 1379 | strval[tp->length] = '\0'; | ||
| 1380 | parse_date(strval, &btime); | ||
| 1381 | timev = timegm64(&btime); | ||
| 1382 | } else { | ||
| 1383 | PLIST_XML_ERR("Invalid text content in date node\n"); | ||
| 1384 | } | ||
| 1385 | if (requires_free) { | ||
| 1386 | free(str_content); | 1462 | free(str_content); |
| 1463 | goto err_out; | ||
| 1387 | } | 1464 | } |
| 1465 | timev = timegm64(&btime); | ||
| 1466 | free(str_content); | ||
| 1467 | } else { | ||
| 1468 | is_empty = 1; | ||
| 1388 | } | 1469 | } |
| 1389 | text_parts_free((text_part_t*)tp->next); | 1470 | text_parts_free((text_part_t*)first_part.next); |
| 1390 | data->realval = (double)(timev - MAC_EPOCH); | 1471 | data->realval = (double)(timev - MAC_EPOCH); |
| 1391 | } | 1472 | } |
| 1473 | if (is_empty) { | ||
| 1474 | PLIST_XML_ERR("Encountered empty " XPLIST_DATE " tag\n"); | ||
| 1475 | ctx->err = PLIST_ERR_PARSE; | ||
| 1476 | goto err_out; | ||
| 1477 | } | ||
| 1392 | data->length = sizeof(double); | 1478 | data->length = sizeof(double); |
| 1393 | data->type = PLIST_DATE; | 1479 | data->type = PLIST_DATE; |
| 1394 | } else if (tag[0] == '/') { | ||
| 1395 | closing_tag = 1; | ||
| 1396 | } else { | 1480 | } else { |
| 1397 | PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : ""); | 1481 | PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : ""); |
| 1398 | ctx->pos = ctx->end; | 1482 | ctx->pos = ctx->end; |
| @@ -1449,7 +1533,9 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1449 | parent = subnode; | 1533 | parent = subnode; |
| 1450 | } | 1534 | } |
| 1451 | subnode = NULL; | 1535 | subnode = NULL; |
| 1452 | } else if (closing_tag) { | 1536 | } |
| 1537 | handle_closing: | ||
| 1538 | if (closing_tag) { | ||
| 1453 | if (!node_path) { | 1539 | if (!node_path) { |
| 1454 | PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n"); | 1540 | PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n"); |
| 1455 | ctx->err = PLIST_ERR_PARSE; | 1541 | ctx->err = PLIST_ERR_PARSE; |
| @@ -1464,11 +1550,8 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) | |||
| 1464 | struct node_path_item *path_item = node_path; | 1550 | struct node_path_item *path_item = node_path; |
| 1465 | node_path = (struct node_path_item*)node_path->prev; | 1551 | node_path = (struct node_path_item*)node_path->prev; |
| 1466 | free(path_item); | 1552 | free(path_item); |
| 1467 | 1553 | parent = (parent) ? ((node_t)parent)->parent : NULL; | |
| 1468 | parent = ((node_t)parent)->parent; | 1554 | /* parent can be NULL when we just closed the root node; keep parsing */ |
| 1469 | if (!parent) { | ||
| 1470 | goto err_out; | ||
| 1471 | } | ||
| 1472 | } | 1555 | } |
| 1473 | free(keyname); | 1556 | free(keyname); |
| 1474 | keyname = NULL; | 1557 | keyname = NULL; |
| @@ -1502,7 +1585,7 @@ err_out: | |||
| 1502 | /* check if we have a UID "dict" so we can replace it with a proper UID node */ | 1585 | /* check if we have a UID "dict" so we can replace it with a proper UID node */ |
| 1503 | if (PLIST_IS_DICT(*plist) && plist_dict_get_size(*plist) == 1) { | 1586 | if (PLIST_IS_DICT(*plist) && plist_dict_get_size(*plist) == 1) { |
| 1504 | plist_t value = plist_dict_get_item(*plist, "CF$UID"); | 1587 | plist_t value = plist_dict_get_item(*plist, "CF$UID"); |
| 1505 | if (PLIST_IS_UINT(value)) { | 1588 | if (PLIST_IS_INT(value)) { |
| 1506 | uint64_t u64val = 0; | 1589 | uint64_t u64val = 0; |
| 1507 | plist_get_uint_val(value, &u64val); | 1590 | plist_get_uint_val(value, &u64val); |
| 1508 | plist_free(*plist); | 1591 | plist_free(*plist); |
diff --git a/test/data/1.plist b/test/data/1.plist index e6e275d..82a112b 100644 --- a/test/data/1.plist +++ b/test/data/1.plist | |||
| @@ -23,11 +23,11 @@ | |||
| 23 | <key>Another Boolean</key> | 23 | <key>Another Boolean</key> |
| 24 | <true/> | 24 | <true/> |
| 25 | <key>Some Int</key> | 25 | <key>Some Int</key> |
| 26 | <integer></integer> | 26 | <integer>0</integer> |
| 27 | <key>Some Real</key> | 27 | <key>Some Real</key> |
| 28 | <real></real> | 28 | <real>1e4</real> |
| 29 | <key>Some Date</key> | 29 | <key>Some Date</key> |
| 30 | <date></date> | 30 | <date>1970-01-01T00:00:00Z</date> |
| 31 | <key>Some Data</key> | 31 | <key>Some Data</key> |
| 32 | <data> | 32 | <data> |
| 33 | </data> | 33 | </data> |
