#include "prop_object_impl.h"
#include <prop/prop_object.h>
#if !defined(_KERNEL) && !defined(_STANDALONE)
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#endif
#define BUF_EXPAND 256
#define PLISTTMP "/.plistXXXXXX"
static prop_format_t _prop_format_default = PROP_FORMAT_XML;
bool
_prop_extern_append_char(
struct _prop_object_externalize_context *ctx, unsigned char c)
{
_PROP_ASSERT(ctx->poec_capacity != 0);
_PROP_ASSERT(ctx->poec_buf != NULL);
_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
if (ctx->poec_len == ctx->poec_capacity) {
char *cp = _PROP_REALLOC(ctx->poec_buf,
ctx->poec_capacity + BUF_EXPAND,
M_TEMP);
if (cp == NULL) {
return false;
}
ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
ctx->poec_buf = cp;
}
ctx->poec_buf[ctx->poec_len++] = c;
return true;
}
bool
_prop_extern_append_cstring(
struct _prop_object_externalize_context *ctx, const char *cp)
{
while (*cp != '\0') {
if (_prop_extern_append_char(ctx,
(unsigned char)*cp) == false) {
return false;
}
cp++;
}
return true;
}
static bool
_prop_json_extern_append_escu(
struct _prop_object_externalize_context *ctx, uint16_t val)
{
char tmpstr[sizeof("\\uXXXX")];
snprintf(tmpstr, sizeof(tmpstr), "\\u%04X", val);
return _prop_extern_append_cstring(ctx, tmpstr);
}
static bool
_prop_json_extern_append_encoded_cstring(
struct _prop_object_externalize_context *ctx, const char *cp)
{
bool esc;
unsigned char ch;
for (; (ch = *cp) != '\0'; cp++) {
esc = true;
switch (ch) {
case '"':
goto emit;
case '\\':
goto emit;
case '/':
goto emit;
case 0x08:
ch = 'b';
goto emit;
case 0x0c:
ch = 'f';
goto emit;
case 0x0a:
ch = 'n';
goto emit;
case 0x0d:
ch = 'r';
goto emit;
case 0x09:
ch = 't';
goto emit;
default:
if (ch < 0x20) {
if (_prop_json_extern_append_escu(ctx,
ch) == false) {
return false;
}
break;
}
esc = false;
emit:
if ((esc && _prop_extern_append_char(ctx,
'\\') == false) ||
_prop_extern_append_char(ctx, ch) == false) {
return false;
}
break;
}
}
return true;
}
static bool
_prop_xml_extern_append_encoded_cstring(
struct _prop_object_externalize_context *ctx, const char *cp)
{
bool rv;
unsigned char ch;
for (rv = true; rv && (ch = *cp) != '\0'; cp++) {
switch (ch) {
case '<':
rv = _prop_extern_append_cstring(ctx, "<");
break;
case '>':
rv = _prop_extern_append_cstring(ctx, ">");
break;
case '&':
rv = _prop_extern_append_cstring(ctx, "&");
break;
default:
rv = _prop_extern_append_char(ctx, ch);
break;
}
}
return rv;
}
bool
_prop_extern_append_encoded_cstring(
struct _prop_object_externalize_context *ctx, const char *cp)
{
_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
ctx->poec_format == PROP_FORMAT_JSON);
switch (ctx->poec_format) {
case PROP_FORMAT_JSON:
return _prop_json_extern_append_encoded_cstring(ctx, cp);
default:
return _prop_xml_extern_append_encoded_cstring(ctx, cp);
}
}
bool
_prop_extern_start_line(
struct _prop_object_externalize_context *ctx)
{
unsigned int i;
for (i = 0; i < ctx->poec_depth; i++) {
if (_prop_extern_append_char(ctx, '\t') == false) {
return false;
}
}
return true;
}
bool
_prop_extern_end_line(
struct _prop_object_externalize_context *ctx, const char *trailer)
{
if (trailer != NULL &&
_prop_extern_append_cstring(ctx, trailer) == false) {
return false;
}
return _prop_extern_append_char(ctx, '\n');
}
bool
_prop_extern_append_start_tag(
struct _prop_object_externalize_context *ctx,
const struct _prop_object_type_tags *tags,
const char *tagattrs)
{
bool rv;
_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
ctx->poec_format == PROP_FORMAT_JSON);
switch (ctx->poec_format) {
case PROP_FORMAT_JSON:
rv = tags->json_open_tag == NULL ||
_prop_extern_append_cstring(ctx, tags->json_open_tag);
break;
default:
rv = _prop_extern_append_char(ctx, '<') &&
_prop_extern_append_cstring(ctx, tags->xml_tag) &&
(tagattrs == NULL ||
(_prop_extern_append_char(ctx, ' ') &&
_prop_extern_append_cstring(ctx, tagattrs))) &&
_prop_extern_append_char(ctx, '>');
break;
}
return rv;
}
bool
_prop_extern_append_end_tag(
struct _prop_object_externalize_context *ctx,
const struct _prop_object_type_tags *tags)
{
bool rv;
_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
ctx->poec_format == PROP_FORMAT_JSON);
switch (ctx->poec_format) {
case PROP_FORMAT_JSON:
rv = tags->json_close_tag == NULL ||
_prop_extern_append_cstring(ctx, tags->json_close_tag);
break;
default:
rv = _prop_extern_append_char(ctx, '<') &&
_prop_extern_append_char(ctx, '/') &&
_prop_extern_append_cstring(ctx, tags->xml_tag) &&
_prop_extern_append_char(ctx, '>');
break;
}
return rv;
}
bool
_prop_extern_append_empty_tag(
struct _prop_object_externalize_context *ctx,
const struct _prop_object_type_tags *tags)
{
bool rv;
_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
ctx->poec_format == PROP_FORMAT_JSON);
switch (ctx->poec_format) {
case PROP_FORMAT_JSON:
if (tags->json_open_tag == NULL ||
_prop_extern_append_cstring(ctx,
tags->json_open_tag) == false) {
return false;
}
if (tags->json_empty_sep != NULL &&
_prop_extern_append_cstring(ctx,
tags->json_empty_sep) == false) {
return false;
}
if (tags->json_close_tag != NULL) {
rv = _prop_extern_append_cstring(ctx,
tags->json_close_tag);
} else {
rv = true;
}
break;
default:
rv = _prop_extern_append_char(ctx, '<') &&
_prop_extern_append_cstring(ctx, tags->xml_tag) &&
_prop_extern_append_char(ctx, '/') &&
_prop_extern_append_char(ctx, '>');
break;
}
return rv;
}
static const struct _prop_object_type_tags _plist_type_tags = {
.xml_tag = "plist",
};
static bool
_prop_extern_append_header(struct _prop_object_externalize_context *ctx)
{
static const char _plist_xml_header[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
if (ctx->poec_format != PROP_FORMAT_XML) {
return true;
}
if (_prop_extern_append_cstring(ctx, _plist_xml_header) == false ||
_prop_extern_append_start_tag(ctx,
&_plist_type_tags,
"version=\"1.0\"") == false ||
_prop_extern_append_char(ctx, '\n') == false) {
return false;
}
return true;
}
static bool
_prop_extern_append_footer(struct _prop_object_externalize_context *ctx)
{
if (_prop_extern_end_line(ctx, NULL) == false) {
return false;
}
if (ctx->poec_format == PROP_FORMAT_XML) {
if (_prop_extern_append_end_tag(ctx,
&_plist_type_tags) == false ||
_prop_extern_end_line(ctx, NULL) == false) {
return false;
}
}
return _prop_extern_append_char(ctx, '\0');
}
static struct _prop_object_externalize_context *
_prop_extern_context_alloc(prop_format_t fmt)
{
struct _prop_object_externalize_context *ctx;
ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
if (ctx != NULL) {
ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
if (ctx->poec_buf == NULL) {
_PROP_FREE(ctx, M_TEMP);
return NULL;
}
ctx->poec_len = 0;
ctx->poec_capacity = BUF_EXPAND;
ctx->poec_depth = 0;
ctx->poec_format = fmt;
}
return ctx;
}
static void
_prop_extern_context_free(struct _prop_object_externalize_context *ctx)
{
_PROP_FREE(ctx, M_TEMP);
}
char *
_prop_object_externalize(struct _prop_object *obj, prop_format_t fmt)
{
struct _prop_object_externalize_context *ctx;
char *cp = NULL;
if (obj == NULL || obj->po_type->pot_extern == NULL) {
return NULL;
}
if (fmt != PROP_FORMAT_XML && fmt != PROP_FORMAT_JSON) {
return NULL;
}
ctx = _prop_extern_context_alloc(fmt);
if (ctx == NULL) {
return NULL;
}
if (_prop_extern_append_header(ctx) == false ||
obj->po_type->pot_extern(ctx, obj) == false ||
_prop_extern_append_footer(ctx) == false) {
_PROP_FREE(ctx->poec_buf, M_TEMP);
goto bad;
}
cp = ctx->poec_buf;
bad:
_prop_extern_context_free(ctx);
return cp;
}
#if !defined(_KERNEL) && !defined(_STANDALONE)
static void
_prop_extern_file_dirname(const char *path, char *result)
{
const char *lastp;
size_t len;
if (path == NULL || *path == '\0') {
goto singledot;
}
lastp = path + strlen(path) - 1;
while (lastp != path && *lastp == '/') {
lastp--;
}
do {
if (*lastp == '/') {
while (lastp != path && *lastp == '/') {
lastp--;
}
len = (lastp - path) + 1 ;
if (len > (PATH_MAX - 1)) {
len = PATH_MAX - 1;
}
memcpy(result, path, len);
result[len] = '\0';
return;
}
} while (--lastp >= path);
singledot:
strcpy(result, ".");
}
static bool
_prop_extern_write_file(const char *fname, const char *data, size_t len)
{
char tname_store[PATH_MAX];
char *tname = NULL;
int fd = -1;
int save_errno;
mode_t myumask;
bool rv = false;
if (len > SSIZE_MAX) {
errno = EFBIG;
return false;
}
_prop_extern_file_dirname(fname, tname_store);
if (strlen(tname_store) + strlen(PLISTTMP) >= sizeof(tname_store)) {
errno = ENAMETOOLONG;
return false;
}
strcat(tname_store, PLISTTMP);
if ((fd = mkstemp(tname_store)) == -1) {
return false;
}
tname = tname_store;
if (write(fd, data, len) != (ssize_t)len) {
goto bad;
}
if (fsync(fd) == -1) {
goto bad;
}
myumask = umask(0);
(void)umask(myumask);
if (fchmod(fd, 0666 & ~myumask) == -1) {
goto bad;
}
if (rename(tname, fname) == -1) {
goto bad;
}
tname = NULL;
rv = true;
bad:
save_errno = errno;
if (fd != -1) {
(void) close(fd);
}
if (tname != NULL) {
(void) unlink(tname);
}
errno = save_errno;
return rv;
}
bool
_prop_object_externalize_to_file(struct _prop_object *obj, const char *fname,
prop_format_t fmt)
{
char *data = _prop_object_externalize(obj, fmt);
if (data == NULL) {
return false;
}
bool rv = _prop_extern_write_file(fname, data, strlen(data));
int save_errno = errno;
_PROP_FREE(data, M_TEMP);
errno = save_errno;
return rv;
}
_PROP_EXPORT bool
prop_object_externalize_to_file(prop_object_t po, const char *fname)
{
return _prop_object_externalize_to_file((struct _prop_object *)po,
fname, _prop_format_default);
}
_PROP_EXPORT bool
prop_object_externalize_to_file_with_format(prop_object_t po,
const char *fname, prop_format_t fmt)
{
return _prop_object_externalize_to_file((struct _prop_object *)po,
fname, fmt);
}
#endif
_PROP_EXPORT char *
prop_object_externalize(prop_object_t po)
{
return _prop_object_externalize((struct _prop_object *)po,
_prop_format_default);
}
_PROP_EXPORT char *
prop_object_externalize_with_format(prop_object_t po, prop_format_t fmt)
{
return _prop_object_externalize((struct _prop_object *)po, fmt);
}