mirror of
https://github.com/php/pecl-file_formats-yaml.git
synced 2026-03-23 23:02:08 +01:00
943 lines
22 KiB
C
943 lines
22 KiB
C
/**
|
|
* YAML parser and emitter PHP extension
|
|
*
|
|
* Copyright (c) 2007 Ryusuke SEKIYAMA. All rights reserved.
|
|
* Copyright (c) 2009 Keynetics Inc. All rights reserved.
|
|
* Copyright (c) 2015 Bryan Davis and contributors. All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* @package php_yaml
|
|
* @author Ryusuke SEKIYAMA <rsky0711@gmail.com>
|
|
* @author Bryan Davis <bd808@bd808.com>
|
|
* @copyright 2007 Ryusuke SEKIYAMA
|
|
* @copyright 2009 Keynetics Inc
|
|
* @copyright 2015 Bryan Davis and contributors
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
|
*/
|
|
|
|
|
|
#include "php_yaml.h"
|
|
#include "php_yaml_int.h"
|
|
|
|
/* {{{ local macros
|
|
*/
|
|
#define Y_PARSER_CONTINUE 0
|
|
#define Y_PARSER_SUCCESS 1
|
|
#define Y_PARSER_FAILURE -1
|
|
|
|
#define Y_FILTER_NONE 0
|
|
#define Y_FILTER_SUCCESS 1
|
|
#define Y_FILTER_FAILURE -1
|
|
|
|
#define NEXT_EVENT() yaml_next_event(state)
|
|
|
|
#define COPY_EVENT(dest, state) \
|
|
memcpy(&dest, &state->event, sizeof(yaml_event_t)); \
|
|
state->have_event = 0; \
|
|
memset(&state->event, 0, sizeof(yaml_event_t))
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ local prototypes
|
|
*/
|
|
static void handle_parser_error(const yaml_parser_t *parser);
|
|
|
|
static inline int yaml_next_event(parser_state_t *state);
|
|
|
|
void get_next_element( parser_state_t *state, zval *retval);
|
|
|
|
void handle_document(parser_state_t *state, zval *retval);
|
|
|
|
void handle_mapping(parser_state_t *state, zval *retval);
|
|
|
|
void handle_sequence(parser_state_t *state, zval *retval);
|
|
|
|
void handle_scalar(parser_state_t *state, zval *retval);
|
|
|
|
void handle_alias(parser_state_t *state, zval *retval);
|
|
|
|
static zval *record_anchor_make_ref(zval *aliases, const char *anchor, zval *value);
|
|
|
|
static int apply_filter(
|
|
zval *zp, yaml_event_t event, HashTable *callbacks);
|
|
|
|
static int eval_timestamp(zval **zpp, const char *ts, size_t ts_len);
|
|
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ php_yaml_read_all()
|
|
* Process events from yaml parser
|
|
*/
|
|
void php_yaml_read_all(parser_state_t *state, zend_long *ndocs, zval *retval)
|
|
{
|
|
zval doc;
|
|
int code = Y_PARSER_CONTINUE;
|
|
|
|
/* create an empty array to hold results */
|
|
array_init(retval);
|
|
|
|
while (Y_PARSER_CONTINUE == code) {
|
|
|
|
if (!NEXT_EVENT()) {
|
|
code = Y_PARSER_FAILURE;
|
|
break;
|
|
|
|
} else if (YAML_STREAM_END_EVENT == state->event.type) {
|
|
code = Y_PARSER_SUCCESS;
|
|
break;
|
|
|
|
} else if (YAML_STREAM_START_EVENT == state->event.type) {
|
|
if (!NEXT_EVENT()) {
|
|
code = Y_PARSER_FAILURE;
|
|
break;
|
|
}
|
|
if (YAML_STREAM_END_EVENT == state->event.type) {
|
|
/* entire stream consisted of an empty document */
|
|
code = Y_PARSER_SUCCESS;
|
|
ZVAL_NULL(&doc);
|
|
add_next_index_zval(retval, &doc);
|
|
(*ndocs)++;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (YAML_DOCUMENT_START_EVENT != state->event.type) {
|
|
code = Y_PARSER_FAILURE;
|
|
|
|
php_error_docref(NULL, E_WARNING,
|
|
"expected DOCUMENT_START event, got %d "
|
|
"(line %zd, column %zd)",
|
|
state->event.type,
|
|
state->parser.mark.line + 1,
|
|
state->parser.mark.column + 1);
|
|
break;
|
|
}
|
|
|
|
handle_document(state, &doc);
|
|
|
|
if (Z_TYPE_P(&doc) == IS_UNDEF) {
|
|
code = Y_PARSER_FAILURE;
|
|
break;
|
|
}
|
|
|
|
add_next_index_zval(retval, &doc);
|
|
|
|
(*ndocs)++;
|
|
}
|
|
|
|
if (state->have_event) {
|
|
yaml_event_delete(&state->event);
|
|
}
|
|
|
|
if (Y_PARSER_FAILURE == code) {
|
|
//TODO sdubois
|
|
//zval_ptr_dtor(&retval);
|
|
ZVAL_UNDEF(retval);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ php_yaml_read_partial()
|
|
* Read a particular document from the parser's document stream.
|
|
*/
|
|
void php_yaml_read_partial(
|
|
parser_state_t *state, zend_long pos, zend_long *ndocs, zval *retval)
|
|
{
|
|
int code = Y_PARSER_CONTINUE;
|
|
|
|
while (Y_PARSER_CONTINUE == code) {
|
|
|
|
if (!NEXT_EVENT()) {
|
|
code = Y_PARSER_FAILURE;
|
|
|
|
} else if (YAML_STREAM_END_EVENT == state->event.type) {
|
|
if (pos != 0) {
|
|
/* reached end of stream without finding what we wanted */
|
|
php_error_docref(NULL, E_WARNING,
|
|
"end of stream reached without finding document " ZEND_LONG_FMT,
|
|
pos);
|
|
code = Y_PARSER_FAILURE;
|
|
} else {
|
|
/* an empty document is valid YAML */
|
|
code = Y_PARSER_SUCCESS;
|
|
ZVAL_NULL(retval);
|
|
}
|
|
} else if (YAML_DOCUMENT_START_EVENT == state->event.type) {
|
|
if (*ndocs == pos) {
|
|
handle_document(state, retval);
|
|
if (Z_TYPE_P(retval) == IS_UNDEF) {
|
|
code = Y_PARSER_FAILURE;
|
|
break;
|
|
}
|
|
code = Y_PARSER_SUCCESS;
|
|
}
|
|
/* count each document as we pass */
|
|
(*ndocs)++;
|
|
}
|
|
}
|
|
|
|
if (state->have_event) {
|
|
yaml_event_delete(&state->event);
|
|
}
|
|
|
|
if (Y_PARSER_FAILURE == code) {
|
|
//TODO sdubois
|
|
if (Z_TYPE_P(retval) != IS_UNDEF) {
|
|
ZVAL_UNDEF(retval);
|
|
}
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ handle_parser_error()
|
|
* Emit a warning about a parser error
|
|
*/
|
|
static void handle_parser_error(const yaml_parser_t *parser)
|
|
{
|
|
const char *error_type;
|
|
|
|
switch (parser->error) {
|
|
case YAML_MEMORY_ERROR:
|
|
error_type = "memory allocation";
|
|
break;
|
|
|
|
case YAML_READER_ERROR:
|
|
error_type = "reading";
|
|
break;
|
|
|
|
case YAML_SCANNER_ERROR:
|
|
error_type = "scanning";
|
|
break;
|
|
|
|
case YAML_PARSER_ERROR:
|
|
error_type = "parsing";
|
|
break;
|
|
|
|
default:
|
|
/* Shouldn't happen. */
|
|
error_type = "unknown";
|
|
break;
|
|
}
|
|
|
|
if (NULL != parser->problem) {
|
|
if (parser->context) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"%s error encountered during parsing: %s "
|
|
"(line %zd, column %zd), "
|
|
"context %s (line %zd, column %zd)",
|
|
error_type,
|
|
parser->problem,
|
|
parser->problem_mark.line + 1,
|
|
parser->problem_mark.column + 1, parser->context,
|
|
parser->context_mark.line + 1,
|
|
parser->context_mark.column + 1);
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"%s error encountered during parsing: %s "
|
|
"(line %zd, column %zd)",
|
|
error_type,
|
|
parser->problem,
|
|
parser->problem_mark.line + 1,
|
|
parser->problem_mark.column + 1);
|
|
}
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"%s error encountred during parsing", error_type);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ yaml_next_event()
|
|
* Load the next parser event
|
|
*/
|
|
static inline int yaml_next_event(parser_state_t *state)
|
|
{
|
|
if (state->have_event) {
|
|
/* free prior event */
|
|
yaml_event_delete(&state->event);
|
|
state->have_event = 0;
|
|
}
|
|
|
|
if (!yaml_parser_parse(&state->parser, &state->event)) {
|
|
/* error encountered parsing input */
|
|
state->have_event = 0;
|
|
handle_parser_error(&state->parser);
|
|
|
|
} else {
|
|
state->have_event = 1;
|
|
}
|
|
|
|
return state->have_event;
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ get_next_element()
|
|
* Extract the next whole element from the parse stream
|
|
*/
|
|
void get_next_element(parser_state_t *state, zval *retval)
|
|
{
|
|
if (!NEXT_EVENT()) {
|
|
/* check state->event if you need to know the difference between
|
|
* this error and a halt event
|
|
*/
|
|
ZVAL_UNDEF(retval);
|
|
return;
|
|
}
|
|
|
|
switch (state->event.type) {
|
|
case YAML_DOCUMENT_END_EVENT:
|
|
case YAML_MAPPING_END_EVENT:
|
|
case YAML_SEQUENCE_END_EVENT:
|
|
case YAML_STREAM_END_EVENT:
|
|
/* halting events */
|
|
break;
|
|
|
|
case YAML_DOCUMENT_START_EVENT:
|
|
handle_document(state, retval);
|
|
break;
|
|
|
|
case YAML_MAPPING_START_EVENT:
|
|
handle_mapping(state, retval);
|
|
break;
|
|
|
|
case YAML_SEQUENCE_START_EVENT:
|
|
handle_sequence(state, retval);
|
|
break;
|
|
|
|
case YAML_SCALAR_EVENT:
|
|
handle_scalar(state, retval);
|
|
break;
|
|
|
|
case YAML_ALIAS_EVENT:
|
|
handle_alias(state, retval);
|
|
break;
|
|
|
|
default:
|
|
/* any other event is an error */
|
|
php_error_docref(NULL, E_WARNING,
|
|
"Unexpected event type %d "
|
|
"(line %zd, column %zd)",
|
|
state->event.type,
|
|
state->parser.mark.line + 1,
|
|
state->parser.mark.column + 1);
|
|
break;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ handle_document()
|
|
* Handle a document event
|
|
*/
|
|
void handle_document(parser_state_t *state, zval *retval)
|
|
{
|
|
/* make a new array to hold aliases */
|
|
array_init(&state->aliases);
|
|
|
|
/* document consists of next element */
|
|
get_next_element(state, retval);
|
|
|
|
/* clean up aliases */
|
|
zval_ptr_dtor(&state->aliases);
|
|
|
|
/* assert that end event is next in stream */
|
|
if (NULL != retval && NEXT_EVENT() &&
|
|
YAML_DOCUMENT_END_EVENT != state->event.type) {
|
|
zval_ptr_dtor(retval);
|
|
ZVAL_UNDEF(retval);
|
|
//retval = NULL;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ handle_mapping()
|
|
* Handle a mapping event
|
|
*/
|
|
void handle_mapping(parser_state_t *state, zval *retval)
|
|
{
|
|
yaml_event_t src_event = { YAML_NO_EVENT }, key_event = { YAML_NO_EVENT };
|
|
zval key = {{0} };
|
|
zval value = {{0} };
|
|
zval *arrval = retval;
|
|
zval *valptr;
|
|
|
|
/* save copy of mapping start event */
|
|
COPY_EVENT(src_event, state);
|
|
|
|
/* make a new array to hold mapping */
|
|
array_init(retval);
|
|
|
|
if (NULL != src_event.data.mapping_start.anchor) {
|
|
/* record anchors in current alias table */
|
|
arrval = record_anchor_make_ref(&state->aliases, (char *) src_event.data.mapping_start.anchor, retval);
|
|
}
|
|
|
|
for (get_next_element(state, &key); Z_TYPE(key) != IS_UNDEF; get_next_element(state, &key)) {
|
|
COPY_EVENT(key_event, state);
|
|
get_next_element(state, &value);
|
|
|
|
if (Z_TYPE(value) == IS_UNDEF) {
|
|
yaml_event_delete(&src_event);
|
|
yaml_event_delete(&key_event);
|
|
zval_ptr_dtor(&key);
|
|
return;
|
|
}
|
|
|
|
valptr = Z_ISREF(value) ? Z_REFVAL(value) : &value;
|
|
|
|
/* check for '<<' and handle merge */
|
|
if (key_event.type == YAML_SCALAR_EVENT &&
|
|
IS_NOT_QUOTED_OR_TAG_IS(key_event, YAML_MERGE_TAG) &&
|
|
Z_TYPE(key) == IS_STRING &&
|
|
Z_TYPE_P(valptr) == IS_ARRAY &&
|
|
STR_EQ("<<", Z_STRVAL(key))) {
|
|
/* zend_hash_merge */
|
|
/*
|
|
* value is either a single ref or a simple array of refs
|
|
*/
|
|
if (YAML_ALIAS_EVENT == state->event.type) {
|
|
/* single ref */
|
|
zend_hash_merge(Z_ARRVAL_P(arrval), Z_ARRVAL_P(valptr), zval_add_ref, 0);
|
|
} else {
|
|
/* array of refs */
|
|
zval *zvalp;
|
|
ZEND_HASH_FOREACH_VAL(HASH_OF(valptr), zvalp) {
|
|
if (Z_ISREF_P(zvalp)) {
|
|
ZVAL_DEREF(zvalp);
|
|
zend_hash_merge(
|
|
Z_ARRVAL_P(arrval), Z_ARRVAL_P(zvalp),
|
|
zval_add_ref, 0);
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"expected a mapping for merging, but found scalar "
|
|
"(line %zd, column %zd)",
|
|
state->parser.mark.line + 1,
|
|
state->parser.mark.column + 1);
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
zval_ptr_dtor(&value);
|
|
} else {
|
|
/* add key => value to arrval */
|
|
zval *offset = &key;
|
|
ZVAL_DEREF(offset);
|
|
switch(Z_TYPE_P(offset)) {
|
|
case IS_DOUBLE:
|
|
case IS_FALSE:
|
|
case IS_LONG:
|
|
case IS_NULL:
|
|
case IS_RESOURCE:
|
|
case IS_STRING:
|
|
case IS_TRUE:
|
|
array_set_zval_key(Z_ARRVAL_P(arrval), offset, &value);
|
|
Z_TRY_DELREF(value);
|
|
break;
|
|
case IS_UNDEF:
|
|
zend_hash_update(Z_ARRVAL_P(arrval), ZSTR_EMPTY_ALLOC(), &value);
|
|
break;
|
|
default:
|
|
php_error_docref(
|
|
NULL, E_WARNING,
|
|
"Illegal offset type %s (line %zd, column %zd)",
|
|
zend_zval_type_name(offset),
|
|
state->parser.mark.line + 1,
|
|
state->parser.mark.column + 1
|
|
);
|
|
zval_ptr_dtor(&value);
|
|
break;
|
|
}
|
|
}
|
|
yaml_event_delete(&key_event);
|
|
zval_ptr_dtor(&key);
|
|
ZVAL_UNDEF(&key);
|
|
}
|
|
|
|
if (YAML_MAPPING_END_EVENT != state->event.type) {
|
|
//TODO Sean-Der
|
|
ZVAL_UNDEF(retval);
|
|
}
|
|
|
|
if (NULL != retval && NULL != state->callbacks) {
|
|
/* apply callbacks to the collected node */
|
|
if (Y_FILTER_FAILURE == apply_filter(
|
|
retval, src_event, state->callbacks)) {
|
|
//TODO Sean-Der
|
|
ZVAL_UNDEF(retval);
|
|
}
|
|
}
|
|
|
|
yaml_event_delete(&src_event);
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ handle_sequence
|
|
* Handle a sequence event
|
|
*/
|
|
void handle_sequence (parser_state_t *state, zval *retval) {
|
|
yaml_event_t src_event = { YAML_NO_EVENT };
|
|
zval value = {{0} };
|
|
zval *arrval = retval;
|
|
|
|
/* save copy of sequence start event */
|
|
COPY_EVENT(src_event, state);
|
|
|
|
/* make a new array to hold mapping */
|
|
array_init(retval);
|
|
|
|
if (NULL != src_event.data.sequence_start.anchor) {
|
|
arrval = record_anchor_make_ref(&state->aliases, (char *) src_event.data.sequence_start.anchor, retval);
|
|
}
|
|
|
|
for (get_next_element(state, &value); Z_TYPE_P(&value) != IS_UNDEF; get_next_element(state, &value)) {
|
|
add_next_index_zval(arrval, &value);
|
|
ZVAL_UNDEF(&value);
|
|
}
|
|
|
|
if (YAML_SEQUENCE_END_EVENT != state->event.type) {
|
|
zval_ptr_dtor(retval);
|
|
ZVAL_UNDEF(retval);
|
|
goto done;
|
|
//TODO Sean-Der
|
|
//zval_ptr_dtor(&retval);
|
|
//retval = NULL;
|
|
}
|
|
|
|
if (NULL != retval && NULL != state->callbacks) {
|
|
/* apply callbacks to the collected node */
|
|
if (Y_FILTER_FAILURE == apply_filter(
|
|
retval, src_event, state->callbacks)) {
|
|
zval_ptr_dtor(retval);
|
|
ZVAL_UNDEF(retval);
|
|
goto done;
|
|
//TODO Sean-Der
|
|
//retval = NULL;
|
|
}
|
|
}
|
|
|
|
done:
|
|
yaml_event_delete(&src_event);
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ handle_scalar()
|
|
* Handle a scalar event
|
|
*/
|
|
void handle_scalar(parser_state_t *state, zval *retval) {
|
|
state->eval_func(state->event, state->callbacks, retval);
|
|
if (NULL != retval && NULL != state->event.data.scalar.anchor) {
|
|
record_anchor_make_ref(&state->aliases, (char *) state->event.data.scalar.anchor, retval);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ handle_alias()
|
|
* Handle an alias event
|
|
*/
|
|
void handle_alias(parser_state_t *state, zval *retval) {
|
|
char *anchor = (char *) state->event.data.alias.anchor;
|
|
zend_string *anchor_zstring = zend_string_init(anchor, strlen(anchor), 0);
|
|
zval *alias;
|
|
|
|
if ((alias = zend_hash_find(Z_ARRVAL_P(&state->aliases), anchor_zstring)) == NULL) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"alias %s is not registered "
|
|
"(line %zd, column %zd)",
|
|
anchor,
|
|
state->parser.mark.line + 1,
|
|
state->parser.mark.column + 1);
|
|
zend_string_release(anchor_zstring);
|
|
ZVAL_UNDEF(retval);
|
|
return;
|
|
}
|
|
zend_string_release(anchor_zstring);
|
|
|
|
/* add a reference to retval's internal counter */
|
|
Z_TRY_ADDREF_P(alias);
|
|
ZVAL_COPY_VALUE(retval, alias);
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
|
|
|
|
/* {{{ record_anchor_make_ref()
|
|
* Record an anchor in alias table
|
|
*/
|
|
static zval *record_anchor_make_ref(zval *aliases, const char *anchor, zval *value)
|
|
{
|
|
ZVAL_MAKE_REF(value);
|
|
Z_TRY_ADDREF_P(value);
|
|
add_assoc_zval(aliases, anchor, value);
|
|
return Z_REFVAL_P(value);
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ apply_filter()
|
|
* Apply user supplied hander to node
|
|
*/
|
|
static int
|
|
apply_filter(zval *zp, yaml_event_t event, HashTable *callbacks)
|
|
{
|
|
char *tag = { 0 };
|
|
zend_string *tag_zstring;
|
|
zval *callback = { 0 };
|
|
|
|
/* detect event type and get tag */
|
|
switch (event.type) {
|
|
case YAML_SEQUENCE_START_EVENT:
|
|
if (event.data.sequence_start.implicit) {
|
|
tag = YAML_SEQ_TAG;
|
|
} else {
|
|
tag = (char *) event.data.sequence_start.tag;
|
|
}
|
|
break;
|
|
|
|
case YAML_MAPPING_START_EVENT:
|
|
if (event.data.sequence_start.implicit) {
|
|
tag = YAML_MAP_TAG;
|
|
} else {
|
|
tag = (char *) event.data.mapping_start.tag;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* don't care about other event types */
|
|
break;
|
|
}
|
|
|
|
if (NULL == tag) {
|
|
return Y_FILTER_NONE;
|
|
}
|
|
|
|
/* find and apply the filter function */
|
|
tag_zstring = zend_string_init(tag, strlen(tag), 0);
|
|
if ((callback = zend_hash_find(callbacks, tag_zstring)) != NULL) {
|
|
int callback_result;
|
|
zval callback_args[3];
|
|
zval retval;
|
|
|
|
callback_args[0] = *zp;
|
|
ZVAL_STRINGL(&callback_args[1], tag, strlen(tag));
|
|
ZVAL_LONG(&callback_args[2], 0);
|
|
|
|
/* call the user function */
|
|
callback_result = call_user_function(EG(function_table), NULL, callback, &retval, 3, callback_args);
|
|
|
|
/* cleanup our temp variables */
|
|
zval_ptr_dtor(&callback_args[1]);
|
|
zval_ptr_dtor(&callback_args[2]);
|
|
zend_string_release(tag_zstring);
|
|
|
|
if (FAILURE == callback_result || Z_TYPE_P(&retval) == IS_UNDEF) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"Failed to apply filter for tag '%s'"
|
|
" with user defined function", tag);
|
|
return Y_FILTER_FAILURE;
|
|
|
|
} else {
|
|
if (&retval == zp) {
|
|
/* throw away duplicate response */
|
|
zval_ptr_dtor(&retval);
|
|
} else {
|
|
/* copy result into our return var */
|
|
if (Z_ISREF_P(zp)) {
|
|
zval *tmp = Z_REFVAL_P(zp);
|
|
zval_ptr_dtor(tmp);
|
|
ZVAL_COPY_VALUE(tmp, &retval);
|
|
} else {
|
|
zval_ptr_dtor(zp);
|
|
ZVAL_COPY_VALUE(zp, &retval);
|
|
}
|
|
}
|
|
return Y_FILTER_SUCCESS;
|
|
}
|
|
|
|
} else {
|
|
zend_string_release(tag_zstring);
|
|
return Y_FILTER_NONE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ eval_scalar()
|
|
* Convert a scalar node to the proper PHP data type.
|
|
*
|
|
* All YAML scalar types found at http://yaml.org/type/index.html.
|
|
*/
|
|
void eval_scalar(yaml_event_t event,
|
|
HashTable * callbacks, zval *retval)
|
|
{
|
|
char *value = (char *) event.data.scalar.value;
|
|
size_t length = event.data.scalar.length;
|
|
int flags = 0;
|
|
|
|
ZVAL_NULL(retval);
|
|
|
|
/* check for non-specific tag (treat as a string) */
|
|
if (SCALAR_TAG_IS(event, YAML_NONSPECIFIC_TAG) ||
|
|
event.data.scalar.quoted_implicit) {
|
|
ZVAL_STRINGL(retval, value, length);
|
|
return;
|
|
}
|
|
|
|
/* check for null */
|
|
if (scalar_is_null(value, length, &event)) {
|
|
return;
|
|
}
|
|
|
|
/* check for bool */
|
|
if (-1 != (flags = scalar_is_bool(value, length, &event))) {
|
|
ZVAL_BOOL(retval, (zend_bool) flags);
|
|
return;
|
|
}
|
|
|
|
/* check for numeric (int or float) */
|
|
if (!event.data.scalar.quoted_implicit &&
|
|
(event.data.scalar.plain_implicit ||
|
|
SCALAR_TAG_IS(event, YAML_INT_TAG) ||
|
|
SCALAR_TAG_IS(event, YAML_FLOAT_TAG))) {
|
|
zend_long lval = 0;
|
|
double dval = 0.0;
|
|
|
|
flags = scalar_is_numeric(
|
|
value, length, &lval, &dval, NULL);
|
|
if (flags != Y_SCALAR_IS_NOT_NUMERIC) {
|
|
if (flags & Y_SCALAR_IS_FLOAT) {
|
|
ZVAL_DOUBLE(retval, dval);
|
|
|
|
} else {
|
|
ZVAL_LONG(retval, lval);
|
|
}
|
|
|
|
if (event.data.scalar.plain_implicit) {
|
|
/* pass */
|
|
|
|
} else if (SCALAR_TAG_IS(event, YAML_FLOAT_TAG) &&
|
|
(flags & Y_SCALAR_IS_INT)) {
|
|
convert_to_double(retval);
|
|
|
|
} else if (SCALAR_TAG_IS(event, YAML_INT_TAG) &&
|
|
(flags & Y_SCALAR_IS_FLOAT)) {
|
|
convert_to_long(retval);
|
|
}
|
|
|
|
return;
|
|
|
|
} else if (IS_NOT_IMPLICIT_AND_TAG_IS(event, YAML_FLOAT_TAG)) {
|
|
ZVAL_STRINGL(retval, value, length);
|
|
convert_to_double(retval);
|
|
return;
|
|
|
|
} else if (IS_NOT_IMPLICIT_AND_TAG_IS(event, YAML_INT_TAG)) {
|
|
ZVAL_STRINGL(retval, value, length);
|
|
convert_to_long(retval);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* check for timestamp */
|
|
if (IS_NOT_IMPLICIT_AND_TAG_IS(event, YAML_TIMESTAMP_TAG) ||
|
|
scalar_is_timestamp(value, length)) {
|
|
if (FAILURE == eval_timestamp(
|
|
&retval, value, (int) length)) {
|
|
ZVAL_NULL(retval);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* check for binary */
|
|
if (YAML_G(decode_binary) &&
|
|
IS_NOT_IMPLICIT_AND_TAG_IS(event, YAML_BINARY_TAG)) {
|
|
zend_string *data;
|
|
|
|
data = php_base64_decode((const unsigned char *) value, (int) length);
|
|
if (NULL == data) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"Failed to decode binary");
|
|
ZVAL_NULL(retval);
|
|
|
|
} else {
|
|
ZVAL_STR(retval, data);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* check for php object */
|
|
if (YAML_G(decode_php) &&
|
|
IS_NOT_IMPLICIT_AND_TAG_IS(event, YAML_PHP_TAG)) {
|
|
const unsigned char *p;
|
|
php_unserialize_data_t var_hash;
|
|
|
|
p = (const unsigned char *) value;
|
|
PHP_VAR_UNSERIALIZE_INIT(var_hash);
|
|
|
|
if (!php_var_unserialize(
|
|
retval, &p, p + (int) length, &var_hash)) {
|
|
php_error_docref(NULL, E_NOTICE,
|
|
"Failed to unserialize class");
|
|
/* return the serialized string directly */
|
|
ZVAL_STRINGL(retval, value, length);
|
|
}
|
|
|
|
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
|
|
return;
|
|
}
|
|
|
|
/* others (treat as a string) */
|
|
ZVAL_STRINGL(retval, value, length);
|
|
|
|
return;
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ eval_scalar_with_callbacks()
|
|
* Convert a scalar node to the proper PHP data type using user supplied input
|
|
* filters if available.
|
|
*/
|
|
void eval_scalar_with_callbacks(yaml_event_t event,
|
|
HashTable *callbacks, zval *retval)
|
|
{
|
|
const char *tag = (char *) event.data.scalar.tag;
|
|
zend_string *tag_zstring;
|
|
zval *callback;
|
|
|
|
if (YAML_PLAIN_SCALAR_STYLE == event.data.scalar.style && NULL == tag) {
|
|
/* plain scalar with no specified type */
|
|
tag = detect_scalar_type(
|
|
(char *) event.data.scalar.value, event.data.scalar.length,
|
|
&event);
|
|
}
|
|
if (NULL == tag) {
|
|
/* couldn't/wouldn't detect tag type, assume string */
|
|
tag = YAML_STR_TAG;
|
|
}
|
|
|
|
tag_zstring = zend_string_init(tag, strlen(tag), 0);
|
|
/* find and apply the evaluation function */
|
|
if ((callback = zend_hash_find(callbacks, tag_zstring)) != NULL) {
|
|
zval argv[3];
|
|
|
|
ZVAL_STRINGL(&argv[0], (const char *) event.data.scalar.value, event.data.scalar.length);
|
|
ZVAL_STRINGL(&argv[1], tag, strlen(tag));
|
|
ZVAL_LONG(&argv[2], event.data.scalar.style);
|
|
|
|
if (FAILURE == call_user_function(EG(function_table), NULL, callback, retval, 3, argv) || Z_TYPE_P(retval) == IS_UNDEF) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"Failed to evaluate value for tag '%s'"
|
|
" with user defined function", tag);
|
|
}
|
|
|
|
zval_ptr_dtor(&argv[0]);
|
|
zval_ptr_dtor(&argv[1]);
|
|
zval_ptr_dtor(&argv[2]);
|
|
} else {
|
|
/* no mapping, so handle raw */
|
|
eval_scalar(event, NULL, retval);
|
|
}
|
|
zend_string_release(tag_zstring);
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ eval_timestamp()
|
|
* Convert a timestamp
|
|
*
|
|
* This is switched on/off by the `yaml.decode_timestamp` ini setting.
|
|
* - yaml.decode_timestamp=0 for no timestamp parsing
|
|
* - yaml.decode_timestamp=1 for strtotime parsing
|
|
* - yaml.decode_timestamp=2 for date_create parsing
|
|
*/
|
|
static int
|
|
eval_timestamp(zval **zpp, const char *ts, size_t ts_len)
|
|
{
|
|
if (NULL != YAML_G(timestamp_decoder) ||
|
|
1L == YAML_G(decode_timestamp) ||
|
|
2L == YAML_G(decode_timestamp)) {
|
|
zval argv[1];
|
|
zval arg, retval, *func, afunc;
|
|
const char *funcs[] = { "strtotime", "date_create" };
|
|
|
|
if (NULL == YAML_G(timestamp_decoder)) {
|
|
if (2L == YAML_G(decode_timestamp)) {
|
|
ZVAL_STRING(&afunc, funcs[1]);
|
|
|
|
} else {
|
|
ZVAL_STRING(&afunc, funcs[0]);
|
|
}
|
|
|
|
func = &afunc;
|
|
} else {
|
|
func = YAML_G(timestamp_decoder);
|
|
}
|
|
|
|
ZVAL_STRINGL(&arg, ts, ts_len);
|
|
argv[0] = arg;
|
|
|
|
if (FAILURE == call_user_function(EG(function_table), NULL, func,
|
|
&retval, 1, argv) || Z_TYPE_P(&retval) == IS_UNDEF) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"Failed to evaluate string '%s' as timestamp", ts);
|
|
if (func != NULL) {
|
|
zval_ptr_dtor(func);
|
|
}
|
|
zval_ptr_dtor(&arg);
|
|
return FAILURE;
|
|
|
|
} else {
|
|
if (func != NULL) {
|
|
zval_ptr_dtor(func);
|
|
}
|
|
zval_ptr_dtor(&arg);
|
|
ZVAL_COPY_VALUE(*zpp, &retval);
|
|
return SUCCESS;
|
|
}
|
|
|
|
} else {
|
|
zval_dtor(*zpp);
|
|
ZVAL_STRINGL(*zpp, ts, ts_len);
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
* vim600: noet sw=4 ts=4 fdm=marker
|
|
* vim<600: noet sw=4 ts=4
|
|
*/
|