bson_ext-1.10.0/0000755000004100000410000000000012317505161013443 5ustar www-datawww-databson_ext-1.10.0/bson_ext.gemspec0000644000004100000410000000237312317505161016636 0ustar www-datawww-dataGem::Specification.new do |s| s.name = 'bson_ext' s.version = File.read(File.join(File.dirname(__FILE__), 'VERSION')) s.platform = Gem::Platform::RUBY s.authors = ['Tyler Brock', 'Gary Murakami', 'Emily Stolfo', 'Brandon Black', 'Durran Jordan'] s.email = 'mongodb-dev@googlegroups.com' s.homepage = 'http://www.mongodb.org' s.summary = 'C extensions for Ruby BSON.' s.description = 'C extensions to accelerate the Ruby BSON serialization. For more information about BSON, see http://bsonspec.org. For information about MongoDB, see http://www.mongodb.org.' s.rubyforge_project = 'bson_ext' s.license = 'Apache License Version 2.0' if File.exists?('gem-private_key.pem') s.signing_key = 'gem-private_key.pem' s.cert_chain = ['gem-public_cert.pem'] else warn 'Warning: No private key present, creating unsigned gem.' end s.files = ['bson_ext.gemspec', 'LICENSE', 'VERSION'] s.files += Dir['ext/**/*.rb'] + Dir['ext/**/*.c'] + Dir['ext/**/*.h'] s.require_paths = ['ext/bson_ext'] s.extensions = ['ext/cbson/extconf.rb'] s.has_rdoc = 'yard' s.add_dependency('bson', "~> #{s.version}") endbson_ext-1.10.0/data.tar.gz.sig0000444000004100000410000000040012317505161016254 0ustar www-datawww-data9 T|lGUF!xP 0R MdBs~g 'ܯh@75`GkcřsM$"6Ql8>=V fiYQe{1{vd%۵_ٗj]ߝ̘$ +y0һ7Mw" - !ruby/object:Gem::Version version: 1.10.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 1.10.0 description: C extensions to accelerate the Ruby BSON serialization. For more information about BSON, see http://bsonspec.org. For information about MongoDB, see http://www.mongodb.org. email: mongodb-dev@googlegroups.com executables: [] extensions: - ext/cbson/extconf.rb extra_rdoc_files: [] files: - LICENSE - VERSION - bson_ext.gemspec - ext/cbson/bson_buffer.c - ext/cbson/bson_buffer.h - ext/cbson/cbson.c - ext/cbson/encoding_helpers.c - ext/cbson/encoding_helpers.h - ext/cbson/extconf.rb - ext/cbson/version.h homepage: http://www.mongodb.org licenses: - Apache License Version 2.0 metadata: {} post_install_message: rdoc_options: [] require_paths: - ext/bson_ext required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: bson_ext rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: C extensions for Ruby BSON. test_files: [] has_rdoc: yard bson_ext-1.10.0/checksums.yaml.gz.sig0000444000004100000410000000040012317505161017504 0ustar www-datawww-data+p7]$ˌ)"ٞx hAS;HXM5Թ y6;Ƅ Uɟ@+ƈ=%Ӱb8lqohӐٞV@G9CU M?fB\נgXf 6IcqFf}^ 34ܴQ|bAX*gySaćxi#"O.Ɛ\!c* /}FFnK{o*N$lY>z{Ʉ'bson_ext-1.10.0/LICENSE0000644000004100000410000002501712317505161014455 0ustar www-datawww-data Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright (C) 2008-2013 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. bson_ext-1.10.0/VERSION0000644000004100000410000000000612317505161014507 0ustar www-datawww-data1.10.0bson_ext-1.10.0/metadata.gz.sig0000444000004100000410000000040012317505161016336 0ustar www-datawww-data#=*%_ə7j ҍ_A$m^!-i%(עh\SFPAS0|1( [Ny3F4iO)'e ,dXc ?naZջSA X:8m޹dDs0ؽ 4TfrL$ b>}Tf ]Eݎذ!2B": G;ry^p:Naobfm2*abson_ext-1.10.0/ext/0000755000004100000410000000000012317505161014243 5ustar www-datawww-databson_ext-1.10.0/ext/cbson/0000755000004100000410000000000012317505161015347 5ustar www-datawww-databson_ext-1.10.0/ext/cbson/extconf.rb0000644000004100000410000000034312317505161017342 0ustar www-datawww-datarequire 'mkmf' have_func("asprintf") have_header("ruby/st.h") || have_header("st.h") have_header("ruby/regex.h") || have_header("regex.h") have_header("ruby/encoding.h") dir_config('cbson') create_makefile('bson_ext/cbson') bson_ext-1.10.0/ext/cbson/bson_buffer.h0000644000004100000410000000437612317505161020024 0ustar www-datawww-data/* * Copyright (C) 2009-2013 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. */ #ifndef _BSON_BUFFER_H #define _BSON_BUFFER_H /* Note: if any of these functions return a failure condition then the buffer * has already been freed. */ /* A buffer */ typedef struct bson_buffer* bson_buffer_t; /* A position in the buffer */ typedef int bson_buffer_position; /* Allocate and return a new buffer. * Return NULL on allocation failure. */ bson_buffer_t bson_buffer_new(void); /* Set the max size for this buffer. * Note: this is not a hard limit. */ void bson_buffer_set_max_size(bson_buffer_t buffer, int max_size); int bson_buffer_get_max_size(bson_buffer_t buffer); /* Free the memory allocated for `buffer`. * Return non-zero on failure. */ int bson_buffer_free(bson_buffer_t buffer); /* Save `size` bytes from the current position in `buffer` (and grow if needed). * Return offset for writing, or -1 on allocation failure. */ bson_buffer_position bson_buffer_save_space(bson_buffer_t buffer, int size); /* Write `size` bytes from `data` to `buffer` (and grow if needed). * Return non-zero on allocation failure. */ int bson_buffer_write(bson_buffer_t buffer, const char* data, int size); /* Write `size` bytes from `data` to `buffer` at position `position`. * Does not change the internal position of `buffer`. * Return non-zero if buffer isn't large enough for write. */ int bson_buffer_write_at_position(bson_buffer_t buffer, bson_buffer_position position, const char* data, int size); /* Getters for the internals of a bson_buffer_t. * Should try to avoid using these as much as possible * since they break the abstraction. */ bson_buffer_position bson_buffer_get_position(bson_buffer_t buffer); char* bson_buffer_get_buffer(bson_buffer_t buffer); #endif bson_ext-1.10.0/ext/cbson/version.h0000644000004100000410000000116612317505161017211 0ustar www-datawww-data/* * Copyright (C) 2009-2013 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. */ #define VERSION "1.10.0" bson_ext-1.10.0/ext/cbson/encoding_helpers.h0000644000004100000410000000276012317505161021035 0ustar www-datawww-data/* * Copyright 2013 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. */ #ifndef ENCODING_HELPERS_H #define ENCODING_HELPERS_H #include typedef enum { VALID_UTF8, INVALID_UTF8, HAS_NULL } result_t; /** * validate_utf8_encoding: * @utf8: A UTF-8 encoded string. * @utf8_len: The length of @utf8 in bytes. * @allow_null: 1 If '\0' is allowed within @utf8, excluding trailing \0. * * Validates that @utf8 is a valid UTF-8 string. * * If @allow_null is 1, then '\0' is allowed within @utf8_len bytes of @utf8. * Generally, this is bad practice since the main point of UTF-8 strings is * that they can be used with strlen() and friends. However, some languages * such as Python can send UTF-8 encoded strings with NUL's in them. * * Returns: enum indicating validity of @utf8. */ result_t validate_utf8_encoding (const char *utf8, size_t utf8_len, int allow_null); #endif /* ENCODING_HELPERS_H */ bson_ext-1.10.0/ext/cbson/encoding_helpers.c0000644000004100000410000000464412317505161021033 0ustar www-datawww-data/* * Copyright 2013 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 #include "encoding_helpers.h" static void get_utf8_sequence (const char *utf8, unsigned char *seq_length, unsigned char *first_mask) { unsigned char c = *(const unsigned char *)utf8; unsigned char m; unsigned char n; /* * See the following[1] for a description of what the given multi-byte * sequences will be based on the bits set of the first byte. We also need * to mask the first byte based on that. All subsequent bytes are masked * against 0x3F. * * [1] http://www.joelonsoftware.com/articles/Unicode.html */ if ((c & 0x80) == 0) { n = 1; m = 0x7F; } else if ((c & 0xE0) == 0xC0) { n = 2; m = 0x1F; } else if ((c & 0xF0) == 0xE0) { n = 3; m = 0x0F; } else if ((c & 0xF8) == 0xF0) { n = 4; m = 0x07; } else if ((c & 0xFC) == 0xF8) { n = 5; m = 0x03; } else if ((c & 0xFE) == 0xFC) { n = 6; m = 0x01; } else { n = 0; m = 0; } *seq_length = n; *first_mask = m; } result_t validate_utf8_encoding (const char *utf8, size_t utf8_len, int allow_null) { unsigned char first_mask; unsigned char seq_length; unsigned i; unsigned j; for (i = 0; i < utf8_len; i += seq_length) { get_utf8_sequence(&utf8[i], &seq_length, &first_mask); if (!seq_length) { return INVALID_UTF8; } for (j = i + 1; j < (i + seq_length); j++) { if ((utf8[j] & 0xC0) != 0x80) { return INVALID_UTF8; } } if (!allow_null) { for (j = 0; j < seq_length; j++) { if (((i + j) > utf8_len) || !utf8[i + j]) { return HAS_NULL; } } } } return VALID_UTF8; } bson_ext-1.10.0/ext/cbson/bson_buffer.c0000644000004100000410000001023212317505161020003 0ustar www-datawww-data/* * Copyright (C) 2009-2013 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 #include #include "bson_buffer.h" #define INITIAL_BUFFER_SIZE 256 #define DEFAULT_MAX_SIZE 4 * 1024 * 1024 struct bson_buffer { char* buffer; int size; int position; int max_size; }; /* Allocate and return a new buffer. * Return NULL on allocation failure. */ bson_buffer_t bson_buffer_new(void) { bson_buffer_t buffer; buffer = (bson_buffer_t)malloc(sizeof(struct bson_buffer)); if (buffer == NULL) { return NULL; } buffer->size = INITIAL_BUFFER_SIZE; buffer->position = 0; buffer->buffer = (char*)malloc(sizeof(char) * INITIAL_BUFFER_SIZE); if (buffer->buffer == NULL) { free(buffer); return NULL; } buffer->max_size = DEFAULT_MAX_SIZE; return buffer; } void bson_buffer_set_max_size(bson_buffer_t buffer, int max_size) { buffer->max_size = max_size; } int bson_buffer_get_max_size(bson_buffer_t buffer) { return buffer->max_size; } /* Free the memory allocated for `buffer`. * Return non-zero on failure. */ int bson_buffer_free(bson_buffer_t buffer) { if (buffer == NULL) { return 1; } free(buffer->buffer); free(buffer); return 0; } /* Grow `buffer` to at least `min_length`. * Return non-zero on allocation failure. */ static int buffer_grow(bson_buffer_t buffer, int min_length) { int size = buffer->size; int old_size; char* old_buffer = buffer->buffer; if (size >= min_length) { return 0; } while (size < min_length) { old_size = size; size *= 2; /* Prevent potential overflow. */ if( size < old_size ) size = min_length; } buffer->buffer = (char*)realloc(buffer->buffer, sizeof(char) * size); if (buffer->buffer == NULL) { free(old_buffer); free(buffer); return 1; } buffer->size = size; return 0; } /* Assure that `buffer` has at least `size` free bytes (and grow if needed). * Return non-zero on allocation failure. */ static int buffer_assure_space(bson_buffer_t buffer, int size) { if (buffer->position + size <= buffer->size) { return 0; } return buffer_grow(buffer, buffer->position + size); } /* Save `size` bytes from the current position in `buffer` (and grow if needed). * Return offset for writing, or -1 on allocation failure. */ bson_buffer_position bson_buffer_save_space(bson_buffer_t buffer, int size) { int position = buffer->position; if (buffer_assure_space(buffer, size) != 0) { return -1; } buffer->position += size; return position; } /* Write `size` bytes from `data` to `buffer` (and grow if needed). * Return non-zero on allocation failure. */ int bson_buffer_write(bson_buffer_t buffer, const char* data, int size) { if (buffer_assure_space(buffer, size) != 0) { return 1; } memcpy(buffer->buffer + buffer->position, data, size); buffer->position += size; return 0; } /* Write `size` bytes from `data` to `buffer` at position `position`. * Does not change the internal position of `buffer`. * Return non-zero if buffer isn't large enough for write. */ int bson_buffer_write_at_position(bson_buffer_t buffer, bson_buffer_position position, const char* data, int size) { if (position + size > buffer->size) { bson_buffer_free(buffer); return 1; } memcpy(buffer->buffer + position, data, size); return 0; } int bson_buffer_get_position(bson_buffer_t buffer) { return buffer->position; } char* bson_buffer_get_buffer(bson_buffer_t buffer) { return buffer->buffer; } bson_ext-1.10.0/ext/cbson/cbson.c0000644000004100000410000012122012317505161016615 0ustar www-datawww-data/* * Copyright (C) 2009-2013 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. */ /* * This file contains C implementations of some of the functions needed by the * bson module. If possible, these implementations should be used to speed up * BSON encoding and decoding. */ #include "ruby.h" #include "version.h" /* Ensure compatibility with early releases of Ruby 1.8.5 */ #ifndef RSTRING_PTR # define RSTRING_PTR(v) RSTRING(v)->ptr #endif #ifndef RSTRING_LEN # define RSTRING_LEN(v) RSTRING(v)->len #endif #ifndef RSTRING_LENINT # define RSTRING_LENINT(v) (int)(RSTRING_LEN(v)) #endif #ifndef RARRAY_LEN # define RARRAY_LEN(v) RARRAY(v)->len #endif #ifndef RARRAY_LENINT # define RARRAY_LENINT(v) (int)(RARRAY_LEN(v)) #endif #if HAVE_RUBY_ST_H #include "ruby/st.h" #endif #if HAVE_ST_H #include "st.h" #endif #if HAVE_RUBY_REGEX_H #include "ruby/regex.h" #endif #if HAVE_REGEX_H #include "regex.h" #endif #include #include #include #include #include "version.h" #include "bson_buffer.h" #include "encoding_helpers.h" #define SAFE_WRITE(buffer, data, size) \ if (bson_buffer_write((buffer), (data), (size)) != 0) \ rb_raise(rb_eNoMemError, "failed to allocate memory in bson_buffer.c") #define SAFE_WRITE_AT_POS(buffer, position, data, size) \ if (bson_buffer_write_at_position((buffer), (position), (data), (size)) != 0) \ rb_raise(rb_eRuntimeError, "invalid write at position in bson_buffer.c") #define MAX_HOSTNAME_LENGTH 256 static ID element_assignment_method; static ID unpack_method; static ID utc_method; static ID lt_operator; static ID gt_operator; static VALUE Binary; static VALUE ObjectId; static VALUE DBRef; static VALUE Code; static VALUE MinKey; static VALUE MaxKey; static VALUE Timestamp; static VALUE Regexp; static VALUE BSONRegex; static VALUE BSONRegex_IGNORECASE; static VALUE BSONRegex_EXTENDED; static VALUE BSONRegex_MULTILINE; static VALUE BSONRegex_DOTALL; static VALUE BSONRegex_LOCALE_DEPENDENT; static VALUE BSONRegex_UNICODE; static VALUE OrderedHash; static VALUE InvalidKeyName; static VALUE InvalidStringEncoding; static VALUE InvalidDocument; static VALUE InvalidObjectId; static VALUE DigestMD5; static VALUE RB_HASH; static int max_bson_size; struct deserialize_opts { int compile_regex; }; #if HAVE_RUBY_ENCODING_H #include "ruby/encoding.h" #define STR_NEW(p,n) \ ({ \ VALUE _str = rb_enc_str_new((p), (n), rb_utf8_encoding()); \ rb_encoding* internal_encoding = rb_default_internal_encoding(); \ if (internal_encoding) { \ _str = rb_str_export_to_enc(_str, internal_encoding); \ } \ _str; \ }) #else #define STR_NEW(p,n) rb_str_new((p), (n)) #endif static void write_utf8(bson_buffer_t buffer, VALUE string, int allow_null) { result_t status = validate_utf8_encoding( (const char*)RSTRING_PTR(string), RSTRING_LEN(string), allow_null); if (status == HAS_NULL) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "Key names / regex patterns must not contain the NULL byte"); } else if (status == INVALID_UTF8) { bson_buffer_free(buffer); rb_raise(InvalidStringEncoding, "String not valid UTF-8"); } SAFE_WRITE(buffer, RSTRING_PTR(string), (int)RSTRING_LEN(string)); } // this sucks. but for some reason these moved around between 1.8 and 1.9 #ifdef ONIGURUMA_H #define IGNORECASE ONIG_OPTION_IGNORECASE #define MULTILINE ONIG_OPTION_MULTILINE #define EXTENDED ONIG_OPTION_EXTEND #else #define IGNORECASE RE_OPTION_IGNORECASE #define MULTILINE RE_OPTION_MULTILINE #define EXTENDED RE_OPTION_EXTENDED #endif /* TODO we ought to check that the malloc or asprintf was successful * and raise an exception if not. */ /* TODO maybe we can use something more portable like vsnprintf instead * of this hack. And share it with the Python extension ;) */ /* If we don't have ASPRINTF, there are two possibilities: * either use _scprintf and _snprintf on for Windows or * use snprintf for solaris. */ #ifndef HAVE_ASPRINTF #ifdef _WIN32 || _MSC_VER #define INT2STRING(buffer, i) \ { \ int vslength = _scprintf("%d", i) + 1; \ *buffer = malloc(vslength); \ _snprintf(*buffer, vslength, "%d", i); \ } #define FREE_INTSTRING(buffer) free(buffer) #else #define INT2STRING(buffer, i) \ { \ int vslength = snprintf(NULL, 0, "%d", i) + 1; \ *buffer = malloc(vslength); \ snprintf(*buffer, vslength, "%d", i); \ } #define FREE_INTSTRING(buffer) free(buffer) #endif #else #define INT2STRING(buffer, i) asprintf(buffer, "%d", i); #ifdef USING_SYSTEM_ALLOCATOR_LIBRARY /* Ruby Enterprise Edition with tcmalloc */ #define FREE_INTSTRING(buffer) system_free(buffer) #else #define FREE_INTSTRING(buffer) free(buffer) #endif #endif #ifndef RREGEXP_SRC #define RREGEXP_SRC(r) rb_str_new(RREGEXP((r))->str, RREGEXP((r))->len) #endif // rubinius compatibility #ifndef RREGEXP_OPTIONS #define RREGEXP_OPTIONS(r) RREGEXP(value)->ptr->options #endif static char zero = 0; static char one = 1; static char hostname_digest[17]; static unsigned int object_id_inc = 0; static int cmp_char(const void* a, const void* b) { return *(char*)a - *(char*)b; } static void write_doc(bson_buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_id); static int write_element_with_id(VALUE key, VALUE value, VALUE extra); static int write_element_without_id(VALUE key, VALUE value, VALUE extra); static VALUE elements_to_hash(const char* buffer, int max, struct deserialize_opts * opts); static VALUE pack_extra(bson_buffer_t buffer, VALUE check_keys) { return rb_ary_new3(2, LL2NUM((long long)buffer), check_keys); } static void write_name_and_type(bson_buffer_t buffer, VALUE name, char type) { SAFE_WRITE(buffer, &type, 1); write_utf8(buffer, name, 0); SAFE_WRITE(buffer, &zero, 1); } static void serialize_regex(bson_buffer_t buffer, VALUE key, VALUE pattern, long flags, VALUE value, int native) { VALUE has_extra; write_name_and_type(buffer, key, 0x0B); write_utf8(buffer, pattern, 0); SAFE_WRITE(buffer, &zero, 1); if (native == 1) { // Ruby regular expressions always use multiline mode char multiline = 'm'; SAFE_WRITE(buffer, &multiline, 1); if (flags & IGNORECASE) { char ignorecase = 'i'; SAFE_WRITE(buffer, &ignorecase, 1); } // dotall on the server is multiline in Ruby if (flags & MULTILINE) { char dotall = 's'; SAFE_WRITE(buffer, &dotall, 1); } if (flags & EXTENDED) { char extended = 'x'; SAFE_WRITE(buffer, &extended, 1); } } else { if (flags & BSONRegex_IGNORECASE) { char ignorecase = 'i'; SAFE_WRITE(buffer, &ignorecase, 1); } if (flags & BSONRegex_LOCALE_DEPENDENT) { char locale_dependent = 'l'; SAFE_WRITE(buffer, &locale_dependent, 1); } if (flags & BSONRegex_MULTILINE) { char multiline = 'm'; SAFE_WRITE(buffer, &multiline, 1); } if (flags & BSONRegex_DOTALL) { char dotall = 's'; SAFE_WRITE(buffer, &dotall, 1); } if (flags & BSONRegex_UNICODE) { char unicode = 'u'; SAFE_WRITE(buffer, &unicode, 1); } if (flags & BSONRegex_EXTENDED) { char extended = 'x'; SAFE_WRITE(buffer, &extended, 1); } } has_extra = rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("extra_options_str")); if (TYPE(has_extra) == T_TRUE) { VALUE extra = rb_funcall(value, rb_intern("extra_options_str"), 0); bson_buffer_position old_position = bson_buffer_get_position(buffer); SAFE_WRITE(buffer, RSTRING_PTR(extra), RSTRING_LENINT(extra)); qsort(bson_buffer_get_buffer(buffer) + old_position, RSTRING_LEN(extra), sizeof(char), cmp_char); } SAFE_WRITE(buffer, &zero, 1); } static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { bson_buffer_t buffer = (bson_buffer_t)NUM2LL(rb_ary_entry(extra, 0)); VALUE check_keys = rb_ary_entry(extra, 1); if (TYPE(key) == T_SYMBOL) { // TODO better way to do this... ? key = rb_str_new2(rb_id2name(SYM2ID(key))); } if (TYPE(key) != T_STRING) { bson_buffer_free(buffer); rb_raise(rb_eTypeError, "keys must be strings or symbols"); } if (allow_id == 0 && strcmp("_id", RSTRING_PTR(key)) == 0) { return ST_CONTINUE; } if (check_keys == Qtrue) { int i; if (RSTRING_LEN(key) > 0 && RSTRING_PTR(key)[0] == '$') { bson_buffer_free(buffer); rb_raise(InvalidKeyName, "key %s must not start with '$'", RSTRING_PTR(key)); } for (i = 0; i < RSTRING_LEN(key); i++) { if (RSTRING_PTR(key)[i] == '.') { bson_buffer_free(buffer); rb_raise(InvalidKeyName, "key %s must not contain '.'", RSTRING_PTR(key)); } } } switch(TYPE(value)) { case T_BIGNUM: { if (rb_funcall(value, gt_operator, 1, LL2NUM(9223372036854775807LL)) == Qtrue || rb_funcall(value, lt_operator, 1, LL2NUM(-9223372036854775808ULL)) == Qtrue) { bson_buffer_free(buffer); rb_raise(rb_eRangeError, "MongoDB can only handle 8-byte ints"); } } // NOTE: falls through to T_FIXNUM code case T_FIXNUM: { long long ll_value; ll_value = NUM2LL(value); if (ll_value > 2147483647LL || ll_value < -2147483648LL) { write_name_and_type(buffer, key, 0x12); SAFE_WRITE(buffer, (char*)&ll_value, 8); } else { int int_value; write_name_and_type(buffer, key, 0x10); int_value = (int)ll_value; SAFE_WRITE(buffer, (char*)&int_value, 4); } break; } case T_TRUE: { write_name_and_type(buffer, key, 0x08); SAFE_WRITE(buffer, &one, 1); break; } case T_FALSE: { write_name_and_type(buffer, key, 0x08); SAFE_WRITE(buffer, &zero, 1); break; } case T_FLOAT: { double d = NUM2DBL(value); write_name_and_type(buffer, key, 0x01); SAFE_WRITE(buffer, (char*)&d, 8); break; } case T_NIL: { write_name_and_type(buffer, key, 0x0A); break; } case T_HASH: { write_name_and_type(buffer, key, 0x03); write_doc(buffer, value, check_keys, Qfalse); break; } case T_ARRAY: { bson_buffer_position length_location, start_position, obj_length; int items, i; write_name_and_type(buffer, key, 0x04); start_position = bson_buffer_get_position(buffer); // save space for length length_location = bson_buffer_save_space(buffer, 4); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } items = RARRAY_LENINT(value); for(i = 0; i < items; i++) { char* name; VALUE key; INT2STRING(&name, i); key = rb_str_new2(name); write_element_with_id(key, rb_ary_entry(value, i), pack_extra(buffer, check_keys)); FREE_INTSTRING(name); } // write null byte and fill in length SAFE_WRITE(buffer, &zero, 1); obj_length = bson_buffer_get_position(buffer) - start_position; SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4); break; } case T_STRING: { int length; write_name_and_type(buffer, key, 0x02); length = RSTRING_LENINT(value) + 1; SAFE_WRITE(buffer, (char*)&length, 4); write_utf8(buffer, value, 1); SAFE_WRITE(buffer, &zero, 1); break; } case T_SYMBOL: { const char* str_value = rb_id2name(SYM2ID(value)); int length = (int)strlen(str_value) + 1; write_name_and_type(buffer, key, 0x0E); SAFE_WRITE(buffer, (char*)&length, 4); SAFE_WRITE(buffer, str_value, length); break; } case T_OBJECT: { // TODO there has to be a better way to do these checks... const char* cls = rb_obj_classname(value); if (strcmp(cls, "BSON::Binary") == 0 || strcmp(cls, "ByteBuffer") == 0) { const char subtype = strcmp(cls, "ByteBuffer") ? (const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2; VALUE string_data = rb_funcall(value, rb_intern("to_s"), 0); int length = RSTRING_LENINT(string_data); write_name_and_type(buffer, key, 0x05); if (subtype == 2) { const int other_length = length + 4; SAFE_WRITE(buffer, (const char*)&other_length, 4); SAFE_WRITE(buffer, &subtype, 1); } SAFE_WRITE(buffer, (const char*)&length, 4); if (subtype != 2) { SAFE_WRITE(buffer, &subtype, 1); } SAFE_WRITE(buffer, RSTRING_PTR(string_data), length); break; } if (strcmp(cls, "BSON::ObjectId") == 0) { VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0); int i; write_name_and_type(buffer, key, 0x07); for (i = 0; i < 12; i++) { char byte = (char)FIX2INT(rb_ary_entry(as_array, i)); SAFE_WRITE(buffer, &byte, 1); } break; } if (strcmp(cls, "BSON::DBRef") == 0) { bson_buffer_position length_location, start_position, obj_length; VALUE ns, oid; write_name_and_type(buffer, key, 0x03); start_position = bson_buffer_get_position(buffer); // save space for length length_location = bson_buffer_save_space(buffer, 4); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } ns = rb_funcall(value, rb_intern("namespace"), 0); write_element_with_id(rb_str_new2("$ref"), ns, pack_extra(buffer, Qfalse)); oid = rb_funcall(value, rb_intern("object_id"), 0); write_element_with_id(rb_str_new2("$id"), oid, pack_extra(buffer, Qfalse)); // write null byte and fill in length SAFE_WRITE(buffer, &zero, 1); obj_length = bson_buffer_get_position(buffer) - start_position; SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4); break; } if (strcmp(cls, "BSON::Code") == 0) { bson_buffer_position length_location, start_position, total_length; int length; VALUE code_str; write_name_and_type(buffer, key, 0x0F); start_position = bson_buffer_get_position(buffer); length_location = bson_buffer_save_space(buffer, 4); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } code_str = rb_funcall(value, rb_intern("code"), 0); length = RSTRING_LENINT(code_str) + 1; SAFE_WRITE(buffer, (char*)&length, 4); SAFE_WRITE(buffer, RSTRING_PTR(code_str), length - 1); SAFE_WRITE(buffer, &zero, 1); write_doc(buffer, rb_funcall(value, rb_intern("scope"), 0), Qfalse, Qfalse); total_length = bson_buffer_get_position(buffer) - start_position; SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&total_length, 4); break; } if (strcmp(cls, "BSON::MaxKey") == 0) { write_name_and_type(buffer, key, 0x7f); break; } if (strcmp(cls, "BSON::MinKey") == 0) { write_name_and_type(buffer, key, 0xff); break; } if (strcmp(cls, "BSON::Timestamp") == 0) { unsigned int seconds; unsigned int increment; write_name_and_type(buffer, key, 0x11); seconds = NUM2UINT( rb_funcall(value, rb_intern("seconds"), 0)); increment = NUM2UINT( rb_funcall(value, rb_intern("increment"), 0)); SAFE_WRITE(buffer, (const char*)&increment, 4); SAFE_WRITE(buffer, (const char*)&seconds, 4); break; } if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls); break; } if(strcmp(cls, "Complex") == 0 || strcmp(cls, "Rational") == 0 || strcmp(cls, "BigDecimal") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls); break; } if (strcmp(cls, "ActiveSupport::Multibyte::Chars") == 0) { int length; VALUE str = StringValue(value); write_name_and_type(buffer, key, 0x02); length = RSTRING_LENINT(str) + 1; SAFE_WRITE(buffer, (char*)&length, 4); write_utf8(buffer, str, 1); SAFE_WRITE(buffer, &zero, 1); break; } if (strcmp(cls, "BSON::Regex") == 0) { serialize_regex(buffer, key, rb_funcall(value, rb_intern("pattern"), 0), FIX2INT(rb_funcall(value, rb_intern("options"), 0)), value, 0); break; } bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls); break; } case T_DATA: { const char* cls = rb_obj_classname(value); if (strcmp(cls, "Time") == 0) { double t = NUM2DBL(rb_funcall(value, rb_intern("to_f"), 0)); long long time_since_epoch = (long long)round(t * 1000); write_name_and_type(buffer, key, 0x09); SAFE_WRITE(buffer, (const char*)&time_since_epoch, 8); break; } // Date classes are TYPE T_DATA in Ruby >= 1.9.3 if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls); break; } if(strcmp(cls, "BigDecimal") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls); break; } bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls); break; } case T_REGEXP: { VALUE pattern = RREGEXP_SRC(value); long flags = RREGEXP_OPTIONS(value); serialize_regex(buffer, key, pattern, flags, value, 1); break; } default: { const char* cls = rb_obj_classname(value); bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s (type %d) into BSON.", cls, TYPE(value)); break; } } return ST_CONTINUE; } static int write_element_without_id(VALUE key, VALUE value, VALUE extra) { return write_element(key, value, extra, 0); } static int write_element_with_id(VALUE key, VALUE value, VALUE extra) { return write_element(key, value, extra, 1); } static void write_doc(bson_buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_id) { bson_buffer_position start_position = bson_buffer_get_position(buffer); bson_buffer_position length_location = bson_buffer_save_space(buffer, 4); bson_buffer_position length; int allow_id; int max_size; int (*write_function)(VALUE, VALUE, VALUE) = NULL; VALUE id_str = rb_str_new2("_id"); VALUE id_sym = ID2SYM(rb_intern("_id")); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } // write '_id' first if move_id is true. then don't allow an id to be written. if(move_id == Qtrue) { allow_id = 0; if (rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) { VALUE id = rb_hash_aref(hash, id_str); write_element_with_id(id_str, id, pack_extra(buffer, check_keys)); } else if (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue) { VALUE id = rb_hash_aref(hash, id_sym); write_element_with_id(id_sym, id, pack_extra(buffer, check_keys)); } } else { allow_id = 1; // Ensure that hash doesn't contain both '_id' and :_id if ((rb_obj_classname(hash), "Hash") == 0) { if ((rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) && (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue)) { VALUE oid_sym = rb_hash_delete(hash, id_sym); rb_funcall(hash, rb_intern("[]="), 2, id_str, oid_sym); } } } if(allow_id == 1) { write_function = write_element_with_id; } else { write_function = write_element_without_id; } // we have to check for an OrderedHash and handle that specially if (strcmp(rb_obj_classname(hash), "BSON::OrderedHash") == 0) { VALUE keys = rb_funcall(hash, rb_intern("keys"), 0); int i; for(i = 0; i < RARRAY_LEN(keys); i++) { VALUE key = rb_ary_entry(keys, i); VALUE value = rb_hash_aref(hash, key); write_function(key, value, pack_extra(buffer, check_keys)); } } else if (rb_obj_is_kind_of(hash, RB_HASH) == Qtrue) { rb_hash_foreach(hash, write_function, pack_extra(buffer, check_keys)); } else { bson_buffer_free(buffer); rb_raise(InvalidDocument, "BSON.serialize takes a Hash but got a %s", rb_obj_classname(hash)); } // write null byte and fill in length SAFE_WRITE(buffer, &zero, 1); length = bson_buffer_get_position(buffer) - start_position; // make sure that length doesn't exceed the max size (determined by server, defaults to 4mb) max_size = bson_buffer_get_max_size(buffer); if (length > max_size) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "Document too large: This BSON document is limited to %d bytes.", max_size); } SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&length, 4); } static VALUE method_serialize(VALUE self, VALUE doc, VALUE check_keys, VALUE move_id, VALUE max_size) { VALUE result; bson_buffer_t buffer = bson_buffer_new(); if (buffer == NULL) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } bson_buffer_set_max_size(buffer, FIX2INT(max_size)); write_doc(buffer, doc, check_keys, move_id); result = rb_str_new(bson_buffer_get_buffer(buffer), bson_buffer_get_position(buffer)); if (bson_buffer_free(buffer) != 0) { rb_raise(rb_eRuntimeError, "failed to free buffer"); } return result; } static VALUE get_value(const char* buffer, int* position, unsigned char type, struct deserialize_opts * opts) { VALUE value; switch (type) { case 255: { value = rb_class_new_instance(0, NULL, MinKey); break; } case 1: { double d; memcpy(&d, buffer + *position, 8); value = rb_float_new(d); *position += 8; break; } case 2: case 13: { int value_length; value_length = *(int*)(buffer + *position) - 1; *position += 4; value = STR_NEW(buffer + *position, value_length); *position += value_length + 1; break; } case 3: { int size; memcpy(&size, buffer + *position, 4); if (strcmp(buffer + *position + 5, "$ref") == 0) { // DBRef int offset = *position + 10; VALUE argv[2]; int collection_length = *(int*)(buffer + offset) - 1; unsigned char id_type; offset += 4; argv[0] = STR_NEW(buffer + offset, collection_length); offset += collection_length + 1; id_type = (unsigned char)buffer[offset]; offset += 5; argv[1] = get_value(buffer, &offset, id_type, opts); value = rb_class_new_instance(2, argv, DBRef); } else { value = elements_to_hash(buffer + *position + 4, size - 5, opts); } *position += size; break; } case 4: { int size, end; memcpy(&size, buffer + *position, 4); end = *position + size - 1; *position += 4; value = rb_ary_new(); while (*position < end) { unsigned char type = (unsigned char)buffer[(*position)++]; int key_size = (int)strlen(buffer + *position); VALUE to_append; *position += key_size + 1; // just skip the key, they're in order. to_append = get_value(buffer, position, type, opts); rb_ary_push(value, to_append); } (*position)++; break; } case 5: { int length, subtype; VALUE data, st; VALUE argv[2]; memcpy(&length, buffer + *position, 4); subtype = (unsigned char)buffer[*position + 4]; if (subtype == 2) { data = rb_str_new(buffer + *position + 9, length - 4); } else { data = rb_str_new(buffer + *position + 5, length); } st = INT2FIX(subtype); argv[0] = data; argv[1] = st; value = rb_class_new_instance(2, argv, Binary); *position += length + 5; break; } case 6: { value = Qnil; break; } case 7: { VALUE str = rb_str_new(buffer + *position, 12); VALUE oid = rb_funcall(str, unpack_method, 1, rb_str_new2("C*")); value = rb_class_new_instance(1, &oid, ObjectId); *position += 12; break; } case 8: { value = buffer[(*position)++] ? Qtrue : Qfalse; break; } case 9: { int64_t millis; memcpy(&millis, buffer + *position, 8); // Support 64-bit time values in 32 bit environments in Ruby > 1.9 // Note: rb_time_num_new is not available pre Ruby 1.9 #if RUBY_API_VERSION_CODE >= 10900 #define add(x,y) (rb_funcall((x), '+', 1, (y))) #define mul(x,y) (rb_funcall((x), '*', 1, (y))) #define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) VALUE d, timev; d = LL2NUM(1000LL); timev = add(LL2NUM(millis / 1000), quo(LL2NUM(millis % 1000), d)); value = rb_time_num_new(timev, Qnil); #else value = rb_time_new(millis / 1000, (millis % 1000) * 1000); #endif value = rb_funcall(value, utc_method, 0); *position += 8; break; } case 10: { value = Qnil; break; } case 11: { int pattern_length = (int)strlen(buffer + *position); VALUE pattern = STR_NEW(buffer + *position, pattern_length); int flags_length; VALUE argv[3]; *position += pattern_length + 1; flags_length = (int)strlen(buffer + *position); VALUE flags_str = STR_NEW(buffer + *position, flags_length); argv[0] = pattern; argv[1] = flags_str; value = rb_class_new_instance(2, argv, BSONRegex); if (opts->compile_regex == 1) { value = rb_funcall(value, rb_intern("try_compile"), 0); } *position += flags_length + 1; break; } case 12: { int collection_length; VALUE collection, str, oid, id, argv[2]; collection_length = *(int*)(buffer + *position) - 1; *position += 4; collection = STR_NEW(buffer + *position, collection_length); *position += collection_length + 1; str = rb_str_new(buffer + *position, 12); oid = rb_funcall(str, unpack_method, 1, rb_str_new2("C*")); id = rb_class_new_instance(1, &oid, ObjectId); *position += 12; argv[0] = collection; argv[1] = id; value = rb_class_new_instance(2, argv, DBRef); break; } case 14: { int value_length; memcpy(&value_length, buffer + *position, 4); value = ID2SYM(rb_intern(buffer + *position + 4)); *position += value_length + 4; break; } case 15: { int code_length, scope_size; VALUE code, scope, argv[2]; *position += 4; code_length = *(int*)(buffer + *position) - 1; *position += 4; code = STR_NEW(buffer + *position, code_length); *position += code_length + 1; memcpy(&scope_size, buffer + *position, 4); scope = elements_to_hash(buffer + *position + 4, scope_size - 5, opts); *position += scope_size; argv[0] = code; argv[1] = scope; value = rb_class_new_instance(2, argv, Code); break; } case 16: { int i; memcpy(&i, buffer + *position, 4); value = LL2NUM(i); *position += 4; break; } case 17: { unsigned int sec, inc; VALUE argv[2]; memcpy(&inc, buffer + *position, 4); memcpy(&sec, buffer + *position + 4, 4); argv[0] = UINT2NUM(sec); argv[1] = UINT2NUM(inc); value = rb_class_new_instance(2, argv, Timestamp); *position += 8; break; } case 18: { long long ll; memcpy(&ll, buffer + *position, 8); value = LL2NUM(ll); *position += 8; break; } case 127: { value = rb_class_new_instance(0, NULL, MaxKey); break; } default: { rb_raise(rb_eTypeError, "no c decoder for this type yet (%d)", type); break; } } return value; } static VALUE elements_to_hash(const char* buffer, int max, struct deserialize_opts * opts) { VALUE hash = rb_class_new_instance(0, NULL, OrderedHash); int position = 0; while (position < max) { unsigned char type = (unsigned char)buffer[position++]; int name_length = (int)strlen(buffer + position); VALUE name = STR_NEW(buffer + position, name_length); VALUE value; position += name_length + 1; value = get_value(buffer, &position, type, opts); rb_funcall(hash, element_assignment_method, 2, name, value); } return hash; } static VALUE method_deserialize(VALUE self, VALUE bson, VALUE opts) { const char* buffer = RSTRING_PTR(bson); int remaining = RSTRING_LENINT(bson); struct deserialize_opts deserialize_opts; deserialize_opts.compile_regex = 1; if (rb_funcall(opts, rb_intern("has_key?"), 1, ID2SYM(rb_intern("compile_regex"))) == Qtrue && rb_hash_aref(opts, ID2SYM(rb_intern("compile_regex"))) == Qfalse) { deserialize_opts.compile_regex = 0; } // NOTE we just swallow the size and end byte here buffer += 4; remaining -= 5; return elements_to_hash(buffer, remaining, &deserialize_opts); } static int legal_objectid_str(VALUE str) { int i; if (TYPE(str) != T_STRING) { return 0; } if (RSTRING_LEN(str) != 24) { return 0; } for(i = 0; i < 24; i++) { char c = RSTRING_PTR(str)[i]; if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { return 0; } } return 1; } static VALUE objectid_legal(VALUE self, VALUE str) { if (legal_objectid_str(str)) return Qtrue; return Qfalse; } static char hexbyte( char hex ) { if (hex >= '0' && hex <= '9') return (hex - '0'); else if (hex >= 'A' && hex <= 'F') return (hex - 'A' + 10); else if (hex >= 'a' && hex <= 'f') return (hex - 'a' + 10); else return 0x0; } static VALUE objectid_from_string(VALUE self, VALUE str) { VALUE oid; int i; if (!legal_objectid_str(str)) { if (TYPE(str) == T_STRING) { rb_raise(InvalidObjectId, "illegal ObjectId format: %s", RSTRING_PTR(str)); } else { VALUE inspect; inspect = rb_funcall(str, rb_intern("to_s"), 0); rb_raise(InvalidObjectId, "not a String: %s", (char *)inspect); } } oid = rb_ary_new2(12); for(i = 0; i < 12; i++) { rb_ary_store(oid, i, INT2FIX( (unsigned)(hexbyte( RSTRING_PTR(str)[2*i] ) << 4 ) | hexbyte( RSTRING_PTR(str)[2*i + 1] ))); } return rb_class_new_instance(1, &oid, ObjectId); } static VALUE objectid_to_s(VALUE self) { VALUE data; char cstr[25]; VALUE rstr; VALUE *data_arr; data = rb_iv_get(self, "@data"); data_arr = RARRAY_PTR(data); sprintf(cstr, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", (unsigned)NUM2INT(data_arr[0]), (unsigned)NUM2INT(data_arr[1]), (unsigned)NUM2INT(data_arr[2]), (unsigned)NUM2INT(data_arr[3]), (unsigned)NUM2INT(data_arr[4]), (unsigned)NUM2INT(data_arr[5]), (unsigned)NUM2INT(data_arr[6]), (unsigned)NUM2INT(data_arr[7]), (unsigned)NUM2INT(data_arr[8]), (unsigned)NUM2INT(data_arr[9]), (unsigned)NUM2INT(data_arr[10]), (unsigned)NUM2INT(data_arr[11])); rstr = rb_str_new(cstr, 24); return rstr; } static VALUE objectid_generate(int argc, VALUE* args, VALUE self) { VALUE oid; unsigned char oid_bytes[12]; unsigned long t, inc; unsigned short pid; int i; if(argc == 0 || (argc == 1 && *args == Qnil)) { t = htonl((int)time(NULL)); } else { t = htonl(NUM2UINT(rb_funcall(*args, rb_intern("to_i"), 0))); } MEMCPY(&oid_bytes, &t, unsigned char, 4); MEMCPY(&oid_bytes[4], hostname_digest, unsigned char, 3); pid = htons(getpid()); MEMCPY(&oid_bytes[7], &pid, unsigned char, 2); /* No need to synchronize modification of this counter between threads; * MRI global interpreter lock guarantees serializability. * * Compiler should optimize out impossible branch. */ if (sizeof(unsigned int) == 4) { object_id_inc++; } else { object_id_inc = (object_id_inc + 1) % 0xFFFFFF; } inc = htonl(object_id_inc); MEMCPY(&oid_bytes[9], ((unsigned char*)&inc + 1), unsigned char, 3); oid = rb_ary_new2(12); for(i = 0; i < 12; i++) { rb_ary_store(oid, i, INT2FIX((unsigned int)oid_bytes[i])); } return oid; } static VALUE method_update_max_bson_size(VALUE self, VALUE connection) { max_bson_size = FIX2INT(rb_funcall(connection, rb_intern("max_bson_size"), 0)); return INT2FIX(max_bson_size); } static VALUE method_max_bson_size(VALUE self) { return INT2FIX(max_bson_size); } void Init_cbson() { VALUE bson, CBson, Digest, ext_version, digest; static char hostname[MAX_HOSTNAME_LENGTH]; element_assignment_method = rb_intern("[]="); unpack_method = rb_intern("unpack"); utc_method = rb_intern("utc"); lt_operator = rb_intern("<"); gt_operator = rb_intern(">"); bson = rb_const_get(rb_cObject, rb_intern("BSON")); rb_require("bson/types/binary"); Binary = rb_const_get(bson, rb_intern("Binary")); rb_require("bson/types/object_id"); ObjectId = rb_const_get(bson, rb_intern("ObjectId")); rb_require("bson/types/dbref"); DBRef = rb_const_get(bson, rb_intern("DBRef")); rb_require("bson/types/code"); Code = rb_const_get(bson, rb_intern("Code")); rb_require("bson/types/min_max_keys"); MinKey = rb_const_get(bson, rb_intern("MinKey")); MaxKey = rb_const_get(bson, rb_intern("MaxKey")); rb_require("bson/types/timestamp"); Timestamp = rb_const_get(bson, rb_intern("Timestamp")); rb_require("bson/types/regex"); BSONRegex = rb_const_get(bson, rb_intern("Regex")); BSONRegex_IGNORECASE = rb_const_get(BSONRegex, rb_intern("IGNORECASE")); BSONRegex_EXTENDED = rb_const_get(BSONRegex, rb_intern("EXTENDED")); BSONRegex_MULTILINE = rb_const_get(BSONRegex, rb_intern("MULTILINE")); BSONRegex_DOTALL = rb_const_get(BSONRegex, rb_intern("DOTALL")); BSONRegex_LOCALE_DEPENDENT = rb_const_get(BSONRegex, rb_intern("LOCALE_DEPENDENT")); BSONRegex_UNICODE = rb_const_get(BSONRegex, rb_intern("UNICODE")); Regexp = rb_const_get(rb_cObject, rb_intern("Regexp")); rb_require("bson/exceptions"); InvalidKeyName = rb_const_get(bson, rb_intern("InvalidKeyName")); InvalidStringEncoding = rb_const_get(bson, rb_intern("InvalidStringEncoding")); InvalidDocument = rb_const_get(bson, rb_intern("InvalidDocument")); InvalidObjectId = rb_const_get(bson, rb_intern("InvalidObjectId")); rb_require("bson/ordered_hash"); OrderedHash = rb_const_get(bson, rb_intern("OrderedHash")); RB_HASH = rb_const_get(bson, rb_intern("Hash")); CBson = rb_define_module("CBson"); ext_version = rb_str_new2(VERSION); rb_define_const(CBson, "VERSION", ext_version); rb_define_module_function(CBson, "serialize", method_serialize, 4); rb_define_module_function(CBson, "deserialize", method_deserialize, 2); rb_define_module_function(CBson, "max_bson_size", method_max_bson_size, 0); rb_define_module_function(CBson, "update_max_bson_size", method_update_max_bson_size, 1); rb_require("digest/md5"); Digest = rb_const_get(rb_cObject, rb_intern("Digest")); DigestMD5 = rb_const_get(Digest, rb_intern("MD5")); rb_define_singleton_method(ObjectId, "legal?", objectid_legal, 1); rb_define_singleton_method(ObjectId, "from_string", objectid_from_string, 1); rb_define_method(ObjectId, "to_s", objectid_to_s, 0); rb_define_method(ObjectId, "generate", objectid_generate, -1); if (gethostname(hostname, MAX_HOSTNAME_LENGTH) != 0) { rb_raise(rb_eRuntimeError, "failed to get hostname"); } digest = rb_funcall(DigestMD5, rb_intern("digest"), 1, rb_str_new2(hostname)); memcpy(hostname_digest, RSTRING_PTR(digest), 16); hostname_digest[16] = '\0'; max_bson_size = 4 * 1024 * 1024; }