/* plist.c
Copyright (c) 2003-2020 HandBrake Team
This file is part of the HandBrake source code
Homepage: .
It may be used under the terms of the GNU General Public License v2.
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
*/
#include
#include
#include
#include
#include
#include "libxml/parser.h"
#include "handbrake/common.h"
#include "handbrake/hb_dict.h"
#include "handbrake/plist.h"
#define BUF_SZ (128*1024)
static char *preamble =
"\n"
"\n"
"\n";
static char *postfix =
"\n";
typedef struct queue_item_s queue_item_t;
struct queue_item_s
{
void *value;
queue_item_t *next;
};
typedef struct
{
queue_item_t *head;
} queue_t;
queue_t * queue_new()
{
return calloc(1, sizeof(queue_t));
}
void queue_free(queue_t **_q)
{
queue_t *q = *_q;
if (q == NULL)
return;
queue_item_t *n, *i = q->head;
while (i != NULL)
{
n = i->next;
free(i);
i = n;
}
free(q);
*_q = NULL;
}
void queue_push_head(queue_t *q, void *v)
{
queue_item_t *i = calloc(1, sizeof(queue_item_t));
i->value = v;
i->next = q->head;
q->head = i;
}
void * queue_peek_head(queue_t *q)
{
if (q->head != NULL)
return q->head->value;
return NULL;
}
void * queue_pop_head(queue_t *q)
{
void *result;
queue_item_t *i;
if (q->head == NULL)
return NULL;
i = q->head;
result = i->value;
q->head = i->next;
free(i);
return result;
}
int queue_is_empty(queue_t *q)
{
return q->head == NULL;
}
char * markup_escape_text(const char *str)
{
int ii, jj;
int len = strlen(str);
int step = 40;
int alloc = len + step;
char *markup = malloc(alloc);
for (ii = 0, jj = 0; ii < len; ii++)
{
if (jj > alloc - 8)
{
alloc += step;
char *tmp = realloc(markup, alloc);
if (tmp == NULL)
{
markup[jj] = 0;
return markup;
}
markup = tmp;
}
switch (str[ii])
{
case '<':
markup[jj++] = '&';
markup[jj++] = 'l';
markup[jj++] = 't';
markup[jj++] = ';';
break;
case '>':
markup[jj++] = '&';
markup[jj++] = 'g';
markup[jj++] = 't';
markup[jj++] = ';';
break;
case '\'':
markup[jj++] = '&';
markup[jj++] = 'a';
markup[jj++] = 'p';
markup[jj++] = 'o';
markup[jj++] = 's';
markup[jj++] = ';';
break;
case '"':
markup[jj++] = '&';
markup[jj++] = 'q';
markup[jj++] = 'u';
markup[jj++] = 'o';
markup[jj++] = 't';
markup[jj++] = ';';
break;
case '&':
markup[jj++] = '&';
markup[jj++] = 'a';
markup[jj++] = 'm';
markup[jj++] = 'p';
markup[jj++] = ';';
break;
default:
markup[jj++] = str[ii];
break;
}
markup[jj] = 0;
}
return markup;
}
enum
{
P_NONE = 0,
P_PLIST,
P_KEY,
P_ARRAY,
P_DICT,
P_INTEGER,
P_REAL,
P_STRING,
P_DATE,
P_TRUE,
P_FALSE,
P_DATA,
};
typedef struct
{
char *tag;
int id;
} tag_map_t;
static tag_map_t tag_map[] =
{
{"plist", P_PLIST},
{"key", P_KEY},
{"array", P_ARRAY},
{"dict", P_DICT},
{"integer", P_INTEGER},
{"real", P_REAL},
{"string", P_STRING},
{"date", P_DATE},
{"true", P_TRUE},
{"false", P_FALSE},
{"data", P_DATA},
};
#define TAG_MAP_SZ (sizeof(tag_map)/sizeof(tag_map_t))
typedef struct
{
char *key;
char *value;
hb_value_t *plist;
queue_t *stack;
queue_t *tag_stack;
int closed_top;
} parse_data_t;
static void
start_element(
void *ud,
const xmlChar *xname,
const xmlChar **attr_names)
{
char *name = (char*)xname;
parse_data_t *pd = (parse_data_t*)ud;
union
{
int id;
void * pid;
} id;
int ii;
// Check to see if the first element found has been closed
// If so, ignore any junk following it.
if (pd->closed_top)
return;
for (ii = 0; ii < TAG_MAP_SZ; ii++)
{
if (strcmp(name, tag_map[ii].tag) == 0)
{
id.id = tag_map[ii].id;
break;
}
}
if (ii == TAG_MAP_SZ)
{
hb_error("Unrecognized start tag (%s)", name);
return;
}
if (pd->value)
{
free(pd->value);
pd->value = NULL;
}
queue_push_head(pd->tag_stack, id.pid);
hb_value_type_t gtype = 0;
hb_value_t *gval = NULL;
hb_value_t *current = queue_peek_head(pd->stack);
switch (id.id)
{
case P_PLIST:
{ // Ignore
} break;
case P_KEY:
{
if (pd->key) free(pd->key);
pd->key = NULL;
} break;
case P_DICT:
{
gval = hb_dict_init();
queue_push_head(pd->stack, gval);
} break;
case P_ARRAY:
{
gval = hb_value_array_init();
queue_push_head(pd->stack, gval);
} break;
case P_INTEGER:
{
} break;
case P_REAL:
{
} break;
case P_STRING:
{
} break;
case P_DATE:
{
} break;
case P_TRUE:
{
} break;
case P_FALSE:
{
} break;
case P_DATA:
{
} break;
}
// Add the element to the current container
if (gval)
{ // There's an element to add
if (current == NULL)
{
pd->plist = gval;
return;
}
gtype = hb_value_type(current);
if (gtype == HB_VALUE_TYPE_ARRAY)
{
hb_value_array_append(current, gval);
}
else if (gtype == HB_VALUE_TYPE_DICT)
{
if (pd->key == NULL)
{
hb_error("No key for dictionary item");
hb_value_free(&gval);
}
else
{
hb_dict_set(current, pd->key, gval);
}
}
else
{
hb_error("Invalid container type. This shouldn't happen");
}
}
}
static void
end_element(
void *ud,
const xmlChar *xname)
{
char *name = (char*)xname;
parse_data_t *pd = (parse_data_t*)ud;
int id;
union
{
int id;
void * pid;
} start_id;
int ii;
// Check to see if the first element found has been closed
// If so, ignore any junk following it.
if (pd->closed_top)
return;
for (ii = 0; ii < TAG_MAP_SZ; ii++)
{
if (strcmp(name, tag_map[ii].tag) == 0)
{
id = tag_map[ii].id;
break;
}
}
if (ii == TAG_MAP_SZ)
{
hb_error("Unrecognized start tag (%s)", name);
return;
}
start_id.pid = queue_pop_head(pd->tag_stack);
if (start_id.id != id)
hb_error("start tag != end tag: (%s %d) %d", name, id, id);
hb_value_t *gval = NULL;
hb_value_t *current = queue_peek_head(pd->stack);
hb_value_type_t gtype = 0;
const char *value;
if (pd->value != NULL)
value = pd->value;
else
value = "";
switch (id)
{
case P_PLIST:
{ // Ignore
} break;
case P_KEY:
{
if (pd->key) free(pd->key);
pd->key = strdup(value);
return;
}
case P_DICT:
{
queue_pop_head(pd->stack);
} break;
case P_ARRAY:
{
queue_pop_head(pd->stack);
} break;
case P_INTEGER:
{
uint64_t val = strtoll(value, NULL, 0);
gval = hb_value_int(val);
} break;
case P_REAL:
{
double val = strtod(value, NULL);
gval = hb_value_double(val);
} break;
case P_STRING:
{
gval = hb_value_string(value);
} break;
case P_TRUE:
{
gval = hb_value_bool(1);
} break;
case P_FALSE:
{
gval = hb_value_bool(0);
} break;
default:
{
hb_error("Unhandled plist type %d", id);
} break;
}
if (gval)
{
// Get the top of the data structure stack and if it's an array
// or dict, add the current element
if (current == NULL)
{
pd->plist = gval;
pd->closed_top = 1;
return;
}
gtype = hb_value_type(current);
if (gtype == HB_VALUE_TYPE_ARRAY)
{
hb_value_array_append(current, gval);
}
else if (gtype == HB_VALUE_TYPE_DICT)
{
if (pd->key == NULL)
{
hb_error("No key for dictionary item");
hb_value_free(&gval);
}
else
{
hb_dict_set(current, pd->key, gval);
}
}
else
{
hb_error("Invalid container type. This shouldn't happen");
}
}
if (queue_is_empty(pd->stack))
pd->closed_top = 1;
}
static void
text_data(
void *ud,
const xmlChar *xtext,
int len)
{
char *text = (char*)xtext;
parse_data_t *pd = (parse_data_t*)ud;
int pos = 0;
if (pd->value != NULL)
{
pos = strlen(pd->value);
}
char *tmp = realloc(pd->value, pos + len + 1);
if (tmp == NULL)
return;
pd->value = tmp;
strncpy(pd->value + pos, text, len);
pd->value[pos + len] = 0;
}
static void
parse_warning(void *ud, const char *msg, ...)
{
va_list args;
va_start(args, msg);
hb_valog(0, "Plist parse warning: ", msg, args);
va_end(args);
}
static void
parse_error(void *ud, const char *msg, ...)
{
va_list args;
va_start(args, msg);
hb_valog(0, "Plist parse error: ", msg, args);
va_end(args);
}
hb_value_t*
hb_plist_parse(const char *buf, size_t len)
{
xmlSAXHandler parser;
parse_data_t pd;
pd.stack = queue_new();
pd.tag_stack = queue_new();
pd.key = NULL;
pd.value = NULL;
pd.plist = NULL;
pd.closed_top = 0;
memset(&parser, 0, sizeof(parser));
parser.initialized = XML_SAX2_MAGIC;
parser.startElement = start_element;
parser.endElement = end_element;
parser.characters = text_data;
parser.warning = parse_warning;
parser.error = parse_error;
int result = xmlSAXUserParseMemory(&parser, &pd, buf, len);
if (result != 0)
{
hb_error("Plist parse failed");
return NULL;
}
xmlCleanupParser();
if (pd.key) free(pd.key);
if (pd.value) free(pd.value);
queue_free(&pd.stack);
queue_free(&pd.tag_stack);
return pd.plist;
}
hb_value_t*
hb_plist_parse_file(const char *filename)
{
char *buffer;
size_t size;
hb_value_t *gval;
FILE *fd;
fd = fopen(filename, "r");
if (fd == NULL)
{
// File doesn't exist
return NULL;
}
fseek(fd, 0, SEEK_END);
size = ftell(fd);
fseek(fd, 0, SEEK_SET);
buffer = malloc(size+1);
size = fread(buffer, 1, size, fd);
buffer[size] = 0;
gval = hb_plist_parse(buffer, size);
free(buffer);
fclose(fd);
return gval;
}
static void
indent_fprintf(FILE *file, int indent, const char *fmt, ...)
{
va_list ap;
for (; indent; indent--)
putc('\t', file);
va_start(ap, fmt);
vfprintf(file, fmt, ap);
va_end(ap);
}
static void
gval_write(FILE *file, hb_value_t *gval)
{
static int indent = 0;
int ii;
hb_value_type_t gtype;
if (gval == NULL) return;
gtype = hb_value_type(gval);
if (gtype == HB_VALUE_TYPE_ARRAY)
{
hb_value_t *val;
int count;
indent_fprintf(file, indent, "\n");
indent++;
count = hb_value_array_len(gval);
for (ii = 0; ii < count; ii++)
{
val = hb_value_array_get(gval, ii);
gval_write(file, val);
}
indent--;
indent_fprintf(file, indent, "\n");
}
else if (gtype == HB_VALUE_TYPE_DICT)
{
const char *key;
hb_value_t *val;
hb_dict_iter_t iter;
indent_fprintf(file, indent, "\n");
indent++;
for (iter = hb_dict_iter_init(gval);
iter != HB_DICT_ITER_DONE;
iter = hb_dict_iter_next(gval, iter))
{
key = hb_dict_iter_key(iter);
val = hb_dict_iter_value(iter);
indent_fprintf(file, indent, "%s\n", key);
gval_write(file, val);
}
indent--;
indent_fprintf(file, indent, "\n");
}
else if (gtype == HB_VALUE_TYPE_BOOL)
{
char *tag;
if (hb_value_get_bool(gval))
{
tag = "true";
}
else
{
tag = "false";
}
indent_fprintf(file, indent, "<%s />\n", tag);
}
else if (gtype == HB_VALUE_TYPE_DOUBLE)
{
double val = hb_value_get_double(gval);
indent_fprintf(file, indent, "%.17g\n", val);
}
else if (gtype == HB_VALUE_TYPE_INT)
{
int64_t val = hb_value_get_int(gval);
indent_fprintf(file, indent, "%"PRId64"\n", val);
}
else if (gtype == HB_VALUE_TYPE_STRING)
{
const char *str = hb_value_get_string(gval);
char *esc = markup_escape_text(str);
indent_fprintf(file, indent, "%s\n", esc);
free(esc);
}
else
{
// Try to make anything that's unrecognized into a string
hb_error("Unhandled data type %d", gtype);
}
}
void
hb_plist_write(FILE *file, hb_value_t *gval)
{
fprintf(file, "%s", preamble);
gval_write(file, gval);
fprintf(file, "%s", postfix);
}
void
hb_plist_write_file(const char *filename, hb_value_t *gval)
{
FILE *file;
file = fopen(filename, "w");
if (file == NULL)
return;
hb_plist_write(file, gval);
fclose(file);
}
#if defined(PL_TEST)
int
main(int argc, char *argv[])
{
hb_value_t *gval;
file = fopen(argv[1], "r");
gval = hb_plist_parse_file(file);
if (argc > 2)
hb_plist_write_file(argv[2], gval);
else
hb_plist_write(stdout, gval);
if (file) fclose (file);
return 0;
}
#endif