%PDF- %PDF-
| Direktori : /home/opti/optisoftcom/.cpanm/work/1544535406.20768/BSON-XS-v0.6.0/xs/ |
| Current File : /home/opti/optisoftcom/.cpanm/work/1544535406.20768/BSON-XS-v0.6.0/xs/XS.c |
/*
* This file was generated automatically by ExtUtils::ParseXS version 3.18 from the
* contents of XS.xs. Do not edit this file, edit XS.xs instead.
*
* ANY CHANGES MADE HERE WILL BE LOST!
*
*/
#line 1 "xs/XS.xs"
/* vim: set ts=2 sts=2 sw=2 et tw=75: */
/*
* Copyright 2009-2016 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bson.h"
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "regcomp.h"
#include "string.h"
#include "limits.h"
/* load after other Perl headers */
#include "ppport.h"
/* adapted from perl.h and must come after it */
#if !defined(Strtoll)
# ifdef __hpux
# define Strtoll __strtoll
# endif
# ifdef WIN32
# define Strtoll _strtoi64
# endif
# if !defined(Strtoll) && defined(HAS_STRTOLL)
# define Strtoll strtoll
# endif
# if !defined(Strtoll) && defined(HAS_STRTOQ)
# define Strtoll strtoq
# endif
# if !defined(Strtoll)
# error strtoll not available
# endif
#endif
/* whether to add an _id field */
#define PREP 1
#define NO_PREP 0
/* define regex macros for Perl 5.8 */
#ifndef RX_PRECOMP
#define RX_PRECOMP(re) ((re)->precomp)
#define RX_PRELEN(re) ((re)->prelen)
#endif
#define SUBTYPE_BINARY_DEPRECATED 2
#define SUBTYPE_BINARY 0
/* struct for circular ref checks */
typedef struct _stackette {
void *ptr;
struct _stackette *prev;
} stackette;
#define EMPTY_STACK 0
#define MAX_DEPTH 100
/* convenience functions taken from Text::CSV_XS by H.M. Brand */
#define _is_reftype(f,x) \
(f && ((SvGMAGICAL (f) && mg_get (f)) || 1) && SvROK (f) && SvTYPE (SvRV (f)) == x)
#define _is_arrayref(f) _is_reftype (f, SVt_PVAV)
#define _is_hashref(f) _is_reftype (f, SVt_PVHV)
#define _is_coderef(f) _is_reftype (f, SVt_PVCV)
/* shorthand for getting an SV* from a hash and key */
#define _hv_fetchs_sv(h,k) \
(((svp = hv_fetchs(h, k, FALSE)) && *svp) ? *svp : 0)
/* perl call helpers
*
* For convenience, these functions encapsulate the verbose stack
* manipulation code necessary to call perl functions from C.
*
*/
static SV * call_method_va(SV *self, const char *method, int num, ...);
static SV * call_method_with_pairs_va(SV *self, const char *method, ...);
static SV * new_object_from_pairs(const char *klass, ...);
static SV * call_method_with_arglist (SV *self, const char *method, va_list args);
static SV * call_sv_va (SV *func, int num, ...);
static SV * call_pv_va (char *func, int num, ...);
static bool call_key_value_iter (SV *func, SV **ret );
#define call_perl_reader(s,m) call_method_va(s,m,0)
/* BSON encoding
*
* Public function perl_mongo_sv_to_bsonis the entry point. It calls one
* of the container encoding functions, hv_doc_to_bson, or
* ixhash_doc_to_bson. Those iterate their contents, encoding them with
* sv_to_bson_elem. sv_to_bson_elem delegates to various append_*
* functions for particular types.
*
* Other functions are utility functions used during encoding.
*/
static void perl_mongo_sv_to_bson (bson_t * bson, SV *sv, HV *opts);
static void hv_to_bson(bson_t * bson, SV *sv, HV *opts, stackette *stack, int depth, bool subdoc);
static void ixhash_to_bson(bson_t * bson, SV *sv, HV *opts, stackette *stack, int depth, bool subdoc);
static void iter_src_to_bson(bson_t * bson, SV *sv, HV *opts, stackette *stack, int depth, bool subdoc);
#define hv_doc_to_bson(b,d,o,s,u) hv_to_bson((b),(d),(o),(s),(u),0)
#define hv_elem_to_bson(b,d,o,s,u) hv_to_bson((b),(d),(o),(s),(u),1)
#define ixhash_doc_to_bson(b,d,o,s,u) ixhash_to_bson((b),(d),(o),(s),(u),0)
#define ixhash_elem_to_bson(b,d,o,s,u) ixhash_to_bson((b),(d),(o),(s),(u),1)
#define iter_doc_to_bson(b,d,o,s,u) iter_src_to_bson((b),(d),(o),(s),(u),0)
#define iter_elem_to_bson(b,d,o,s,u) iter_src_to_bson((b),(d),(o),(s),(u),1)
static void sv_to_bson_elem (bson_t * bson, const char *key, SV *sv, HV *opts, stackette *stack, int depth);
const char * maybe_append_first_key(bson_t *bson, HV *opts, stackette *stack, int depth);
static void append_binary(bson_t * bson, const char * key, bson_subtype_t subtype, SV * sv);
static void append_regex(bson_t * bson, const char *key, REGEXP *re, SV * sv);
static void append_decomposed_regex(bson_t *bson, const char *key, const char *pattern, const char *flags);
static void append_fit_int(bson_t * bson, const char *key, SV * sv);
static void append_utf8(bson_t * bson, const char *key, SV * sv);
static void assert_valid_key(const char* str, STRLEN len);
static const char * bson_key(const char * str, HV *opts);
static void get_regex_flags(char * flags, SV *sv);
static int64_t math_bigint_to_int64(SV *sv, const char *key);
static SV* int64_as_SV(int64_t value);
static stackette * check_circular_ref(void *ptr, stackette *stack);
static SV* bson_parent_type(SV *sv);
/* BSON decoding
*
* Public function _decode_bson is the entry point. It calls
* bson_doc_to_hashref, which construct a container and fills it using
* bson_elem_to_sv. That may call bson_doc_to_hashref or
* bson_doc_to_arrayref to decode sub-containers.
*
* The bson_oid_to_sv function manually constructs a BSON::OID object to
* avoid the overhead of calling its constructor. This optimization is
* fragile and might need to be reconsidered.
*
*/
static SV * bson_doc_to_hashref(bson_iter_t * iter, HV *opts, int depth, bool top);
static SV * bson_doc_to_tiedhash(bson_iter_t * iter, HV *opts, int depth, bool top);
static SV * bson_array_to_arrayref(bson_iter_t * iter, HV *opts, int depth);
static SV * bson_elem_to_sv(const bson_iter_t * iter, const char *key, HV *opts, int depth);
static SV * bson_oid_to_sv(const bson_iter_t * iter);
/********************************************************************
* Some C libraries (e.g. MSVCRT) do not have a "timegm" function.
* Here is a surrogate implementation.
********************************************************************/
#if defined(WIN32) || defined(sun)
static int
is_leap_year(unsigned year) {
year += 1900;
return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
}
static time_t
timegm(struct tm *tm) {
static const unsigned month_start[2][12] = {
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 },
};
time_t ret = 0;
int i;
for (i = 70; i < tm->tm_year; ++i)
ret += is_leap_year(i) ? 366 : 365;
ret += month_start[is_leap_year(tm->tm_year)][tm->tm_mon];
ret += tm->tm_mday - 1;
ret *= 24;
ret += tm->tm_hour;
ret *= 60;
ret += tm->tm_min;
ret *= 60;
ret += tm->tm_sec;
return ret;
}
#endif /* WIN32 */
/********************************************************************
* perl call helpers
********************************************************************/
/* call_method_va -- calls a method with a variable number
* of SV * arguments. The SV* arguments are NOT mortalized.
* Must give the number of arguments before the variable list */
static SV *
call_method_va (SV *self, const char *method, int num, ...) {
dSP;
SV *ret;
I32 count;
va_list args;
ENTER;
SAVETMPS;
PUSHMARK (SP);
XPUSHs (self);
va_start (args, num);
for( ; num > 0; num-- ) {
XPUSHs (va_arg( args, SV* ));
}
va_end(args);
PUTBACK;
count = call_method (method, G_SCALAR);
SPAGAIN;
if (count != 1) {
croak ("method didn't return a value");
}
ret = POPs;
SvREFCNT_inc (ret);
PUTBACK;
FREETMPS;
LEAVE;
return ret;
}
/* call_method_va_paris -- calls a method with a variable number
* of key/value pairs as paired char* and SV* arguments. The SV* arguments
* are NOT mortalized. The final argument must be a NULL key. */
static SV *
call_method_with_pairs_va (SV *self, const char *method, ...) {
SV *ret;
va_list args;
va_start (args, method);
ret = call_method_with_arglist(self, method, args);
va_end(args);
return ret;
}
/* new_object_from_pairs -- calls 'new' with a variable number of
* of key/value pairs as paired char* and SV* arguments. The SV* arguments
* are NOT mortalized. The final argument must be a NULL key. */
static SV *
new_object_from_pairs(const char *klass, ...) {
SV *ret;
va_list args;
va_start (args, klass);
ret = call_method_with_arglist(sv_2mortal(newSVpv(klass,0)), "new", args);
va_end(args);
return ret;
}
static SV *
call_method_with_arglist (SV *self, const char *method, va_list args) {
dSP;
SV *ret = NULL;
char *key;
I32 count;
ENTER;
SAVETMPS;
PUSHMARK (SP);
XPUSHs (self);
while ((key = va_arg (args, char *))) {
mXPUSHp (key, strlen (key));
XPUSHs (va_arg (args, SV *));
}
PUTBACK;
count = call_method (method, G_SCALAR);
SPAGAIN;
if (count != 1) {
croak ("method didn't return a value");
}
ret = POPs;
SvREFCNT_inc (ret);
PUTBACK;
FREETMPS;
LEAVE;
return ret;
}
static SV *
call_sv_va (SV *func, int num, ...) {
dSP;
SV *ret;
I32 count;
va_list args;
ENTER;
SAVETMPS;
PUSHMARK (SP);
va_start (args, num);
for( ; num > 0; num-- ) {
XPUSHs (va_arg( args, SV* ));
}
va_end(args);
PUTBACK;
count = call_sv(func, G_SCALAR);
SPAGAIN;
if (count != 1) {
croak ("method didn't return a value");
}
ret = POPs;
SvREFCNT_inc (ret);
PUTBACK;
FREETMPS;
LEAVE;
return ret;
}
/* Call func and return key value pairs.
*
* ret is address of (SV*)[2] where key and value will be put.
*
* return value is true if key is defined and false otherwise.
*/
static bool
call_key_value_iter (SV *func, SV **ret ) {
dSP;
I32 count;
bool ok;
ENTER;
SAVETMPS;
PUSHMARK (SP);
PUTBACK;
count = call_sv(func, G_ARRAY);
SPAGAIN;
if ( count == 0 ) {
ok = false;
}
else {
SvREFCNT_inc (ret[1] = POPs);
SvREFCNT_inc (ret[0] = POPs);
ok = SvOK(ret[0]) != 0;
}
PUTBACK;
FREETMPS;
LEAVE;
return ok;
}
static SV *
call_pv_va (char *func, int num, ...) {
dSP;
SV *ret;
I32 count;
va_list args;
ENTER;
SAVETMPS;
PUSHMARK (SP);
va_start (args, num);
for( ; num > 0; num-- ) {
XPUSHs (va_arg( args, SV* ));
}
va_end(args);
PUTBACK;
count = call_pv(func, G_SCALAR);
SPAGAIN;
if (count != 1) {
croak ("function %s didn't return a value", func);
}
ret = POPs;
SvREFCNT_inc (ret);
PUTBACK;
FREETMPS;
LEAVE;
return ret;
}
/********************************************************************
* BSON encoding
********************************************************************/
void
perl_mongo_sv_to_bson (bson_t * bson, SV *sv, HV *opts) {
if (!SvROK (sv)) {
croak ("not a reference");
}
if ( ! sv_isobject(sv) ) {
switch ( SvTYPE(SvRV(sv)) ) {
case SVt_PVHV:
hv_doc_to_bson (bson, sv, opts, EMPTY_STACK, 0);
break;
default:
sv_dump(sv);
croak ("Can't encode unhandled variable type");
}
}
else {
SV *obj;
char *class;
obj = SvRV(sv);
class = HvNAME(SvSTASH(obj));
if ( strEQ(class, "Tie::IxHash") ) {
ixhash_doc_to_bson(bson, sv, opts, EMPTY_STACK, 0);
}
else if ( strEQ(class, "BSON::Doc") ) {
iter_doc_to_bson(bson, sv, opts, EMPTY_STACK, 0);
}
else if ( strEQ(class, "BSON::Raw") ) {
STRLEN str_len;
SV *encoded;
const char *bson_str;
bson_t *child;
encoded = sv_2mortal(call_perl_reader(sv, "bson"));
bson_str = SvPV(encoded, str_len);
child = bson_new_from_data((uint8_t*) bson_str, str_len);
bson_concat(bson, child);
bson_destroy(child);
}
else if ( strEQ(class, "MongoDB::BSON::_EncodedDoc") ) {
STRLEN str_len;
SV **svp;
SV *encoded;
const char *bson_str;
bson_t *child;
encoded = _hv_fetchs_sv((HV *)obj, "bson");
bson_str = SvPV(encoded, str_len);
child = bson_new_from_data((uint8_t*) bson_str, str_len);
bson_concat(bson, child);
bson_destroy(child);
}
else if ( strEQ(class, "MongoDB::BSON::Raw") ) {
SV *str_sv;
char *str;
STRLEN str_len;
bson_t *child;
str_sv = SvRV(sv);
// check type ok
if (!SvPOK(str_sv)) {
croak("MongoDB::BSON::Raw must be a blessed string reference");
}
str = SvPV(str_sv, str_len);
child = bson_new_from_data((uint8_t*) str, str_len);
bson_concat(bson, child);
bson_destroy(child);
}
else if (SvTYPE(obj) == SVt_PVHV) {
hv_doc_to_bson(bson, sv, opts, EMPTY_STACK, 0);
}
else {
croak ("Can't encode non-container of type '%s'", class);
}
}
}
static void
hv_to_bson(bson_t * bson, SV *sv, HV *opts, stackette *stack, int depth, bool subdoc) {
HE *he;
HV *hv;
const char *first_key = NULL;
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
hv = (HV*)SvRV(sv);
if (!(stack = check_circular_ref(hv, stack))) {
croak("circular reference detected");
}
if ( ! subdoc ) {
first_key = maybe_append_first_key(bson, opts, stack, depth);
}
(void)hv_iterinit (hv);
while ((he = hv_iternext (hv))) {
SV **hval;
STRLEN len;
const char *key = HePV (he, len);
uint32_t utf8 = HeUTF8(he);
assert_valid_key(key, len);
/* if we've already added the first key, continue */
if (first_key && strcmp(key, first_key) == 0) {
continue;
}
/*
* HeVAL doesn't return the correct value for tie(%foo, 'Tie::IxHash')
* so we're using hv_fetch
*/
if ((hval = hv_fetch(hv, key, utf8 ? -len : len, 0)) == 0) {
croak("could not find hash value for key %s, len:%lu", key, (long unsigned int)len);
}
if (!utf8) {
key = (const char *) bytes_to_utf8((U8 *)key, &len);
}
if ( ! is_utf8_string((const U8*)key,len)) {
croak( "Invalid UTF-8 detected while encoding BSON" );
}
sv_to_bson_elem (bson, key, *hval, opts, stack, depth);
if (!utf8) {
Safefree(key);
}
}
/* free the hv elem */
if ( ! subdoc ) {
Safefree(stack);
}
depth--;
}
static void
ixhash_to_bson(bson_t * bson, SV *sv, HV *opts, stackette *stack, int depth, bool subdoc) {
int i;
SV **keys_sv, **values_sv;
AV *array, *keys, *values;
const char *first_key = NULL;
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
/*
* a Tie::IxHash is of the form:
* [ {hash}, [keys], [order], 0 ]
*/
array = (AV*)SvRV(sv);
/* check if we're in an infinite loop */
if (!(stack = check_circular_ref(array, stack))) {
croak("circular ref");
}
/* keys in order, from position 1 */
keys_sv = av_fetch(array, 1, 0);
keys = (AV*)SvRV(*keys_sv);
/* values in order, from position 2 */
values_sv = av_fetch(array, 2, 0);
values = (AV*)SvRV(*values_sv);
if ( ! subdoc ) {
first_key = maybe_append_first_key(bson, opts, stack, depth);
}
for (i=0; i<=av_len(keys); i++) {
SV **k, **v;
STRLEN len;
const char *str;
if (!(k = av_fetch(keys, i, 0)) ||
!(v = av_fetch(values, i, 0))) {
croak ("failed to fetch associative array value");
}
str = SvPVutf8(*k, len);
assert_valid_key(str,len);
if (first_key && strcmp(str, first_key) == 0) {
continue;
}
sv_to_bson_elem(bson, str, *v, opts, stack, depth);
}
/* free the ixhash elem */
if ( ! subdoc ) {
Safefree(stack);
}
depth--;
}
/* Construct a BSON document from an iterator code ref that returns key
* value pairs */
static void
iter_src_to_bson(bson_t * bson, SV *sv, HV *opts, stackette *stack, int depth, bool subdoc) {
int i;
SV *iter;
SV * kv[2];
const char *first_key = NULL;
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
/* check if we're in an infinite loop */
if (!(stack = check_circular_ref(SvRV(sv), stack))) {
croak("circular ref: %s", SvPV_nolen(sv));
}
if ( ! subdoc ) {
first_key = maybe_append_first_key(bson, opts, stack, depth);
}
iter = sv_2mortal(call_perl_reader(sv, "_iterator"));
if ( !SvROK(iter) || SvTYPE(SvRV(iter)) != SVt_PVCV ) {
croak("invalid iterator from %s", SvPV_nolen(sv));
}
while ( call_key_value_iter( iter, kv ) ) {
sv_2mortal(kv[0]);
sv_2mortal(kv[1]);
STRLEN len;
const char *str;
str = SvPVutf8(kv[0], len);
assert_valid_key(str,len);
if (first_key && strcmp(str, first_key) == 0) {
continue;
}
sv_to_bson_elem(bson, str, kv[1], opts, stack, depth);
}
/* free the stack elem for sv */
if ( ! subdoc ) {
Safefree(stack);
}
depth--;
}
/* This is for an array reference contained *within* a document */
static void
av_to_bson (bson_t * bson, AV *av, HV *opts, stackette *stack, int depth) {
I32 i;
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
if (!(stack = check_circular_ref(av, stack))) {
croak("circular ref");
}
for (i = 0; i <= av_len (av); i++) {
SV **sv;
SV *key = sv_2mortal(newSViv (i));
if (!(sv = av_fetch (av, i, 0)))
sv_to_bson_elem (bson, SvPV_nolen(key), newSV(0), opts, stack, depth);
else
sv_to_bson_elem (bson, SvPV_nolen(key), *sv, opts, stack, depth);
}
/* free the av elem */
Safefree(stack);
depth--;
}
/* verify and transform key, if necessary */
static const char *
bson_key(const char * str, HV *opts) {
SV **svp;
SV *tempsv;
STRLEN len;
/* first swap op_char if necessary */
if (
(tempsv = _hv_fetchs_sv(opts, "op_char"))
&& SvOK(tempsv)
&& SvPV_nolen(tempsv)[0] == str[0]
) {
char *out = savepv(str);
SAVEFREEPV(out);
*out = '$';
str = out;
}
/* then check for validity */
if (
(tempsv = _hv_fetchs_sv(opts, "invalid_chars"))
&& SvOK(tempsv)
&& (len = sv_len(tempsv))
) {
STRLEN i;
const char *invalid = SvPV_nolen(tempsv);
for (i=0; i<len; i++) {
if (strchr(str, invalid[i])) {
croak("key '%s' has invalid character(s) '%s'", str, invalid);
}
}
}
return str;
}
static void
sv_to_bson_elem (bson_t * bson, const char * in_key, SV *sv, HV *opts, stackette *stack, int depth) {
SV **svp;
const char * key = bson_key(in_key,opts);
if (!SvOK(sv)) {
if (SvGMAGICAL(sv)) {
mg_get(sv);
}
}
if (!SvOK(sv)) {
bson_append_null(bson, key, -1);
return;
}
else if (SvROK (sv)) {
if (sv_isobject (sv)) {
const char* obj_type = sv_reftype(SvRV(sv), true);
SV* parent = bson_parent_type(SvRV(sv));
if ( parent != NULL ) {
obj_type = (const char *) SvPV_nolen(parent);
}
/* OIDs */
if (strEQ(obj_type, "BSON::OID")) {
SV *attr = sv_2mortal(call_perl_reader(sv, "oid"));
char *bytes = SvPV_nolen(attr);
bson_oid_t oid;
bson_oid_init_from_data(&oid, (uint8_t*) bytes);
bson_append_oid(bson, key, -1, &oid);
}
else if (strEQ(obj_type, "MongoDB::OID")) {
SV *attr = sv_2mortal(call_perl_reader(sv, "value"));
char *str = SvPV_nolen (attr);
bson_oid_t oid;
bson_oid_init_from_string(&oid, str);
bson_append_oid(bson, key, -1, &oid);
}
/* Tie::IxHash */
else if (strEQ(obj_type, "Tie::IxHash")) {
bson_t child;
bson_append_document_begin(bson, key, -1, &child);
ixhash_elem_to_bson(&child, sv, opts, stack, depth);
bson_append_document_end(bson, &child);
}
else if (strEQ(obj_type, "BSON::Doc")) {
bson_t child;
bson_append_document_begin(bson, key, -1, &child);
iter_elem_to_bson(&child, sv, opts, stack, depth);
bson_append_document_end(bson, &child);
}
else if (strEQ(obj_type, "BSON::Raw")) {
STRLEN str_len;
SV *encoded;
const char *bson_str;
bson_t *child;
encoded = sv_2mortal(call_perl_reader(sv, "bson"));
bson_str = SvPV(encoded, str_len);
child = bson_new_from_data((uint8_t*) bson_str, str_len);
bson_append_document(bson, key, -1, child);
bson_destroy(child);
}
else if (strEQ(obj_type, "MongoDB::BSON::Raw")) {
SV *str_sv;
char *str;
STRLEN str_len;
bson_t *child;
str_sv = SvRV(sv);
// check type ok
if (!SvPOK(str_sv)) {
croak("MongoDB::BSON::Raw must be a blessed string reference");
}
str = SvPV(str_sv, str_len);
child = bson_new_from_data((uint8_t*) str, str_len);
bson_append_document(bson, key, -1, child);
bson_destroy(child);
}
else if (strEQ(obj_type, "BSON::Time")) {
SV *ms = sv_2mortal(call_perl_reader(sv, "value"));
if ( sv_isa(ms, "Math::BigInt") ) {
int64_t t = math_bigint_to_int64(ms,key);
bson_append_date_time(bson, key, -1, t);
}
else {
bson_append_date_time(bson, key, -1, (int64_t)SvIV(ms));
}
}
/* Time::Moment */
else if (strEQ(obj_type, "Time::Moment")) {
SV *sec = sv_2mortal(call_perl_reader(sv, "epoch"));
SV *ms = sv_2mortal(call_perl_reader(sv, "millisecond"));
bson_append_date_time(bson, key, -1, (int64_t)SvIV(sec)*1000+SvIV(ms));
}
/* DateTime */
else if (strEQ(obj_type, "DateTime")) {
SV *sec, *ms, *tz, *tz_name;
STRLEN len;
char *str;
/* check for floating tz */
tz = sv_2mortal(call_perl_reader (sv, "time_zone"));
tz_name = sv_2mortal(call_perl_reader (tz, "name"));
str = SvPV(tz_name, len);
if (len == 8 && strncmp("floating", str, 8) == 0) {
warn("saving floating timezone as UTC");
}
sec = sv_2mortal(call_perl_reader (sv, "epoch"));
ms = sv_2mortal(call_perl_reader(sv, "millisecond"));
bson_append_date_time(bson, key, -1, (int64_t)SvIV(sec)*1000+SvIV(ms));
}
/* DateTime::TIny */
else if (strEQ(obj_type, "DateTime::Tiny")) {
struct tm t;
time_t epoch_secs = time(NULL);
int64_t epoch_ms;
t.tm_year = SvIV( sv_2mortal(call_perl_reader( sv, "year" )) ) - 1900;
t.tm_mon = SvIV( sv_2mortal(call_perl_reader( sv, "month" )) ) - 1;
t.tm_mday = SvIV( sv_2mortal(call_perl_reader( sv, "day" )) ) ;
t.tm_hour = SvIV( sv_2mortal(call_perl_reader( sv, "hour" )) ) ;
t.tm_min = SvIV( sv_2mortal(call_perl_reader( sv, "minute" )) ) ;
t.tm_sec = SvIV( sv_2mortal(call_perl_reader( sv, "second" )) ) ;
t.tm_isdst = -1; /* no dst/tz info in DateTime::Tiny */
epoch_secs = timegm( &t );
/* no miliseconds in DateTime::Tiny, so just multiply by 1000 */
epoch_ms = (int64_t)epoch_secs*1000;
bson_append_date_time(bson, key, -1, epoch_ms);
}
else if (strEQ(obj_type, "Mango::BSON::Time")) {
SV *ms = _hv_fetchs_sv((HV *)SvRV(sv), "time");
bson_append_date_time(bson, key, -1, (int64_t)SvIV(ms));
}
/* DBRef */
else if (strEQ(obj_type, "BSON::DBRef") || strEQ(obj_type, "MongoDB::DBRef")) {
SV *dbref;
bson_t child;
dbref = sv_2mortal(call_perl_reader(sv, "_ordered"));
bson_append_document_begin(bson, key, -1, &child);
ixhash_elem_to_bson(&child, dbref, opts, stack, depth);
bson_append_document_end(bson, &child);
}
/* boolean -- these are the most well-known boolean libraries
* on CPAN. Type::Serialiser::Boolean now aliases to
* JSON::PP::Boolean so it is listed at the end for compatibility
* with old versions of it. Old versions of Cpanel::JSON::XS
* similarly have their own type, but now use JSON::PP::Boolean.
*/
else if (
strEQ(obj_type, "boolean") ||
strEQ(obj_type, "BSON::Bool") ||
strEQ(obj_type, "JSON::XS::Boolean") ||
strEQ(obj_type, "JSON::PP::Boolean") ||
strEQ(obj_type, "JSON::Tiny::_Bool") ||
strEQ(obj_type, "Mojo::JSON::_Bool") ||
strEQ(obj_type, "Cpanel::JSON::XS::Boolean") ||
strEQ(obj_type, "Types::Serialiser::Boolean")
) {
bson_append_bool(bson, key, -1, SvIV(SvRV(sv)));
}
else if (strEQ(obj_type, "BSON::Code") || strEQ(obj_type, "MongoDB::Code")) {
SV *code, *scope;
char *code_str;
STRLEN code_len;
code = sv_2mortal(call_perl_reader (sv, "code"));
code_str = SvPVutf8(code, code_len);
if ( ! is_utf8_string((const U8*)code_str,code_len)) {
croak( "Invalid UTF-8 detected while encoding BSON from %s", SvPV_nolen(sv) );
}
scope = sv_2mortal(call_perl_reader(sv, "scope"));
if (SvOK(scope)) {
bson_t * child = bson_new();
hv_elem_to_bson(child, scope, opts, EMPTY_STACK, 0);
bson_append_code_with_scope(bson, key, -1, code_str, code_len, child);
bson_destroy(child);
} else {
bson_append_code(bson, key, -1, code_str);
}
}
else if (strEQ(obj_type, "BSON::Timestamp")) {
SV *sec, *inc;
inc = sv_2mortal(call_perl_reader(sv, "increment"));
sec = sv_2mortal(call_perl_reader(sv, "seconds"));
bson_append_timestamp(bson, key, -1, SvIV(sec), SvIV(inc));
}
else if (strEQ(obj_type, "MongoDB::Timestamp")) {
SV *sec, *inc;
inc = sv_2mortal(call_perl_reader(sv, "inc"));
sec = sv_2mortal(call_perl_reader(sv, "sec"));
bson_append_timestamp(bson, key, -1, SvIV(sec), SvIV(inc));
}
else if (strEQ(obj_type, "BSON::MinKey") || strEQ(obj_type, "MongoDB::MinKey")) {
bson_append_minkey(bson, key, -1);
}
else if (strEQ(obj_type, "BSON::MaxKey") || strEQ(obj_type, "MongoDB::MaxKey")) {
bson_append_maxkey(bson, key, -1);
}
else if (strEQ(obj_type, "MongoDB::BSON::_EncodedDoc")) {
STRLEN str_len;
SV **svp;
SV *encoded;
const char *bson_str;
bson_t *child;
encoded = _hv_fetchs_sv((HV *)SvRV(sv), "bson");
bson_str = SvPV(encoded, str_len);
child = bson_new_from_data((uint8_t*) bson_str, str_len);
bson_append_document(bson, key, -1, child);
bson_destroy(child);
}
else if (strEQ(obj_type, "BSON::String")) {
SV *str_sv;
char *str;
STRLEN str_len;
str_sv = sv_2mortal(call_perl_reader(sv,"value"));
append_utf8(bson, key, str_sv);
}
else if (strEQ(obj_type, "MongoDB::BSON::String")) {
SV *str_sv;
char *str;
STRLEN str_len;
str_sv = SvRV(sv);
/* check type ok */
if (!SvPOK(str_sv)) {
croak("MongoDB::BSON::String must be a blessed string reference");
}
append_utf8(bson, key, str_sv);
}
else if (strEQ(obj_type, "BSON::Bytes") || strEQ(obj_type, "MongoDB::BSON::Binary")) {
SV *data, *subtype;
subtype = sv_2mortal(call_perl_reader(sv, "subtype"));
data = sv_2mortal(call_perl_reader(sv, "data"));
append_binary(bson, key, SvIV(subtype), data);
}
else if (strEQ(obj_type, "BSON::Binary")) {
SV *data, *packed, *subtype;
bson_subtype_t int_subtype;
char *pat = "C*";
subtype = sv_2mortal(call_perl_reader(sv, "subtype"));
int_subtype = SvOK(subtype) ? SvIV(subtype) : 0;
data = sv_2mortal(call_perl_reader(sv, "data"));
packed = sv_2mortal(newSVpvs(""));
/* if data is an array ref, pack it; othewise, pack an empty binary */
if ( SvOK(data) && ( SvTYPE(SvRV(data)) == SVt_PVAV) ) {
AV *d_array = (AV*) SvRV(data);
packlist(packed, pat, pat+2,
av_fetch(d_array,0,0), av_fetch(d_array,av_len(d_array),0)
);
}
append_binary(bson, key, int_subtype, packed);
}
else if (strEQ(obj_type, "Regexp")) {
#if PERL_REVISION==5 && PERL_VERSION>=12
REGEXP * re = SvRX(sv);
#else
REGEXP * re = (REGEXP *) mg_find((SV*)SvRV(sv), PERL_MAGIC_qr)->mg_obj;
#endif
append_regex(bson, key, re, sv);
}
else if (strEQ(obj_type, "BSON::Regex") || strEQ(obj_type, "MongoDB::BSON::Regexp") ) {
/* Abstract regexp object */
SV *pattern, *flags;
pattern = sv_2mortal(call_perl_reader( sv, "pattern" ));
flags = sv_2mortal(call_perl_reader( sv, "flags" ));
append_decomposed_regex( bson, key, SvPV_nolen( pattern ), SvPV_nolen( flags ) );
}
/* 64-bit integers */
else if (strEQ(obj_type, "Math::BigInt")) {
bson_append_int64(bson, key, -1, math_bigint_to_int64(sv,key));
}
else if (strEQ(obj_type, "BSON::Int64") ) {
SV *v = sv_2mortal(call_perl_reader(sv, "value"));
if ( SvROK(v) ) {
/* delegate to wrapped value type */
return sv_to_bson_elem(bson,in_key,v,opts,stack,depth);
}
bson_append_int64(bson, key, -1, (int64_t)SvIV(sv));
}
else if (strEQ(obj_type, "Math::Int64")) {
uint64_t v_int;
SV *v_sv = call_pv_va("Math::Int64::int64_to_native",1,sv);
Copy(SvPVbyte_nolen(v_sv), &v_int, 1, uint64_t);
bson_append_int64(bson, key, -1, v_int);
}
else if (strEQ(obj_type, "BSON::Int32") ) {
bson_append_int32(bson, key, -1, (int32_t)SvIV(sv));
}
else if (strEQ(obj_type, "BSON::Double") ) {
bson_append_double(bson, key, -1, (double)SvNV(sv));
}
else if (strEQ(obj_type, "BSON::Decimal128") ) {
bson_decimal128_t dec;
SV *dec_sv;
char *bid_bytes;
dec_sv = sv_2mortal(call_perl_reader( sv, "bytes" ));
bid_bytes = SvPV_nolen(dec_sv);
/* normalize from little endian back to native byte order */
Copy(bid_bytes, &dec.low, 1, uint64_t);
Copy(bid_bytes + 8, &dec.high, 1, uint64_t);
dec.low = BSON_UINT64_FROM_LE(dec.low);
dec.high = BSON_UINT64_FROM_LE(dec.high);
bson_append_decimal128(bson, key, -1, &dec);
}
else {
croak ("For key '%s', can't encode value of type '%s'", key, HvNAME(SvSTASH(SvRV(sv))));
}
} else {
SV *deref = SvRV(sv);
switch (SvTYPE (deref)) {
case SVt_PVHV: {
/* hash */
bson_t child;
bson_append_document_begin(bson, key, -1, &child);
/* don't add a _id to inner objs */
hv_elem_to_bson (&child, sv, opts, stack, depth);
bson_append_document_end(bson, &child);
break;
}
case SVt_PVAV: {
/* array */
bson_t child;
bson_append_array_begin(bson, key, -1, &child);
av_to_bson (&child, (AV *)SvRV (sv), opts, stack, depth);
bson_append_array_end(bson, &child);
break;
}
default: {
if ( SvPOK(deref) ) {
/* binary */
append_binary(bson, key, BSON_SUBTYPE_BINARY, deref);
}
else {
croak ("For key '%s', can't encode value '%s'", key, SvPV_nolen(sv));
}
}
}
}
} else {
/* Value is a defined, non-reference scalar */
SV *tempsv;
bool prefer_numeric;
tempsv = _hv_fetchs_sv(opts, "prefer_numeric");
prefer_numeric = SvTRUE(tempsv);
#if PERL_REVISION==5 && PERL_VERSION<=18
/* Before 5.18, get magic would clear public flags. This restores them
* from private flags but ONLY if there is no public flag already, as
* we have nothing else to go on for serialization.
*/
if (!(SvFLAGS(sv) & (SVf_IOK|SVf_NOK|SVf_POK))) {
SvFLAGS(sv) |= (SvFLAGS(sv) & (SVp_IOK|SVp_NOK|SVp_POK)) >> PRIVSHIFT;
}
#endif
I32 is_number = looks_like_number(sv);
if ( SvNOK(sv) ) {
bson_append_double(bson, key, -1, (double)SvNV(sv));
} else if ( SvIOK(sv) ) {
append_fit_int(bson, key, sv);
} else if ( prefer_numeric && is_number ) {
/* copy to avoid modifying flags of the original */
tempsv = sv_2mortal(newSVsv(sv));
if (is_number & IS_NUMBER_NOT_INT) { /* double */
bson_append_double(bson, key, -1, (double)SvNV(tempsv));
} else {
append_fit_int(bson, key, tempsv);
}
} else {
append_utf8(bson, key, sv);
}
}
}
const char *
maybe_append_first_key(bson_t *bson, HV *opts, stackette *stack, int depth) {
SV *tempsv;
SV **svp;
const char *first_key = NULL;
if ( (tempsv = _hv_fetchs_sv(opts, "first_key")) && SvOK (tempsv) ) {
STRLEN len;
first_key = SvPVutf8(tempsv, len);
assert_valid_key(first_key, len);
if ( (tempsv = _hv_fetchs_sv(opts, "first_value")) ) {
sv_to_bson_elem(bson, first_key, tempsv, opts, stack, depth);
}
else {
bson_append_null(bson, first_key, -1);
}
}
return first_key;
}
static void
append_decomposed_regex(bson_t *bson, const char *key, const char *pattern, const char *flags ) {
size_t pattern_length = strlen( pattern );
char *buf;
Newx(buf, pattern_length + 1, char );
Copy(pattern, buf, pattern_length, char );
buf[ pattern_length ] = '\0';
bson_append_regex(bson, key, -1, buf, flags);
Safefree(buf);
}
static void
append_regex(bson_t * bson, const char *key, REGEXP *re, SV * sv) {
char flags[] = {0,0,0,0,0,0,0}; /* space for imxslu + \0 */
char *buf;
int i, j;
get_regex_flags(flags, sv);
/* sort flags -- how cool to write a sort algorithm by hand! Since we're
* only sorting a tiny array, who cares if it's n-squared? */
for ( i=0; flags[i]; i++ ) {
for ( j=i+1; flags[j] ; j++ ) {
if ( flags[i] > flags[j] ) {
char t = flags[j];
flags[j] = flags[i];
flags[i] = t;
}
}
}
Newx(buf, (RX_PRELEN(re) + 1), char );
Copy(RX_PRECOMP(re), buf, RX_PRELEN(re), char );
buf[RX_PRELEN(re)] = '\0';
bson_append_regex(bson, key, -1, buf, flags);
Safefree(buf);
}
static void
append_binary(bson_t * bson, const char * key, bson_subtype_t subtype, SV * sv) {
STRLEN len;
uint8_t * bytes = (uint8_t *) SvPVbyte(sv, len);
bson_append_binary(bson, key, -1, subtype, bytes, len);
}
static void
append_fit_int(bson_t * bson, const char *key, SV * sv) {
#if defined(MONGO_USE_64_BIT_INT)
IV i = SvIV(sv);
if ( i >= INT32_MIN && i <= INT32_MAX) {
bson_append_int32(bson, key, -1, (int32_t)i);
}
else {
bson_append_int64(bson, key, -1, (int64_t)i);
}
#else
bson_append_int32(bson, key, -1, (int32_t)SvIV(sv));
#endif
return;
}
static void
append_utf8(bson_t * bson, const char *key, SV * sv) {
STRLEN len;
const char *str = SvPVutf8(sv, len);
if ( ! is_utf8_string((const U8*)str,len)) {
croak( "Invalid UTF-8 detected while encoding BSON" );
}
bson_append_utf8(bson, key, -1, str, len);
return;
}
static void
assert_valid_key(const char* str, STRLEN len) {
if(strlen(str) < len) {
SV *clean = call_pv_va("BSON::XS::_printable",1,sv_2mortal(newSVpvn(str,len)));
croak("Key '%s' contains null character", SvPV_nolen(clean));
}
}
static void
get_regex_flags(char * flags, SV *sv) {
unsigned int i = 0, f = 0;
#if PERL_REVISION == 5 && PERL_VERSION < 10
/* pre-5.10 doesn't have the re API */
STRLEN string_length;
char *re_string = SvPV( sv, string_length );
/* pre-5.14 regexes are stringified in the format: (?ix-sm:foo) where
everything between ? and - are the current flags. The format changed
around 5.14, but for everything after 5.10 we use the re API anyway. */
for( i = 2; i < string_length && re_string[i] != '-'; i++ ) {
if ( re_string[i] == 'i' ||
re_string[i] == 'm' ||
re_string[i] == 'x' ||
re_string[i] == 'l' ||
re_string[i] == 'u' ||
re_string[i] == 's' ) {
flags[f++] = re_string[i];
} else if ( re_string[i] == ':' ) {
break;
}
}
#else
/* 5.10 added an API to extract flags, so we use that */
int ret_count;
SV *flags_sv;
SV *pat_sv;
char *flags_tmp;
dSP;
ENTER;
SAVETMPS;
PUSHMARK (SP);
XPUSHs (sv);
PUTBACK;
ret_count = call_pv( "re::regexp_pattern", G_ARRAY );
SPAGAIN;
if ( ret_count != 2 ) {
croak( "error introspecting regex" );
}
/* regexp_pattern returns two items (in list context), the pattern and a list of flags */
flags_sv = POPs;
pat_sv = POPs; /* too bad we throw this away */
flags_tmp = SvPVutf8_nolen(flags_sv);
for ( i = 0; i < sizeof( flags_tmp ); i++ ) {
if ( flags_tmp[i] == 0 ) break;
/* MongoDB supports only flags /imxslu */
if ( flags_tmp[i] == 'i' ||
flags_tmp[i] == 'm' ||
flags_tmp[i] == 'x' ||
flags_tmp[i] == 'l' ||
flags_tmp[i] == 'u' ||
flags_tmp[i] == 's' ) {
flags[f++] = flags_tmp[i];
}
else {
/* do nothing; just ignore it */
}
}
PUTBACK;
FREETMPS;
LEAVE;
#endif
}
/* Converts Math::BigInt to int64_t; sv must be Math::BigInt */
static int64_t math_bigint_to_int64(SV *sv, const char *key) {
SV *tempsv;
char *str;
int64_t big;
char *end = NULL;
tempsv = sv_2mortal(call_perl_reader(sv, "bstr"));
str = SvPV_nolen(tempsv);
errno = 0;
big = Strtoll(str, &end, 10);
/* check for conversion problems */
if ( end && (*end != '\0') ) {
if ( errno == ERANGE && ( big == LLONG_MAX || big == LLONG_MIN ) ) {
croak( "For key '%s', Math::BigInt '%s' can't fit into a 64-bit integer", key, str );
}
else {
croak( "For key '%s', couldn't convert Math::BigInt '%s' to 64-bit integer", key, str );
}
}
return big;
}
static SV* int64_to_math_bigint(int64_t value) {
char buf[22];
SV *class;
SV *as_str;
SV *bigint;
sprintf(buf, "%" PRIi64, value);
as_str = sv_2mortal(newSVpv(buf,strlen(buf)));
class = sv_2mortal(newSVpvs("Math::BigInt"));
bigint = call_method_va(class, "new", 1, as_str);
return bigint;
}
/**
* checks if a ptr has been parsed already and, if not, adds it to the stack. If
* we do have a circular ref, this function returns 0.
*/
static stackette*
check_circular_ref(void *ptr, stackette *stack) {
stackette *ette, *start = stack;
while (stack) {
if (ptr == stack->ptr) {
return 0;
}
stack = stack->prev;
}
/* push this onto the circular ref stack */
Newx(ette, 1, stackette);
ette->ptr = ptr;
/* if stack has not been initialized, stack will be 0 so this will work out */
ette->prev = start;
return ette;
}
/**
* Given an object SV, finds the first superclass in reverse mro order that
* starts with "BSON::" and returns it as a mortal SV. Otherwise, returns
* NULL if no such type is found.
*/
static SV*
bson_parent_type(SV* sv) {
SV** handle;
AV* mro;
int i;
if (! SvOBJECT(sv)) {
return NULL;
}
mro = mro_get_linear_isa(SvSTASH(sv));
if (av_len(mro) == -1) {
return NULL;
}
/* iterate backwards */
for ( i=av_len(mro); i >= 0; i-- ) {
handle = av_fetch(mro, i, 0);
if (handle != NULL) {
char* klass = SvPV_nolen(*handle);
if (strnEQ(klass, "BSON::", 6)) {
return sv_2mortal(newSVpvn(klass,strlen(klass)));
}
}
}
return NULL;
}
/********************************************************************
* BSON decoding
********************************************************************/
static SV *
bson_doc_to_hashref(bson_iter_t * iter, HV *opts, int depth, bool top) {
SV **svp;
SV *wrap;
SV *ordered;
SV *ret;
HV *hv = newHV();
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
/* delegate if 'ordered' option is true */
if ( (ordered = _hv_fetchs_sv(opts, "ordered")) && SvTRUE(ordered) ) {
return bson_doc_to_tiedhash(iter, opts, depth, top);
}
int is_dbref = 1;
int key_num = 0;
while (bson_iter_next(iter)) {
const char *name;
SV *value;
name = bson_iter_key(iter);
if ( ! is_utf8_string((const U8*)name,strlen(name))) {
croak( "Invalid UTF-8 detected while decoding BSON" );
}
key_num++;
/* check if this is a DBref. We must see the keys
$ref, $id, and optionally $db in that order, with no extra keys */
if ( key_num == 1 && strcmp( name, "$ref" ) ) is_dbref = 0;
if ( key_num == 2 && is_dbref == 1 && strcmp( name, "$id" ) ) is_dbref = 0;
/* get value and store into hash */
value = bson_elem_to_sv(iter, name, opts, depth);
if (!hv_store (hv, name, 0-strlen(name), value, 0)) {
croak ("failed storing value in hash");
}
}
ret = newRV_noinc ((SV *)hv);
/* XXX shouldn't need to limit to size 3 */
if ( ! top && key_num >= 2 && is_dbref == 1
&& (wrap = _hv_fetchs_sv(opts, "wrap_dbrefs")) && SvTRUE(wrap)
) {
SV *class = sv_2mortal(newSVpvs("BSON::DBRef"));
SV *dbref = call_method_va(class, "new", 1, sv_2mortal(ret) );
return dbref;
}
depth--;
return ret;
}
static SV *
bson_doc_to_tiedhash(bson_iter_t * iter, HV *opts, int depth, bool top) {
SV **svp;
SV *wrap;
SV *ret;
SV *ixhash;
SV *tie;
SV *key;
HV *hv = newHV();
int is_dbref = 1;
int key_num = 0;
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
ixhash = new_object_from_pairs("Tie::IxHash",NULL);
while (bson_iter_next(iter)) {
const char *name;
SV *value;
name = bson_iter_key(iter);
if ( ! is_utf8_string((const U8*)name,strlen(name))) {
croak( "Invalid UTF-8 detected while decoding BSON" );
}
key_num++;
/* check if this is a DBref. We must see the keys
$ref, $id, and optionally $db in that order, with no extra keys */
if ( key_num == 1 && strcmp( name, "$ref" ) ) is_dbref = 0;
if ( key_num == 2 && is_dbref == 1 && strcmp( name, "$id" ) ) is_dbref = 0;
/* get key and value and store into hash */
key = sv_2mortal( newSVpvn(name, strlen(name)) );
SvUTF8_on(key);
value = bson_elem_to_sv(iter, name, opts, depth);
call_method_va(ixhash, "STORE", 2, key, value);
}
/* tie the ixhash to the return hash */
sv_magic((SV*) hv, ixhash, PERL_MAGIC_tied, NULL, 0);
ret = newRV_noinc((SV*) hv);
/* XXX shouldn't need to limit to size 3 */
if ( !top && key_num >= 2 && is_dbref == 1
&& (wrap = _hv_fetchs_sv(opts, "wrap_dbrefs")) && SvTRUE(wrap)
) {
SV *class = sv_2mortal(newSVpvs("BSON::DBRef"));
SV *dbref = call_method_va(class, "new", 1, ret );
return dbref;
}
depth--;
return ret;
}
static SV *
bson_array_to_arrayref(bson_iter_t * iter, HV *opts, int depth) {
AV *ret = newAV ();
depth++;
if ( depth > MAX_DEPTH ) {
croak("Exceeded max object depth of %d", MAX_DEPTH);
}
while (bson_iter_next(iter)) {
SV *sv;
const char *name = bson_iter_key(iter);
/* get value */
if ((sv = bson_elem_to_sv(iter, name, opts, depth))) {
av_push (ret, sv);
}
}
depth--;
return newRV_noinc ((SV *)ret);
}
static SV *
bson_elem_to_sv (const bson_iter_t * iter, const char *key, HV *opts, int depth) {
SV **svp;
SV *value = 0;
switch(bson_iter_type(iter)) {
case BSON_TYPE_OID: {
value = bson_oid_to_sv(iter);
break;
}
case BSON_TYPE_DOUBLE: {
SV *tempsv;
SV *d = newSVnv(bson_iter_double(iter));
/* Check for Inf and NaN */
if (Perl_isinf(SvNV(d)) || Perl_isnan(SvNV(d)) ) {
SvPV_nolen(d); /* force to PVNV for compatibility */
}
if ( (tempsv = _hv_fetchs_sv(opts, "wrap_numbers")) && SvTRUE(tempsv) ) {
value = new_object_from_pairs("BSON::Double", "value", sv_2mortal(d), NULL);
}
else {
value = d;
}
break;
}
case BSON_TYPE_SYMBOL:
case BSON_TYPE_UTF8: {
SV *wrap;
SV *s;
const char * str;
uint32_t len;
if (bson_iter_type(iter) == BSON_TYPE_SYMBOL) {
str = bson_iter_symbol(iter, &len);
} else {
str = bson_iter_utf8(iter, &len);
}
if ( ! is_utf8_string((const U8*)str,len)) {
croak( "Invalid UTF-8 detected while decoding BSON" );
}
/* this makes a copy of the buffer */
/* len includes \0 */
s = newSVpvn(str, len);
SvUTF8_on(s);
if ( (wrap = _hv_fetchs_sv(opts, "wrap_strings")) && SvTRUE(wrap) ) {
value = new_object_from_pairs("BSON::String", "value", sv_2mortal(s), NULL);
}
else {
value = s;
}
break;
}
case BSON_TYPE_DOCUMENT: {
bson_iter_t child;
bson_iter_recurse(iter, &child);
value = bson_doc_to_hashref(&child, opts, depth, FALSE);
break;
}
case BSON_TYPE_ARRAY: {
bson_iter_t child;
bson_iter_recurse(iter, &child);
value = bson_array_to_arrayref(&child, opts, depth);
break;
}
case BSON_TYPE_BINARY: {
const char * buf;
uint32_t len;
bson_subtype_t type;
bson_iter_binary(iter, &type, &len, (const uint8_t **)&buf);
if ( BSON_UNLIKELY(type == BSON_SUBTYPE_BINARY_DEPRECATED) ) {
/* for the deprecated subtype, bson_iter_binary gives
* buffer pointer just past the inner length and adjusted len */
int32_t sublen;
Copy(buf-4, &sublen, 1, int32_t);
sublen = BSON_UINT32_FROM_LE(sublen);
/* adjusted len must match sublen */
if ( sublen != len ) {
croak("key '%s' (binary subtype 0x02) is invalid", key);
}
}
value = new_object_from_pairs(
"BSON::Bytes",
"data", sv_2mortal(newSVpvn(buf, len)),
"subtype", sv_2mortal(newSViv(type)),
NULL
);
break;
}
case BSON_TYPE_BOOL: {
value = bson_iter_bool(iter)
? newSVsv(get_sv("BSON::XS::_boolean_true", GV_ADD))
: newSVsv(get_sv("BSON::XS::_boolean_false", GV_ADD));
break;
}
case BSON_TYPE_UNDEFINED:
case BSON_TYPE_NULL: {
value = newSV(0);
break;
}
case BSON_TYPE_INT32: {
SV *tempsv;
SV *i = newSViv(bson_iter_int32(iter));
if ( (tempsv = _hv_fetchs_sv(opts, "wrap_numbers")) && SvTRUE(tempsv) ) {
value = new_object_from_pairs("BSON::Int32", "value", sv_2mortal(i), NULL);
}
else {
value = i;
}
break;
}
case BSON_TYPE_INT64: {
SV *tempsv;
#if defined(MONGO_USE_64_BIT_INT)
SV *i = newSViv(bson_iter_int64(iter));
if ( (tempsv = _hv_fetchs_sv(opts, "wrap_numbers")) && SvTRUE(tempsv) ) {
value = new_object_from_pairs("BSON::Int64", "value", sv_2mortal(i), NULL);
}
else {
value = i;
}
#else
SV *bigint = int64_to_math_bigint(bson_iter_int64(iter));
if ( (tempsv = _hv_fetchs_sv(opts, "wrap_numbers")) && SvTRUE(tempsv) ) {
value = new_object_from_pairs("BSON::Int64", "value", sv_2mortal(bigint), NULL);
}
else {
value = bigint;
}
#endif
break;
}
case BSON_TYPE_DATE_TIME: {
const int64_t msec = bson_iter_date_time(iter);
SV *obj;
SV *temp;
SV *dt_type_sv;
#if defined(MONGO_USE_64_BIT_INT)
obj = new_object_from_pairs("BSON::Time", "value", sv_2mortal(newSViv(msec)), NULL);
#else
obj = new_object_from_pairs("BSON::Time", "value", sv_2mortal(int64_to_math_bigint(msec)), NULL);
#endif
if ( (dt_type_sv = _hv_fetchs_sv(opts, "dt_type")) && SvOK(dt_type_sv) ) {
char *dt_type = SvPV_nolen(dt_type_sv);
if ( strEQ(dt_type, "BSON::Time") ) {
/* already BSON::Time */
value = obj;
} else if ( strEQ(dt_type, "Time::Moment") ) {
value = call_perl_reader(sv_2mortal(obj),"as_time_moment");
} else if ( strEQ(dt_type, "DateTime") ) {
value = call_perl_reader(sv_2mortal(obj),"as_datetime");
} else if ( strEQ(dt_type, "DateTime::Tiny") ) {
value = call_perl_reader(sv_2mortal(obj),"as_datetime_tiny");
} else if ( strEQ(dt_type, "Mango::BSON::Time") ) {
value = call_perl_reader(sv_2mortal(obj),"as_mango_time");
} else {
croak( "unsupported dt_type \"%s\"", dt_type );
}
}
else {
value = obj;
}
break;
}
case BSON_TYPE_REGEX: {
const char * regex_str;
const char * options;
regex_str = bson_iter_regex(iter, &options);
/* always make a BSON::Regex object instead of a native Perl
* regexp to prevent the risk of compilation failure as well as
* security risks compiling unknown regular expressions. */
value = new_object_from_pairs(
"BSON::Regex",
"pattern", sv_2mortal(newSVpv(regex_str,0)),
"flags", sv_2mortal(newSVpv(options,0)),
NULL
);
break;
}
case BSON_TYPE_CODE: {
const char * code;
uint32_t len;
SV *code_sv;
code = bson_iter_code(iter, &len);
if ( ! is_utf8_string((const U8*)code,len)) {
croak( "Invalid UTF-8 detected while decoding BSON" );
}
code_sv = sv_2mortal(newSVpvn(code, len));
SvUTF8_on(code_sv);
value = new_object_from_pairs("BSON::Code", "code", code_sv, NULL);
break;
}
case BSON_TYPE_CODEWSCOPE: {
const char * code;
const uint8_t * scope;
uint32_t code_len, scope_len;
SV * code_sv;
SV * scope_sv;
bson_t bson;
bson_iter_t child;
code = bson_iter_codewscope(iter, &code_len, &scope_len, &scope);
if ( ! is_utf8_string((const U8*)code,code_len)) {
croak( "Invalid UTF-8 detected while decoding BSON" );
}
code_sv = sv_2mortal(newSVpvn(code, code_len));
SvUTF8_on(code_sv);
if ( ! ( bson_init_static(&bson, scope, scope_len) && bson_iter_init(&child, &bson) ) ) {
croak("error iterating BSON type %d\n", bson_iter_type(iter));
}
scope_sv = sv_2mortal(bson_doc_to_hashref(&child, opts, depth, TRUE));
value = new_object_from_pairs("BSON::Code", "code", code_sv, "scope", scope_sv, NULL);
break;
}
case BSON_TYPE_TIMESTAMP: {
SV *sec_sv, *inc_sv;
uint32_t sec, inc;
bson_iter_timestamp(iter, &sec, &inc);
sec_sv = sv_2mortal(newSVuv(sec));
inc_sv = sv_2mortal(newSVuv(inc));
value = new_object_from_pairs("BSON::Timestamp", "seconds", sec_sv, "increment", inc_sv, NULL);
break;
}
case BSON_TYPE_MINKEY: {
HV *stash = gv_stashpv("BSON::MinKey", GV_ADD);
value = sv_bless(newRV_noinc((SV*)newHV()), stash);
break;
}
case BSON_TYPE_MAXKEY: {
HV *stash = gv_stashpv("BSON::MaxKey", GV_ADD);
value = sv_bless(newRV_noinc((SV*)newHV()), stash);
break;
}
case BSON_TYPE_DECIMAL128: {
bson_decimal128_t dec;
char bid_bytes[16];
SV *dec_sv;
if ( ! bson_iter_decimal128(iter, &dec) ) {
croak("could not decode decimal128");
}
/* normalize to little endian regardless of native byte order */
dec.low = BSON_UINT64_TO_LE(dec.low);
dec.high = BSON_UINT64_TO_LE(dec.high);
Copy(&dec.low, bid_bytes, 1, uint64_t);
Copy(&dec.high, bid_bytes + 8, 1, uint64_t);
dec_sv = sv_2mortal(newSVpvn(bid_bytes, 16));
value = new_object_from_pairs("BSON::Decimal128", "bytes", dec_sv, NULL);
break;
}
case BSON_TYPE_DBPOINTER: {
uint32_t len;
const char *collection;
const bson_oid_t *oid_ptr;
SV *coll;
SV *oid;
bson_iter_dbpointer(iter, &len, &collection, &oid_ptr);
if ( ! is_utf8_string((const U8*)collection,len)) {
croak( "Invalid UTF-8 detected while decoding BSON" );
}
coll = newSVpvn(collection, len);
SvUTF8_on(coll);
oid = new_object_from_pairs(
"BSON::OID", "oid", newSVpvn((const char *) oid_ptr->bytes, 12), NULL
);
value = new_object_from_pairs( "BSON::DBRef",
"ref", sv_2mortal(coll), "id", sv_2mortal(oid), NULL
);
break;
}
default: {
/* Should already have been caught during bson_validate() but in case not: */
croak("unsupported BSON type \\x%02X for key '%s'. Are you using the latest version of BSON::XS?", bson_iter_type(iter), key );
}
}
return value;
}
static SV *
bson_oid_to_sv (const bson_iter_t * iter) {
HV *stash, *id_hv;
const bson_oid_t * oid = bson_iter_oid(iter);
id_hv = newHV();
(void)hv_stores(id_hv, "oid", newSVpvn((const char *) oid->bytes, 12));
stash = gv_stashpv("BSON::OID", 0);
return sv_bless(newRV_noinc((SV *)id_hv), stash);
}
#line 1917 "xs/XS.c"
#ifndef PERL_UNUSED_VAR
# define PERL_UNUSED_VAR(var) if (0) var = var
#endif
#ifndef dVAR
# define dVAR dNOOP
#endif
/* This stuff is not part of the API! You have been warned. */
#ifndef PERL_VERSION_DECIMAL
# define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
#endif
#ifndef PERL_DECIMAL_VERSION
# define PERL_DECIMAL_VERSION \
PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
#endif
#ifndef PERL_VERSION_GE
# define PERL_VERSION_GE(r,v,s) \
(PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
#endif
#ifndef PERL_VERSION_LE
# define PERL_VERSION_LE(r,v,s) \
(PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
#endif
/* XS_INTERNAL is the explicit static-linkage variant of the default
* XS macro.
*
* XS_EXTERNAL is the same as XS_INTERNAL except it does not include
* "STATIC", ie. it exports XSUB symbols. You probably don't want that
* for anything but the BOOT XSUB.
*
* See XSUB.h in core!
*/
/* TODO: This might be compatible further back than 5.10.0. */
#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
# undef XS_EXTERNAL
# undef XS_INTERNAL
# if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
# define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
# define XS_INTERNAL(name) STATIC XSPROTO(name)
# endif
# if defined(__SYMBIAN32__)
# define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
# define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
# endif
# ifndef XS_EXTERNAL
# if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
# define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
# define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
# else
# ifdef __cplusplus
# define XS_EXTERNAL(name) extern "C" XSPROTO(name)
# define XS_INTERNAL(name) static XSPROTO(name)
# else
# define XS_EXTERNAL(name) XSPROTO(name)
# define XS_INTERNAL(name) STATIC XSPROTO(name)
# endif
# endif
# endif
#endif
/* perl >= 5.10.0 && perl <= 5.15.1 */
/* The XS_EXTERNAL macro is used for functions that must not be static
* like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
* macro defined, the best we can do is assume XS is the same.
* Dito for XS_INTERNAL.
*/
#ifndef XS_EXTERNAL
# define XS_EXTERNAL(name) XS(name)
#endif
#ifndef XS_INTERNAL
# define XS_INTERNAL(name) XS(name)
#endif
/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
* internal macro that we're free to redefine for varying linkage due
* to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
* XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
*/
#undef XS_EUPXS
#if defined(PERL_EUPXS_ALWAYS_EXPORT)
# define XS_EUPXS(name) XS_EXTERNAL(name)
#else
/* default to internal */
# define XS_EUPXS(name) XS_INTERNAL(name)
#endif
#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
/* prototype to pass -Wmissing-prototypes */
STATIC void
S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params);
STATIC void
S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params)
{
const GV *const gv = CvGV(cv);
PERL_ARGS_ASSERT_CROAK_XS_USAGE;
if (gv) {
const char *const gvname = GvNAME(gv);
const HV *const stash = GvSTASH(gv);
const char *const hvname = stash ? HvNAME(stash) : NULL;
if (hvname)
Perl_croak(aTHX_ "Usage: %s::%s(%s)", hvname, gvname, params);
else
Perl_croak(aTHX_ "Usage: %s(%s)", gvname, params);
} else {
/* Pants. I don't think that it should be possible to get here. */
Perl_croak(aTHX_ "Usage: CODE(0x%"UVxf")(%s)", PTR2UV(cv), params);
}
}
#undef PERL_ARGS_ASSERT_CROAK_XS_USAGE
#ifdef PERL_IMPLICIT_CONTEXT
#define croak_xs_usage(a,b) S_croak_xs_usage(aTHX_ a,b)
#else
#define croak_xs_usage S_croak_xs_usage
#endif
#endif
/* NOTE: the prototype of newXSproto() is different in versions of perls,
* so we define a portable version of newXSproto()
*/
#ifdef newXS_flags
#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
#else
#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
#endif /* !defined(newXS_flags) */
#line 2059 "xs/XS.c"
XS_EUPXS(XS_BSON__XS__decode_bson); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_BSON__XS__decode_bson)
{
dVAR; dXSARGS;
if (items != 2)
croak_xs_usage(cv, "msg, options");
PERL_UNUSED_VAR(ax); /* -Wall */
SP -= items;
{
SV * msg = ST(0)
;
SV * options = ST(1)
;
#line 1917 "xs/XS.xs"
char * data;
bson_t bson;
bson_iter_t iter;
size_t error_offset;
STRLEN length;
HV *opts;
uint32_t invalid_type;
const char *invalid_key;
#line 2084 "xs/XS.c"
#line 1927 "xs/XS.xs"
data = SvPV(msg, length);
opts = NULL;
if ( options ) {
if ( SvROK(options) && SvTYPE(SvRV(options)) == SVt_PVHV ) {
opts = (HV *) SvRV(options);
}
else {
croak("options must be a reference to a hash");
}
}
if ( ! bson_init_static(&bson, (uint8_t *) data, length) ) {
croak("Error reading BSON document");
}
if ( ! bson_validate(&bson, BSON_VALIDATE_NONE, &error_offset, &invalid_key, &invalid_type) ) {
croak( "Invalid BSON input" );
}
if ( invalid_type != 0 ) {
croak("unsupported BSON type \\x%02X for key '%s'. Are you using the latest version of BSON::XS?", invalid_type, invalid_key );
}
if ( ! bson_iter_init(&iter, &bson) ) {
croak( "Error creating BSON iterator" );
}
XPUSHs(sv_2mortal(bson_doc_to_hashref(&iter, opts, 0, TRUE)));
#line 2115 "xs/XS.c"
PUTBACK;
return;
}
}
XS_EUPXS(XS_BSON__XS__encode_bson); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_BSON__XS__encode_bson)
{
dVAR; dXSARGS;
if (items != 2)
croak_xs_usage(cv, "doc, options");
PERL_UNUSED_VAR(ax); /* -Wall */
SP -= items;
{
SV * doc = ST(0)
;
SV * options = ST(1)
;
#line 1962 "xs/XS.xs"
bson_t * bson;
HV *opts;
#line 2138 "xs/XS.c"
#line 1965 "xs/XS.xs"
opts = NULL;
bson = bson_new();
if ( options ) {
if ( SvROK(options) && SvTYPE(SvRV(options)) == SVt_PVHV ) {
opts = (HV *) SvRV(options);
}
else {
croak("options must be a reference to a hash");
}
}
perl_mongo_sv_to_bson(bson, doc, opts);
XPUSHs(sv_2mortal(newSVpvn((const char *)bson_get_data(bson), bson->len)));
bson_destroy(bson);
#line 2153 "xs/XS.c"
PUTBACK;
return;
}
}
XS_EUPXS(XS_BSON__XS__generate_oid); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_BSON__XS__generate_oid)
{
dVAR; dXSARGS;
if (items != 0)
croak_xs_usage(cv, "");
{
#line 1982 "xs/XS.xs"
bson_oid_t boid;
#line 2169 "xs/XS.c"
SV * RETVAL;
#line 1984 "xs/XS.xs"
bson_oid_init(&boid, NULL);
RETVAL = newSVpvn((const char *) boid.bytes, 12);
#line 2174 "xs/XS.c"
ST(0) = RETVAL;
sv_2mortal(ST(0));
}
XSRETURN(1);
}
#ifdef __cplusplus
extern "C"
#endif
XS_EXTERNAL(boot_BSON__XS); /* prototype to pass -Wmissing-prototypes */
XS_EXTERNAL(boot_BSON__XS)
{
dVAR; dXSARGS;
#if (PERL_REVISION == 5 && PERL_VERSION < 9)
char* file = __FILE__;
#else
const char* file = __FILE__;
#endif
PERL_UNUSED_VAR(cv); /* -W */
PERL_UNUSED_VAR(items); /* -W */
#ifdef XS_APIVERSION_BOOTCHECK
XS_APIVERSION_BOOTCHECK;
#endif
XS_VERSION_BOOTCHECK;
newXS("BSON::XS::_decode_bson", XS_BSON__XS__decode_bson, file);
newXS("BSON::XS::_encode_bson", XS_BSON__XS__encode_bson, file);
newXS("BSON::XS::_generate_oid", XS_BSON__XS__generate_oid, file);
#if (PERL_REVISION == 5 && PERL_VERSION >= 9)
if (PL_unitcheckav)
call_list(PL_scopestack_ix, PL_unitcheckav);
#endif
XSRETURN_YES;
}