diff options
84 files changed, 2955 insertions, 15364 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt index 7374b7c8..f304f6af 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -16,12 +16,6 @@ ZeroTier One includes or links with the following third party software: * LZ4 compression algorithm by Yann Collet (BSD license) http://code.google.com/p/lz4/ - * JsonCPP by Baptiste Lepilleur (public domain) - http://jsoncpp.sourceforge.net - - * http-parser, a simple C http parser library (MIT license) - https://github.com/joyent/http-parser - * OpenSSL libcrypto (BSD-style OpenSSL license) http://www.openssl.org/ diff --git a/Makefile.linux b/Makefile.linux index dd570911..33b88bad 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -21,7 +21,7 @@ CXXFLAGS=$(CFLAGS) -fno-rtti # separate binaries for the RedHat and Debian universes to distribute via # auto-update. This way we get one Linux binary for all systems of a given # architecture. -LIBS=ext/bin/libcrypto/linux-$(ARCH)/libcrypto.a +LIBS=ext/bin/libcrypto/linux-$(ARCH)/libcrypto.a -lm include objects.mk @@ -39,10 +39,6 @@ idtool: $(OBJS) $(CXX) $(CXXFLAGS) -o zerotier-idtool idtool.cpp $(OBJS) $(LIBS) $(STRIP) zerotier-idtool -packtool: $(OBJS) - $(CXX) $(CXXFLAGS) -o zerotier-packtool packtool.cpp $(OBJS) $(LIBS) - $(STRIP) zerotier-packtool - launcher: $(CC) -Os -o zerotier-launcher launcher.c $(STRIP) zerotier-launcher diff --git a/Makefile.mac b/Makefile.mac index 89203243..d47d18cc 100644 --- a/Makefile.mac +++ b/Makefile.mac @@ -13,19 +13,20 @@ STRIP=strip #STRIP=echo CXXFLAGS=$(CFLAGS) -fno-rtti - -# We statically link against libcrypto since Apple has apparently decided -# to deprecate it and may remove it in future OS releases. -LIBS=ext/bin/libcrypto/mac-x86_combined/libcrypto.a +LIBS=-lcrypto -lm include objects.mk -all: one launcher mac-tap +all: one cli launcher mac-tap one: $(OBJS) $(CXX) $(CXXFLAGS) -o zerotier-one main.cpp $(OBJS) $(LIBS) $(STRIP) zerotier-one +cli: $(OBJS) + $(CXX) $(CXXFLAGS) -o zerotier-cli cli.cpp $(OBJS) $(LIBS) + $(STRIP) zerotier-cli + selftest: $(OBJS) $(CXX) $(CXXFLAGS) -o zerotier-selftest selftest.cpp $(OBJS) $(LIBS) $(STRIP) zerotier-selftest @@ -34,10 +35,6 @@ idtool: $(OBJS) $(CXX) $(CXXFLAGS) -o zerotier-idtool idtool.cpp $(OBJS) $(LIBS) $(STRIP) zerotier-idtool -packtool: $(OBJS) - $(CXX) $(CXXFLAGS) -o zerotier-packtool packtool.cpp $(OBJS) $(LIBS) - $(STRIP) zerotier-packtool - mac-tap: FORCE cd mac-tap/tuntap ; make tap.kext @@ -55,6 +52,9 @@ launcher-fakebin: $(CC) $(CFLAGS) -DZEROTIER_FAKE_VERSION_MAJOR=1 -DZEROTIER_FAKE_VERSION_MINOR=2 -DZEROTIER_FAKE_VERSION_REV $(CC) $(CFLAGS) -DZEROTIER_FAKE_VERSION_MAJOR=1 -DZEROTIER_FAKE_VERSION_MINOR=2 -DZEROTIER_FAKE_VERSION_REV +makekeypair: $(OBJS) + $(CXX) $(CXXFLAGS) -o zerotier-makekeypair makekeypair.cpp $(OBJS) $(LIBS) + clean: rm -rf *.dSYM rm -f $(OBJS) zerotier-* diff --git a/cli.cpp b/cli.cpp new file mode 100644 index 00000000..a72a0890 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,124 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef __WINDOWS__ +#include <unistd.h> +#endif + +#include "node/Node.hpp" +#include "node/Constants.hpp" +#include "node/Utils.hpp" +#include "node/Thread.hpp" + +using namespace ZeroTier; + +static void printHelp(FILE *out,const char *exename) +{ + fprintf(out,"Usage: %s [-switches] <command>"ZT_EOL_S,exename); + fprintf(out,ZT_EOL_S); + fprintf(out,"Switches:"ZT_EOL_S); + fprintf(out," -t<token> - Specify token on command line"ZT_EOL_S); + fprintf(out," -T<file> - Read token from file"ZT_EOL_S); + fprintf(out,ZT_EOL_S); + fprintf(out,"Use the 'help' command to get help from ZeroTier One itself."ZT_EOL_S); +} + +static volatile uint64_t lastResultTime = 0ULL; +static volatile unsigned int numResults = 0; + +static void resultHandler(void *arg,unsigned long id,const char *line) +{ + lastResultTime = Utils::now(); + ++numResults; + fprintf(stdout,"%s"ZT_EOL_S,line); +} + +int main(int argc,char **argv) +{ + if (argc <= 1) { + printHelp(stdout,argv[0]); + return -1; + } + + std::string authToken; + + for(int i=1;i<argc;++i) { + if (argv[i][0] == '-') { + if (strlen(argv[i]) <= 1) { + printHelp(stdout,argv[0]); + return -1; + } + switch(argv[i][1]) { + case 't': + authToken.assign(argv[i] + 2); + break; + case 'T': + if (!Utils::readFile(argv[i] + 2,authToken)) { + fprintf(stdout,"FATAL ERROR: unable to read token from '%s'"ZT_EOL_S,argv[i] + 2); + return -2; + } + break; + default: + return -1; + } + } + } + + if (!authToken.length()) { + const char *home = getenv("HOME"); + if (home) { + std::string dotZeroTierAuthToken(home); + dotZeroTierAuthToken.push_back(ZT_PATH_SEPARATOR); + dotZeroTierAuthToken.append(".zerotierOneAuthToken"); + if (!Utils::readFile(dotZeroTierAuthToken.c_str(),authToken)) { + fprintf(stdout,"FATAL ERROR: no token specified on command line and could not read '%s'"ZT_EOL_S,dotZeroTierAuthToken.c_str()); + return -2; + } + } + } + if (!authToken.length()) { + fprintf(stdout,"FATAL ERROR: could not find auth token"ZT_EOL_S); + return -2; + } + + Node::LocalClient(authToken.c_str(),&resultHandler,(void *)0); + + lastResultTime = Utils::now(); + while ((Utils::now() - lastResultTime) < 300) + Thread::sleep(50); + + if (!numResults) { + fprintf(stdout,"ERROR: no results received. Is ZeroTier One running?"ZT_EOL_S); + return -1; + } + + return 0; +} diff --git a/ext/http-parser/AUTHORS b/ext/http-parser/AUTHORS deleted file mode 100644 index 92ee45ca..00000000 --- a/ext/http-parser/AUTHORS +++ /dev/null @@ -1,41 +0,0 @@ -# Authors ordered by first contribution. -Ryan Dahl <ry@tinyclouds.org> -Jeremy Hinegardner <jeremy@hinegardner.org> -Sergey Shepelev <temotor@gmail.com> -Joe Damato <ice799@gmail.com> -tomika <tomika_nospam@freemail.hu> -Phoenix Sol <phoenix@burninglabs.com> -Cliff Frey <cliff@meraki.com> -Ewen Cheslack-Postava <ewencp@cs.stanford.edu> -Santiago Gala <sgala@apache.org> -Tim Becker <tim.becker@syngenio.de> -Jeff Terrace <jterrace@gmail.com> -Ben Noordhuis <info@bnoordhuis.nl> -Nathan Rajlich <nathan@tootallnate.net> -Mark Nottingham <mnot@mnot.net> -Aman Gupta <aman@tmm1.net> -Tim Becker <tim.becker@kuriositaet.de> -Sean Cunningham <sean.cunningham@mandiant.com> -Peter Griess <pg@std.in> -Salman Haq <salman.haq@asti-usa.com> -Cliff Frey <clifffrey@gmail.com> -Jon Kolb <jon@b0g.us> -Fouad Mardini <f.mardini@gmail.com> -Paul Querna <pquerna@apache.org> -Felix Geisendörfer <felix@debuggable.com> -koichik <koichik@improvement.jp> -Andre Caron <andre.l.caron@gmail.com> -Ivo Raisr <ivosh@ivosh.net> -James McLaughlin <jamie@lacewing-project.org> -David Gwynne <loki@animata.net> -Thomas LE ROUX <thomas@november-eleven.fr> -Randy Rizun <rrizun@ortivawireless.com> -Andre Louis Caron <andre.louis.caron@usherbrooke.ca> -Simon Zimmermann <simonz05@gmail.com> -Erik Dubbelboer <erik@dubbelboer.com> -Martell Malone <martellmalone@gmail.com> -Bertrand Paquet <bpaquet@octo.com> -BogDan Vatra <bogdan@kde.org> -Peter Faiman <peter@thepicard.org> -Corey Richardson <corey@octayn.net> -Tóth Tamás <tomika_nospam@freemail.hu> diff --git a/ext/http-parser/CONTRIBUTIONS b/ext/http-parser/CONTRIBUTIONS deleted file mode 100644 index 11ba31e4..00000000 --- a/ext/http-parser/CONTRIBUTIONS +++ /dev/null @@ -1,4 +0,0 @@ -Contributors must agree to the Contributor License Agreement before patches -can be accepted. - -http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ diff --git a/ext/http-parser/LICENSE-MIT b/ext/http-parser/LICENSE-MIT deleted file mode 100644 index 58010b38..00000000 --- a/ext/http-parser/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright -Igor Sysoev. - -Additional changes are licensed under the same terms as NGINX and -copyright Joyent, Inc. and other Node contributors. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/ext/http-parser/README.md b/ext/http-parser/README.md deleted file mode 100644 index b63418af..00000000 --- a/ext/http-parser/README.md +++ /dev/null @@ -1,180 +0,0 @@ -HTTP Parser -=========== - -[](https://travis-ci.org/joyent/http-parser) - -This is a parser for HTTP messages written in C. It parses both requests and -responses. The parser is designed to be used in performance HTTP -applications. It does not make any syscalls nor allocations, it does not -buffer data, it can be interrupted at anytime. Depending on your -architecture, it only requires about 40 bytes of data per message -stream (in a web server that is per connection). - -Features: - - * No dependencies - * Handles persistent streams (keep-alive). - * Decodes chunked encoding. - * Upgrade support - * Defends against buffer overflow attacks. - -The parser extracts the following information from HTTP messages: - - * Header fields and values - * Content-Length - * Request method - * Response status code - * Transfer-Encoding - * HTTP version - * Request URL - * Message body - - -Usage ------ - -One `http_parser` object is used per TCP connection. Initialize the struct -using `http_parser_init()` and set the callbacks. That might look something -like this for a request parser: - - http_parser_settings settings; - settings.on_url = my_url_callback; - settings.on_header_field = my_header_field_callback; - /* ... */ - - http_parser *parser = malloc(sizeof(http_parser)); - http_parser_init(parser, HTTP_REQUEST); - parser->data = my_socket; - -When data is received on the socket execute the parser and check for errors. - - size_t len = 80*1024, nparsed; - char buf[len]; - ssize_t recved; - - recved = recv(fd, buf, len, 0); - - if (recved < 0) { - /* Handle error. */ - } - - /* Start up / continue the parser. - * Note we pass recved==0 to signal that EOF has been recieved. - */ - nparsed = http_parser_execute(parser, &settings, buf, recved); - - if (parser->upgrade) { - /* handle new protocol */ - } else if (nparsed != recved) { - /* Handle error. Usually just close the connection. */ - } - -HTTP needs to know where the end of the stream is. For example, sometimes -servers send responses without Content-Length and expect the client to -consume input (for the body) until EOF. To tell http_parser about EOF, give -`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors -can still be encountered during an EOF, so one must still be prepared -to receive them. - -Scalar valued message information such as `status_code`, `method`, and the -HTTP version are stored in the parser structure. This data is only -temporally stored in `http_parser` and gets reset on each new message. If -this information is needed later, copy it out of the structure during the -`headers_complete` callback. - -The parser decodes the transfer-encoding for both requests and responses -transparently. That is, a chunked encoding is decoded before being sent to -the on_body callback. - - -The Special Problem of Upgrade ------------------------------- - -HTTP supports upgrading the connection to a different protocol. An -increasingly common example of this is the Web Socket protocol which sends -a request like - - GET /demo HTTP/1.1 - Upgrade: WebSocket - Connection: Upgrade - Host: example.com - Origin: http://example.com - WebSocket-Protocol: sample - -followed by non-HTTP data. - -(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more -information the Web Socket protocol.) - -To support this, the parser will treat this as a normal HTTP message without a -body. Issuing both on_headers_complete and on_message_complete callbacks. However -http_parser_execute() will stop parsing at the end of the headers and return. - -The user is expected to check if `parser->upgrade` has been set to 1 after -`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied -offset by the return value of `http_parser_execute()`. - - -Callbacks ---------- - -During the `http_parser_execute()` call, the callbacks set in -`http_parser_settings` will be executed. The parser maintains state and -never looks behind, so buffering the data is not necessary. If you need to -save certain data for later usage, you can do that from the callbacks. - -There are two types of callbacks: - -* notification `typedef int (*http_cb) (http_parser*);` - Callbacks: on_message_begin, on_headers_complete, on_message_complete. -* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` - Callbacks: (requests only) on_uri, - (common) on_header_field, on_header_value, on_body; - -Callbacks must return 0 on success. Returning a non-zero value indicates -error to the parser, making it exit immediately. - -In case you parse HTTP message in chunks (i.e. `read()` request line -from socket, parse, read half headers, parse, etc) your data callbacks -may be called more than once. Http-parser guarantees that data pointer is only -valid for the lifetime of callback. You can also `read()` into a heap allocated -buffer to avoid copying memory around if this fits your application. - -Reading headers may be a tricky task if you read/parse headers partially. -Basically, you need to remember whether last header callback was field or value -and apply following logic: - - (on_header_field and on_header_value shortened to on_h_*) - ------------------------ ------------ -------------------------------------------- - | State (prev. callback) | Callback | Description/action | - ------------------------ ------------ -------------------------------------------- - | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | - | | | into it | - ------------------------ ------------ -------------------------------------------- - | value | on_h_field | New header started. | - | | | Copy current name,value buffers to headers | - | | | list and allocate new buffer for new name | - ------------------------ ------------ -------------------------------------------- - | field | on_h_field | Previous name continues. Reallocate name | - | | | buffer and append callback data to it | - ------------------------ ------------ -------------------------------------------- - | field | on_h_value | Value for current header started. Allocate | - | | | new buffer and copy callback data to it | - ------------------------ ------------ -------------------------------------------- - | value | on_h_value | Value continues. Reallocate value buffer | - | | | and append callback data to it | - ------------------------ ------------ -------------------------------------------- - - -Parsing URLs ------------- - -A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. -Users of this library may wish to use it to parse URLs constructed from -consecutive `on_url` callbacks. - -See examples of reading in headers: - -* [partial example](http://gist.github.com/155877) in C -* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C -* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript diff --git a/ext/http-parser/contrib/parsertrace.c b/ext/http-parser/contrib/parsertrace.c deleted file mode 100644 index c9bc71ec..00000000 --- a/ext/http-parser/contrib/parsertrace.c +++ /dev/null @@ -1,156 +0,0 @@ -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -/* Dump what the parser finds to stdout as it happen */ - -#include "http_parser.h" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -int on_message_begin(http_parser* _) { - (void)_; - printf("\n***MESSAGE BEGIN***\n\n"); - return 0; -} - -int on_headers_complete(http_parser* _) { - (void)_; - printf("\n***HEADERS COMPLETE***\n\n"); - return 0; -} - -int on_message_complete(http_parser* _) { - (void)_; - printf("\n***MESSAGE COMPLETE***\n\n"); - return 0; -} - -int on_url(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Url: %.*s\n", (int)length, at); - return 0; -} - -int on_header_field(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Header field: %.*s\n", (int)length, at); - return 0; -} - -int on_header_value(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Header value: %.*s\n", (int)length, at); - return 0; -} - -int on_body(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Body: %.*s\n", (int)length, at); - return 0; -} - -void usage(const char* name) { - fprintf(stderr, - "Usage: %s $type $filename\n" - " type: -x, where x is one of {r,b,q}\n" - " parses file as a Response, reQuest, or Both\n", - name); - exit(EXIT_FAILURE); -} - -int main(int argc, char* argv[]) { - enum http_parser_type file_type; - - if (argc != 3) { - usage(argv[0]); - } - - char* type = argv[1]; - if (type[0] != '-') { - usage(argv[0]); - } - - switch (type[1]) { - /* in the case of "-", type[1] will be NUL */ - case 'r': - file_type = HTTP_RESPONSE; - break; - case 'q': - file_type = HTTP_REQUEST; - break; - case 'b': - file_type = HTTP_BOTH; - break; - default: - usage(argv[0]); - } - - char* filename = argv[2]; - FILE* file = fopen(filename, "r"); - if (file == NULL) { - perror("fopen"); - return EXIT_FAILURE; - } - - fseek(file, 0, SEEK_END); - long file_length = ftell(file); - if (file_length == -1) { - perror("ftell"); - return EXIT_FAILURE; - } - fseek(file, 0, SEEK_SET); - - char* data = malloc(file_length); - if (fread(data, 1, file_length, file) != (size_t)file_length) { - fprintf(stderr, "couldn't read entire file\n"); - free(data); - return EXIT_FAILURE; - } - - http_parser_settings settings; - memset(&settings, 0, sizeof(settings)); - settings.on_message_begin = on_message_begin; - settings.on_url = on_url; - settings.on_header_field = on_header_field; - settings.on_header_value = on_header_value; - settings.on_headers_complete = on_headers_complete; - settings.on_body = on_body; - settings.on_message_complete = on_message_complete; - - http_parser parser; - http_parser_init(&parser, file_type); - size_t nparsed = http_parser_execute(&parser, &settings, data, file_length); - free(data); - - if (nparsed != (size_t)file_length) { - fprintf(stderr, - "Error: %s (%s)\n", - http_errno_description(HTTP_PARSER_ERRNO(&parser)), - http_errno_name(HTTP_PARSER_ERRNO(&parser))); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/ext/http-parser/contrib/url_parser.c b/ext/http-parser/contrib/url_parser.c deleted file mode 100644 index b1f9c979..00000000 --- a/ext/http-parser/contrib/url_parser.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "http_parser.h" -#include <stdio.h> -#include <string.h> - -void -dump_url (const char *url, const struct http_parser_url *u) -{ - unsigned int i; - - printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); - for (i = 0; i < UF_MAX; i++) { - if ((u->field_set & (1 << i)) == 0) { - printf("\tfield_data[%u]: unset\n", i); - continue; - } - - printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n", - i, - u->field_data[i].off, - u->field_data[i].len, - u->field_data[i].len, - url + u->field_data[i].off); - } -} - -int main(int argc, char ** argv) { - if (argc != 3) { - printf("Syntax : %s connect|get url\n", argv[0]); - return 1; - } - struct http_parser_url u; - int len = strlen(argv[2]); - int connect = strcmp("connect", argv[1]) == 0 ? 1 : 0; - printf("Parsing %s, connect %d\n", argv[2], connect); - - int result = http_parser_parse_url(argv[2], len, connect, &u); - if (result != 0) { - printf("Parse error : %d\n", result); - return result; - } - printf("Parse ok, result : \n"); - dump_url(argv[2], &u); - return 0; -}
\ No newline at end of file diff --git a/ext/http-parser/http_parser.c b/ext/http-parser/http_parser.c deleted file mode 100644 index ed3a9232..00000000 --- a/ext/http-parser/http_parser.c +++ /dev/null @@ -1,2175 +0,0 @@ -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -#include "http_parser.h" -#include <assert.h> -#include <stddef.h> -#include <ctype.h> -#include <stdlib.h> -#include <string.h> -#include <limits.h> - -#ifndef ULLONG_MAX -# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ -#endif - -#ifndef MIN -# define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef ARRAY_SIZE -# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#endif - -#ifndef BIT_AT -# define BIT_AT(a, i) \ - (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ - (1 << ((unsigned int) (i) & 7)))) -#endif - -#ifndef ELEM_AT -# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) -#endif - -#define SET_ERRNO(e) \ -do { \ - parser->http_errno = (e); \ -} while(0) - - -/* Run the notify callback FOR, returning ER if it fails */ -#define CALLBACK_NOTIFY_(FOR, ER) \ -do { \ - assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ - \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser)) { \ - SET_ERRNO(HPE_CB_##FOR); \ - } \ - \ - /* We either errored above or got paused; get out */ \ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ - return (ER); \ - } \ - } \ -} while (0) - -/* Run the notify callback FOR and consume the current byte */ -#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) - -/* Run the notify callback FOR and don't consume the current byte */ -#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) - -/* Run data callback FOR with LEN bytes, returning ER if it fails */ -#define CALLBACK_DATA_(FOR, LEN, ER) \ -do { \ - assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ - \ - if (FOR##_mark) { \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ - SET_ERRNO(HPE_CB_##FOR); \ - } \ - \ - /* We either errored above or got paused; get out */ \ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ - return (ER); \ - } \ - } \ - FOR##_mark = NULL; \ - } \ -} while (0) - -/* Run the data callback FOR and consume the current byte */ -#define CALLBACK_DATA(FOR) \ - CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) - -/* Run the data callback FOR and don't consume the current byte */ -#define CALLBACK_DATA_NOADVANCE(FOR) \ - CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) - -/* Set the mark FOR; non-destructive if mark is already set */ -#define MARK(FOR) \ -do { \ - if (!FOR##_mark) { \ - FOR##_mark = p; \ - } \ -} while (0) - - -#define PROXY_CONNECTION "proxy-connection" -#define CONNECTION "connection" -#define CONTENT_LENGTH "content-length" -#define TRANSFER_ENCODING "transfer-encoding" -#define UPGRADE "upgrade" -#define CHUNKED "chunked" -#define KEEP_ALIVE "keep-alive" -#define CLOSE "close" - - -static const char *method_strings[] = - { -#define XX(num, name, string) #string, - HTTP_METHOD_MAP(XX) -#undef XX - }; - - -/* Tokens as defined by rfc 2616. Also lowercases them. - * token = 1*<any CHAR except CTLs or separators> - * separators = "(" | ")" | "<" | ">" | "@" - * | "," | ";" | ":" | "\" | <"> - * | "/" | "[" | "]" | "?" | "=" - * | "{" | "}" | SP | HT - */ -static const char tokens[256] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0, '!', 0, '#', '$', '%', '&', '\'', -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 0, 0, '*', '+', 0, '-', '.', 0, -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - '0', '1', '2', '3', '4', '5', '6', '7', -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - '8', '9', 0, 0, 0, 0, 0, 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 'x', 'y', 'z', 0, 0, 0, '^', '_', -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 'x', 'y', 'z', 0, '|', 0, '~', 0 }; - - -static const int8_t unhex[256] = - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - }; - - -#if HTTP_PARSER_STRICT -# define T(v) 0 -#else -# define T(v) v -#endif - - -static const uint8_t normal_url_char[32] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; - -#undef T - -enum state - { s_dead = 1 /* important that this is > 0 */ - - , s_start_req_or_res - , s_res_or_resp_H - , s_start_res - , s_res_H - , s_res_HT - , s_res_HTT - , s_res_HTTP - , s_res_first_http_major - , s_res_http_major - , s_res_first_http_minor - , s_res_http_minor - , s_res_first_status_code - , s_res_status_code - , s_res_status - , s_res_line_almost_done - - , s_start_req - - , s_req_method - , s_req_spaces_before_url - , s_req_schema - , s_req_schema_slash - , s_req_schema_slash_slash - , s_req_server_start - , s_req_server - , s_req_server_with_at - , s_req_path - , s_req_query_string_start - , s_req_query_string - , s_req_fragment_start - , s_req_fragment - , s_req_http_start - , s_req_http_H - , s_req_http_HT - , s_req_http_HTT - , s_req_http_HTTP - , s_req_first_http_major - , s_req_http_major - , s_req_first_http_minor - , s_req_http_minor - , s_req_line_almost_done - - , s_header_field_start - , s_header_field - , s_header_value_start - , s_header_value - , s_header_value_lws - - , s_header_almost_done - - , s_chunk_size_start - , s_chunk_size - , s_chunk_parameters - , s_chunk_size_almost_done - - , s_headers_almost_done - , s_headers_done - - /* Important: 's_headers_done' must be the last 'header' state. All - * states beyond this must be 'body' states. It is used for overflow - * checking. See the PARSING_HEADER() macro. - */ - - , s_chunk_data - , s_chunk_data_almost_done - , s_chunk_data_done - - , s_body_identity - , s_body_identity_eof - - , s_message_done - }; - - -#define PARSING_HEADER(state) (state <= s_headers_done) - - -enum header_states - { h_general = 0 - , h_C - , h_CO - , h_CON - - , h_matching_connection - , h_matching_proxy_connection - , h_matching_content_length - , h_matching_transfer_encoding - , h_matching_upgrade - - , h_connection - , h_content_length - , h_transfer_encoding - , h_upgrade - - , h_matching_transfer_encoding_chunked - , h_matching_connection_keep_alive - , h_matching_connection_close - - , h_transfer_encoding_chunked - , h_connection_keep_alive - , h_connection_close - }; - -enum http_host_state - { - s_http_host_dead = 1 - , s_http_userinfo_start - , s_http_userinfo - , s_http_host_start - , s_http_host_v6_start - , s_http_host - , s_http_host_v6 - , s_http_host_v6_end - , s_http_host_port_start - , s_http_host_port -}; - -/* Macros for character classes; depends on strict-mode */ -#define CR '\r' -#define LF '\n' -#define LOWER(c) (unsigned char)(c | 0x20) -#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') -#define IS_NUM(c) ((c) >= '0' && (c) <= '9') -#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) -#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) -#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ - (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ - (c) == ')') -#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ - (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ - (c) == '$' || (c) == ',') - -#if HTTP_PARSER_STRICT -#define TOKEN(c) (tokens[(unsigned char)c]) -#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) -#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') -#else -#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) -#define IS_URL_CHAR(c) \ - (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) -#define IS_HOST_CHAR(c) \ - (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') -#endif - - -#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) - - -#if HTTP_PARSER_STRICT -# define STRICT_CHECK(cond) \ -do { \ - if (cond) { \ - SET_ERRNO(HPE_STRICT); \ - goto error; \ - } \ -} while (0) -# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) -#else -# define STRICT_CHECK(cond) -# define NEW_MESSAGE() start_state -#endif - - -/* Map errno values to strings for human-readable output */ -#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, -static struct { - const char *name; - const char *description; -} http_strerror_tab[] = { - HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) -}; -#undef HTTP_STRERROR_GEN - -int http_message_needs_eof(const http_parser *parser); - -/* Our URL parser. - * - * This is designed to be shared by http_parser_execute() for URL validation, - * hence it has a state transition + byte-for-byte interface. In addition, it - * is meant to be embedded in http_parser_parse_url(), which does the dirty - * work of turning state transitions URL components for its API. - * - * This function should only be invoked with non-space characters. It is - * assumed that the caller cares about (and can detect) the transition between - * URL and non-URL states by looking for these. - */ -static enum state -parse_url_char(enum state s, const char ch) -{ - if (ch == ' ' || ch == '\r' || ch == '\n') { - return s_dead; - } - -#if HTTP_PARSER_STRICT - if (ch == '\t' || ch == '\f') { - return s_dead; - } -#endif - - switch (s) { - case s_req_spaces_before_url: - /* Proxied requests are followed by scheme of an absolute URI (alpha). - * All methods except CONNECT are followed by '/' or '*'. - */ - - if (ch == '/' || ch == '*') { - return s_req_path; - } - - if (IS_ALPHA(ch)) { - return s_req_schema; - } - - break; - - case s_req_schema: - if (IS_ALPHA(ch)) { - return s; - } - - if (ch == ':') { - return s_req_schema_slash; - } - - break; - - case s_req_schema_slash: - if (ch == '/') { - return s_req_schema_slash_slash; - } - - break; - - case s_req_schema_slash_slash: - if (ch == '/') { - return s_req_server_start; - } - - break; - - case s_req_server_with_at: - if (ch == '@') { - return s_dead; - } - - /* FALLTHROUGH */ - case s_req_server_start: - case s_req_server: - if (ch == '/') { - return s_req_path; - } - - if (ch == '?') { - return s_req_query_string_start; - } - - if (ch == '@') { - return s_req_server_with_at; - } - - if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { - return s_req_server; - } - - break; - - case s_req_path: - if (IS_URL_CHAR(ch)) { - return s; - } - - switch (ch) { - case '?': - return s_req_query_string_start; - - case '#': - return s_req_fragment_start; - } - - break; - - case s_req_query_string_start: - case s_req_query_string: - if (IS_URL_CHAR(ch)) { - return s_req_query_string; - } - - switch (ch) { - case '?': - /* allow extra '?' in query string */ - return s_req_query_string; - - case '#': - return s_req_fragment_start; - } - - break; - - case s_req_fragment_start: - if (IS_URL_CHAR(ch)) { - return s_req_fragment; - } - - switch (ch) { - case '?': - return s_req_fragment; - - case '#': - return s; - } - - break; - - case s_req_fragment: - if (IS_URL_CHAR(ch)) { - return s; - } - - switch (ch) { - case '?': - case '#': - return s; - } - - break; - - default: - break; - } - - /* We should never fall out of the switch above unless there's an error */ - return s_dead; -} - -size_t http_parser_execute (http_parser *parser, - const http_parser_settings *settings, - const char *data, - size_t len) -{ - char c, ch; - int8_t unhex_val; - const char *p = data; - const char *header_field_mark = 0; - const char *header_value_mark = 0; - const char *url_mark = 0; - const char *body_mark = 0; - - /* We're in an error state. Don't bother doing anything. */ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - return 0; - } - - if (len == 0) { - switch (parser->state) { - case s_body_identity_eof: - /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if - * we got paused. - */ - CALLBACK_NOTIFY_NOADVANCE(message_complete); - return 0; - - case s_dead: - case s_start_req_or_res: - case s_start_res: - case s_start_req: - return 0; - - default: - SET_ERRNO(HPE_INVALID_EOF_STATE); - return 1; - } - } - - - if (parser->state == s_header_field) - header_field_mark = data; - if (parser->state == s_header_value) - header_value_mark = data; - switch (parser->state) { - case s_req_path: - case s_req_schema: - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - case s_req_server: - case s_req_server_with_at: - case s_req_query_string_start: - case s_req_query_string: - case s_req_fragment_start: - case s_req_fragment: - url_mark = data; - break; - } - - for (p=data; p != data + len; p++) { - ch = *p; - - if (PARSING_HEADER(parser->state)) { - ++parser->nread; - /* Buffer overflow attack */ - if (parser->nread > HTTP_MAX_HEADER_SIZE) { - SET_ERRNO(HPE_HEADER_OVERFLOW); - goto error; - } - } - - reexecute_byte: - switch (parser->state) { - - case s_dead: - /* this state is used after a 'Connection: close' message - * the parser will error out if it reads another message - */ - if (ch == CR || ch == LF) - break; - - SET_ERRNO(HPE_CLOSED_CONNECTION); - goto error; - - case s_start_req_or_res: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - if (ch == 'H') { - parser->state = s_res_or_resp_H; - - CALLBACK_NOTIFY(message_begin); - } else { - parser->type = HTTP_REQUEST; - parser->state = s_start_req; - goto reexecute_byte; - } - - break; - } - - case s_res_or_resp_H: - if (ch == 'T') { - parser->type = HTTP_RESPONSE; - parser->state = s_res_HT; - } else { - if (ch != 'E') { - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - - parser->type = HTTP_REQUEST; - parser->method = HTTP_HEAD; - parser->index = 2; - parser->state = s_req_method; - } - break; - - case s_start_res: - { - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - switch (ch) { - case 'H': - parser->state = s_res_H; - break; - - case CR: - case LF: - break; - - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - - CALLBACK_NOTIFY(message_begin); - break; - } - - case s_res_H: - STRICT_CHECK(ch != 'T'); - parser->state = s_res_HT; - break; - - case s_res_HT: - STRICT_CHECK(ch != 'T'); - parser->state = s_res_HTT; - break; - - case s_res_HTT: - STRICT_CHECK(ch != 'P'); - parser->state = s_res_HTTP; - break; - - case s_res_HTTP: - STRICT_CHECK(ch != '/'); - parser->state = s_res_first_http_major; - break; - - case s_res_first_http_major: - if (ch < '0' || ch > '9') { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major = ch - '0'; - parser->state = s_res_http_major; - break; - - /* major HTTP version or dot */ - case s_res_http_major: - { - if (ch == '.') { - parser->state = s_res_first_http_minor; - break; - } - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (parser->http_major > 999) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* first digit of minor HTTP version */ - case s_res_first_http_minor: - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor = ch - '0'; - parser->state = s_res_http_minor; - break; - - /* minor HTTP version or end of request line */ - case s_res_http_minor: - { - if (ch == ' ') { - parser->state = s_res_first_status_code; - break; - } - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (parser->http_minor > 999) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - case s_res_first_status_code: - { - if (!IS_NUM(ch)) { - if (ch == ' ') { - break; - } - - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - parser->status_code = ch - '0'; - parser->state = s_res_status_code; - break; - } - - case s_res_status_code: - { - if (!IS_NUM(ch)) { - switch (ch) { - case ' ': - parser->state = s_res_status; - break; - case CR: - parser->state = s_res_line_almost_done; - break; - case LF: - parser->state = s_header_field_start; - break; - default: - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - break; - } - - parser->status_code *= 10; - parser->status_code += ch - '0'; - - if (parser->status_code > 999) { - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - - break; - } - - case s_res_status: - /* the human readable status. e.g. "NOT FOUND" - * we are not humans so just ignore this */ - if (ch == CR) { - parser->state = s_res_line_almost_done; - break; - } - - if (ch == LF) { - parser->state = s_header_field_start; - break; - } - break; - - case s_res_line_almost_done: - STRICT_CHECK(ch != LF); - parser->state = s_header_field_start; - CALLBACK_NOTIFY(status_complete); - break; - - case s_start_req: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - if (!IS_ALPHA(ch)) { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - parser->method = (enum http_method) 0; - parser->index = 1; - switch (ch) { - case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; - case 'D': parser->method = HTTP_DELETE; break; - case 'G': parser->method = HTTP_GET; break; - case 'H': parser->method = HTTP_HEAD; break; - case 'L': parser->method = HTTP_LOCK; break; - case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; - case 'N': parser->method = HTTP_NOTIFY; break; - case 'O': parser->method = HTTP_OPTIONS; break; - case 'P': parser->method = HTTP_POST; - /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ - break; - case 'R': parser->method = HTTP_REPORT; break; - case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; - case 'T': parser->method = HTTP_TRACE; break; - case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; - default: - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - parser->state = s_req_method; - - CALLBACK_NOTIFY(message_begin); - - break; - } - - case s_req_method: - { - const char *matcher; - if (ch == '\0') { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - matcher = method_strings[parser->method]; - if (ch == ' ' && matcher[parser->index] == '\0') { - parser->state = s_req_spaces_before_url; - } else if (ch == matcher[parser->index]) { - ; /* nada */ - } else if (parser->method == HTTP_CONNECT) { - if (parser->index == 1 && ch == 'H') { - parser->method = HTTP_CHECKOUT; - } else if (parser->index == 2 && ch == 'P') { - parser->method = HTTP_COPY; - } else { - goto error; - } - } else if (parser->method == HTTP_MKCOL) { - if (parser->index == 1 && ch == 'O') { - parser->method = HTTP_MOVE; - } else if (parser->index == 1 && ch == 'E') { - parser->method = HTTP_MERGE; - } else if (parser->index == 1 && ch == '-') { - parser->method = HTTP_MSEARCH; - } else if (parser->index == 2 && ch == 'A') { - parser->method = HTTP_MKACTIVITY; - } else { - goto error; - } - } else if (parser->method == HTTP_SUBSCRIBE) { - if (parser->index == 1 && ch == 'E') { - parser->method = HTTP_SEARCH; - } else { - goto error; - } - } else if (parser->index == 1 && parser->method == HTTP_POST) { - if (ch == 'R') { - parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ - } else if (ch == 'U') { - parser->method = HTTP_PUT; /* or HTTP_PURGE */ - } else if (ch == 'A') { - parser->method = HTTP_PATCH; - } else { - goto error; - } - } else if (parser->index == 2) { - if (parser->method == HTTP_PUT) { - if (ch == 'R') parser->method = HTTP_PURGE; - } else if (parser->method == HTTP_UNLOCK) { - if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE; - } - } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { - parser->method = HTTP_PROPPATCH; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - ++parser->index; - break; - } - - case s_req_spaces_before_url: - { - if (ch == ' ') break; - - MARK(url); - if (parser->method == HTTP_CONNECT) { - parser->state = s_req_server_start; - } - - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - - break; - } - - case s_req_schema: - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - { - switch (ch) { - /* No whitespace allowed here */ - case ' ': - case CR: - case LF: - SET_ERRNO(HPE_INVALID_URL); - goto error; - default: - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - } - - break; - } - - case s_req_server: - case s_req_server_with_at: - case s_req_path: - case s_req_query_string_start: - case s_req_query_string: - case s_req_fragment_start: - case s_req_fragment: - { - switch (ch) { - case ' ': - parser->state = s_req_http_start; - CALLBACK_DATA(url); - break; - case CR: - case LF: - parser->http_major = 0; - parser->http_minor = 9; - parser->state = (ch == CR) ? - s_req_line_almost_done : - s_header_field_start; - CALLBACK_DATA(url); - break; - default: - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - } - break; - } - - case s_req_http_start: - switch (ch) { - case 'H': - parser->state = s_req_http_H; - break; - case ' ': - break; - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - break; - - case s_req_http_H: - STRICT_CHECK(ch != 'T'); - parser->state = s_req_http_HT; - break; - - case s_req_http_HT: - STRICT_CHECK(ch != 'T'); - parser->state = s_req_http_HTT; - break; - - case s_req_http_HTT: - STRICT_CHECK(ch != 'P'); - parser->state = s_req_http_HTTP; - break; - - case s_req_http_HTTP: - STRICT_CHECK(ch != '/'); - parser->state = s_req_first_http_major; - break; - - /* first digit of major HTTP version */ - case s_req_first_http_major: - if (ch < '1' || ch > '9') { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major = ch - '0'; - parser->state = s_req_http_major; - break; - - /* major HTTP version or dot */ - case s_req_http_major: - { - if (ch == '.') { - parser->state = s_req_first_http_minor; - break; - } - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (parser->http_major > 999) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* first digit of minor HTTP version */ - case s_req_first_http_minor: - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor = ch - '0'; - parser->state = s_req_http_minor; - break; - - /* minor HTTP version or end of request line */ - case s_req_http_minor: - { - if (ch == CR) { - parser->state = s_req_line_almost_done; - break; - } - - if (ch == LF) { - parser->state = s_header_field_start; - break; - } - - /* XXX allow spaces after digit? */ - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (parser->http_minor > 999) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* end of request line */ - case s_req_line_almost_done: - { - if (ch != LF) { - SET_ERRNO(HPE_LF_EXPECTED); - goto error; - } - - parser->state = s_header_field_start; - break; - } - - case s_header_field_start: - { - if (ch == CR) { - parser->state = s_headers_almost_done; - break; - } - - if (ch == LF) { - /* they might be just sending \n instead of \r\n so this would be - * the second \n to denote the end of headers*/ - parser->state = s_headers_almost_done; - goto reexecute_byte; - } - - c = TOKEN(ch); - - if (!c) { - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - MARK(header_field); - - parser->index = 0; - parser->state = s_header_field; - - switch (c) { - case 'c': - parser->header_state = h_C; - break; - - case 'p': - parser->header_state = h_matching_proxy_connection; - break; - - case 't': - parser->header_state = h_matching_transfer_encoding; - break; - - case 'u': - parser->header_state = h_matching_upgrade; - break; - - default: - parser->header_state = h_general; - break; - } - break; - } - - case s_header_field: - { - c = TOKEN(ch); - - if (c) { - switch (parser->header_state) { - case h_general: - break; - - case h_C: - parser->index++; - parser->header_state = (c == 'o' ? h_CO : h_general); - break; - - case h_CO: - parser->index++; - parser->header_state = (c == 'n' ? h_CON : h_general); - break; - - case h_CON: - parser->index++; - switch (c) { - case 'n': - parser->header_state = h_matching_connection; - break; - case 't': - parser->header_state = h_matching_content_length; - break; - default: - parser->header_state = h_general; - break; - } - break; - - /* connection */ - - case h_matching_connection: - parser->index++; - if (parser->index > sizeof(CONNECTION)-1 - || c != CONNECTION[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CONNECTION)-2) { - parser->header_state = h_connection; - } - break; - - /* proxy-connection */ - - case h_matching_proxy_connection: - parser->index++; - if (parser->index > sizeof(PROXY_CONNECTION)-1 - || c != PROXY_CONNECTION[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { - parser->header_state = h_connection; - } - break; - - /* content-length */ - - case h_matching_content_length: - parser->index++; - if (parser->index > sizeof(CONTENT_LENGTH)-1 - || c != CONTENT_LENGTH[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { - parser->header_state = h_content_length; - } - break; - - /* transfer-encoding */ - - case h_matching_transfer_encoding: - parser->index++; - if (parser->index > sizeof(TRANSFER_ENCODING)-1 - || c != TRANSFER_ENCODING[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { - parser->header_state = h_transfer_encoding; - } - break; - - /* upgrade */ - - case h_matching_upgrade: - parser->index++; - if (parser->index > sizeof(UPGRADE)-1 - || c != UPGRADE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(UPGRADE)-2) { - parser->header_state = h_upgrade; - } - break; - - case h_connection: - case h_content_length: - case h_transfer_encoding: - case h_upgrade: - if (ch != ' ') parser->header_state = h_general; - break; - - default: - assert(0 && "Unknown header_state"); - break; - } - break; - } - - if (ch == ':') { - parser->state = s_header_value_start; - CALLBACK_DATA(header_field); - break; - } - - if (ch == CR) { - parser->state = s_header_almost_done; - CALLBACK_DATA(header_field); - break; - } - - if (ch == LF) { - parser->state = s_header_field_start; - CALLBACK_DATA(header_field); - break; - } - - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - case s_header_value_start: - { - if (ch == ' ' || ch == '\t') break; - - MARK(header_value); - - parser->state = s_header_value; - parser->index = 0; - - if (ch == CR) { - parser->header_state = h_general; - parser->state = s_header_almost_done; - CALLBACK_DATA(header_value); - break; - } - - if (ch == LF) { - parser->state = s_header_field_start; - CALLBACK_DATA(header_value); - break; - } - - c = LOWER(ch); - - switch (parser->header_state) { - case h_upgrade: - parser->flags |= F_UPGRADE; - parser->header_state = h_general; - break; - - case h_transfer_encoding: - /* looking for 'Transfer-Encoding: chunked' */ - if ('c' == c) { - parser->header_state = h_matching_transfer_encoding_chunked; - } else { - parser->header_state = h_general; - } - break; - - case h_content_length: - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = ch - '0'; - break; - - case h_connection: - /* looking for 'Connection: keep-alive' */ - if (c == 'k') { - parser->header_state = h_matching_connection_keep_alive; - /* looking for 'Connection: close' */ - } else if (c == 'c') { - parser->header_state = h_matching_connection_close; - } else { - parser->header_state = h_general; - } - break; - - default: - parser->header_state = h_general; - break; - } - break; - } - - case s_header_value: - { - - if (ch == CR) { - parser->state = s_header_almost_done; - CALLBACK_DATA(header_value); - break; - } - - if (ch == LF) { - parser->state = s_header_almost_done; - CALLBACK_DATA_NOADVANCE(header_value); - goto reexecute_byte; - } - - c = LOWER(ch); - - switch (parser->header_state) { - case h_general: - break; - - case h_connection: - case h_transfer_encoding: - assert(0 && "Shouldn't get here."); - break; - - case h_content_length: - { - uint64_t t; - - if (ch == ' ') break; - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - t = parser->content_length; - t *= 10; - t += ch - '0'; - - /* Overflow? */ - if (t < parser->content_length || t == ULLONG_MAX) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = t; - break; - } - - /* Transfer-Encoding: chunked */ - case h_matching_transfer_encoding_chunked: - parser->index++; - if (parser->index > sizeof(CHUNKED)-1 - || c != CHUNKED[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CHUNKED)-2) { - parser->header_state = h_transfer_encoding_chunked; - } - break; - - /* looking for 'Connection: keep-alive' */ - case h_matching_connection_keep_alive: - parser->index++; - if (parser->index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(KEEP_ALIVE)-2) { - parser->header_state = h_connection_keep_alive; - } - break; - - /* looking for 'Connection: close' */ - case h_matching_connection_close: - parser->index++; - if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CLOSE)-2) { - parser->header_state = h_connection_close; - } - break; - - case h_transfer_encoding_chunked: - case h_connection_keep_alive: - case h_connection_close: - if (ch != ' ') parser->header_state = h_general; - break; - - default: - parser->state = s_header_value; - parser->header_state = h_general; - break; - } - break; - } - - case s_header_almost_done: - { - STRICT_CHECK(ch != LF); - - parser->state = s_header_value_lws; - - switch (parser->header_state) { - case h_connection_keep_alive: - parser->flags |= F_CONNECTION_KEEP_ALIVE; - break; - case h_connection_close: - parser->flags |= F_CONNECTION_CLOSE; - break; - case h_transfer_encoding_chunked: - parser->flags |= F_CHUNKED; - break; - default: - break; - } - - break; - } - - case s_header_value_lws: - { - if (ch == ' ' || ch == '\t') - parser->state = s_header_value_start; - else - { - parser->state = s_header_field_start; - goto reexecute_byte; - } - break; - } - - case s_headers_almost_done: - { - STRICT_CHECK(ch != LF); - - if (parser->flags & F_TRAILING) { - /* End of a chunked request */ - parser->state = NEW_MESSAGE(); - CALLBACK_NOTIFY(message_complete); - break; - } - - parser->state = s_headers_done; - - /* Set this here so that on_headers_complete() callbacks can see it */ - parser->upgrade = - (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); - - /* Here we call the headers_complete callback. This is somewhat - * different than other callbacks because if the user returns 1, we - * will interpret that as saying that this message has no body. This - * is needed for the annoying case of recieving a response to a HEAD - * request. - * - * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so - * we have to simulate it by handling a change in errno below. - */ - if (settings->on_headers_complete) { - switch (settings->on_headers_complete(parser)) { - case 0: - break; - - case 1: - parser->flags |= F_SKIPBODY; - break; - - default: - SET_ERRNO(HPE_CB_headers_complete); - return p - data; /* Error */ - } - } - - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - return p - data; - } - - goto reexecute_byte; - } - - case s_headers_done: - { - STRICT_CHECK(ch != LF); - - parser->nread = 0; - - /* Exit, the rest of the connect is in a different protocol. */ - if (parser->upgrade) { - parser->state = NEW_MESSAGE(); - CALLBACK_NOTIFY(message_complete); - return (p - data) + 1; - } - - if (parser->flags & F_SKIPBODY) { - parser->state = NEW_MESSAGE(); - CALLBACK_NOTIFY(message_complete); - } else if (parser->flags & F_CHUNKED) { - /* chunked encoding - ignore Content-Length header */ - parser->state = s_chunk_size_start; - } else { - if (parser->content_length == 0) { - /* Content-Length header given but zero: Content-Length: 0\r\n */ - parser->state = NEW_MESSAGE(); - CALLBACK_NOTIFY(message_complete); - } else if (parser->content_length != ULLONG_MAX) { - /* Content-Length header given and non-zero */ - parser->state = s_body_identity; - } else { - if (parser->type == HTTP_REQUEST || - !http_message_needs_eof(parser)) { - /* Assume content-length 0 - read the next */ - parser->state = NEW_MESSAGE(); - CALLBACK_NOTIFY(message_complete); - } else { - /* Read body until EOF */ - parser->state = s_body_identity_eof; - } - } - } - - break; - } - - case s_body_identity: - { - uint64_t to_read = MIN(parser->content_length, - (uint64_t) ((data + len) - p)); - - assert(parser->content_length != 0 - && parser->content_length != ULLONG_MAX); - - /* The difference between advancing content_length and p is because - * the latter will automaticaly advance on the next loop iteration. - * Further, if content_length ends up at 0, we want to see the last - * byte again for our message complete callback. - */ - MARK(body); - parser->content_length -= to_read; - p += to_read - 1; - - if (parser->content_length == 0) { - parser->state = s_message_done; - - /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. - * - * The alternative to doing this is to wait for the next byte to - * trigger the data callback, just as in every other case. The - * problem with this is that this makes it difficult for the test - * harness to distinguish between complete-on-EOF and - * complete-on-length. It's not clear that this distinction is - * important for applications, but let's keep it for now. - */ - CALLBACK_DATA_(body, p - body_mark + 1, p - data); - goto reexecute_byte; - } - - break; - } - - /* read until EOF */ - case s_body_identity_eof: - MARK(body); - p = data + len - 1; - - break; - - case s_message_done: - parser->state = NEW_MESSAGE(); - CALLBACK_NOTIFY(message_complete); - break; - - case s_chunk_size_start: - { - assert(parser->nread == 1); - assert(parser->flags & F_CHUNKED); - - unhex_val = unhex[(unsigned char)ch]; - if (unhex_val == -1) { - SET_ERRNO(HPE_INVALID_CHUNK_SIZE); - goto error; - } - - parser->content_length = unhex_val; - parser->state = s_chunk_size; - break; - } - - case s_chunk_size: - { - uint64_t t; - - assert(parser->flags & F_CHUNKED); - - if (ch == CR) { - parser->state = s_chunk_size_almost_done; - break; - } - - unhex_val = unhex[(unsigned char)ch]; - - if (unhex_val == -1) { - if (ch == ';' || ch == ' ') { - parser->state = s_chunk_parameters; - break; - } - - SET_ERRNO(HPE_INVALID_CHUNK_SIZE); - goto error; - } - - t = parser->content_length; - t *= 16; - t += unhex_val; - - /* Overflow? */ - if (t < parser->content_length || t == ULLONG_MAX) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = t; - break; - } - - case s_chunk_parameters: - { - assert(parser->flags & F_CHUNKED); - /* just ignore this shit. TODO check for overflow */ - if (ch == CR) { - parser->state = s_chunk_size_almost_done; - break; - } - break; - } - - case s_chunk_size_almost_done: - { - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - - parser->nread = 0; - - if (parser->content_length == 0) { - parser->flags |= F_TRAILING; - parser->state = s_header_field_start; - } else { - parser->state = s_chunk_data; - } - break; - } - - case s_chunk_data: - { - uint64_t to_read = MIN(parser->content_length, - (uint64_t) ((data + len) - p)); - - assert(parser->flags & F_CHUNKED); - assert(parser->content_length != 0 - && parser->content_length != ULLONG_MAX); - - /* See the explanation in s_body_identity for why the content - * length and data pointers are managed this way. - */ - MARK(body); - parser->content_length -= to_read; - p += to_read - 1; - - if (parser->content_length == 0) { - parser->state = s_chunk_data_almost_done; - } - - break; - } - - case s_chunk_data_almost_done: - assert(parser->flags & F_CHUNKED); - assert(parser->content_length == 0); - STRICT_CHECK(ch != CR); - parser->state = s_chunk_data_done; - CALLBACK_DATA(body); - break; - - case s_chunk_data_done: - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - parser->nread = 0; - parser->state = s_chunk_size_start; - break; - - default: - assert(0 && "unhandled state"); - SET_ERRNO(HPE_INVALID_INTERNAL_STATE); - goto error; - } - } - - /* Run callbacks for any marks that we have leftover after we ran our of - * bytes. There should be at most one of these set, so it's OK to invoke - * them in series (unset marks will not result in callbacks). - * - * We use the NOADVANCE() variety of callbacks here because 'p' has already - * overflowed 'data' and this allows us to correct for the off-by-one that - * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' - * value that's in-bounds). - */ - - assert(((header_field_mark ? 1 : 0) + - (header_value_mark ? 1 : 0) + - (url_mark ? 1 : 0) + - (body_mark ? 1 : 0)) <= 1); - - CALLBACK_DATA_NOADVANCE(header_field); - CALLBACK_DATA_NOADVANCE(header_value); - CALLBACK_DATA_NOADVANCE(url); - CALLBACK_DATA_NOADVANCE(body); - - return len; - -error: - if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { - SET_ERRNO(HPE_UNKNOWN); - } - - return (p - data); -} - - -/* Does the parser need to see an EOF to find the end of the message? */ -int -http_message_needs_eof (const http_parser *parser) -{ - if (parser->type == HTTP_REQUEST) { - return 0; - } - - /* See RFC 2616 section 4.4 */ - if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ - parser->status_code == 204 || /* No Content */ - parser->status_code == 304 || /* Not Modified */ - parser->flags & F_SKIPBODY) { /* response to a HEAD request */ - return 0; - } - - if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { - return 0; - } - - return 1; -} - - -int -http_should_keep_alive (const http_parser *parser) -{ - if (parser->http_major > 0 && parser->http_minor > 0) { - /* HTTP/1.1 */ - if (parser->flags & F_CONNECTION_CLOSE) { - return 0; - } - } else { - /* HTTP/1.0 or earlier */ - if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { - return 0; - } - } - - return !http_message_needs_eof(parser); -} - - -const char * -http_method_str (enum http_method m) -{ - return ELEM_AT(method_strings, m, "<unknown>"); -} - - -void -http_parser_init (http_parser *parser, enum http_parser_type t) -{ - void *data = parser->data; /* preserve application data */ - memset(parser, 0, sizeof(*parser)); - parser->data = data; - parser->type = t; - parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); - parser->http_errno = HPE_OK; -} - -const char * -http_errno_name(enum http_errno err) { - assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); - return http_strerror_tab[err].name; -} - -const char * -http_errno_description(enum http_errno err) { - assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); - return http_strerror_tab[err].description; -} - -static enum http_host_state -http_parse_host_char(enum http_host_state s, const char ch) { - switch(s) { - case s_http_userinfo: - case s_http_userinfo_start: - if (ch == '@') { - return s_http_host_start; - } - - if (IS_USERINFO_CHAR(ch)) { - return s_http_userinfo; - } - break; - - case s_http_host_start: - if (ch == '[') { - return s_http_host_v6_start; - } - - if (IS_HOST_CHAR(ch)) { - return s_http_host; - } - - break; - - case s_http_host: - if (IS_HOST_CHAR(ch)) { - return s_http_host; - } - - /* FALLTHROUGH */ - case s_http_host_v6_end: - if (ch == ':') { - return s_http_host_port_start; - } - - break; - - case s_http_host_v6: - if (ch == ']') { - return s_http_host_v6_end; - } - - /* FALLTHROUGH */ - case s_http_host_v6_start: - if (IS_HEX(ch) || ch == ':' || ch == '.') { - return s_http_host_v6; - } - - break; - - case s_http_host_port: - case s_http_host_port_start: - if (IS_NUM(ch)) { - return s_http_host_port; - } - - break; - - default: - break; - } - return s_http_host_dead; -} - -static int -http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { - enum http_host_state s; - - const char *p; - size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; - - u->field_data[UF_HOST].len = 0; - - s = found_at ? s_http_userinfo_start : s_http_host_start; - - for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { - enum http_host_state new_s = http_parse_host_char(s, *p); - - if (new_s == s_http_host_dead) { - return 1; - } - - switch(new_s) { - case s_http_host: - if (s != s_http_host) { - u->field_data[UF_HOST].off = p - buf; - } - u->field_data[UF_HOST].len++; - break; - - case s_http_host_v6: - if (s != s_http_host_v6) { - u->field_data[UF_HOST].off = p - buf; - } - u->field_data[UF_HOST].len++; - break; - - case s_http_host_port: - if (s != s_http_host_port) { - u->field_data[UF_PORT].off = p - buf; - u->field_data[UF_PORT].len = 0; - u->field_set |= (1 << UF_PORT); - } - u->field_data[UF_PORT].len++; - break; - - case s_http_userinfo: - if (s != s_http_userinfo) { - u->field_data[UF_USERINFO].off = p - buf ; - u->field_data[UF_USERINFO].len = 0; - u->field_set |= (1 << UF_USERINFO); - } - u->field_data[UF_USERINFO].len++; - break; - - default: - break; - } - s = new_s; - } - - /* Make sure we don't end somewhere unexpected */ - switch (s) { - case s_http_host_start: - case s_http_host_v6_start: - case s_http_host_v6: - case s_http_host_port_start: - case s_http_userinfo: - case s_http_userinfo_start: - return 1; - default: - break; - } - - return 0; -} - -int -http_parser_parse_url(const char *buf, size_t buflen, int is_connect, - struct http_parser_url *u) -{ - enum state s; - const char *p; - enum http_parser_url_fields uf, old_uf; - int found_at = 0; - - u->port = u->field_set = 0; - s = is_connect ? s_req_server_start : s_req_spaces_before_url; - uf = old_uf = UF_MAX; - - for (p = buf; p < buf + buflen; p++) { - s = parse_url_char(s, *p); - - /* Figure out the next field that we're operating on */ - switch (s) { - case s_dead: - return 1; - - /* Skip delimeters */ - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - case s_req_query_string_start: - case s_req_fragment_start: - continue; - - case s_req_schema: - uf = UF_SCHEMA; - break; - - case s_req_server_with_at: - found_at = 1; - - /* FALLTROUGH */ - case s_req_server: - uf = UF_HOST; - break; - - case s_req_path: - uf = UF_PATH; - break; - - case s_req_query_string: - uf = UF_QUERY; - break; - - case s_req_fragment: - uf = UF_FRAGMENT; - break; - - default: - assert(!"Unexpected state"); - return 1; - } - - /* Nothing's changed; soldier on */ - if (uf == old_uf) { - u->field_data[uf].len++; - continue; - } - - u->field_data[uf].off = p - buf; - u->field_data[uf].len = 1; - - u->field_set |= (1 << uf); - old_uf = uf; - } - - /* host must be present if there is a schema */ - /* parsing http:///toto will fail */ - if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { - if (http_parse_host(buf, u, found_at) != 0) { - return 1; - } - } - - /* CONNECT requests can only contain "hostname:port" */ - if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { - return 1; - } - - if (u->field_set & (1 << UF_PORT)) { - /* Don't bother with endp; we've already validated the string */ - unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); - - /* Ports have a max value of 2^16 */ - if (v > 0xffff) { - return 1; - } - - u->port = (uint16_t) v; - } - - return 0; -} - -void -http_parser_pause(http_parser *parser, int paused) { - /* Users should only be pausing/unpausing a parser that is not in an error - * state. In non-debug builds, there's not much that we can do about this - * other than ignore it. - */ - if (HTTP_PARSER_ERRNO(parser) == HPE_OK || - HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { - SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); - } else { - assert(0 && "Attempting to pause parser in error state"); - } -} - -int -http_body_is_final(const struct http_parser *parser) { - return parser->state == s_message_done; -} diff --git a/ext/http-parser/http_parser.gyp b/ext/http-parser/http_parser.gyp deleted file mode 100644 index ef34ecae..00000000 --- a/ext/http-parser/http_parser.gyp +++ /dev/null @@ -1,111 +0,0 @@ -# This file is used with the GYP meta build system. -# http://code.google.com/p/gyp/ -# To build try this: -# svn co http://gyp.googlecode.com/svn/trunk gyp -# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp -# ./out/Debug/test -{ - 'target_defaults': { - 'default_configuration': 'Debug', - 'configurations': { - # TODO: hoist these out and put them somewhere common, because - # RuntimeLibrary MUST MATCH across the entire project - 'Debug': { - 'defines': [ 'DEBUG', '_DEBUG' ], - 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'RuntimeLibrary': 1, # static debug - }, - }, - }, - 'Release': { - 'defines': [ 'NDEBUG' ], - 'cflags': [ '-Wall', '-Wextra', '-O3' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'RuntimeLibrary': 0, # static release - }, - }, - } - }, - 'msvs_settings': { - 'VCCLCompilerTool': { - }, - 'VCLibrarianTool': { - }, - 'VCLinkerTool': { - 'GenerateDebugInformation': 'true', - }, - }, - 'conditions': [ - ['OS == "win"', { - 'defines': [ - 'WIN32' - ], - }] - ], - }, - - 'targets': [ - { - 'target_name': 'http_parser', - 'type': 'static_library', - 'include_dirs': [ '.' ], - 'direct_dependent_settings': { - 'defines': [ 'HTTP_PARSER_STRICT=0' ], - 'include_dirs': [ '.' ], - }, - 'defines': [ 'HTTP_PARSER_STRICT=0' ], - 'sources': [ './http_parser.c', ], - 'conditions': [ - ['OS=="win"', { - 'msvs_settings': { - 'VCCLCompilerTool': { - # Compile as C++. http_parser.c is actually C99, but C++ is - # close enough in this case. - 'CompileAs': 2, - }, - }, - }] - ], - }, - - { - 'target_name': 'http_parser_strict', - 'type': 'static_library', - 'include_dirs': [ '.' ], - 'direct_dependent_settings': { - 'defines': [ 'HTTP_PARSER_STRICT=1' ], - 'include_dirs': [ '.' ], - }, - 'defines': [ 'HTTP_PARSER_STRICT=1' ], - 'sources': [ './http_parser.c', ], - 'conditions': [ - ['OS=="win"', { - 'msvs_settings': { - 'VCCLCompilerTool': { - # Compile as C++. http_parser.c is actually C99, but C++ is - # close enough in this case. - 'CompileAs': 2, - }, - }, - }] - ], - }, - - { - 'target_name': 'test-nonstrict', - 'type': 'executable', - 'dependencies': [ 'http_parser' ], - 'sources': [ 'test.c' ] - }, - - { - 'target_name': 'test-strict', - 'type': 'executable', - 'dependencies': [ 'http_parser_strict' ], - 'sources': [ 'test.c' ] - } - ] -} diff --git a/ext/http-parser/http_parser.h b/ext/http-parser/http_parser.h deleted file mode 100644 index a992c742..00000000 --- a/ext/http-parser/http_parser.h +++ /dev/null @@ -1,304 +0,0 @@ -/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -#ifndef http_parser_h -#define http_parser_h -#ifdef __cplusplus -extern "C" { -#endif - -#define HTTP_PARSER_VERSION_MAJOR 2 -#define HTTP_PARSER_VERSION_MINOR 0 - -#include <sys/types.h> -#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) -#include <BaseTsd.h> -#include <stddef.h> -typedef __int8 int8_t; -typedef unsigned __int8 uint8_t; -typedef __int16 int16_t; -typedef unsigned __int16 uint16_t; -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -#else -#include <stdint.h> -#endif - -/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run - * faster - */ -#ifndef HTTP_PARSER_STRICT -# define HTTP_PARSER_STRICT 1 -#endif - -/* Maximium header size allowed */ -#define HTTP_MAX_HEADER_SIZE (80*1024) - - -typedef struct http_parser http_parser; -typedef struct http_parser_settings http_parser_settings; - - -/* Callbacks should return non-zero to indicate an error. The parser will - * then halt execution. - * - * The one exception is on_headers_complete. In a HTTP_RESPONSE parser - * returning '1' from on_headers_complete will tell the parser that it - * should not expect a body. This is used when receiving a response to a - * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: - * chunked' headers that indicate the presence of a body. - * - * http_data_cb does not return data chunks. It will be call arbitrarally - * many times for each string. E.G. you might get 10 callbacks for "on_url" - * each providing just a few characters more data. - */ -typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); -typedef int (*http_cb) (http_parser*); - - -/* Request Methods */ -#define HTTP_METHOD_MAP(XX) \ - XX(0, DELETE, DELETE) \ - XX(1, GET, GET) \ - XX(2, HEAD, HEAD) \ - XX(3, POST, POST) \ - XX(4, PUT, PUT) \ - /* pathological */ \ - XX(5, CONNECT, CONNECT) \ - XX(6, OPTIONS, OPTIONS) \ - XX(7, TRACE, TRACE) \ - /* webdav */ \ - XX(8, COPY, COPY) \ - XX(9, LOCK, LOCK) \ - XX(10, MKCOL, MKCOL) \ - XX(11, MOVE, MOVE) \ - XX(12, PROPFIND, PROPFIND) \ - XX(13, PROPPATCH, PROPPATCH) \ - XX(14, SEARCH, SEARCH) \ - XX(15, UNLOCK, UNLOCK) \ - /* subversion */ \ - XX(16, REPORT, REPORT) \ - XX(17, MKACTIVITY, MKACTIVITY) \ - XX(18, CHECKOUT, CHECKOUT) \ - XX(19, MERGE, MERGE) \ - /* upnp */ \ - XX(20, MSEARCH, M-SEARCH) \ - XX(21, NOTIFY, NOTIFY) \ - XX(22, SUBSCRIBE, SUBSCRIBE) \ - XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ - /* RFC-5789 */ \ - XX(24, PATCH, PATCH) \ - XX(25, PURGE, PURGE) \ - -enum http_method - { -#define XX(num, name, string) HTTP_##name = num, - HTTP_METHOD_MAP(XX) -#undef XX - }; - - -enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; - - -/* Flag values for http_parser.flags field */ -enum flags - { F_CHUNKED = 1 << 0 - , F_CONNECTION_KEEP_ALIVE = 1 << 1 - , F_CONNECTION_CLOSE = 1 << 2 - , F_TRAILING = 1 << 3 - , F_UPGRADE = 1 << 4 - , F_SKIPBODY = 1 << 5 - }; - - -/* Map for errno-related constants - * - * The provided argument should be a macro that takes 2 arguments. - */ -#define HTTP_ERRNO_MAP(XX) \ - /* No error */ \ - XX(OK, "success") \ - \ - /* Callback-related errors */ \ - XX(CB_message_begin, "the on_message_begin callback failed") \ - XX(CB_status_complete, "the on_status_complete callback failed") \ - XX(CB_url, "the on_url callback failed") \ - XX(CB_header_field, "the on_header_field callback failed") \ - XX(CB_header_value, "the on_header_value callback failed") \ - XX(CB_headers_complete, "the on_headers_complete callback failed") \ - XX(CB_body, "the on_body callback failed") \ - XX(CB_message_complete, "the on_message_complete callback failed") \ - \ - /* Parsing-related errors */ \ - XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ - XX(HEADER_OVERFLOW, \ - "too many header bytes seen; overflow detected") \ - XX(CLOSED_CONNECTION, \ - "data received after completed connection: close message") \ - XX(INVALID_VERSION, "invalid HTTP version") \ - XX(INVALID_STATUS, "invalid HTTP status code") \ - XX(INVALID_METHOD, "invalid HTTP method") \ - XX(INVALID_URL, "invalid URL") \ - XX(INVALID_HOST, "invalid host") \ - XX(INVALID_PORT, "invalid port") \ - XX(INVALID_PATH, "invalid path") \ - XX(INVALID_QUERY_STRING, "invalid query string") \ - XX(INVALID_FRAGMENT, "invalid fragment") \ - XX(LF_EXPECTED, "LF character expected") \ - XX(INVALID_HEADER_TOKEN, "invalid character in header") \ - XX(INVALID_CONTENT_LENGTH, \ - "invalid character in content-length header") \ - XX(INVALID_CHUNK_SIZE, \ - "invalid character in chunk size header") \ - XX(INVALID_CONSTANT, "invalid constant string") \ - XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ - XX(STRICT, "strict mode assertion failed") \ - XX(PAUSED, "parser is paused") \ - XX(UNKNOWN, "an unknown error occurred") - - -/* Define HPE_* values for each errno value above */ -#define HTTP_ERRNO_GEN(n, s) HPE_##n, -enum http_errno { - HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) -}; -#undef HTTP_ERRNO_GEN - - -/* Get an http_errno value from an http_parser */ -#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) - - -struct http_parser { - /** PRIVATE **/ - unsigned char type : 2; /* enum http_parser_type */ - unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ - unsigned char state; /* enum state from http_parser.c */ - unsigned char header_state; /* enum header_state from http_parser.c */ - unsigned char index; /* index into current matcher */ - - uint32_t nread; /* # bytes read in various scenarios */ - uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ - - /** READ-ONLY **/ - unsigned short http_major; - unsigned short http_minor; - unsigned short status_code; /* responses only */ - unsigned char method; /* requests only */ - unsigned char http_errno : 7; - - /* 1 = Upgrade header was present and the parser has exited because of that. - * 0 = No upgrade header present. - * Should be checked when http_parser_execute() returns in addition to - * error checking. - */ - unsigned char upgrade : 1; - - /** PUBLIC **/ - void *data; /* A pointer to get hook to the "connection" or "socket" object */ -}; - - -struct http_parser_settings { - http_cb on_message_begin; - http_data_cb on_url; - http_cb on_status_complete; - http_data_cb on_header_field; - http_data_cb on_header_value; - http_cb on_headers_complete; - http_data_cb on_body; - http_cb on_message_complete; -}; - - -enum http_parser_url_fields - { UF_SCHEMA = 0 - , UF_HOST = 1 - , UF_PORT = 2 - , UF_PATH = 3 - , UF_QUERY = 4 - , UF_FRAGMENT = 5 - , UF_USERINFO = 6 - , UF_MAX = 7 - }; - - -/* Result structure for http_parser_parse_url(). - * - * Callers should index into field_data[] with UF_* values iff field_set - * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and - * because we probably have padding left over), we convert any port to - * a uint16_t. - */ -struct http_parser_url { - uint16_t field_set; /* Bitmask of (1 << UF_*) values */ - uint16_t port; /* Converted UF_PORT string */ - - struct { - uint16_t off; /* Offset into buffer in which field starts */ - uint16_t len; /* Length of run in buffer */ - } field_data[UF_MAX]; -}; - - -void http_parser_init(http_parser *parser, enum http_parser_type type); - - -size_t http_parser_execute(http_parser *parser, - const http_parser_settings *settings, - const char *data, - size_t len); - - -/* If http_should_keep_alive() in the on_headers_complete or - * on_message_complete callback returns 0, then this should be - * the last message on the connection. - * If you are the server, respond with the "Connection: close" header. - * If you are the client, close the connection. - */ -int http_should_keep_alive(const http_parser *parser); - -/* Returns a string version of the HTTP method. */ -const char *http_method_str(enum http_method m); - -/* Return a string name of the given error */ -const char *http_errno_name(enum http_errno err); - -/* Return a string description of the given error */ -const char *http_errno_description(enum http_errno err); - -/* Parse a URL; return nonzero on failure */ -int http_parser_parse_url(const char *buf, size_t buflen, - int is_connect, - struct http_parser_url *u); - -/* Pause or un-pause the parser; a nonzero value pauses */ -void http_parser_pause(http_parser *parser, int paused); - -/* Checks if this is the final chunk of the body. */ -int http_body_is_final(const http_parser *parser); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/ext/http-parser/test.c b/ext/http-parser/test.c deleted file mode 100644 index 83723b7f..00000000 --- a/ext/http-parser/test.c +++ /dev/null @@ -1,3425 +0,0 @@ -/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -#include "http_parser.h" -#include <stdlib.h> -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> /* rand */ -#include <string.h> -#include <stdarg.h> - -#undef TRUE -#define TRUE 1 -#undef FALSE -#define FALSE 0 - -#define MAX_HEADERS 13 -#define MAX_ELEMENT_SIZE 2048 - -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - -static http_parser *parser; - -struct message { - const char *name; // for debugging purposes - const char *raw; - enum http_parser_type type; - enum http_method method; - int status_code; - char request_path[MAX_ELEMENT_SIZE]; - char request_url[MAX_ELEMENT_SIZE]; - char fragment[MAX_ELEMENT_SIZE]; - char query_string[MAX_ELEMENT_SIZE]; - char body[MAX_ELEMENT_SIZE]; - size_t body_size; - const char *host; - const char *userinfo; - uint16_t port; - int num_headers; - enum { NONE=0, FIELD, VALUE } last_header_element; - char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; - int should_keep_alive; - - const char *upgrade; // upgraded body - - unsigned short http_major; - unsigned short http_minor; - - int message_begin_cb_called; - int headers_complete_cb_called; - int message_complete_cb_called; - int message_complete_on_eof; - int body_is_final; -}; - -static int currently_parsing_eof; - -static struct message messages[5]; -static int num_messages; -static http_parser_settings *current_pause_parser; - -/* * R E Q U E S T S * */ -const struct message requests[] = -#define CURL_GET 0 -{ {.name= "curl get" - ,.type= HTTP_REQUEST - ,.raw= "GET /test HTTP/1.1\r\n" - "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" - "Host: 0.0.0.0=5000\r\n" - "Accept: */*\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 3 - ,.headers= - { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } - , { "Host", "0.0.0.0=5000" } - , { "Accept", "*/*" } - } - ,.body= "" - } - -#define FIREFOX_GET 1 -, {.name= "firefox get" - ,.type= HTTP_REQUEST - ,.raw= "GET /favicon.ico HTTP/1.1\r\n" - "Host: 0.0.0.0=5000\r\n" - "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" - "Accept-Language: en-us,en;q=0.5\r\n" - "Accept-Encoding: gzip,deflate\r\n" - "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" - "Keep-Alive: 300\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/favicon.ico" - ,.request_url= "/favicon.ico" - ,.num_headers= 8 - ,.headers= - { { "Host", "0.0.0.0=5000" } - , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } - , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } - , { "Accept-Language", "en-us,en;q=0.5" } - , { "Accept-Encoding", "gzip,deflate" } - , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } - , { "Keep-Alive", "300" } - , { "Connection", "keep-alive" } - } - ,.body= "" - } - -#define DUMBFUCK 2 -, {.name= "dumbfuck" - ,.type= HTTP_REQUEST - ,.raw= "GET /dumbfuck HTTP/1.1\r\n" - "aaaaaaaaaaaaa:++++++++++\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/dumbfuck" - ,.request_url= "/dumbfuck" - ,.num_headers= 1 - ,.headers= - { { "aaaaaaaaaaaaa", "++++++++++" } - } - ,.body= "" - } - -#define FRAGMENT_IN_URI 3 -, {.name= "fragment in url" - ,.type= HTTP_REQUEST - ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "page=1" - ,.fragment= "posts-17408" - ,.request_path= "/forums/1/topics/2375" - /* XXX request url does include fragment? */ - ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" - ,.num_headers= 0 - ,.body= "" - } - -#define GET_NO_HEADERS_NO_BODY 4 -, {.name= "get no headers no body" - ,.type= HTTP_REQUEST - ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE /* would need Connection: close */ - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_no_headers_no_body/world" - ,.request_url= "/get_no_headers_no_body/world" - ,.num_headers= 0 - ,.body= "" - } - -#define GET_ONE_HEADER_NO_BODY 5 -, {.name= "get one header no body" - ,.type= HTTP_REQUEST - ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" - "Accept: */*\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE /* would need Connection: close */ - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_one_header_no_body" - ,.request_url= "/get_one_header_no_body" - ,.num_headers= 1 - ,.headers= - { { "Accept" , "*/*" } - } - ,.body= "" - } - -#define GET_FUNKY_CONTENT_LENGTH 6 -, {.name= "get funky content length body hello" - ,.type= HTTP_REQUEST - ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" - "conTENT-Length: 5\r\n" - "\r\n" - "HELLO" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_funky_content_length_body_hello" - ,.request_url= "/get_funky_content_length_body_hello" - ,.num_headers= 1 - ,.headers= - { { "conTENT-Length" , "5" } - } - ,.body= "HELLO" - } - -#define POST_IDENTITY_BODY_WORLD 7 -, {.name= "post identity body world" - ,.type= HTTP_REQUEST - ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" - "Accept: */*\r\n" - "Transfer-Encoding: identity\r\n" - "Content-Length: 5\r\n" - "\r\n" - "World" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "q=search" - ,.fragment= "hey" - ,.request_path= "/post_identity_body_world" - ,.request_url= "/post_identity_body_world?q=search#hey" - ,.num_headers= 3 - ,.headers= - { { "Accept", "*/*" } - , { "Transfer-Encoding", "identity" } - , { "Content-Length", "5" } - } - ,.body= "World" - } - -#define POST_CHUNKED_ALL_YOUR_BASE 8 -, {.name= "post - chunked body: all your base are belong to us" - ,.type= HTTP_REQUEST - ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1e\r\nall your base are belong to us\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/post_chunked_all_your_base" - ,.request_url= "/post_chunked_all_your_base" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding" , "chunked" } - } - ,.body= "all your base are belong to us" - } - -#define TWO_CHUNKS_MULT_ZERO_END 9 -, {.name= "two chunks ; triple zero ending" - ,.type= HTTP_REQUEST - ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5\r\nhello\r\n" - "6\r\n world\r\n" - "000\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/two_chunks_mult_zero_end" - ,.request_url= "/two_chunks_mult_zero_end" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding", "chunked" } - } - ,.body= "hello world" - } - -#define CHUNKED_W_TRAILING_HEADERS 10 -, {.name= "chunked with trailing headers. blech." - ,.type= HTTP_REQUEST - ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5\r\nhello\r\n" - "6\r\n world\r\n" - "0\r\n" - "Vary: *\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/chunked_w_trailing_headers" - ,.request_url= "/chunked_w_trailing_headers" - ,.num_headers= 3 - ,.headers= - { { "Transfer-Encoding", "chunked" } - , { "Vary", "*" } - , { "Content-Type", "text/plain" } - } - ,.body= "hello world" - } - -#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11 -, {.name= "with bullshit after the length" - ,.type= HTTP_REQUEST - ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" - "6; blahblah; blah\r\n world\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/chunked_w_bullshit_after_length" - ,.request_url= "/chunked_w_bullshit_after_length" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding", "chunked" } - } - ,.body= "hello world" - } - -#define WITH_QUOTES 12 -, {.name= "with quotes" - ,.type= HTTP_REQUEST - ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "foo=\"bar\"" - ,.fragment= "" - ,.request_path= "/with_\"stupid\"_quotes" - ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define APACHEBENCH_GET 13 -/* The server receiving this request SHOULD NOT wait for EOF - * to know that content-length == 0. - * How to represent this in a unit test? message_complete_on_eof - * Compare with NO_CONTENT_LENGTH_RESPONSE. - */ -, {.name = "apachebench get" - ,.type= HTTP_REQUEST - ,.raw= "GET /test HTTP/1.0\r\n" - "Host: 0.0.0.0:5000\r\n" - "User-Agent: ApacheBench/2.3\r\n" - "Accept: */*\r\n\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 3 - ,.headers= { { "Host", "0.0.0.0:5000" } - , { "User-Agent", "ApacheBench/2.3" } - , { "Accept", "*/*" } - } - ,.body= "" - } - -#define QUERY_URL_WITH_QUESTION_MARK_GET 14 -/* Some clients include '?' characters in query strings. - */ -, {.name = "query url with question mark" - ,.type= HTTP_REQUEST - ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "foo=bar?baz" - ,.fragment= "" - ,.request_path= "/test.cgi" - ,.request_url= "/test.cgi?foo=bar?baz" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define PREFIX_NEWLINE_GET 15 -/* Some clients, especially after a POST in a keep-alive connection, - * will send an extra CRLF before the next request - */ -, {.name = "newline prefix get" - ,.type= HTTP_REQUEST - ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define UPGRADE_REQUEST 16 -, {.name = "upgrade request" - ,.type= HTTP_REQUEST - ,.raw= "GET /demo HTTP/1.1\r\n" - "Host: example.com\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Upgrade: WebSocket\r\n" - "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" - "Origin: http://example.com\r\n" - "\r\n" - "Hot diggity dogg" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/demo" - ,.request_url= "/demo" - ,.num_headers= 7 - ,.upgrade="Hot diggity dogg" - ,.headers= { { "Host", "example.com" } - , { "Connection", "Upgrade" } - , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } - , { "Sec-WebSocket-Protocol", "sample" } - , { "Upgrade", "WebSocket" } - , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } - , { "Origin", "http://example.com" } - } - ,.body= "" - } - -#define CONNECT_REQUEST 17 -, {.name = "connect request" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "\r\n" - "some data\r\n" - "and yet even more data" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "0-home0.netscape.com:443" - ,.num_headers= 2 - ,.upgrade="some data\r\nand yet even more data" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - } - ,.body= "" - } - -#define REPORT_REQ 18 -, {.name= "report request" - ,.type= HTTP_REQUEST - ,.raw= "REPORT /test HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_REPORT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define NO_HTTP_VERSION 19 -, {.name= "request with no http version" - ,.type= HTTP_REQUEST - ,.raw= "GET /\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 0 - ,.http_minor= 9 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define MSEARCH_REQ 20 -, {.name= "m-search request" - ,.type= HTTP_REQUEST - ,.raw= "M-SEARCH * HTTP/1.1\r\n" - "HOST: 239.255.255.250:1900\r\n" - "MAN: \"ssdp:discover\"\r\n" - "ST: \"ssdp:all\"\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_MSEARCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "*" - ,.request_url= "*" - ,.num_headers= 3 - ,.headers= { { "HOST", "239.255.255.250:1900" } - , { "MAN", "\"ssdp:discover\"" } - , { "ST", "\"ssdp:all\"" } - } - ,.body= "" - } - -#define LINE_FOLDING_IN_HEADER 21 -, {.name= "line folding in header value" - ,.type= HTTP_REQUEST - ,.raw= "GET / HTTP/1.1\r\n" - "Line1: abc\r\n" - "\tdef\r\n" - " ghi\r\n" - "\t\tjkl\r\n" - " mno \r\n" - "\t \tqrs\r\n" - "Line2: \t line2\t\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 2 - ,.headers= { { "Line1", "abcdefghijklmno qrs" } - , { "Line2", "line2\t" } - } - ,.body= "" - } - - -#define QUERY_TERMINATED_HOST 22 -, {.name= "host terminated by a query string" - ,.type= HTTP_REQUEST - ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "hail=all" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "http://hypnotoad.org?hail=all" - ,.host= "hypnotoad.org" - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define QUERY_TERMINATED_HOSTPORT 23 -, {.name= "host:port terminated by a query string" - ,.type= HTTP_REQUEST - ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "hail=all" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "http://hypnotoad.org:1234?hail=all" - ,.host= "hypnotoad.org" - ,.port= 1234 - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define SPACE_TERMINATED_HOSTPORT 24 -, {.name= "host:port terminated by a space" - ,.type= HTTP_REQUEST - ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "http://hypnotoad.org:1234" - ,.host= "hypnotoad.org" - ,.port= 1234 - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define PATCH_REQ 25 -, {.name = "PATCH request" - ,.type= HTTP_REQUEST - ,.raw= "PATCH /file.txt HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Content-Type: application/example\r\n" - "If-Match: \"e0023aa4e\"\r\n" - "Content-Length: 10\r\n" - "\r\n" - "cccccccccc" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_PATCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/file.txt" - ,.request_url= "/file.txt" - ,.num_headers= 4 - ,.headers= { { "Host", "www.example.com" } - , { "Content-Type", "application/example" } - , { "If-Match", "\"e0023aa4e\"" } - , { "Content-Length", "10" } - } - ,.body= "cccccccccc" - } - -#define CONNECT_CAPS_REQUEST 26 -, {.name = "connect caps request" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "HOME0.NETSCAPE.COM:443" - ,.num_headers= 2 - ,.upgrade="" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - } - ,.body= "" - } - -#if !HTTP_PARSER_STRICT -#define UTF8_PATH_REQ 27 -, {.name= "utf-8 path request" - ,.type= HTTP_REQUEST - ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" - "Host: github.com\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "q=1" - ,.fragment= "narf" - ,.request_path= "/δ¶/δt/pope" - ,.request_url= "/δ¶/δt/pope?q=1#narf" - ,.num_headers= 1 - ,.headers= { {"Host", "github.com" } - } - ,.body= "" - } - -#define HOSTNAME_UNDERSCORE 28 -, {.name = "hostname underscore" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "home_0.netscape.com:443" - ,.num_headers= 2 - ,.upgrade="" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - } - ,.body= "" - } -#endif /* !HTTP_PARSER_STRICT */ - -/* see https://github.com/ry/http-parser/issues/47 */ -#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29 -, {.name = "eat CRLF between requests, no \"Connection: close\" header" - ,.raw= "POST / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: 4\r\n" - "\r\n" - "q=42\r\n" /* note the trailing CRLF */ - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 3 - ,.upgrade= 0 - ,.headers= { { "Host", "www.example.com" } - , { "Content-Type", "application/x-www-form-urlencoded" } - , { "Content-Length", "4" } - } - ,.body= "q=42" - } - -/* see https://github.com/ry/http-parser/issues/47 */ -#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30 -, {.name = "eat CRLF between requests even if \"Connection: close\" is set" - ,.raw= "POST / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: 4\r\n" - "Connection: close\r\n" - "\r\n" - "q=42\r\n" /* note the trailing CRLF */ - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 4 - ,.upgrade= 0 - ,.headers= { { "Host", "www.example.com" } - , { "Content-Type", "application/x-www-form-urlencoded" } - , { "Content-Length", "4" } - , { "Connection", "close" } - } - ,.body= "q=42" - } - -#define PURGE_REQ 31 -, {.name = "PURGE request" - ,.type= HTTP_REQUEST - ,.raw= "PURGE /file.txt HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_PURGE - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/file.txt" - ,.request_url= "/file.txt" - ,.num_headers= 1 - ,.headers= { { "Host", "www.example.com" } } - ,.body= "" - } - -#define SEARCH_REQ 32 -, {.name = "SEARCH request" - ,.type= HTTP_REQUEST - ,.raw= "SEARCH / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_SEARCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 1 - ,.headers= { { "Host", "www.example.com" } } - ,.body= "" - } - -#define PROXY_WITH_BASIC_AUTH 33 -, {.name= "host:port and basic_auth" - ,.type= HTTP_REQUEST - ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.fragment= "" - ,.request_path= "/toto" - ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" - ,.host= "hypnotoad.org" - ,.userinfo= "a%12:b!&*$" - ,.port= 1234 - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - - -, {.name= NULL } /* sentinel */ -}; - -/* * R E S P O N S E S * */ -const struct message responses[] = -#define GOOGLE_301 0 -{ {.name= "google 301" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" - "Location: http://www.google.com/\r\n" - "Content-Type: text/html; charset=UTF-8\r\n" - "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" - "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" - "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ - "Cache-Control: public, max-age=2592000\r\n" - "Server: gws\r\n" - "Content-Length: 219 \r\n" - "\r\n" - "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" - "<TITLE>301 Moved</TITLE></HEAD><BODY>\n" - "<H1>301 Moved</H1>\n" - "The document has moved\n" - "<A HREF=\"http://www.google.com/\">here</A>.\r\n" - "</BODY></HTML>\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 301 - ,.num_headers= 8 - ,.headers= - { { "Location", "http://www.google.com/" } - , { "Content-Type", "text/html; charset=UTF-8" } - , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } - , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } - , { "X-$PrototypeBI-Version", "1.6.0.3" } - , { "Cache-Control", "public, max-age=2592000" } - , { "Server", "gws" } - , { "Content-Length", "219 " } - } - ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" - "<TITLE>301 Moved</TITLE></HEAD><BODY>\n" - "<H1>301 Moved</H1>\n" - "The document has moved\n" - "<A HREF=\"http://www.google.com/\">here</A>.\r\n" - "</BODY></HTML>\r\n" - } - -#define NO_CONTENT_LENGTH_RESPONSE 1 -/* The client should wait for the server's EOF. That is, when content-length - * is not specified, and "Connection: close", the end of body is specified - * by the EOF. - * Compare with APACHEBENCH_GET - */ -, {.name= "no content-length response" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" - "Server: Apache\r\n" - "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" - "Content-Type: text/xml; charset=utf-8\r\n" - "Connection: close\r\n" - "\r\n" - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" - " <SOAP-ENV:Body>\n" - " <SOAP-ENV:Fault>\n" - " <faultcode>SOAP-ENV:Client</faultcode>\n" - " <faultstring>Client Error</faultstring>\n" - " </SOAP-ENV:Fault>\n" - " </SOAP-ENV:Body>\n" - "</SOAP-ENV:Envelope>" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 5 - ,.headers= - { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } - , { "Server", "Apache" } - , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } - , { "Content-Type", "text/xml; charset=utf-8" } - , { "Connection", "close" } - } - ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" - " <SOAP-ENV:Body>\n" - " <SOAP-ENV:Fault>\n" - " <faultcode>SOAP-ENV:Client</faultcode>\n" - " <faultstring>Client Error</faultstring>\n" - " </SOAP-ENV:Fault>\n" - " </SOAP-ENV:Body>\n" - "</SOAP-ENV:Envelope>" - } - -#define NO_HEADERS_NO_BODY_404 2 -, {.name= "404 no headers no body" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 404 - ,.num_headers= 0 - ,.headers= {} - ,.body_size= 0 - ,.body= "" - } - -#define NO_REASON_PHRASE 3 -, {.name= "301 no response phrase" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 301\r\n\r\n" - ,.should_keep_alive = FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 301 - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define TRAILING_SPACE_ON_CHUNKED_BODY 4 -, {.name="200 trailing space on chunked body" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "25 \r\n" - "This is the data in the first chunk\r\n" - "\r\n" - "1C\r\n" - "and this is the second one\r\n" - "\r\n" - "0 \r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 2 - ,.headers= - { {"Content-Type", "text/plain" } - , {"Transfer-Encoding", "chunked" } - } - ,.body_size = 37+28 - ,.body = - "This is the data in the first chunk\r\n" - "and this is the second one\r\n" - - } - -#define NO_CARRIAGE_RET 5 -, {.name="no carriage ret" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\n" - "Content-Type: text/html; charset=utf-8\n" - "Connection: close\n" - "\n" - "these headers are from http://news.ycombinator.com/" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 2 - ,.headers= - { {"Content-Type", "text/html; charset=utf-8" } - , {"Connection", "close" } - } - ,.body= "these headers are from http://news.ycombinator.com/" - } - -#define PROXY_CONNECTION 6 -, {.name="proxy connection" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=UTF-8\r\n" - "Content-Length: 11\r\n" - "Proxy-Connection: close\r\n" - "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" - "\r\n" - "hello world" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 4 - ,.headers= - { {"Content-Type", "text/html; charset=UTF-8" } - , {"Content-Length", "11" } - , {"Proxy-Connection", "close" } - , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} - } - ,.body= "hello world" - } - -#define UNDERSTORE_HEADER_KEY 7 - // shown by - // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" -, {.name="underscore header key" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Server: DCLK-AdSvr\r\n" - "Content-Type: text/xml\r\n" - "Content-Length: 0\r\n" - "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 4 - ,.headers= - { {"Server", "DCLK-AdSvr" } - , {"Content-Type", "text/xml" } - , {"Content-Length", "0" } - , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } - } - ,.body= "" - } - -#define BONJOUR_MADAME_FR 8 -/* The client should not merge two headers fields when the first one doesn't - * have a value. - */ -, {.name= "bonjourmadame.fr" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" - "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" - "Server: Apache/2.2.3 (Red Hat)\r\n" - "Cache-Control: public\r\n" - "Pragma: \r\n" - "Location: http://www.bonjourmadame.fr/\r\n" - "Vary: Accept-Encoding\r\n" - "Content-Length: 0\r\n" - "Content-Type: text/html; charset=UTF-8\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 301 - ,.num_headers= 9 - ,.headers= - { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } - , { "Server", "Apache/2.2.3 (Red Hat)" } - , { "Cache-Control", "public" } - , { "Pragma", "" } - , { "Location", "http://www.bonjourmadame.fr/" } - , { "Vary", "Accept-Encoding" } - , { "Content-Length", "0" } - , { "Content-Type", "text/html; charset=UTF-8" } - , { "Connection", "keep-alive" } - } - ,.body= "" - } - -#define RES_FIELD_UNDERSCORE 9 -/* Should handle spaces in header fields */ -, {.name= "field underscore" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" - "Server: Apache\r\n" - "Cache-Control: no-cache, must-revalidate\r\n" - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" - ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" - "Vary: Accept-Encoding\r\n" - "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ - "_onnection: Keep-Alive\r\n" /* semantic value ignored */ - "Transfer-Encoding: chunked\r\n" - "Content-Type: text/html\r\n" - "Connection: close\r\n" - "\r\n" - "0\r\n\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 11 - ,.headers= - { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } - , { "Server", "Apache" } - , { "Cache-Control", "no-cache, must-revalidate" } - , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } - , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } - , { "Vary", "Accept-Encoding" } - , { "_eep-Alive", "timeout=45" } - , { "_onnection", "Keep-Alive" } - , { "Transfer-Encoding", "chunked" } - , { "Content-Type", "text/html" } - , { "Connection", "close" } - } - ,.body= "" - } - -#define NON_ASCII_IN_STATUS_LINE 10 -/* Should handle non-ASCII in status line */ -, {.name= "non-ASCII in status line" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" - "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" - "Content-Length: 0\r\n" - "Connection: close\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 500 - ,.num_headers= 3 - ,.headers= - { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } - , { "Content-Length", "0" } - , { "Connection", "close" } - } - ,.body= "" - } - -#define HTTP_VERSION_0_9 11 -/* Should handle HTTP/0.9 */ -, {.name= "http version 0.9" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/0.9 200 OK\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 0 - ,.http_minor= 9 - ,.status_code= 200 - ,.num_headers= 0 - ,.headers= - {} - ,.body= "" - } - -#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 -/* The client should wait for the server's EOF. That is, when neither - * content-length nor transfer-encoding is specified, the end of body - * is specified by the EOF. - */ -, {.name= "neither content-length nor transfer-encoding response" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - "hello world" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 1 - ,.headers= - { { "Content-Type", "text/plain" } - } - ,.body= "hello world" - } - -#define NO_BODY_HTTP10_KA_200 13 -, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.0 200 OK\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 200 - ,.num_headers= 1 - ,.headers= - { { "Connection", "keep-alive" } - } - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP10_KA_204 14 -, {.name= "HTTP/1.0 with keep-alive and a 204 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.0 204 No content\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 204 - ,.num_headers= 1 - ,.headers= - { { "Connection", "keep-alive" } - } - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_KA_200 15 -, {.name= "HTTP/1.1 with an EOF-terminated 200 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 0 - ,.headers={} - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_KA_204 16 -, {.name= "HTTP/1.1 with a 204 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 204 No content\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 204 - ,.num_headers= 0 - ,.headers={} - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_NOKA_204 17 -, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 204 No content\r\n" - "Connection: close\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 204 - ,.num_headers= 1 - ,.headers= - { { "Connection", "close" } - } - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_KA_CHUNKED_200 18 -, {.name= "HTTP/1.1 with chunked endocing and a 200 response" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding", "chunked" } - } - ,.body_size= 0 - ,.body= "" - } - -#if !HTTP_PARSER_STRICT -#define SPACE_IN_FIELD_RES 19 -/* Should handle spaces in header fields */ -, {.name= "field space" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Server: Microsoft-IIS/6.0\r\n" - "X-Powered-By: ASP.NET\r\n" - "en-US Content-Type: text/xml\r\n" /* this is the problem */ - "Content-Type: text/xml\r\n" - "Content-Length: 16\r\n" - "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" - "Connection: keep-alive\r\n" - "\r\n" - "<xml>hello</xml>" /* fake body */ - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.num_headers= 7 - ,.headers= - { { "Server", "Microsoft-IIS/6.0" } - , { "X-Powered-By", "ASP.NET" } - , { "en-US Content-Type", "text/xml" } - , { "Content-Type", "text/xml" } - , { "Content-Length", "16" } - , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } - , { "Connection", "keep-alive" } - } - ,.body= "<xml>hello</xml>" - } -#endif /* !HTTP_PARSER_STRICT */ - -, {.name= NULL } /* sentinel */ -}; - -/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so - * define it ourselves. - */ -size_t -strnlen(const char *s, size_t maxlen) -{ - const char *p; - - p = memchr(s, '\0', maxlen); - if (p == NULL) - return maxlen; - - return p - s; -} - -size_t -strlncat(char *dst, size_t len, const char *src, size_t n) -{ - size_t slen; - size_t dlen; - size_t rlen; - size_t ncpy; - - slen = strnlen(src, n); - dlen = strnlen(dst, len); - - if (dlen < len) { - rlen = len - dlen; - ncpy = slen < rlen ? slen : (rlen - 1); - memcpy(dst + dlen, src, ncpy); - dst[dlen + ncpy] = '\0'; - } - - assert(len > slen + dlen); - return slen + dlen; -} - -size_t -strlcat(char *dst, const char *src, size_t len) -{ - return strlncat(dst, len, src, (size_t) -1); -} - -size_t -strlncpy(char *dst, size_t len, const char *src, size_t n) -{ - size_t slen; - size_t ncpy; - - slen = strnlen(src, n); - - if (len > 0) { - ncpy = slen < len ? slen : (len - 1); - memcpy(dst, src, ncpy); - dst[ncpy] = '\0'; - } - - assert(len > slen); - return slen; -} - -size_t -strlcpy(char *dst, const char *src, size_t len) -{ - return strlncpy(dst, len, src, (size_t) -1); -} - -int -request_url_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strlncat(messages[num_messages].request_url, - sizeof(messages[num_messages].request_url), - buf, - len); - return 0; -} - -int -status_complete_cb (http_parser *p) { - assert(p == parser); - p->data++; - return 0; -} - -int -header_field_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - struct message *m = &messages[num_messages]; - - if (m->last_header_element != FIELD) - m->num_headers++; - - strlncat(m->headers[m->num_headers-1][0], - sizeof(m->headers[m->num_headers-1][0]), - buf, - len); - - m->last_header_element = FIELD; - - return 0; -} - -int -header_value_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - struct message *m = &messages[num_messages]; - - strlncat(m->headers[m->num_headers-1][1], - sizeof(m->headers[m->num_headers-1][1]), - buf, - len); - - m->last_header_element = VALUE; - - return 0; -} - -void -check_body_is_final (const http_parser *p) -{ - if (messages[num_messages].body_is_final) { - fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " - "on last on_body callback call " - "but it doesn't! ***\n\n"); - assert(0); - abort(); - } - messages[num_messages].body_is_final = http_body_is_final(p); -} - -int -body_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strlncat(messages[num_messages].body, - sizeof(messages[num_messages].body), - buf, - len); - messages[num_messages].body_size += len; - check_body_is_final(p); - // printf("body_cb: '%s'\n", requests[num_messages].body); - return 0; -} - -int -count_body_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - assert(buf); - messages[num_messages].body_size += len; - check_body_is_final(p); - return 0; -} - -int -message_begin_cb (http_parser *p) -{ - assert(p == parser); - messages[num_messages].message_begin_cb_called = TRUE; - return 0; -} - -int -headers_complete_cb (http_parser *p) -{ - assert(p == parser); - messages[num_messages].method = parser->method; - messages[num_messages].status_code = parser->status_code; - messages[num_messages].http_major = parser->http_major; - messages[num_messages].http_minor = parser->http_minor; - messages[num_messages].headers_complete_cb_called = TRUE; - messages[num_messages].should_keep_alive = http_should_keep_alive(parser); - return 0; -} - -int -message_complete_cb (http_parser *p) -{ - assert(p == parser); - if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser)) - { - fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " - "value in both on_message_complete and on_headers_complete " - "but it doesn't! ***\n\n"); - assert(0); - abort(); - } - - if (messages[num_messages].body_size && - http_body_is_final(p) && - !messages[num_messages].body_is_final) - { - fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " - "on last on_body callback call " - "but it doesn't! ***\n\n"); - assert(0); - abort(); - } - - messages[num_messages].message_complete_cb_called = TRUE; - - messages[num_messages].message_complete_on_eof = currently_parsing_eof; - - num_messages++; - return 0; -} - -/* These dontcall_* callbacks exist so that we can verify that when we're - * paused, no additional callbacks are invoked */ -int -dontcall_message_begin_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_body_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_headers_complete_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_headers_complete() called on paused " - "parser ***\n\n"); - abort(); -} - -int -dontcall_message_complete_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_message_complete() called on paused " - "parser ***\n\n"); - abort(); -} - -static http_parser_settings settings_dontcall = - {.on_message_begin = dontcall_message_begin_cb - ,.on_header_field = dontcall_header_field_cb - ,.on_header_value = dontcall_header_value_cb - ,.on_url = dontcall_request_url_cb - ,.on_body = dontcall_body_cb - ,.on_headers_complete = dontcall_headers_complete_cb - ,.on_message_complete = dontcall_message_complete_cb - }; - -/* These pause_* callbacks always pause the parser and just invoke the regular - * callback that tracks content. Before returning, we overwrite the parser - * settings to point to the _dontcall variety so that we can verify that - * the pause actually did, you know, pause. */ -int -pause_message_begin_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return message_begin_cb(p); -} - -int -pause_header_field_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return header_field_cb(p, buf, len); -} - -int -pause_header_value_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return header_value_cb(p, buf, len); -} - -int -pause_request_url_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return request_url_cb(p, buf, len); -} - -int -pause_body_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return body_cb(p, buf, len); -} - -int -pause_headers_complete_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return headers_complete_cb(p); -} - -int -pause_message_complete_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return message_complete_cb(p); -} - -static http_parser_settings settings_pause = - {.on_message_begin = pause_message_begin_cb - ,.on_header_field = pause_header_field_cb - ,.on_header_value = pause_header_value_cb - ,.on_url = pause_request_url_cb - ,.on_body = pause_body_cb - ,.on_headers_complete = pause_headers_complete_cb - ,.on_message_complete = pause_message_complete_cb - }; - -static http_parser_settings settings = - {.on_message_begin = message_begin_cb - ,.on_header_field = header_field_cb - ,.on_header_value = header_value_cb - ,.on_url = request_url_cb - ,.on_body = body_cb - ,.on_headers_complete = headers_complete_cb - ,.on_message_complete = message_complete_cb - }; - -static http_parser_settings settings_count_body = - {.on_message_begin = message_begin_cb - ,.on_header_field = header_field_cb - ,.on_header_value = header_value_cb - ,.on_url = request_url_cb - ,.on_body = count_body_cb - ,.on_headers_complete = headers_complete_cb - ,.on_message_complete = message_complete_cb - }; - -static http_parser_settings settings_null = - {.on_message_begin = 0 - ,.on_header_field = 0 - ,.on_header_value = 0 - ,.on_url = 0 - ,.on_body = 0 - ,.on_headers_complete = 0 - ,.on_message_complete = 0 - }; - -void -parser_init (enum http_parser_type type) -{ - num_messages = 0; - - assert(parser == NULL); - - parser = malloc(sizeof(http_parser)); - - http_parser_init(parser, type); - - memset(&messages, 0, sizeof messages); - -} - -void -parser_free () -{ - assert(parser); - free(parser); - parser = NULL; -} - -size_t parse (const char *buf, size_t len) -{ - size_t nparsed; - currently_parsing_eof = (len == 0); - nparsed = http_parser_execute(parser, &settings, buf, len); - return nparsed; -} - -size_t parse_count_body (const char *buf, size_t len) -{ - size_t nparsed; - currently_parsing_eof = (len == 0); - nparsed = http_parser_execute(parser, &settings_count_body, buf, len); - return nparsed; -} - -size_t parse_pause (const char *buf, size_t len) -{ - size_t nparsed; - http_parser_settings s = settings_pause; - - currently_parsing_eof = (len == 0); - current_pause_parser = &s; - nparsed = http_parser_execute(parser, current_pause_parser, buf, len); - return nparsed; -} - -static inline int -check_str_eq (const struct message *m, - const char *prop, - const char *expected, - const char *found) { - if ((expected == NULL) != (found == NULL)) { - printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); - printf("expected %s\n", (expected == NULL) ? "NULL" : expected); - printf(" found %s\n", (found == NULL) ? "NULL" : found); - return 0; - } - if (expected != NULL && 0 != strcmp(expected, found)) { - printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); - printf("expected '%s'\n", expected); - printf(" found '%s'\n", found); - return 0; - } - return 1; -} - -static inline int -check_num_eq (const struct message *m, - const char *prop, - int expected, - int found) { - if (expected != found) { - printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); - printf("expected %d\n", expected); - printf(" found %d\n", found); - return 0; - } - return 1; -} - -#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ - if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 - -#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ - if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 - -#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ -do { \ - char ubuf[256]; \ - \ - if ((u)->field_set & (1 << (fn))) { \ - memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ - (u)->field_data[(fn)].len); \ - ubuf[(u)->field_data[(fn)].len] = '\0'; \ - } else { \ - ubuf[0] = '\0'; \ - } \ - \ - check_str_eq(expected, #prop, expected->prop, ubuf); \ -} while(0) - -int -message_eq (int index, const struct message *expected) -{ - int i; - struct message *m = &messages[index]; - - MESSAGE_CHECK_NUM_EQ(expected, m, http_major); - MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); - - if (expected->type == HTTP_REQUEST) { - MESSAGE_CHECK_NUM_EQ(expected, m, method); - } else { - MESSAGE_CHECK_NUM_EQ(expected, m, status_code); - } - - MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); - MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); - - assert(m->message_begin_cb_called); - assert(m->headers_complete_cb_called); - assert(m->message_complete_cb_called); - - - MESSAGE_CHECK_STR_EQ(expected, m, request_url); - - /* Check URL components; we can't do this w/ CONNECT since it doesn't - * send us a well-formed URL. - */ - if (*m->request_url && m->method != HTTP_CONNECT) { - struct http_parser_url u; - - if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { - fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", - m->request_url); - abort(); - } - - if (expected->host) { - MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST); - } - - if (expected->userinfo) { - MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO); - } - - m->port = (u.field_set & (1 << UF_PORT)) ? - u.port : 0; - - MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); - MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); - MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); - MESSAGE_CHECK_NUM_EQ(expected, m, port); - } - - if (expected->body_size) { - MESSAGE_CHECK_NUM_EQ(expected, m, body_size); - } else { - MESSAGE_CHECK_STR_EQ(expected, m, body); - } - - MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); - - int r; - for (i = 0; i < m->num_headers; i++) { - r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); - if (!r) return 0; - r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); - if (!r) return 0; - } - - MESSAGE_CHECK_STR_EQ(expected, m, upgrade); - - return 1; -} - -/* Given a sequence of varargs messages, return the number of them that the - * parser should successfully parse, taking into account that upgraded - * messages prevent all subsequent messages from being parsed. - */ -size_t -count_parsed_messages(const size_t nmsgs, ...) { - size_t i; - va_list ap; - - va_start(ap, nmsgs); - - for (i = 0; i < nmsgs; i++) { - struct message *m = va_arg(ap, struct message *); - - if (m->upgrade) { - va_end(ap); - return i + 1; - } - } - - va_end(ap); - return nmsgs; -} - -/* Given a sequence of bytes and the number of these that we were able to - * parse, verify that upgrade bodies are correct. - */ -void -upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { - va_list ap; - size_t i; - size_t off = 0; - - va_start(ap, nmsgs); - - for (i = 0; i < nmsgs; i++) { - struct message *m = va_arg(ap, struct message *); - - off += strlen(m->raw); - - if (m->upgrade) { - off -= strlen(m->upgrade); - - /* Check the portion of the response after its specified upgrade */ - if (!check_str_eq(m, "upgrade", body + off, body + nread)) { - abort(); - } - - /* Fix up the response so that message_eq() will verify the beginning - * of the upgrade */ - *(body + nread + strlen(m->upgrade)) = '\0'; - messages[num_messages -1 ].upgrade = body + nread; - - va_end(ap); - return; - } - } - - va_end(ap); - printf("\n\n*** Error: expected a message with upgrade ***\n"); - - abort(); -} - -static void -print_error (const char *raw, size_t error_location) -{ - fprintf(stderr, "\n*** %s ***\n\n", - http_errno_description(HTTP_PARSER_ERRNO(parser))); - - int this_line = 0, char_len = 0; - size_t i, j, len = strlen(raw), error_location_line = 0; - for (i = 0; i < len; i++) { - if (i == error_location) this_line = 1; - switch (raw[i]) { - case '\r': - char_len = 2; - fprintf(stderr, "\\r"); - break; - - case '\n': - char_len = 2; - fprintf(stderr, "\\n\n"); - - if (this_line) goto print; - - error_location_line = 0; - continue; - - default: - char_len = 1; - fputc(raw[i], stderr); - break; - } - if (!this_line) error_location_line += char_len; - } - - fprintf(stderr, "[eof]\n"); - - print: - for (j = 0; j < error_location_line; j++) { - fputc(' ', stderr); - } - fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); -} - -void -test_preserve_data (void) -{ - char my_data[] = "application-specific data"; - http_parser parser; - parser.data = my_data; - http_parser_init(&parser, HTTP_REQUEST); - if (parser.data != my_data) { - printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); - abort(); - } -} - -struct url_test { - const char *name; - const char *url; - int is_connect; - struct http_parser_url u; - int rv; -}; - -const struct url_test url_tests[] = -{ {.name="proxy request" - ,.url="http://hostname/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 15, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="proxy request with port" - ,.url="http://hostname:444/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) - ,.port=444 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 8 } /* UF_HOST */ - ,{ 16, 3 } /* UF_PORT */ - ,{ 19, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="CONNECT request" - ,.url="hostname:443" - ,.is_connect=1 - ,.u= - {.field_set=(1 << UF_HOST) | (1 << UF_PORT) - ,.port=443 - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 0, 8 } /* UF_HOST */ - ,{ 9, 3 } /* UF_PORT */ - ,{ 0, 0 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="CONNECT request but not connect" - ,.url="hostname:443" - ,.is_connect=0 - ,.rv=1 - } - -, {.name="proxy ipv6 request" - ,.url="http://[1:2::3:4]/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 17, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="proxy ipv6 request with port" - ,.url="http://[1:2::3:4]:67/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) - ,.port=67 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 8 } /* UF_HOST */ - ,{ 18, 2 } /* UF_PORT */ - ,{ 20, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="CONNECT ipv6 address" - ,.url="[1:2::3:4]:443" - ,.is_connect=1 - ,.u= - {.field_set=(1 << UF_HOST) | (1 << UF_PORT) - ,.port=443 - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 1, 8 } /* UF_HOST */ - ,{ 11, 3 } /* UF_PORT */ - ,{ 0, 0 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="ipv4 in ipv6 address" - ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 37 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 46, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="extra ? in query string" - ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css," - "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css," - "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" - ,.is_connect=0 - ,.u= - {.field_set=(1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 10 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 17, 12 } /* UF_PATH */ - ,{ 30,187 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="space URL encoded" - ,.url="/toto.html?toto=a%20b" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_PATH) | (1<<UF_QUERY) - ,.port=0 - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 0, 0 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 0, 10 } /* UF_PATH */ - ,{ 11, 10 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - - -, {.name="URL fragment" - ,.url="/toto.html#titi" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_PATH) | (1<<UF_FRAGMENT) - ,.port=0 - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 0, 0 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 0, 10 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 11, 4 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="complex URL fragment" - ,.url="http://www.webmasterworld.com/r.cgi?f=21&d=8405&url=" - "http://www.example.com/index.html?foo=bar&hello=world#midpage" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) |\ - (1<<UF_FRAGMENT) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 22 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 29, 6 } /* UF_PATH */ - ,{ 36, 69 } /* UF_QUERY */ - ,{106, 7 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="complex URL from node js url parser doc" - ,.url="http://host.com:8080/p/a/t/h?query=string#hash" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\ - (1<<UF_QUERY) | (1<<UF_FRAGMENT) - ,.port=8080 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 8 } /* UF_HOST */ - ,{ 16, 4 } /* UF_PORT */ - ,{ 20, 8 } /* UF_PATH */ - ,{ 29, 12 } /* UF_QUERY */ - ,{ 42, 4 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="complex URL with basic auth from node js url parser doc" - ,.url="http://a:b@host.com:8080/p/a/t/h?query=string#hash" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\ - (1<<UF_QUERY) | (1<<UF_FRAGMENT) | (1<<UF_USERINFO) - ,.port=8080 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 11, 8 } /* UF_HOST */ - ,{ 20, 4 } /* UF_PORT */ - ,{ 24, 8 } /* UF_PATH */ - ,{ 33, 12 } /* UF_QUERY */ - ,{ 46, 4 } /* UF_FRAGMENT */ - ,{ 7, 3 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="double @" - ,.url="http://a:b@@hostname:443/" - ,.is_connect=0 - ,.rv=1 - } - -, {.name="proxy empty host" - ,.url="http://:443/" - ,.is_connect=0 - ,.rv=1 - } - -, {.name="proxy empty port" - ,.url="http://hostname:/" - ,.is_connect=0 - ,.rv=1 - } - -, {.name="CONNECT with basic auth" - ,.url="a:b@hostname:443" - ,.is_connect=1 - ,.rv=1 - } - -, {.name="CONNECT empty host" - ,.url=":443" - ,.is_connect=1 - ,.rv=1 - } - -, {.name="CONNECT empty port" - ,.url="hostname:" - ,.is_connect=1 - ,.rv=1 - } - -, {.name="CONNECT with extra bits" - ,.url="hostname:443/" - ,.is_connect=1 - ,.rv=1 - } - -, {.name="space in URL" - ,.url="/foo bar/" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy basic auth with space url encoded" - ,.url="http://a%20:b@host.com/" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 14, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 22, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 7, 6 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="carriage return in URL" - ,.url="/foo\rbar/" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy double : in URL" - ,.url="http://hostname::443/" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy basic auth with double :" - ,.url="http://a::b@host.com/" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 12, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 20, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 7, 4 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="line feed in URL" - ,.url="/foo\nbar/" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy empty basic auth" - ,.url="http://@hostname/fo" - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 16, 3 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } -, {.name="proxy line feed in hostname" - ,.url="http://host\name/fo" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy % in hostname" - ,.url="http://host%name/fo" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy ; in hostname" - ,.url="http://host;ame/fo" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy basic auth with unreservedchars" - ,.url="http://a!;-_!=+$@host.com/" - ,.is_connect=0 - ,.u= - {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 17, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 25, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 7, 9 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="proxy only empty basic auth" - ,.url="http://@/fo" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy only basic auth" - ,.url="http://toto@/fo" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy emtpy hostname" - ,.url="http:///fo" - ,.rv=1 /* s_dead */ - } - -, {.name="proxy = in URL" - ,.url="http://host=ame/fo" - ,.rv=1 /* s_dead */ - } - -#if HTTP_PARSER_STRICT - -, {.name="tab in URL" - ,.url="/foo\tbar/" - ,.rv=1 /* s_dead */ - } - -, {.name="form feed in URL" - ,.url="/foo\fbar/" - ,.rv=1 /* s_dead */ - } - -#else /* !HTTP_PARSER_STRICT */ - -, {.name="tab in URL" - ,.url="/foo\tbar/" - ,.u= - {.field_set=(1 << UF_PATH) - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 0, 0 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 0, 9 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="form feed in URL" - ,.url="/foo\fbar/" - ,.u= - {.field_set=(1 << UF_PATH) - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 0, 0 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 0, 9 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } -#endif -}; - -void -dump_url (const char *url, const struct http_parser_url *u) -{ - unsigned int i; - - printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); - for (i = 0; i < UF_MAX; i++) { - if ((u->field_set & (1 << i)) == 0) { - printf("\tfield_data[%u]: unset\n", i); - continue; - } - - printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", - i, - u->field_data[i].off, - u->field_data[i].len, - u->field_data[i].len, - url + u->field_data[i].off); - } -} - -void -test_parse_url (void) -{ - struct http_parser_url u; - const struct url_test *test; - unsigned int i; - int rv; - - for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { - test = &url_tests[i]; - memset(&u, 0, sizeof(u)); - - rv = http_parser_parse_url(test->url, - strlen(test->url), - test->is_connect, - &u); - - if (test->rv == 0) { - if (rv != 0) { - printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " - "unexpected rv %d ***\n\n", test->url, test->name, rv); - abort(); - } - - if (memcmp(&u, &test->u, sizeof(u)) != 0) { - printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", - test->url, test->name); - - printf("target http_parser_url:\n"); - dump_url(test->url, &test->u); - printf("result http_parser_url:\n"); - dump_url(test->url, &u); - - abort(); - } - } else { - /* test->rv != 0 */ - if (rv == 0) { - printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " - "unexpected rv %d ***\n\n", test->url, test->name, rv); - abort(); - } - } - } -} - -void -test_method_str (void) -{ - assert(0 == strcmp("GET", http_method_str(HTTP_GET))); - assert(0 == strcmp("<unknown>", http_method_str(1337))); -} - -void -test_message (const struct message *message) -{ - size_t raw_len = strlen(message->raw); - size_t msg1len; - for (msg1len = 0; msg1len < raw_len; msg1len++) { - parser_init(message->type); - - size_t read; - const char *msg1 = message->raw; - const char *msg2 = msg1 + msg1len; - size_t msg2len = raw_len - msg1len; - - if (msg1len) { - read = parse(msg1, msg1len); - - if (message->upgrade && parser->upgrade) { - messages[num_messages - 1].upgrade = msg1 + read; - goto test; - } - - if (read != msg1len) { - print_error(msg1, read); - abort(); - } - } - - - read = parse(msg2, msg2len); - - if (message->upgrade && parser->upgrade) { - messages[num_messages - 1].upgrade = msg2 + read; - goto test; - } - - if (read != msg2len) { - print_error(msg2, read); - abort(); - } - - read = parse(NULL, 0); - - if (read != 0) { - print_error(message->raw, read); - abort(); - } - - test: - - if (num_messages != 1) { - printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); - abort(); - } - - if(!message_eq(0, message)) abort(); - - parser_free(); - } -} - -void -test_message_count_body (const struct message *message) -{ - parser_init(message->type); - - size_t read; - size_t l = strlen(message->raw); - size_t i, toread; - size_t chunk = 4024; - - for (i = 0; i < l; i+= chunk) { - toread = MIN(l-i, chunk); - read = parse_count_body(message->raw + i, toread); - if (read != toread) { - print_error(message->raw, read); - abort(); - } - } - - - read = parse_count_body(NULL, 0); - if (read != 0) { - print_error(message->raw, read); - abort(); - } - - if (num_messages != 1) { - printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); - abort(); - } - - if(!message_eq(0, message)) abort(); - - parser_free(); -} - -void -test_simple (const char *buf, enum http_errno err_expected) -{ - parser_init(HTTP_REQUEST); - - size_t parsed; - int pass; - enum http_errno err; - - parsed = parse(buf, strlen(buf)); - pass = (parsed == strlen(buf)); - err = HTTP_PARSER_ERRNO(parser); - parsed = parse(NULL, 0); - pass &= (parsed == 0); - - parser_free(); - - /* In strict mode, allow us to pass with an unexpected HPE_STRICT as - * long as the caller isn't expecting success. - */ -#if HTTP_PARSER_STRICT - if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { -#else - if (err_expected != err) { -#endif - fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", - http_errno_name(err_expected), http_errno_name(err), buf); - abort(); - } -} - -void -test_header_overflow_error (int req) -{ - http_parser parser; - http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); - size_t parsed; - const char *buf; - buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; - parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); - assert(parsed == strlen(buf)); - - buf = "header-key: header-value\r\n"; - size_t buflen = strlen(buf); - - int i; - for (i = 0; i < 10000; i++) { - parsed = http_parser_execute(&parser, &settings_null, buf, buflen); - if (parsed != buflen) { - //fprintf(stderr, "error found on iter %d\n", i); - assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); - return; - } - } - - fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); - abort(); -} - -static void -test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) -{ - http_parser parser; - http_parser_init(&parser, HTTP_RESPONSE); - http_parser_execute(&parser, &settings_null, buf, buflen); - - if (expect_ok) - assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); - else - assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); -} - -void -test_header_content_length_overflow_error (void) -{ -#define X(size) \ - "HTTP/1.1 200 OK\r\n" \ - "Content-Length: " #size "\r\n" \ - "\r\n" - const char a[] = X(18446744073709551614); /* 2^64-2 */ - const char b[] = X(18446744073709551615); /* 2^64-1 */ - const char c[] = X(18446744073709551616); /* 2^64 */ -#undef X - test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ - test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ - test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ -} - -void -test_chunk_content_length_overflow_error (void) -{ -#define X(size) \ - "HTTP/1.1 200 OK\r\n" \ - "Transfer-Encoding: chunked\r\n" \ - "\r\n" \ - #size "\r\n" \ - "..." - const char a[] = X(FFFFFFFFFFFFFFFE); /* 2^64-2 */ - const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ - const char c[] = X(10000000000000000); /* 2^64 */ -#undef X - test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ - test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ - test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ -} - -void -test_no_overflow_long_body (int req, size_t length) -{ - http_parser parser; - http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); - size_t parsed; - size_t i; - char buf1[3000]; - size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", - req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length); - parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); - if (parsed != buf1len) - goto err; - - for (i = 0; i < length; i++) { - char foo = 'a'; - parsed = http_parser_execute(&parser, &settings_null, &foo, 1); - if (parsed != 1) - goto err; - } - - parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); - if (parsed != buf1len) goto err; - return; - - err: - fprintf(stderr, - "\n*** error in test_no_overflow_long_body %s of length %lu ***\n", - req ? "REQUEST" : "RESPONSE", - (unsigned long)length); - abort(); -} - -void -test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) -{ - int message_count = count_parsed_messages(3, r1, r2, r3); - - char total[ strlen(r1->raw) - + strlen(r2->raw) - + strlen(r3->raw) - + 1 - ]; - total[0] = '\0'; - - strcat(total, r1->raw); - strcat(total, r2->raw); - strcat(total, r3->raw); - - parser_init(r1->type); - - size_t read; - - read = parse(total, strlen(total)); - - if (parser->upgrade) { - upgrade_message_fix(total, read, 3, r1, r2, r3); - goto test; - } - - if (read != strlen(total)) { - print_error(total, read); - abort(); - } - - read = parse(NULL, 0); - - if (read != 0) { - print_error(total, read); - abort(); - } - -test: - - if (message_count != num_messages) { - fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); - abort(); - } - - if (!message_eq(0, r1)) abort(); - if (message_count > 1 && !message_eq(1, r2)) abort(); - if (message_count > 2 && !message_eq(2, r3)) abort(); - - parser_free(); -} - -/* SCAN through every possible breaking to make sure the - * parser can handle getting the content in any chunks that - * might come from the socket - */ -void -test_scan (const struct message *r1, const struct message *r2, const struct message *r3) -{ - char total[80*1024] = "\0"; - char buf1[80*1024] = "\0"; - char buf2[80*1024] = "\0"; - char buf3[80*1024] = "\0"; - - strcat(total, r1->raw); - strcat(total, r2->raw); - strcat(total, r3->raw); - - size_t read; - - int total_len = strlen(total); - - int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; - int ops = 0 ; - - size_t buf1_len, buf2_len, buf3_len; - int message_count = count_parsed_messages(3, r1, r2, r3); - - int i,j,type_both; - for (type_both = 0; type_both < 2; type_both ++ ) { - for (j = 2; j < total_len; j ++ ) { - for (i = 1; i < j; i ++ ) { - - if (ops % 1000 == 0) { - printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); - fflush(stdout); - } - ops += 1; - - parser_init(type_both ? HTTP_BOTH : r1->type); - - buf1_len = i; - strlncpy(buf1, sizeof(buf1), total, buf1_len); - buf1[buf1_len] = 0; - - buf2_len = j - i; - strlncpy(buf2, sizeof(buf1), total+i, buf2_len); - buf2[buf2_len] = 0; - - buf3_len = total_len - j; - strlncpy(buf3, sizeof(buf1), total+j, buf3_len); - buf3[buf3_len] = 0; - - read = parse(buf1, buf1_len); - - if (parser->upgrade) goto test; - - if (read != buf1_len) { - print_error(buf1, read); - goto error; - } - - read += parse(buf2, buf2_len); - - if (parser->upgrade) goto test; - - if (read != buf1_len + buf2_len) { - print_error(buf2, read); - goto error; - } - - read += parse(buf3, buf3_len); - - if (parser->upgrade) goto test; - - if (read != buf1_len + buf2_len + buf3_len) { - print_error(buf3, read); - goto error; - } - - parse(NULL, 0); - -test: - if (parser->upgrade) { - upgrade_message_fix(total, read, 3, r1, r2, r3); - } - - if (message_count != num_messages) { - fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", - message_count, num_messages); - goto error; - } - - if (!message_eq(0, r1)) { - fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); - goto error; - } - - if (message_count > 1 && !message_eq(1, r2)) { - fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); - goto error; - } - - if (message_count > 2 && !message_eq(2, r3)) { - fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); - goto error; - } - - parser_free(); - } - } - } - puts("\b\b\b\b100%"); - return; - - error: - fprintf(stderr, "i=%d j=%d\n", i, j); - fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); - fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); - fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); - abort(); -} - -// user required to free the result -// string terminated by \0 -char * -create_large_chunked_message (int body_size_in_kb, const char* headers) -{ - int i; - size_t wrote = 0; - size_t headers_len = strlen(headers); - size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; - char * buf = malloc(bufsize); - - memcpy(buf, headers, headers_len); - wrote += headers_len; - - for (i = 0; i < body_size_in_kb; i++) { - // write 1kb chunk into the body. - memcpy(buf + wrote, "400\r\n", 5); - wrote += 5; - memset(buf + wrote, 'C', 1024); - wrote += 1024; - strcpy(buf + wrote, "\r\n"); - wrote += 2; - } - - memcpy(buf + wrote, "0\r\n\r\n", 6); - wrote += 6; - assert(wrote == bufsize); - - return buf; -} - -void -test_status_complete (void) -{ - parser_init(HTTP_RESPONSE); - parser->data = 0; - http_parser_settings settings = settings_null; - settings.on_status_complete = status_complete_cb; - - char *response = "don't mind me, just a simple response"; - http_parser_execute(parser, &settings, response, strlen(response)); - assert(parser->data == (void*)0); // the status_complete callback was never called - assert(parser->http_errno == HPE_INVALID_CONSTANT); // the errno for an invalid status line -} - -/* Verify that we can pause parsing at any of the bytes in the - * message and still get the result that we're expecting. */ -void -test_message_pause (const struct message *msg) -{ - char *buf = (char*) msg->raw; - size_t buflen = strlen(msg->raw); - size_t nread; - - parser_init(msg->type); - - do { - nread = parse_pause(buf, buflen); - - // We can only set the upgrade buffer once we've gotten our message - // completion callback. - if (messages[0].message_complete_cb_called && - msg->upgrade && - parser->upgrade) { - messages[0].upgrade = buf + nread; - goto test; - } - - if (nread < buflen) { - - // Not much do to if we failed a strict-mode check - if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) { - parser_free(); - return; - } - - assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED); - } - - buf += nread; - buflen -= nread; - http_parser_pause(parser, 0); - } while (buflen > 0); - - nread = parse_pause(NULL, 0); - assert (nread == 0); - -test: - if (num_messages != 1) { - printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); - abort(); - } - - if(!message_eq(0, msg)) abort(); - - parser_free(); -} - -int -main (void) -{ - parser = NULL; - int i, j, k; - int request_count; - int response_count; - - printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); - - for (request_count = 0; requests[request_count].name; request_count++); - for (response_count = 0; responses[response_count].name; response_count++); - - //// API - test_preserve_data(); - test_parse_url(); - test_method_str(); - - //// OVERFLOW CONDITIONS - - test_header_overflow_error(HTTP_REQUEST); - test_no_overflow_long_body(HTTP_REQUEST, 1000); - test_no_overflow_long_body(HTTP_REQUEST, 100000); - - test_header_overflow_error(HTTP_RESPONSE); - test_no_overflow_long_body(HTTP_RESPONSE, 1000); - test_no_overflow_long_body(HTTP_RESPONSE, 100000); - - test_header_content_length_overflow_error(); - test_chunk_content_length_overflow_error(); - - //// RESPONSES - - for (i = 0; i < response_count; i++) { - test_message(&responses[i]); - } - - for (i = 0; i < response_count; i++) { - test_message_pause(&responses[i]); - } - - for (i = 0; i < response_count; i++) { - if (!responses[i].should_keep_alive) continue; - for (j = 0; j < response_count; j++) { - if (!responses[j].should_keep_alive) continue; - for (k = 0; k < response_count; k++) { - test_multiple3(&responses[i], &responses[j], &responses[k]); - } - } - } - - test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); - test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); - - // test very large chunked response - { - char * msg = create_large_chunked_message(31337, - "HTTP/1.0 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "Content-Type: text/plain\r\n" - "\r\n"); - struct message large_chunked = - {.name= "large chunked" - ,.type= HTTP_RESPONSE - ,.raw= msg - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 200 - ,.num_headers= 2 - ,.headers= - { { "Transfer-Encoding", "chunked" } - , { "Content-Type", "text/plain" } - } - ,.body_size= 31337*1024 - }; - test_message_count_body(&large_chunked); - free(msg); - } - - - - printf("response scan 1/2 "); - test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] - , &responses[NO_BODY_HTTP10_KA_204] - , &responses[NO_REASON_PHRASE] - ); - - printf("response scan 2/2 "); - test_scan( &responses[BONJOUR_MADAME_FR] - , &responses[UNDERSTORE_HEADER_KEY] - , &responses[NO_CARRIAGE_RET] - ); - - puts("responses okay"); - - - /// REQUESTS - - test_simple("hello world", HPE_INVALID_METHOD); - test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); - - - test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); - test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); - test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); - - // Well-formed but incomplete - test_simple("GET / HTTP/1.1\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: 6\r\n" - "\r\n" - "fooba", - HPE_OK); - - static const char *all_methods[] = { - "DELETE", - "GET", - "HEAD", - "POST", - "PUT", - //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel - "OPTIONS", - "TRACE", - "COPY", - "LOCK", - "MKCOL", - "MOVE", - "PROPFIND", - "PROPPATCH", - "UNLOCK", - "REPORT", - "MKACTIVITY", - "CHECKOUT", - "MERGE", - "M-SEARCH", - "NOTIFY", - "SUBSCRIBE", - "UNSUBSCRIBE", - "PATCH", - 0 }; - const char **this_method; - for (this_method = all_methods; *this_method; this_method++) { - char buf[200]; - sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); - test_simple(buf, HPE_OK); - } - - static const char *bad_methods[] = { - "C******", - "M****", - 0 }; - for (this_method = bad_methods; *this_method; this_method++) { - char buf[200]; - sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); - test_simple(buf, HPE_UNKNOWN); - } - - const char *dumbfuck2 = - "GET / HTTP/1.1\r\n" - "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" - "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" - "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" - "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" - "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" - "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" - "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" - "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" - "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" - "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" - "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" - "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" - "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" - "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" - "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" - "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" - "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" - "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" - "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" - "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" - "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" - "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" - "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" - "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" - "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" - "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" - "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" - "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" - "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" - "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" - "\tRA==\r\n" - "\t-----END CERTIFICATE-----\r\n" - "\r\n"; - test_simple(dumbfuck2, HPE_OK); - -#if 0 - // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body - // until EOF. - // - // no content-length - // error if there is a body without content length - const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" - "Accept: */*\r\n" - "\r\n" - "HELLO"; - test_simple(bad_get_no_headers_no_body, 0); -#endif - /* TODO sending junk and large headers gets rejected */ - - - /* check to make sure our predefined requests are okay */ - for (i = 0; requests[i].name; i++) { - test_message(&requests[i]); - } - - for (i = 0; i < request_count; i++) { - test_message_pause(&requests[i]); - } - - for (i = 0; i < request_count; i++) { - if (!requests[i].should_keep_alive) continue; - for (j = 0; j < request_count; j++) { - if (!requests[j].should_keep_alive) continue; - for (k = 0; k < request_count; k++) { - test_multiple3(&requests[i], &requests[j], &requests[k]); - } - } - } - - printf("request scan 1/4 "); - test_scan( &requests[GET_NO_HEADERS_NO_BODY] - , &requests[GET_ONE_HEADER_NO_BODY] - , &requests[GET_NO_HEADERS_NO_BODY] - ); - - printf("request scan 2/4 "); - test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] - , &requests[POST_IDENTITY_BODY_WORLD] - , &requests[GET_FUNKY_CONTENT_LENGTH] - ); - - printf("request scan 3/4 "); - test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] - , &requests[CHUNKED_W_TRAILING_HEADERS] - , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] - ); - - printf("request scan 4/4 "); - test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] - , &requests[PREFIX_NEWLINE_GET ] - , &requests[CONNECT_REQUEST] - ); - - test_status_complete(); - - puts("requests okay"); - - return 0; -} diff --git a/ext/huffandpuff/huffman.c b/ext/huffandpuff/huffman.c deleted file mode 100644 index f4d8314a..00000000 --- a/ext/huffandpuff/huffman.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Huffandpuff minimal Huffman coder - * - * (c)2013 Adam Ierymenko <adam.ierymenko@zerotier.com> - * This code is in the public domain and is distributed with NO WARRANTY. - */ - -#include "huffman.h" - -struct _huffman_node -{ - struct _huffman_node *lr[2]; - struct _huffman_node *qprev,*qnext; - double prob; - unsigned long c; -}; - -struct _huffman_encode_table -{ - unsigned long code; - unsigned long bits; -}; - -static void _huffman_write_tree_and_make_encode_table(unsigned char *out,unsigned long *outbitctr,unsigned long outlen,struct _huffman_encode_table *et,unsigned long code,unsigned int bits,struct _huffman_node *t) -{ - struct _huffman_encode_table *eti; - unsigned int i; - unsigned long byte_index; - - byte_index = (*outbitctr)++ >> 3; - byte_index *= (byte_index < outlen); - if (t->lr[0]) { - out[byte_index] <<= 1; - _huffman_write_tree_and_make_encode_table(out,outbitctr,outlen,et,code,bits + 1,t->lr[0]); - _huffman_write_tree_and_make_encode_table(out,outbitctr,outlen,et,code | (1 << bits),bits + 1,t->lr[1]); - } else { - out[byte_index] = (out[byte_index] << 1) | 1; - for(i=0;i<9;++i) { - byte_index = (*outbitctr)++ >> 3; - if (byte_index >= outlen) return; - out[byte_index] = (out[byte_index] << 1) | ((unsigned char)((t->c >> i) & 1)); - } - eti = &(et[t->c]); - eti->code = code; - eti->bits = bits; - } -} - -static struct _huffman_node *_huffman_read_tree(const unsigned char *in,unsigned long *inbitctr,unsigned long inlen,unsigned char **heapptr,unsigned char *heapend) -{ - struct _huffman_node *n; - unsigned int i; - unsigned long byte_index; - - n = (struct _huffman_node *)(*heapptr); - *heapptr += sizeof(struct _huffman_node); - if (*heapptr > heapend) return (struct _huffman_node *)0; - - byte_index = *inbitctr >> 3; - byte_index *= (byte_index < inlen); - if (((in[byte_index] >> (~((*inbitctr)++) & 7)) & 1)) { - n->lr[0] = (struct _huffman_node *)0; - n->lr[1] = (struct _huffman_node *)0; - n->c = 0; - for(i=0;i<9;++i) { - byte_index = *inbitctr >> 3; - if (byte_index >= inlen) return (struct _huffman_node *)0; - n->c |= (((unsigned int)(in[byte_index] >> (~((*inbitctr)++) & 7))) & 1) << i; - } - } else { - n->lr[0] = _huffman_read_tree(in,inbitctr,inlen,heapptr,heapend); - n->lr[1] = _huffman_read_tree(in,inbitctr,inlen,heapptr,heapend); - if (!((n->lr[0])&&(n->lr[1]))) - return (struct _huffman_node *)0; - } - - return n; -} - -unsigned long huffman_compress(const unsigned char *in,unsigned long inlen,unsigned char *out,unsigned long outlen,void *huffheap) -{ - struct _huffman_encode_table *et,*eti; - struct _huffman_node *t,*n; - struct _huffman_node *pair[2]; - unsigned char *heapptr = (unsigned char *)huffheap; - unsigned long i,code,byte_index,outbitctr; - unsigned int bits,b; - double *counts,lowest_prob,total_symbols; - - counts = (double *)heapptr; - heapptr += (sizeof(double) * 257); - for(i=0;i<256;++i) - counts[i] = 0.0; - counts[256] = 1.0; /* one stop code at end */ - for(i=0;i<inlen;++i) - counts[(unsigned long)in[i]] += 1.0; - - t = (struct _huffman_node *)0; - total_symbols = (double)(inlen + 1); - for(i=0;i<=256;++i) { - if (counts[i] > 0.0) { - n = (struct _huffman_node *)heapptr; - heapptr += sizeof(struct _huffman_node); - if (t) - t->qprev = n; - n->qprev = (struct _huffman_node *)0; - n->qnext = t; - n->lr[0] = (struct _huffman_node *)0; - n->lr[1] = (struct _huffman_node *)0; - n->prob = counts[i] / total_symbols; - n->c = (unsigned int)i; - t = n; - } - } - - while (t->qnext) { - for(i=0;i<2;++i) { - lowest_prob = 1.0; - pair[i] = (struct _huffman_node *)0; - n = t; - while (n) { - if (n->prob <= lowest_prob) { - lowest_prob = n->prob; - pair[i] = n; - } - n = n->qnext; - } - if (pair[i]->qprev) - pair[i]->qprev->qnext = pair[i]->qnext; - else t = pair[i]->qnext; - if (pair[i]->qnext) - pair[i]->qnext->qprev = pair[i]->qprev; - } - n = (struct _huffman_node *)heapptr; - heapptr += sizeof(struct _huffman_node); - n->lr[0] = pair[0]; - n->lr[1] = pair[1]; - n->prob = pair[0]->prob + pair[1]->prob; - if (t) - t->qprev = n; - n->qprev = (struct _huffman_node *)0; - n->qnext = t; - t = n; - } - - et = (struct _huffman_encode_table *)heapptr; - heapptr += (sizeof(struct _huffman_encode_table) * 257); - outbitctr = 0; - _huffman_write_tree_and_make_encode_table(out,&outbitctr,outlen,et,0,0,t); - - for(i=0;i<inlen;++i) { - eti = &(et[(unsigned long)in[i]]); - code = eti->code; - bits = eti->bits; - for(b=0;b<bits;++b) { - byte_index = outbitctr++ >> 3; - if (byte_index >= outlen) return 0; - out[byte_index] = (out[byte_index] << 1) | (unsigned char)(code & 1); - code >>= 1; - } - } - code = et[256].code; - bits = et[256].bits; - for(b=0;b<bits;++b) { - byte_index = outbitctr++ >> 3; - if (byte_index >= outlen) return 0; - out[byte_index] = (out[byte_index] << 1) | (unsigned char)(code & 1); - code >>= 1; - } - - if (outbitctr > (outlen << 3)) - return 0; - else if ((outbitctr & 7)) { - out[i = (outbitctr >> 3)] <<= 8 - (outbitctr & 7); - return (i + 1); - } else return (outbitctr >> 3); -} - -unsigned long huffman_decompress(const unsigned char *in,unsigned long inlen,unsigned char *out,unsigned long outlen,void *huffheap) -{ - struct _huffman_node *t,*n; - unsigned char *heapptr = (unsigned char *)huffheap; - unsigned long inbitctr,outptr,byte_index = 0; - - inbitctr = 0; - t = _huffman_read_tree(in,&inbitctr,inlen,&heapptr,heapptr + HUFFHEAP_SIZE); - if (!t) return 0; - outptr = 0; - for(;;) { - n = t; - while (n->lr[0]) { - byte_index = inbitctr >> 3; - if (byte_index >= inlen) return 0; - n = n->lr[((unsigned long)(in[byte_index] >> (~(inbitctr++) & 7))) & 1]; - } - if (n->c == 256) return outptr; - if (outptr == outlen) return 0; - out[outptr++] = (unsigned char)n->c; - } -} - -#ifdef HUFFANDPUFF_TEST - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#define HUFFANDPUFF_TEST_MAXLEN 1048576 -#define HUFFANDPUFF_TEST_ITER 1024 - -static unsigned char testin[HUFFANDPUFF_TEST_MAXLEN]; -static unsigned char testout[HUFFANDPUFF_TEST_MAXLEN * 2]; -static unsigned char testver[HUFFANDPUFF_TEST_MAXLEN]; -static unsigned char huffbuf[HUFFHEAP_SIZE]; - -int main(int argc,char **argv) -{ - unsigned long i,k,l,cl,dcl; - int v; - unsigned char mask; - - srand(time(0)); - - for(k=0;k<HUFFANDPUFF_TEST_ITER;++k) { - l = (rand() % HUFFANDPUFF_TEST_MAXLEN) + 1; - mask = (rand() & 0xff); - for(i=0;i<l;++i) - testin[i] = (unsigned char)(rand() & 0xff) & mask; - cl = huffman_compress(testin,l,testout,sizeof(testout),huffbuf); - if (cl) { - memset(testver,0,sizeof(testver)); - dcl = huffman_decompress(testout,cl,testver,sizeof(testver),huffbuf); - v = ((dcl)&&(!memcmp(testver,testin,l))); - printf("[%d] in: %d, out: %d, verified: %s\n",(int)k,(int)l,(int)cl,(v) ? "OK" : "FAIL"); - } else printf("[%d] in: %d, out: FAIL\n",(int)k,(int)l); - } - - printf("\nFuzzing decompress function...\n"); - for(;;) { - l = (rand() % HUFFANDPUFF_TEST_MAXLEN) + 1; - mask = (rand() & 0xff); - for(i=0;i<l;++i) - testin[i] = (unsigned char)(rand() & 0xff) & mask; - huffman_decompress(testin,l,testver,sizeof(testver),huffbuf); - printf("."); fflush(stdout); - } - - return 0; -} - -#endif diff --git a/ext/huffandpuff/huffman.h b/ext/huffandpuff/huffman.h deleted file mode 100644 index 5e2f8996..00000000 --- a/ext/huffandpuff/huffman.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Huffandpuff minimal Huffman coder - * - * (c)2013 Adam Ierymenko <adam.ierymenko@zerotier.com> - * This code is in the public domain and is distributed with NO WARRANTY. - */ - -#ifndef ____HUFFMAN_H -#define ____HUFFMAN_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Required size of huffheap parameter to compress and decompress - * - * Note: if you change any of the data types in the _huffman_node - * or _huffman_encode_table structs in huffman.c, this also must be - * changed. - */ -#define HUFFHEAP_SIZE ((sizeof(double) * 257) + (((sizeof(void *) * 4) + sizeof(double) + sizeof(unsigned long)) * (257 * 3)) + ((sizeof(unsigned long) + sizeof(unsigned long)) * 257)) - -/** - * Huffman encode a block of data - * - * @param in Input data - * @param inlen Input data length - * @param out Output buffer - * @param outlen Output buffer length - * @param huffheap Heap memory to use for compression (must be HUFFHEAP_SIZE in size) - * @return Size of encoded result or 0 on out buffer overrun - */ -extern unsigned long huffman_compress(const unsigned char *in,unsigned long inlen,unsigned char *out,unsigned long outlen,void *huffheap); - -/** - * Huffman decode a block of data - * - * @param in Input data - * @param inlen Length of input data - * @param out Output buffer - * @param outlen Length of output buffer - * @param huffheap Heap memory to use for decompression (must be HUFFHEAP_SIZE in size) - * @return Size of decoded result or 0 on out buffer overrun or corrupt input data - */ -extern unsigned long huffman_decompress(const unsigned char *in,unsigned long inlen,unsigned char *out,unsigned long outlen,void *huffheap); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ext/jsoncpp/include/json/autolink.h b/ext/jsoncpp/include/json/autolink.h deleted file mode 100644 index 02328d1f..00000000 --- a/ext/jsoncpp/include/json/autolink.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_AUTOLINK_H_INCLUDED -# define JSON_AUTOLINK_H_INCLUDED - -# include "config.h" - -# ifdef JSON_IN_CPPTL -# include <cpptl/cpptl_autolink.h> -# endif - -# if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && !defined(JSON_IN_CPPTL) -# define CPPTL_AUTOLINK_NAME "json" -# undef CPPTL_AUTOLINK_DLL -# ifdef JSON_DLL -# define CPPTL_AUTOLINK_DLL -# endif -# include "autolink.h" -# endif - -#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/ext/jsoncpp/include/json/config.h b/ext/jsoncpp/include/json/config.h deleted file mode 100644 index 7609d45e..00000000 --- a/ext/jsoncpp/include/json/config.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_CONFIG_H_INCLUDED -# define JSON_CONFIG_H_INCLUDED - -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 -/// If defined, indicates that Json specific container should be used -/// (hash table & simple deque container with customizable allocator). -/// THIS FEATURE IS STILL EXPERIMENTAL! There is know bugs: See #3177332 -//# define JSON_VALUE_USE_INTERNAL_MAP 1 -/// Force usage of standard new/malloc based allocator instead of memory pool based allocator. -/// The memory pools allocator used optimization (initializing Value and ValueInternalLink -/// as if it was a POD) that may cause some validation tool to report errors. -/// Only has effects if JSON_VALUE_USE_INTERNAL_MAP is defined. -//# define JSON_USE_SIMPLE_INTERNAL_ALLOCATOR 1 - -/// If defined, indicates that Json use exception to report invalid type manipulation -/// instead of C assert macro. -# define JSON_USE_EXCEPTION 1 - -/// If defined, indicates that the source file is amalgated -/// to prevent private header inclusion. -/// Remarks: it is automatically defined in the generated amalgated header. -// #define JSON_IS_AMALGAMATION - - -# ifdef JSON_IN_CPPTL -# include <cpptl/config.h> -# ifndef JSON_USE_CPPTL -# define JSON_USE_CPPTL 1 -# endif -# endif - -# ifdef JSON_IN_CPPTL -# define JSON_API CPPTL_API -# elif defined(JSON_DLL_BUILD) -# define JSON_API __declspec(dllexport) -# elif defined(JSON_DLL) -# define JSON_API __declspec(dllimport) -# else -# define JSON_API -# endif - -// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for integer -// Storages, and 64 bits integer support is disabled. -// #define JSON_NO_INT64 1 - -#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 -// Microsoft Visual Studio 6 only support conversion from __int64 to double -// (no conversion from unsigned __int64). -#define JSON_USE_INT64_DOUBLE_CONVERSION 1 -#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 - -#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 -/// Indicates that the following function is deprecated. -# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) -#endif - -#if !defined(JSONCPP_DEPRECATED) -# define JSONCPP_DEPRECATED(message) -#endif // if !defined(JSONCPP_DEPRECATED) - -namespace Json { - typedef int Int; - typedef unsigned int UInt; -# if defined(JSON_NO_INT64) - typedef int LargestInt; - typedef unsigned int LargestUInt; -# undef JSON_HAS_INT64 -# else // if defined(JSON_NO_INT64) - // For Microsoft Visual use specific types as long long is not supported -# if defined(_MSC_VER) // Microsoft Visual Studio - typedef __int64 Int64; - typedef unsigned __int64 UInt64; -# else // if defined(_MSC_VER) // Other platforms, use long long - typedef long long int Int64; - typedef unsigned long long int UInt64; -# endif // if defined(_MSC_VER) - typedef Int64 LargestInt; - typedef UInt64 LargestUInt; -# define JSON_HAS_INT64 -# endif // if defined(JSON_NO_INT64) -} // end namespace Json - - -#endif // JSON_CONFIG_H_INCLUDED diff --git a/ext/jsoncpp/include/json/features.h b/ext/jsoncpp/include/json/features.h deleted file mode 100644 index 43532784..00000000 --- a/ext/jsoncpp/include/json/features.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_FEATURES_H_INCLUDED -# define CPPTL_JSON_FEATURES_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -# include "forwards.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - - /** \brief Configuration passed to reader and writer. - * This configuration object can be used to force the Reader or Writer - * to behave in a standard conforming way. - */ - class JSON_API Features - { - public: - /** \brief A configuration that allows all features and assumes all strings are UTF-8. - * - C & C++ comments are allowed - * - Root object can be any JSON value - * - Assumes Value strings are encoded in UTF-8 - */ - static Features all(); - - /** \brief A configuration that is strictly compatible with the JSON specification. - * - Comments are forbidden. - * - Root object must be either an array or an object value. - * - Assumes Value strings are encoded in UTF-8 - */ - static Features strictMode(); - - /** \brief Initialize the configuration like JsonConfig::allFeatures; - */ - Features(); - - /// \c true if comments are allowed. Default: \c true. - bool allowComments_; - - /// \c true if root must be either an array or an object value. Default: \c false. - bool strictRoot_; - }; - -} // namespace Json - -#endif // CPPTL_JSON_FEATURES_H_INCLUDED diff --git a/ext/jsoncpp/include/json/forwards.h b/ext/jsoncpp/include/json/forwards.h deleted file mode 100644 index ab863da8..00000000 --- a/ext/jsoncpp/include/json/forwards.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_FORWARDS_H_INCLUDED -# define JSON_FORWARDS_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -# include "config.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - - // writer.h - class FastWriter; - class StyledWriter; - - // reader.h - class Reader; - - // features.h - class Features; - - // value.h - typedef unsigned int ArrayIndex; - class StaticString; - class Path; - class PathArgument; - class Value; - class ValueIteratorBase; - class ValueIterator; - class ValueConstIterator; -#ifdef JSON_VALUE_USE_INTERNAL_MAP - class ValueMapAllocator; - class ValueInternalLink; - class ValueInternalArray; - class ValueInternalMap; -#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP - -} // namespace Json - - -#endif // JSON_FORWARDS_H_INCLUDED diff --git a/ext/jsoncpp/include/json/json.h b/ext/jsoncpp/include/json/json.h deleted file mode 100644 index da5fc967..00000000 --- a/ext/jsoncpp/include/json/json.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_JSON_H_INCLUDED -# define JSON_JSON_H_INCLUDED - -# include "autolink.h" -# include "value.h" -# include "reader.h" -# include "writer.h" -# include "features.h" - -#endif // JSON_JSON_H_INCLUDED diff --git a/ext/jsoncpp/include/json/reader.h b/ext/jsoncpp/include/json/reader.h deleted file mode 100644 index 0a324dfc..00000000 --- a/ext/jsoncpp/include/json/reader.h +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_READER_H_INCLUDED -# define CPPTL_JSON_READER_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -# include "features.h" -# include "value.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -# include <deque> -# include <stack> -# include <string> -# include <iostream> - -namespace Json { - - /** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a Value. - * - */ - class JSON_API Reader - { - public: - typedef char Char; - typedef const Char *Location; - - /** \brief Constructs a Reader allowing all features - * for parsing. - */ - Reader(); - - /** \brief Constructs a Reader allowing the specified feature set - * for parsing. - */ - Reader( const Features &features ); - - /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them back during - * serialization, \c false to discard comments. - * This parameter is ignored if Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an error occurred. - */ - bool parse( const std::string &document, - Value &root, - bool collectComments = true ); - - /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document. - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the document to read. - \ Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them back during - * serialization, \c false to discard comments. - * This parameter is ignored if Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an error occurred. - */ - bool parse( const char *beginDoc, const char *endDoc, - Value &root, - bool collectComments = true ); - - /// \brief Parse from input stream. - /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse( std::istream &is, - Value &root, - bool collectComments = true ); - - /** \brief Returns a user friendly string that list errors in the parsed document. - * \return Formatted error message with the list of errors with their location in - * the parsed document. An empty string is returned if no error occurred - * during parsing. - * \deprecated Use getFormattedErrorMessages() instead (typo fix). - */ - JSONCPP_DEPRECATED("Use getFormattedErrorMessages instead") - std::string getFormatedErrorMessages() const; - - /** \brief Returns a user friendly string that list errors in the parsed document. - * \return Formatted error message with the list of errors with their location in - * the parsed document. An empty string is returned if no error occurred - * during parsing. - */ - std::string getFormattedErrorMessages() const; - - private: - enum TokenType - { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token - { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo - { - public: - Token token_; - std::string message_; - Location extra_; - }; - - typedef std::deque<ErrorInfo> Errors; - - bool expectToken( TokenType type, Token &token, const char *message ); - bool readToken( Token &token ); - void skipSpaces(); - bool match( Location pattern, - int patternLength ); - bool readComment(); - bool readCStyleComment(); - bool readCppStyleComment(); - bool readString(); - void readNumber(); - bool readValue(); - bool readObject( Token &token ); - bool readArray( Token &token ); - bool decodeNumber( Token &token ); - bool decodeString( Token &token ); - bool decodeString( Token &token, std::string &decoded ); - bool decodeDouble( Token &token ); - bool decodeUnicodeCodePoint( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ); - bool decodeUnicodeEscapeSequence( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ); - bool addError( const std::string &message, - Token &token, - Location extra = 0 ); - bool recoverFromError( TokenType skipUntilToken ); - bool addErrorAndRecover( const std::string &message, - Token &token, - TokenType skipUntilToken ); - void skipUntilSpace(); - Value ¤tValue(); - Char getNextChar(); - void getLocationLineAndColumn( Location location, - int &line, - int &column ) const; - std::string getLocationLineAndColumn( Location location ) const; - void addComment( Location begin, - Location end, - CommentPlacement placement ); - void skipCommentTokens( Token &token ); - - typedef std::stack<Value *> Nodes; - Nodes nodes_; - Errors errors_; - std::string document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value *lastValue_; - std::string commentsBefore_; - Features features_; - bool collectComments_; - }; - - /** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - Json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { - "dir": { - "file": { - // The input stream JSON would be nested here. - } - } - } - \endverbatim - \throw std::exception on parse error. - \see Json::operator<<() - */ - std::istream& operator>>( std::istream&, Value& ); - -} // namespace Json - -#endif // CPPTL_JSON_READER_H_INCLUDED diff --git a/ext/jsoncpp/include/json/value.h b/ext/jsoncpp/include/json/value.h deleted file mode 100644 index 32e3455e..00000000 --- a/ext/jsoncpp/include/json/value.h +++ /dev/null @@ -1,1103 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_H_INCLUDED -# define CPPTL_JSON_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -# include "forwards.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -# include <string> -# include <vector> - -# ifndef JSON_USE_CPPTL_SMALLMAP -# include <map> -# else -# include <cpptl/smallmap.h> -# endif -# ifdef JSON_USE_CPPTL -# include <cpptl/forwards.h> -# endif - -/** \brief JSON (JavaScript Object Notation). - */ -namespace Json { - - /** \brief Type of the value held by a Value object. - */ - enum ValueType - { - nullValue = 0, ///< 'null' value - intValue, ///< signed integer value - uintValue, ///< unsigned integer value - realValue, ///< double value - stringValue, ///< UTF-8 string value - booleanValue, ///< bool value - arrayValue, ///< array value (ordered list) - objectValue ///< object value (collection of name/value pairs). - }; - - enum CommentPlacement - { - commentBefore = 0, ///< a comment placed on the line before a value - commentAfterOnSameLine, ///< a comment just after a value on the same line - commentAfter, ///< a comment on the line after a value (only make sense for root value) - numberOfCommentPlacement - }; - -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator<const char *> EnumMemberNames; -// typedef CppTL::AnyEnumerator<const Value &> EnumValues; -//# endif - - /** \brief Lightweight wrapper to tag static string. - * - * Value constructor and objectValue member assignement takes advantage of the - * StaticString and avoid the cost of string duplication when storing the - * string or the member name. - * - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - class JSON_API StaticString - { - public: - explicit StaticString( const char *czstring ) - : str_( czstring ) - { - } - - operator const char *() const - { - return str_; - } - - const char *c_str() const - { - return str_; - } - - private: - const char *str_; - }; - - /** \brief Represents a <a HREF="http://www.json.org">JSON</a> value. - * - * This class is a discriminated union wrapper that can represents a: - * - signed integer [range: Value::minInt - Value::maxInt] - * - unsigned integer (range: 0 - Value::maxUInt) - * - double - * - UTF-8 string - * - boolean - * - 'null' - * - an ordered list of Value - * - collection of name/value pairs (javascript object) - * - * The type of the held value is represented by a #ValueType and - * can be obtained using type(). - * - * values of an #objectValue or #arrayValue can be accessed using operator[]() methods. - * Non const methods will automatically create the a #nullValue element - * if it does not exist. - * The sequence of an #arrayValue will be automatically resize and initialized - * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. - * - * The get() methods can be used to obtanis default value in the case the required element - * does not exist. - * - * It is possible to iterate over the list of a #objectValue values using - * the getMemberNames() method. - */ - class JSON_API Value - { - friend class ValueIteratorBase; -# ifdef JSON_VALUE_USE_INTERNAL_MAP - friend class ValueInternalLink; - friend class ValueInternalMap; -# endif - public: - typedef std::vector<std::string> Members; - typedef ValueIterator iterator; - typedef ValueConstIterator const_iterator; - typedef Json::UInt UInt; - typedef Json::Int Int; -# if defined(JSON_HAS_INT64) - typedef Json::UInt64 UInt64; - typedef Json::Int64 Int64; -#endif // defined(JSON_HAS_INT64) - typedef Json::LargestInt LargestInt; - typedef Json::LargestUInt LargestUInt; - typedef Json::ArrayIndex ArrayIndex; - - static const Value null; - /// Minimum signed integer value that can be stored in a Json::Value. - static const LargestInt minLargestInt; - /// Maximum signed integer value that can be stored in a Json::Value. - static const LargestInt maxLargestInt; - /// Maximum unsigned integer value that can be stored in a Json::Value. - static const LargestUInt maxLargestUInt; - - /// Minimum signed int value that can be stored in a Json::Value. - static const Int minInt; - /// Maximum signed int value that can be stored in a Json::Value. - static const Int maxInt; - /// Maximum unsigned int value that can be stored in a Json::Value. - static const UInt maxUInt; - - /// Minimum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 minInt64; - /// Maximum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 maxInt64; - /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. - static const UInt64 maxUInt64; - - private: -#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION -# ifndef JSON_VALUE_USE_INTERNAL_MAP - class CZString - { - public: - enum DuplicationPolicy - { - noDuplication = 0, - duplicate, - duplicateOnCopy - }; - CZString( ArrayIndex index ); - CZString( const char *cstr, DuplicationPolicy allocate ); - CZString( const CZString &other ); - ~CZString(); - CZString &operator =( const CZString &other ); - bool operator<( const CZString &other ) const; - bool operator==( const CZString &other ) const; - ArrayIndex index() const; - const char *c_str() const; - bool isStaticString() const; - private: - void swap( CZString &other ); - const char *cstr_; - ArrayIndex index_; - }; - - public: -# ifndef JSON_USE_CPPTL_SMALLMAP - typedef std::map<CZString, Value> ObjectValues; -# else - typedef CppTL::SmallMap<CZString, Value> ObjectValues; -# endif // ifndef JSON_USE_CPPTL_SMALLMAP -# endif // ifndef JSON_VALUE_USE_INTERNAL_MAP -#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - public: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass arrayValue. - To create an empty object, pass objectValue. - Another Value can then be set to this one by assignment. - This is useful since clear() and resize() will not alter types. - - Examples: - \code - Json::Value null_value; // null - Json::Value arr_value(Json::arrayValue); // [] - Json::Value obj_value(Json::objectValue); // {} - \endcode - */ - Value( ValueType type = nullValue ); - Value( Int value ); - Value( UInt value ); -#if defined(JSON_HAS_INT64) - Value( Int64 value ); - Value( UInt64 value ); -#endif // if defined(JSON_HAS_INT64) - Value( double value ); - Value( const char *value ); - Value( const char *beginValue, const char *endValue ); - /** \brief Constructs a value from a static string. - - * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to this - * constructor. - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * \endcode - */ - Value( const StaticString &value ); - Value( const std::string &value ); -# ifdef JSON_USE_CPPTL - Value( const CppTL::ConstString &value ); -# endif - Value( bool value ); - Value( const Value &other ); - ~Value(); - - Value &operator=( const Value &other ); - /// Swap values. - /// \note Currently, comments are intentionally not swapped, for - /// both logic and efficiency. - void swap( Value &other ); - - ValueType type() const; - - bool operator <( const Value &other ) const; - bool operator <=( const Value &other ) const; - bool operator >=( const Value &other ) const; - bool operator >( const Value &other ) const; - - bool operator ==( const Value &other ) const; - bool operator !=( const Value &other ) const; - - int compare( const Value &other ) const; - - const char *asCString() const; - std::string asString() const; -# ifdef JSON_USE_CPPTL - CppTL::ConstString asConstString() const; -# endif - Int asInt() const; - UInt asUInt() const; - Int64 asInt64() const; - UInt64 asUInt64() const; - LargestInt asLargestInt() const; - LargestUInt asLargestUInt() const; - float asFloat() const; - double asDouble() const; - bool asBool() const; - - bool isNull() const; - bool isBool() const; - bool isInt() const; - bool isUInt() const; - bool isIntegral() const; - bool isDouble() const; - bool isNumeric() const; - bool isString() const; - bool isArray() const; - bool isObject() const; - - bool isConvertibleTo( ValueType other ) const; - - /// Number of values in array or object - ArrayIndex size() const; - - /// \brief Return true if empty array, empty object, or null; - /// otherwise, false. - bool empty() const; - - /// Return isNull() - bool operator!() const; - - /// Remove all object members and array elements. - /// \pre type() is arrayValue, objectValue, or nullValue - /// \post type() is unchanged - void clear(); - - /// Resize the array to size elements. - /// New elements are initialized to null. - /// May only be called on nullValue or arrayValue. - /// \pre type() is arrayValue or nullValue - /// \post type() is arrayValue - void resize( ArrayIndex size ); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value &operator[]( ArrayIndex index ); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value &operator[]( int index ); - - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value &operator[]( ArrayIndex index ) const; - - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value &operator[]( int index ) const; - - /// If the array contains at least index+1 elements, returns the element value, - /// otherwise returns defaultValue. - Value get( ArrayIndex index, - const Value &defaultValue ) const; - /// Return true if index < size(). - bool isValidIndex( ArrayIndex index ) const; - /// \brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; - Value &append( const Value &value ); - - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const char *key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const char *key ) const; - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const std::string &key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const std::string &key ) const; - /** \brief Access an object value by name, create a null member if it does not exist. - - * If the object as no entry for that name, then the member name used to store - * the new entry is not duplicated. - * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - Value &operator[]( const StaticString &key ); -# ifdef JSON_USE_CPPTL - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const CppTL::ConstString &key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const CppTL::ConstString &key ) const; -# endif - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const char *key, - const Value &defaultValue ) const; - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const std::string &key, - const Value &defaultValue ) const; -# ifdef JSON_USE_CPPTL - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const CppTL::ConstString &key, - const Value &defaultValue ) const; -# endif - /// \brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \return the removed Value, or null. - /// \pre type() is objectValue or nullValue - /// \post type() is unchanged - Value removeMember( const char* key ); - /// Same as removeMember(const char*) - Value removeMember( const std::string &key ); - - /// Return true if the object has a member named key. - bool isMember( const char *key ) const; - /// Return true if the object has a member named key. - bool isMember( const std::string &key ) const; -# ifdef JSON_USE_CPPTL - /// Return true if the object has a member named key. - bool isMember( const CppTL::ConstString &key ) const; -# endif - - /// \brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is objectValue or nullValue - /// \post if type() was nullValue, it remains nullValue - Members getMemberNames() const; - -//# ifdef JSON_USE_CPPTL -// EnumMemberNames enumMemberNames() const; -// EnumValues enumValues() const; -//# endif - - /// Comments must be //... or /* ... */ - void setComment( const char *comment, - CommentPlacement placement ); - /// Comments must be //... or /* ... */ - void setComment( const std::string &comment, - CommentPlacement placement ); - bool hasComment( CommentPlacement placement ) const; - /// Include delimiters and embedded newlines. - std::string getComment( CommentPlacement placement ) const; - - std::string toStyledString() const; - - const_iterator begin() const; - const_iterator end() const; - - iterator begin(); - iterator end(); - - private: - Value &resolveReference( const char *key, - bool isStatic ); - -# ifdef JSON_VALUE_USE_INTERNAL_MAP - inline bool isItemAvailable() const - { - return itemIsUsed_ == 0; - } - - inline void setItemUsed( bool isUsed = true ) - { - itemIsUsed_ = isUsed ? 1 : 0; - } - - inline bool isMemberNameStatic() const - { - return memberNameIsStatic_ == 0; - } - - inline void setMemberNameIsStatic( bool isStatic ) - { - memberNameIsStatic_ = isStatic ? 1 : 0; - } -# endif // # ifdef JSON_VALUE_USE_INTERNAL_MAP - - private: - struct CommentInfo - { - CommentInfo(); - ~CommentInfo(); - - void setComment( const char *text ); - - char *comment_; - }; - - //struct MemberNamesTransform - //{ - // typedef const char *result_type; - // const char *operator()( const CZString &name ) const - // { - // return name.c_str(); - // } - //}; - - union ValueHolder - { - LargestInt int_; - LargestUInt uint_; - double real_; - bool bool_; - char *string_; -# ifdef JSON_VALUE_USE_INTERNAL_MAP - ValueInternalArray *array_; - ValueInternalMap *map_; -#else - ObjectValues *map_; -# endif - } value_; - ValueType type_ : 8; - int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. -# ifdef JSON_VALUE_USE_INTERNAL_MAP - unsigned int itemIsUsed_ : 1; // used by the ValueInternalMap container. - int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. -# endif - CommentInfo *comments_; - }; - - - /** \brief Experimental and untested: represents an element of the "path" to access a node. - */ - class PathArgument - { - public: - friend class Path; - - PathArgument(); - PathArgument( ArrayIndex index ); - PathArgument( const char *key ); - PathArgument( const std::string &key ); - - private: - enum Kind - { - kindNone = 0, - kindIndex, - kindKey - }; - std::string key_; - ArrayIndex index_; - Kind kind_; - }; - - /** \brief Experimental and untested: represents a "path" to access a node. - * - * Syntax: - * - "." => root node - * - ".[n]" => elements at index 'n' of root node (an array value) - * - ".name" => member named 'name' of root node (an object value) - * - ".name1.name2.name3" - * - ".[0][1][2].name1[3]" - * - ".%" => member name is provided as parameter - * - ".[%]" => index is provied as parameter - */ - class Path - { - public: - Path( const std::string &path, - const PathArgument &a1 = PathArgument(), - const PathArgument &a2 = PathArgument(), - const PathArgument &a3 = PathArgument(), - const PathArgument &a4 = PathArgument(), - const PathArgument &a5 = PathArgument() ); - - const Value &resolve( const Value &root ) const; - Value resolve( const Value &root, - const Value &defaultValue ) const; - /// Creates the "path" to access the specified node and returns a reference on the node. - Value &make( Value &root ) const; - - private: - typedef std::vector<const PathArgument *> InArgs; - typedef std::vector<PathArgument> Args; - - void makePath( const std::string &path, - const InArgs &in ); - void addPathInArg( const std::string &path, - const InArgs &in, - InArgs::const_iterator &itInArg, - PathArgument::Kind kind ); - void invalidPath( const std::string &path, - int location ); - - Args args_; - }; - - - -#ifdef JSON_VALUE_USE_INTERNAL_MAP - /** \brief Allocator to customize Value internal map. - * Below is an example of a simple implementation (default implementation actually - * use memory pool for speed). - * \code - class DefaultValueMapAllocator : public ValueMapAllocator - { - public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - return new ValueInternalMap(); - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - return new ValueInternalMap( other ); - } - - virtual void destructMap( ValueInternalMap *map ) - { - delete map; - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - return new ValueInternalLink(); - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - delete link; - } - }; - * \endcode - */ - class JSON_API ValueMapAllocator - { - public: - virtual ~ValueMapAllocator(); - virtual ValueInternalMap *newMap() = 0; - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) = 0; - virtual void destructMap( ValueInternalMap *map ) = 0; - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) = 0; - virtual void releaseMapBuckets( ValueInternalLink *links ) = 0; - virtual ValueInternalLink *allocateMapLink() = 0; - virtual void releaseMapLink( ValueInternalLink *link ) = 0; - }; - - /** \brief ValueInternalMap hash-map bucket chain link (for internal use only). - * \internal previous_ & next_ allows for bidirectional traversal. - */ - class JSON_API ValueInternalLink - { - public: - enum { itemPerLink = 6 }; // sizeof(ValueInternalLink) = 128 on 32 bits architecture. - enum InternalFlags { - flagAvailable = 0, - flagUsed = 1 - }; - - ValueInternalLink(); - - ~ValueInternalLink(); - - Value items_[itemPerLink]; - char *keys_[itemPerLink]; - ValueInternalLink *previous_; - ValueInternalLink *next_; - }; - - - /** \brief A linked page based hash-table implementation used internally by Value. - * \internal ValueInternalMap is a tradional bucket based hash-table, with a linked - * list in each bucket to handle collision. There is an addional twist in that - * each node of the collision linked list is a page containing a fixed amount of - * value. This provides a better compromise between memory usage and speed. - * - * Each bucket is made up of a chained list of ValueInternalLink. The last - * link of a given bucket can be found in the 'previous_' field of the following bucket. - * The last link of the last bucket is stored in tailLink_ as it has no following bucket. - * Only the last link of a bucket may contains 'available' item. The last link always - * contains at least one element unless is it the bucket one very first link. - */ - class JSON_API ValueInternalMap - { - friend class ValueIteratorBase; - friend class Value; - public: - typedef unsigned int HashKey; - typedef unsigned int BucketIndex; - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - struct IteratorState - { - IteratorState() - : map_(0) - , link_(0) - , itemIndex_(0) - , bucketIndex_(0) - { - } - ValueInternalMap *map_; - ValueInternalLink *link_; - BucketIndex itemIndex_; - BucketIndex bucketIndex_; - }; -# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - ValueInternalMap(); - ValueInternalMap( const ValueInternalMap &other ); - ValueInternalMap &operator =( const ValueInternalMap &other ); - ~ValueInternalMap(); - - void swap( ValueInternalMap &other ); - - BucketIndex size() const; - - void clear(); - - bool reserveDelta( BucketIndex growth ); - - bool reserve( BucketIndex newItemCount ); - - const Value *find( const char *key ) const; - - Value *find( const char *key ); - - Value &resolveReference( const char *key, - bool isStatic ); - - void remove( const char *key ); - - void doActualRemove( ValueInternalLink *link, - BucketIndex index, - BucketIndex bucketIndex ); - - ValueInternalLink *&getLastLinkInBucket( BucketIndex bucketIndex ); - - Value &setNewItem( const char *key, - bool isStatic, - ValueInternalLink *link, - BucketIndex index ); - - Value &unsafeAdd( const char *key, - bool isStatic, - HashKey hashedKey ); - - HashKey hash( const char *key ) const; - - int compare( const ValueInternalMap &other ) const; - - private: - void makeBeginIterator( IteratorState &it ) const; - void makeEndIterator( IteratorState &it ) const; - static bool equals( const IteratorState &x, const IteratorState &other ); - static void increment( IteratorState &iterator ); - static void incrementBucket( IteratorState &iterator ); - static void decrement( IteratorState &iterator ); - static const char *key( const IteratorState &iterator ); - static const char *key( const IteratorState &iterator, bool &isStatic ); - static Value &value( const IteratorState &iterator ); - static int distance( const IteratorState &x, const IteratorState &y ); - - private: - ValueInternalLink *buckets_; - ValueInternalLink *tailLink_; - BucketIndex bucketsSize_; - BucketIndex itemCount_; - }; - - /** \brief A simplified deque implementation used internally by Value. - * \internal - * It is based on a list of fixed "page", each page contains a fixed number of items. - * Instead of using a linked-list, a array of pointer is used for fast item look-up. - * Look-up for an element is as follow: - * - compute page index: pageIndex = itemIndex / itemsPerPage - * - look-up item in page: pages_[pageIndex][itemIndex % itemsPerPage] - * - * Insertion is amortized constant time (only the array containing the index of pointers - * need to be reallocated when items are appended). - */ - class JSON_API ValueInternalArray - { - friend class Value; - friend class ValueIteratorBase; - public: - enum { itemsPerPage = 8 }; // should be a power of 2 for fast divide and modulo. - typedef Value::ArrayIndex ArrayIndex; - typedef unsigned int PageIndex; - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - struct IteratorState // Must be a POD - { - IteratorState() - : array_(0) - , currentPageIndex_(0) - , currentItemIndex_(0) - { - } - ValueInternalArray *array_; - Value **currentPageIndex_; - unsigned int currentItemIndex_; - }; -# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - ValueInternalArray(); - ValueInternalArray( const ValueInternalArray &other ); - ValueInternalArray &operator =( const ValueInternalArray &other ); - ~ValueInternalArray(); - void swap( ValueInternalArray &other ); - - void clear(); - void resize( ArrayIndex newSize ); - - Value &resolveReference( ArrayIndex index ); - - Value *find( ArrayIndex index ) const; - - ArrayIndex size() const; - - int compare( const ValueInternalArray &other ) const; - - private: - static bool equals( const IteratorState &x, const IteratorState &other ); - static void increment( IteratorState &iterator ); - static void decrement( IteratorState &iterator ); - static Value &dereference( const IteratorState &iterator ); - static Value &unsafeDereference( const IteratorState &iterator ); - static int distance( const IteratorState &x, const IteratorState &y ); - static ArrayIndex indexOf( const IteratorState &iterator ); - void makeBeginIterator( IteratorState &it ) const; - void makeEndIterator( IteratorState &it ) const; - void makeIterator( IteratorState &it, ArrayIndex index ) const; - - void makeIndexValid( ArrayIndex index ); - - Value **pages_; - ArrayIndex size_; - PageIndex pageCount_; - }; - - /** \brief Experimental: do not use. Allocator to customize Value internal array. - * Below is an example of a simple implementation (actual implementation use - * memory pool). - \code -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - return new ValueInternalArray(); - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - return new ValueInternalArray( other ); - } - - virtual void destruct( ValueInternalArray *array ) - { - delete array; - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast<Value **>( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast<Value *>( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - free( value ); - } -}; - \endcode - */ - class JSON_API ValueArrayAllocator - { - public: - virtual ~ValueArrayAllocator(); - virtual ValueInternalArray *newArray() = 0; - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) = 0; - virtual void destructArray( ValueInternalArray *array ) = 0; - /** \brief Reallocate array page index. - * Reallocates an array of pointer on each page. - * \param indexes [input] pointer on the current index. May be \c NULL. - * [output] pointer on the new index of at least - * \a minNewIndexCount pages. - * \param indexCount [input] current number of pages in the index. - * [output] number of page the reallocated index can handle. - * \b MUST be >= \a minNewIndexCount. - * \param minNewIndexCount Minimum number of page the new index must be able to - * handle. - */ - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) = 0; - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) = 0; - virtual Value *allocateArrayPage() = 0; - virtual void releaseArrayPage( Value *value ) = 0; - }; -#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP - - - /** \brief base class for Value iterators. - * - */ - class ValueIteratorBase - { - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef ValueIteratorBase SelfType; - - ValueIteratorBase(); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueIteratorBase( const Value::ObjectValues::iterator ¤t ); -#else - ValueIteratorBase( const ValueInternalArray::IteratorState &state ); - ValueIteratorBase( const ValueInternalMap::IteratorState &state ); -#endif - - bool operator ==( const SelfType &other ) const - { - return isEqual( other ); - } - - bool operator !=( const SelfType &other ) const - { - return !isEqual( other ); - } - - difference_type operator -( const SelfType &other ) const - { - return computeDistance( other ); - } - - /// Return either the index or the member name of the referenced value as a Value. - Value key() const; - - /// Return the index of the referenced Value. -1 if it is not an arrayValue. - UInt index() const; - - /// Return the member name of the referenced Value. "" if it is not an objectValue. - const char *memberName() const; - - protected: - Value &deref() const; - - void increment(); - - void decrement(); - - difference_type computeDistance( const SelfType &other ) const; - - bool isEqual( const SelfType &other ) const; - - void copy( const SelfType &other ); - - private: -#ifndef JSON_VALUE_USE_INTERNAL_MAP - Value::ObjectValues::iterator current_; - // Indicates that iterator is for a null value. - bool isNull_; -#else - union - { - ValueInternalArray::IteratorState array_; - ValueInternalMap::IteratorState map_; - } iterator_; - bool isArray_; -#endif - }; - - /** \brief const iterator for object and array value. - * - */ - class ValueConstIterator : public ValueIteratorBase - { - friend class Value; - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef const Value &reference; - typedef const Value *pointer; - typedef ValueConstIterator SelfType; - - ValueConstIterator(); - private: - /*! \internal Use by Value to create an iterator. - */ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueConstIterator( const Value::ObjectValues::iterator ¤t ); -#else - ValueConstIterator( const ValueInternalArray::IteratorState &state ); - ValueConstIterator( const ValueInternalMap::IteratorState &state ); -#endif - public: - SelfType &operator =( const ValueIteratorBase &other ); - - SelfType operator++( int ) - { - SelfType temp( *this ); - ++*this; - return temp; - } - - SelfType operator--( int ) - { - SelfType temp( *this ); - --*this; - return temp; - } - - SelfType &operator--() - { - decrement(); - return *this; - } - - SelfType &operator++() - { - increment(); - return *this; - } - - reference operator *() const - { - return deref(); - } - }; - - - /** \brief Iterator for object and array value. - */ - class ValueIterator : public ValueIteratorBase - { - friend class Value; - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef Value &reference; - typedef Value *pointer; - typedef ValueIterator SelfType; - - ValueIterator(); - ValueIterator( const ValueConstIterator &other ); - ValueIterator( const ValueIterator &other ); - private: - /*! \internal Use by Value to create an iterator. - */ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueIterator( const Value::ObjectValues::iterator ¤t ); -#else - ValueIterator( const ValueInternalArray::IteratorState &state ); - ValueIterator( const ValueInternalMap::IteratorState &state ); -#endif - public: - - SelfType &operator =( const SelfType &other ); - - SelfType operator++( int ) - { - SelfType temp( *this ); - ++*this; - return temp; - } - - SelfType operator--( int ) - { - SelfType temp( *this ); - --*this; - return temp; - } - - SelfType &operator--() - { - decrement(); - return *this; - } - - SelfType &operator++() - { - increment(); - return *this; - } - - reference operator *() const - { - return deref(); - } - }; - - -} // namespace Json - - -#endif // CPPTL_JSON_H_INCLUDED diff --git a/ext/jsoncpp/include/json/writer.h b/ext/jsoncpp/include/json/writer.h deleted file mode 100644 index 47893636..00000000 --- a/ext/jsoncpp/include/json/writer.h +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_WRITER_H_INCLUDED -# define JSON_WRITER_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -# include "value.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -# include <vector> -# include <string> -# include <iostream> - -namespace Json { - - class Value; - - /** \brief Abstract class for writers. - */ - class JSON_API Writer - { - public: - virtual ~Writer(); - - virtual std::string write( const Value &root ) = 0; - }; - - /** \brief Outputs a Value in <a HREF="http://www.json.org">JSON</a> format without formatting (not human friendly). - * - * The JSON document is written in a single line. It is not intended for 'human' consumption, - * but may be usefull to support feature such as RPC where bandwith is limited. - * \sa Reader, Value - */ - class JSON_API FastWriter : public Writer - { - public: - FastWriter(); - virtual ~FastWriter(){} - - void enableYAMLCompatibility(); - - public: // overridden from Writer - virtual std::string write( const Value &root ); - - private: - void writeValue( const Value &value ); - - std::string document_; - bool yamlCompatiblityEnabled_; - }; - - /** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a human friendly way. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value types, - * and all the values fit on one lines, then print the array on a single line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their #CommentPlacement. - * - * \sa Reader, Value, Value::setComment() - */ - class JSON_API StyledWriter: public Writer - { - public: - StyledWriter(); - virtual ~StyledWriter(){} - - public: // overridden from Writer - /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format. - * \param root Value to serialize. - * \return String containing the JSON document that represents the root value. - */ - virtual std::string write( const Value &root ); - - private: - void writeValue( const Value &value ); - void writeArrayValue( const Value &value ); - bool isMultineArray( const Value &value ); - void pushValue( const std::string &value ); - void writeIndent(); - void writeWithIndent( const std::string &value ); - void indent(); - void unindent(); - void writeCommentBeforeValue( const Value &root ); - void writeCommentAfterValueOnSameLine( const Value &root ); - bool hasCommentForValue( const Value &value ); - static std::string normalizeEOL( const std::string &text ); - - typedef std::vector<std::string> ChildValues; - - ChildValues childValues_; - std::string document_; - std::string indentString_; - int rightMargin_; - int indentSize_; - bool addChildValues_; - }; - - /** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a human friendly way, - to a stream rather than to a string. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value types, - * and all the values fit on one lines, then print the array on a single line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their #CommentPlacement. - * - * \param indentation Each level will be indented by this amount extra. - * \sa Reader, Value, Value::setComment() - */ - class JSON_API StyledStreamWriter - { - public: - StyledStreamWriter( std::string indentation="\t" ); - ~StyledStreamWriter(){} - - public: - /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format. - * \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not return a value. - */ - void write( std::ostream &out, const Value &root ); - - private: - void writeValue( const Value &value ); - void writeArrayValue( const Value &value ); - bool isMultineArray( const Value &value ); - void pushValue( const std::string &value ); - void writeIndent(); - void writeWithIndent( const std::string &value ); - void indent(); - void unindent(); - void writeCommentBeforeValue( const Value &root ); - void writeCommentAfterValueOnSameLine( const Value &root ); - bool hasCommentForValue( const Value &value ); - static std::string normalizeEOL( const std::string &text ); - - typedef std::vector<std::string> ChildValues; - - ChildValues childValues_; - std::ostream* document_; - std::string indentString_; - int rightMargin_; - std::string indentation_; - bool addChildValues_; - }; - -# if defined(JSON_HAS_INT64) - std::string JSON_API valueToString( Int value ); - std::string JSON_API valueToString( UInt value ); -# endif // if defined(JSON_HAS_INT64) - std::string JSON_API valueToString( LargestInt value ); - std::string JSON_API valueToString( LargestUInt value ); - std::string JSON_API valueToString( double value ); - std::string JSON_API valueToString( bool value ); - std::string JSON_API valueToQuotedString( const char *value ); - - /// \brief Output using the StyledStreamWriter. - /// \see Json::operator>>() - std::ostream& operator<<( std::ostream&, const Value &root ); - -} // namespace Json - - - -#endif // JSON_WRITER_H_INCLUDED diff --git a/ext/jsoncpp/src/json_batchallocator.h b/ext/jsoncpp/src/json_batchallocator.h deleted file mode 100644 index 173e2ed2..00000000 --- a/ext/jsoncpp/src/json_batchallocator.h +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED -# define JSONCPP_BATCHALLOCATOR_H_INCLUDED - -# include <stdlib.h> -# include <assert.h> - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - -namespace Json { - -/* Fast memory allocator. - * - * This memory allocator allocates memory for a batch of object (specified by - * the page size, the number of object in each page). - * - * It does not allow the destruction of a single object. All the allocated objects - * can be destroyed at once. The memory can be either released or reused for future - * allocation. - * - * The in-place new operator must be used to construct the object using the pointer - * returned by allocate. - */ -template<typename AllocatedType - ,const unsigned int objectPerAllocation> -class BatchAllocator -{ -public: - typedef AllocatedType Type; - - BatchAllocator( unsigned int objectsPerPage = 255 ) - : freeHead_( 0 ) - , objectsPerPage_( objectsPerPage ) - { -// printf( "Size: %d => %s\n", sizeof(AllocatedType), typeid(AllocatedType).name() ); - assert( sizeof(AllocatedType) * objectPerAllocation >= sizeof(AllocatedType *) ); // We must be able to store a slist in the object free space. - assert( objectsPerPage >= 16 ); - batches_ = allocateBatch( 0 ); // allocated a dummy page - currentBatch_ = batches_; - } - - ~BatchAllocator() - { - for ( BatchInfo *batch = batches_; batch; ) - { - BatchInfo *nextBatch = batch->next_; - free( batch ); - batch = nextBatch; - } - } - - /// allocate space for an array of objectPerAllocation object. - /// @warning it is the responsability of the caller to call objects constructors. - AllocatedType *allocate() - { - if ( freeHead_ ) // returns node from free list. - { - AllocatedType *object = freeHead_; - freeHead_ = *(AllocatedType **)object; - return object; - } - if ( currentBatch_->used_ == currentBatch_->end_ ) - { - currentBatch_ = currentBatch_->next_; - while ( currentBatch_ && currentBatch_->used_ == currentBatch_->end_ ) - currentBatch_ = currentBatch_->next_; - - if ( !currentBatch_ ) // no free batch found, allocate a new one - { - currentBatch_ = allocateBatch( objectsPerPage_ ); - currentBatch_->next_ = batches_; // insert at the head of the list - batches_ = currentBatch_; - } - } - AllocatedType *allocated = currentBatch_->used_; - currentBatch_->used_ += objectPerAllocation; - return allocated; - } - - /// Release the object. - /// @warning it is the responsability of the caller to actually destruct the object. - void release( AllocatedType *object ) - { - assert( object != 0 ); - *(AllocatedType **)object = freeHead_; - freeHead_ = object; - } - -private: - struct BatchInfo - { - BatchInfo *next_; - AllocatedType *used_; - AllocatedType *end_; - AllocatedType buffer_[objectPerAllocation]; - }; - - // disabled copy constructor and assignement operator. - BatchAllocator( const BatchAllocator & ); - void operator =( const BatchAllocator &); - - static BatchInfo *allocateBatch( unsigned int objectsPerPage ) - { - const unsigned int mallocSize = sizeof(BatchInfo) - sizeof(AllocatedType)* objectPerAllocation - + sizeof(AllocatedType) * objectPerAllocation * objectsPerPage; - BatchInfo *batch = static_cast<BatchInfo*>( malloc( mallocSize ) ); - batch->next_ = 0; - batch->used_ = batch->buffer_; - batch->end_ = batch->buffer_ + objectsPerPage; - return batch; - } - - BatchInfo *batches_; - BatchInfo *currentBatch_; - /// Head of a single linked list within the allocated space of freeed object - AllocatedType *freeHead_; - unsigned int objectsPerPage_; -}; - - -} // namespace Json - -# endif // ifndef JSONCPP_DOC_INCLUDE_IMPLEMENTATION - -#endif // JSONCPP_BATCHALLOCATOR_H_INCLUDED - diff --git a/ext/jsoncpp/src/json_internalarray.inl b/ext/jsoncpp/src/json_internalarray.inl deleted file mode 100644 index 3a532ad7..00000000 --- a/ext/jsoncpp/src/json_internalarray.inl +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -// included by json_value.cpp - -namespace Json { - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueInternalArray -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueArrayAllocator::~ValueArrayAllocator() -{ -} - -// ////////////////////////////////////////////////////////////////// -// class DefaultValueArrayAllocator -// ////////////////////////////////////////////////////////////////// -#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - return new ValueInternalArray(); - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - return new ValueInternalArray( other ); - } - - virtual void destructArray( ValueInternalArray *array ) - { - delete array; - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast<Value **>( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast<Value *>( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - free( value ); - } -}; - -#else // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -/// @todo make this thread-safe (lock when accessign batch allocator) -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - ValueInternalArray *array = arraysAllocator_.allocate(); - new (array) ValueInternalArray(); // placement new - return array; - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - ValueInternalArray *array = arraysAllocator_.allocate(); - new (array) ValueInternalArray( other ); // placement new - return array; - } - - virtual void destructArray( ValueInternalArray *array ) - { - if ( array ) - { - array->~ValueInternalArray(); - arraysAllocator_.release( array ); - } - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast<Value **>( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast<Value *>( pagesAllocator_.allocate() ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - pagesAllocator_.release( value ); - } -private: - BatchAllocator<ValueInternalArray,1> arraysAllocator_; - BatchAllocator<Value,ValueInternalArray::itemsPerPage> pagesAllocator_; -}; -#endif // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR - -static ValueArrayAllocator *&arrayAllocator() -{ - static DefaultValueArrayAllocator defaultAllocator; - static ValueArrayAllocator *arrayAllocator = &defaultAllocator; - return arrayAllocator; -} - -static struct DummyArrayAllocatorInitializer { - DummyArrayAllocatorInitializer() - { - arrayAllocator(); // ensure arrayAllocator() statics are initialized before main(). - } -} dummyArrayAllocatorInitializer; - -// ////////////////////////////////////////////////////////////////// -// class ValueInternalArray -// ////////////////////////////////////////////////////////////////// -bool -ValueInternalArray::equals( const IteratorState &x, - const IteratorState &other ) -{ - return x.array_ == other.array_ - && x.currentItemIndex_ == other.currentItemIndex_ - && x.currentPageIndex_ == other.currentPageIndex_; -} - - -void -ValueInternalArray::increment( IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && - (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ - != it.array_->size_, - "ValueInternalArray::increment(): moving iterator beyond end" ); - ++(it.currentItemIndex_); - if ( it.currentItemIndex_ == itemsPerPage ) - { - it.currentItemIndex_ = 0; - ++(it.currentPageIndex_); - } -} - - -void -ValueInternalArray::decrement( IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && it.currentPageIndex_ == it.array_->pages_ - && it.currentItemIndex_ == 0, - "ValueInternalArray::decrement(): moving iterator beyond end" ); - if ( it.currentItemIndex_ == 0 ) - { - it.currentItemIndex_ = itemsPerPage-1; - --(it.currentPageIndex_); - } - else - { - --(it.currentItemIndex_); - } -} - - -Value & -ValueInternalArray::unsafeDereference( const IteratorState &it ) -{ - return (*(it.currentPageIndex_))[it.currentItemIndex_]; -} - - -Value & -ValueInternalArray::dereference( const IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && - (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ - < it.array_->size_, - "ValueInternalArray::dereference(): dereferencing invalid iterator" ); - return unsafeDereference( it ); -} - -void -ValueInternalArray::makeBeginIterator( IteratorState &it ) const -{ - it.array_ = const_cast<ValueInternalArray *>( this ); - it.currentItemIndex_ = 0; - it.currentPageIndex_ = pages_; -} - - -void -ValueInternalArray::makeIterator( IteratorState &it, ArrayIndex index ) const -{ - it.array_ = const_cast<ValueInternalArray *>( this ); - it.currentItemIndex_ = index % itemsPerPage; - it.currentPageIndex_ = pages_ + index / itemsPerPage; -} - - -void -ValueInternalArray::makeEndIterator( IteratorState &it ) const -{ - makeIterator( it, size_ ); -} - - -ValueInternalArray::ValueInternalArray() - : pages_( 0 ) - , size_( 0 ) - , pageCount_( 0 ) -{ -} - - -ValueInternalArray::ValueInternalArray( const ValueInternalArray &other ) - : pages_( 0 ) - , pageCount_( 0 ) - , size_( other.size_ ) -{ - PageIndex minNewPages = other.size_ / itemsPerPage; - arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); - JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, - "ValueInternalArray::reserve(): bad reallocation" ); - IteratorState itOther; - other.makeBeginIterator( itOther ); - Value *value; - for ( ArrayIndex index = 0; index < size_; ++index, increment(itOther) ) - { - if ( index % itemsPerPage == 0 ) - { - PageIndex pageIndex = index / itemsPerPage; - value = arrayAllocator()->allocateArrayPage(); - pages_[pageIndex] = value; - } - new (value) Value( dereference( itOther ) ); - } -} - - -ValueInternalArray & -ValueInternalArray::operator =( const ValueInternalArray &other ) -{ - ValueInternalArray temp( other ); - swap( temp ); - return *this; -} - - -ValueInternalArray::~ValueInternalArray() -{ - // destroy all constructed items - IteratorState it; - IteratorState itEnd; - makeBeginIterator( it); - makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - value->~Value(); - } - // release all pages - PageIndex lastPageIndex = size_ / itemsPerPage; - for ( PageIndex pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex ) - arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); - // release pages index - arrayAllocator()->releaseArrayPageIndex( pages_, pageCount_ ); -} - - -void -ValueInternalArray::swap( ValueInternalArray &other ) -{ - Value **tempPages = pages_; - pages_ = other.pages_; - other.pages_ = tempPages; - ArrayIndex tempSize = size_; - size_ = other.size_; - other.size_ = tempSize; - PageIndex tempPageCount = pageCount_; - pageCount_ = other.pageCount_; - other.pageCount_ = tempPageCount; -} - -void -ValueInternalArray::clear() -{ - ValueInternalArray dummy; - swap( dummy ); -} - - -void -ValueInternalArray::resize( ArrayIndex newSize ) -{ - if ( newSize == 0 ) - clear(); - else if ( newSize < size_ ) - { - IteratorState it; - IteratorState itEnd; - makeIterator( it, newSize ); - makeIterator( itEnd, size_ ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - value->~Value(); - } - PageIndex pageIndex = (newSize + itemsPerPage - 1) / itemsPerPage; - PageIndex lastPageIndex = size_ / itemsPerPage; - for ( ; pageIndex < lastPageIndex; ++pageIndex ) - arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); - size_ = newSize; - } - else if ( newSize > size_ ) - resolveReference( newSize ); -} - - -void -ValueInternalArray::makeIndexValid( ArrayIndex index ) -{ - // Need to enlarge page index ? - if ( index >= pageCount_ * itemsPerPage ) - { - PageIndex minNewPages = (index + 1) / itemsPerPage; - arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); - JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, "ValueInternalArray::reserve(): bad reallocation" ); - } - - // Need to allocate new pages ? - ArrayIndex nextPageIndex = - (size_ % itemsPerPage) != 0 ? size_ - (size_%itemsPerPage) + itemsPerPage - : size_; - if ( nextPageIndex <= index ) - { - PageIndex pageIndex = nextPageIndex / itemsPerPage; - PageIndex pageToAllocate = (index - nextPageIndex) / itemsPerPage + 1; - for ( ; pageToAllocate-- > 0; ++pageIndex ) - pages_[pageIndex] = arrayAllocator()->allocateArrayPage(); - } - - // Initialize all new entries - IteratorState it; - IteratorState itEnd; - makeIterator( it, size_ ); - size_ = index + 1; - makeIterator( itEnd, size_ ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - new (value) Value(); // Construct a default value using placement new - } -} - -Value & -ValueInternalArray::resolveReference( ArrayIndex index ) -{ - if ( index >= size_ ) - makeIndexValid( index ); - return pages_[index/itemsPerPage][index%itemsPerPage]; -} - -Value * -ValueInternalArray::find( ArrayIndex index ) const -{ - if ( index >= size_ ) - return 0; - return &(pages_[index/itemsPerPage][index%itemsPerPage]); -} - -ValueInternalArray::ArrayIndex -ValueInternalArray::size() const -{ - return size_; -} - -int -ValueInternalArray::distance( const IteratorState &x, const IteratorState &y ) -{ - return indexOf(y) - indexOf(x); -} - - -ValueInternalArray::ArrayIndex -ValueInternalArray::indexOf( const IteratorState &iterator ) -{ - if ( !iterator.array_ ) - return ArrayIndex(-1); - return ArrayIndex( - (iterator.currentPageIndex_ - iterator.array_->pages_) * itemsPerPage - + iterator.currentItemIndex_ ); -} - - -int -ValueInternalArray::compare( const ValueInternalArray &other ) const -{ - int sizeDiff( size_ - other.size_ ); - if ( sizeDiff != 0 ) - return sizeDiff; - - for ( ArrayIndex index =0; index < size_; ++index ) - { - int diff = pages_[index/itemsPerPage][index%itemsPerPage].compare( - other.pages_[index/itemsPerPage][index%itemsPerPage] ); - if ( diff != 0 ) - return diff; - } - return 0; -} - -} // namespace Json diff --git a/ext/jsoncpp/src/json_internalmap.inl b/ext/jsoncpp/src/json_internalmap.inl deleted file mode 100644 index f2fa1606..00000000 --- a/ext/jsoncpp/src/json_internalmap.inl +++ /dev/null @@ -1,615 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -// included by json_value.cpp - -namespace Json { - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueInternalMap -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/** \internal MUST be safely initialized using memset( this, 0, sizeof(ValueInternalLink) ); - * This optimization is used by the fast allocator. - */ -ValueInternalLink::ValueInternalLink() - : previous_( 0 ) - , next_( 0 ) -{ -} - -ValueInternalLink::~ValueInternalLink() -{ - for ( int index =0; index < itemPerLink; ++index ) - { - if ( !items_[index].isItemAvailable() ) - { - if ( !items_[index].isMemberNameStatic() ) - free( keys_[index] ); - } - else - break; - } -} - - - -ValueMapAllocator::~ValueMapAllocator() -{ -} - -#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -class DefaultValueMapAllocator : public ValueMapAllocator -{ -public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - return new ValueInternalMap(); - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - return new ValueInternalMap( other ); - } - - virtual void destructMap( ValueInternalMap *map ) - { - delete map; - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - return new ValueInternalLink(); - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - delete link; - } -}; -#else -/// @todo make this thread-safe (lock when accessign batch allocator) -class DefaultValueMapAllocator : public ValueMapAllocator -{ -public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - ValueInternalMap *map = mapsAllocator_.allocate(); - new (map) ValueInternalMap(); // placement new - return map; - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - ValueInternalMap *map = mapsAllocator_.allocate(); - new (map) ValueInternalMap( other ); // placement new - return map; - } - - virtual void destructMap( ValueInternalMap *map ) - { - if ( map ) - { - map->~ValueInternalMap(); - mapsAllocator_.release( map ); - } - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - ValueInternalLink *link = linksAllocator_.allocate(); - memset( link, 0, sizeof(ValueInternalLink) ); - return link; - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - link->~ValueInternalLink(); - linksAllocator_.release( link ); - } -private: - BatchAllocator<ValueInternalMap,1> mapsAllocator_; - BatchAllocator<ValueInternalLink,1> linksAllocator_; -}; -#endif - -static ValueMapAllocator *&mapAllocator() -{ - static DefaultValueMapAllocator defaultAllocator; - static ValueMapAllocator *mapAllocator = &defaultAllocator; - return mapAllocator; -} - -static struct DummyMapAllocatorInitializer { - DummyMapAllocatorInitializer() - { - mapAllocator(); // ensure mapAllocator() statics are initialized before main(). - } -} dummyMapAllocatorInitializer; - - - -// h(K) = value * K >> w ; with w = 32 & K prime w.r.t. 2^32. - -/* -use linked list hash map. -buckets array is a container. -linked list element contains 6 key/values. (memory = (16+4) * 6 + 4 = 124) -value have extra state: valid, available, deleted -*/ - - -ValueInternalMap::ValueInternalMap() - : buckets_( 0 ) - , tailLink_( 0 ) - , bucketsSize_( 0 ) - , itemCount_( 0 ) -{ -} - - -ValueInternalMap::ValueInternalMap( const ValueInternalMap &other ) - : buckets_( 0 ) - , tailLink_( 0 ) - , bucketsSize_( 0 ) - , itemCount_( 0 ) -{ - reserve( other.itemCount_ ); - IteratorState it; - IteratorState itEnd; - other.makeBeginIterator( it ); - other.makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - bool isStatic; - const char *memberName = key( it, isStatic ); - const Value &aValue = value( it ); - resolveReference(memberName, isStatic) = aValue; - } -} - - -ValueInternalMap & -ValueInternalMap::operator =( const ValueInternalMap &other ) -{ - ValueInternalMap dummy( other ); - swap( dummy ); - return *this; -} - - -ValueInternalMap::~ValueInternalMap() -{ - if ( buckets_ ) - { - for ( BucketIndex bucketIndex =0; bucketIndex < bucketsSize_; ++bucketIndex ) - { - ValueInternalLink *link = buckets_[bucketIndex].next_; - while ( link ) - { - ValueInternalLink *linkToRelease = link; - link = link->next_; - mapAllocator()->releaseMapLink( linkToRelease ); - } - } - mapAllocator()->releaseMapBuckets( buckets_ ); - } -} - - -void -ValueInternalMap::swap( ValueInternalMap &other ) -{ - ValueInternalLink *tempBuckets = buckets_; - buckets_ = other.buckets_; - other.buckets_ = tempBuckets; - ValueInternalLink *tempTailLink = tailLink_; - tailLink_ = other.tailLink_; - other.tailLink_ = tempTailLink; - BucketIndex tempBucketsSize = bucketsSize_; - bucketsSize_ = other.bucketsSize_; - other.bucketsSize_ = tempBucketsSize; - BucketIndex tempItemCount = itemCount_; - itemCount_ = other.itemCount_; - other.itemCount_ = tempItemCount; -} - - -void -ValueInternalMap::clear() -{ - ValueInternalMap dummy; - swap( dummy ); -} - - -ValueInternalMap::BucketIndex -ValueInternalMap::size() const -{ - return itemCount_; -} - -bool -ValueInternalMap::reserveDelta( BucketIndex growth ) -{ - return reserve( itemCount_ + growth ); -} - -bool -ValueInternalMap::reserve( BucketIndex newItemCount ) -{ - if ( !buckets_ && newItemCount > 0 ) - { - buckets_ = mapAllocator()->allocateMapBuckets( 1 ); - bucketsSize_ = 1; - tailLink_ = &buckets_[0]; - } -// BucketIndex idealBucketCount = (newItemCount + ValueInternalLink::itemPerLink) / ValueInternalLink::itemPerLink; - return true; -} - - -const Value * -ValueInternalMap::find( const char *key ) const -{ - if ( !bucketsSize_ ) - return 0; - HashKey hashedKey = hash( key ); - BucketIndex bucketIndex = hashedKey % bucketsSize_; - for ( const ValueInternalLink *current = &buckets_[bucketIndex]; - current != 0; - current = current->next_ ) - { - for ( BucketIndex index=0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( current->items_[index].isItemAvailable() ) - return 0; - if ( strcmp( key, current->keys_[index] ) == 0 ) - return ¤t->items_[index]; - } - } - return 0; -} - - -Value * -ValueInternalMap::find( const char *key ) -{ - const ValueInternalMap *constThis = this; - return const_cast<Value *>( constThis->find( key ) ); -} - - -Value & -ValueInternalMap::resolveReference( const char *key, - bool isStatic ) -{ - HashKey hashedKey = hash( key ); - if ( bucketsSize_ ) - { - BucketIndex bucketIndex = hashedKey % bucketsSize_; - ValueInternalLink **previous = 0; - BucketIndex index; - for ( ValueInternalLink *current = &buckets_[bucketIndex]; - current != 0; - previous = ¤t->next_, current = current->next_ ) - { - for ( index=0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( current->items_[index].isItemAvailable() ) - return setNewItem( key, isStatic, current, index ); - if ( strcmp( key, current->keys_[index] ) == 0 ) - return current->items_[index]; - } - } - } - - reserveDelta( 1 ); - return unsafeAdd( key, isStatic, hashedKey ); -} - - -void -ValueInternalMap::remove( const char *key ) -{ - HashKey hashedKey = hash( key ); - if ( !bucketsSize_ ) - return; - BucketIndex bucketIndex = hashedKey % bucketsSize_; - for ( ValueInternalLink *link = &buckets_[bucketIndex]; - link != 0; - link = link->next_ ) - { - BucketIndex index; - for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( link->items_[index].isItemAvailable() ) - return; - if ( strcmp( key, link->keys_[index] ) == 0 ) - { - doActualRemove( link, index, bucketIndex ); - return; - } - } - } -} - -void -ValueInternalMap::doActualRemove( ValueInternalLink *link, - BucketIndex index, - BucketIndex bucketIndex ) -{ - // find last item of the bucket and swap it with the 'removed' one. - // set removed items flags to 'available'. - // if last page only contains 'available' items, then desallocate it (it's empty) - ValueInternalLink *&lastLink = getLastLinkInBucket( index ); - BucketIndex lastItemIndex = 1; // a link can never be empty, so start at 1 - for ( ; - lastItemIndex < ValueInternalLink::itemPerLink; - ++lastItemIndex ) // may be optimized with dicotomic search - { - if ( lastLink->items_[lastItemIndex].isItemAvailable() ) - break; - } - - BucketIndex lastUsedIndex = lastItemIndex - 1; - Value *valueToDelete = &link->items_[index]; - Value *valueToPreserve = &lastLink->items_[lastUsedIndex]; - if ( valueToDelete != valueToPreserve ) - valueToDelete->swap( *valueToPreserve ); - if ( lastUsedIndex == 0 ) // page is now empty - { // remove it from bucket linked list and delete it. - ValueInternalLink *linkPreviousToLast = lastLink->previous_; - if ( linkPreviousToLast != 0 ) // can not deleted bucket link. - { - mapAllocator()->releaseMapLink( lastLink ); - linkPreviousToLast->next_ = 0; - lastLink = linkPreviousToLast; - } - } - else - { - Value dummy; - valueToPreserve->swap( dummy ); // restore deleted to default Value. - valueToPreserve->setItemUsed( false ); - } - --itemCount_; -} - - -ValueInternalLink *& -ValueInternalMap::getLastLinkInBucket( BucketIndex bucketIndex ) -{ - if ( bucketIndex == bucketsSize_ - 1 ) - return tailLink_; - ValueInternalLink *&previous = buckets_[bucketIndex+1].previous_; - if ( !previous ) - previous = &buckets_[bucketIndex]; - return previous; -} - - -Value & -ValueInternalMap::setNewItem( const char *key, - bool isStatic, - ValueInternalLink *link, - BucketIndex index ) -{ - char *duplicatedKey = makeMemberName( key ); - ++itemCount_; - link->keys_[index] = duplicatedKey; - link->items_[index].setItemUsed(); - link->items_[index].setMemberNameIsStatic( isStatic ); - return link->items_[index]; // items already default constructed. -} - - -Value & -ValueInternalMap::unsafeAdd( const char *key, - bool isStatic, - HashKey hashedKey ) -{ - JSON_ASSERT_MESSAGE( bucketsSize_ > 0, "ValueInternalMap::unsafeAdd(): internal logic error." ); - BucketIndex bucketIndex = hashedKey % bucketsSize_; - ValueInternalLink *&previousLink = getLastLinkInBucket( bucketIndex ); - ValueInternalLink *link = previousLink; - BucketIndex index; - for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( link->items_[index].isItemAvailable() ) - break; - } - if ( index == ValueInternalLink::itemPerLink ) // need to add a new page - { - ValueInternalLink *newLink = mapAllocator()->allocateMapLink(); - index = 0; - link->next_ = newLink; - previousLink = newLink; - link = newLink; - } - return setNewItem( key, isStatic, link, index ); -} - - -ValueInternalMap::HashKey -ValueInternalMap::hash( const char *key ) const -{ - HashKey hash = 0; - while ( *key ) - hash += *key++ * 37; - return hash; -} - - -int -ValueInternalMap::compare( const ValueInternalMap &other ) const -{ - int sizeDiff( itemCount_ - other.itemCount_ ); - if ( sizeDiff != 0 ) - return sizeDiff; - // Strict order guaranty is required. Compare all keys FIRST, then compare values. - IteratorState it; - IteratorState itEnd; - makeBeginIterator( it ); - makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - if ( !other.find( key( it ) ) ) - return 1; - } - - // All keys are equals, let's compare values - makeBeginIterator( it ); - for ( ; !equals(it,itEnd); increment(it) ) - { - const Value *otherValue = other.find( key( it ) ); - int valueDiff = value(it).compare( *otherValue ); - if ( valueDiff != 0 ) - return valueDiff; - } - return 0; -} - - -void -ValueInternalMap::makeBeginIterator( IteratorState &it ) const -{ - it.map_ = const_cast<ValueInternalMap *>( this ); - it.bucketIndex_ = 0; - it.itemIndex_ = 0; - it.link_ = buckets_; -} - - -void -ValueInternalMap::makeEndIterator( IteratorState &it ) const -{ - it.map_ = const_cast<ValueInternalMap *>( this ); - it.bucketIndex_ = bucketsSize_; - it.itemIndex_ = 0; - it.link_ = 0; -} - - -bool -ValueInternalMap::equals( const IteratorState &x, const IteratorState &other ) -{ - return x.map_ == other.map_ - && x.bucketIndex_ == other.bucketIndex_ - && x.link_ == other.link_ - && x.itemIndex_ == other.itemIndex_; -} - - -void -ValueInternalMap::incrementBucket( IteratorState &iterator ) -{ - ++iterator.bucketIndex_; - JSON_ASSERT_MESSAGE( iterator.bucketIndex_ <= iterator.map_->bucketsSize_, - "ValueInternalMap::increment(): attempting to iterate beyond end." ); - if ( iterator.bucketIndex_ == iterator.map_->bucketsSize_ ) - iterator.link_ = 0; - else - iterator.link_ = &(iterator.map_->buckets_[iterator.bucketIndex_]); - iterator.itemIndex_ = 0; -} - - -void -ValueInternalMap::increment( IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterator using invalid iterator." ); - ++iterator.itemIndex_; - if ( iterator.itemIndex_ == ValueInternalLink::itemPerLink ) - { - JSON_ASSERT_MESSAGE( iterator.link_ != 0, - "ValueInternalMap::increment(): attempting to iterate beyond end." ); - iterator.link_ = iterator.link_->next_; - if ( iterator.link_ == 0 ) - incrementBucket( iterator ); - } - else if ( iterator.link_->items_[iterator.itemIndex_].isItemAvailable() ) - { - incrementBucket( iterator ); - } -} - - -void -ValueInternalMap::decrement( IteratorState &iterator ) -{ - if ( iterator.itemIndex_ == 0 ) - { - JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterate using invalid iterator." ); - if ( iterator.link_ == &iterator.map_->buckets_[iterator.bucketIndex_] ) - { - JSON_ASSERT_MESSAGE( iterator.bucketIndex_ > 0, "Attempting to iterate beyond beginning." ); - --(iterator.bucketIndex_); - } - iterator.link_ = iterator.link_->previous_; - iterator.itemIndex_ = ValueInternalLink::itemPerLink - 1; - } -} - - -const char * -ValueInternalMap::key( const IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - return iterator.link_->keys_[iterator.itemIndex_]; -} - -const char * -ValueInternalMap::key( const IteratorState &iterator, bool &isStatic ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - isStatic = iterator.link_->items_[iterator.itemIndex_].isMemberNameStatic(); - return iterator.link_->keys_[iterator.itemIndex_]; -} - - -Value & -ValueInternalMap::value( const IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - return iterator.link_->items_[iterator.itemIndex_]; -} - - -int -ValueInternalMap::distance( const IteratorState &x, const IteratorState &y ) -{ - int offset = 0; - IteratorState it = x; - while ( !equals( it, y ) ) - increment( it ); - return offset; -} - -} // namespace Json diff --git a/ext/jsoncpp/src/json_reader.cpp b/ext/jsoncpp/src/json_reader.cpp deleted file mode 100644 index 8bb03042..00000000 --- a/ext/jsoncpp/src/json_reader.cpp +++ /dev/null @@ -1,880 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -# include <json/reader.h> -# include <json/value.h> -# include "json_tool.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include <utility> -#include <cstdio> -#include <cassert> -#include <cstring> -#include <iostream> -#include <stdexcept> - -#if _MSC_VER >= 1400 // VC++ 8.0 -#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. -#endif - -namespace Json { - -// Implementation of class Features -// //////////////////////////////// - -Features::Features() - : allowComments_( true ) - , strictRoot_( false ) -{ -} - - -Features -Features::all() -{ - return Features(); -} - - -Features -Features::strictMode() -{ - Features features; - features.allowComments_ = false; - features.strictRoot_ = true; - return features; -} - -// Implementation of class Reader -// //////////////////////////////// - - -static inline bool -in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 ) -{ - return c == c1 || c == c2 || c == c3 || c == c4; -} - -static inline bool -in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 ) -{ - return c == c1 || c == c2 || c == c3 || c == c4 || c == c5; -} - - -static bool -containsNewLine( Reader::Location begin, - Reader::Location end ) -{ - for ( ;begin < end; ++begin ) - if ( *begin == '\n' || *begin == '\r' ) - return true; - return false; -} - - -// Class Reader -// ////////////////////////////////////////////////////////////////// - -Reader::Reader() - : features_( Features::all() ) -{ -} - - -Reader::Reader( const Features &features ) - : features_( features ) -{ -} - - -bool -Reader::parse( const std::string &document, - Value &root, - bool collectComments ) -{ - document_ = document; - const char *begin = document_.c_str(); - const char *end = begin + document_.length(); - return parse( begin, end, root, collectComments ); -} - - -bool -Reader::parse( std::istream& sin, - Value &root, - bool collectComments ) -{ - //std::istream_iterator<char> begin(sin); - //std::istream_iterator<char> end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since std::string is reference-counted, this at least does not - // create an extra copy. - std::string doc; - std::getline(sin, doc, (char)EOF); - return parse( doc, root, collectComments ); -} - -bool -Reader::parse( const char *beginDoc, const char *endDoc, - Value &root, - bool collectComments ) -{ - if ( !features_.allowComments_ ) - { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; - commentsBefore_ = ""; - errors_.clear(); - while ( !nodes_.empty() ) - nodes_.pop(); - nodes_.push( &root ); - - bool successful = readValue(); - Token token; - skipCommentTokens( token ); - if ( collectComments_ && !commentsBefore_.empty() ) - root.setComment( commentsBefore_, commentAfter ); - if ( features_.strictRoot_ ) - { - if ( !root.isArray() && !root.isObject() ) - { - // Set error location to start of doc, ideally should be first token found in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( "A valid JSON document must be either an array or an object value.", - token ); - return false; - } - } - return successful; -} - - -bool -Reader::readValue() -{ - Token token; - skipCommentTokens( token ); - bool successful = true; - - if ( collectComments_ && !commentsBefore_.empty() ) - { - currentValue().setComment( commentsBefore_, commentBefore ); - commentsBefore_ = ""; - } - - - switch ( token.type_ ) - { - case tokenObjectBegin: - successful = readObject( token ); - break; - case tokenArrayBegin: - successful = readArray( token ); - break; - case tokenNumber: - successful = decodeNumber( token ); - break; - case tokenString: - successful = decodeString( token ); - break; - case tokenTrue: - currentValue() = true; - break; - case tokenFalse: - currentValue() = false; - break; - case tokenNull: - currentValue() = Value(); - break; - default: - return addError( "Syntax error: value, object or array expected.", token ); - } - - if ( collectComments_ ) - { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - return successful; -} - - -void -Reader::skipCommentTokens( Token &token ) -{ - if ( features_.allowComments_ ) - { - do - { - readToken( token ); - } - while ( token.type_ == tokenComment ); - } - else - { - readToken( token ); - } -} - - -bool -Reader::expectToken( TokenType type, Token &token, const char *message ) -{ - readToken( token ); - if ( token.type_ != type ) - return addError( message, token ); - return true; -} - - -bool -Reader::readToken( Token &token ) -{ - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch ( c ) - { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match( "rue", 3 ); - break; - case 'f': - token.type_ = tokenFalse; - ok = match( "alse", 4 ); - break; - case 'n': - token.type_ = tokenNull; - ok = match( "ull", 3 ); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if ( !ok ) - token.type_ = tokenError; - token.end_ = current_; - return true; -} - - -void -Reader::skipSpaces() -{ - while ( current_ != end_ ) - { - Char c = *current_; - if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) - ++current_; - else - break; - } -} - - -bool -Reader::match( Location pattern, - int patternLength ) -{ - if ( end_ - current_ < patternLength ) - return false; - int index = patternLength; - while ( index-- ) - if ( current_[index] != pattern[index] ) - return false; - current_ += patternLength; - return true; -} - - -bool -Reader::readComment() -{ - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if ( c == '*' ) - successful = readCStyleComment(); - else if ( c == '/' ) - successful = readCppStyleComment(); - if ( !successful ) - return false; - - if ( collectComments_ ) - { - CommentPlacement placement = commentBefore; - if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) ) - { - if ( c != '*' || !containsNewLine( commentBegin, current_ ) ) - placement = commentAfterOnSameLine; - } - - addComment( commentBegin, current_, placement ); - } - return true; -} - - -void -Reader::addComment( Location begin, - Location end, - CommentPlacement placement ) -{ - assert( collectComments_ ); - if ( placement == commentAfterOnSameLine ) - { - assert( lastValue_ != 0 ); - lastValue_->setComment( std::string( begin, end ), placement ); - } - else - { - if ( !commentsBefore_.empty() ) - commentsBefore_ += "\n"; - commentsBefore_ += std::string( begin, end ); - } -} - - -bool -Reader::readCStyleComment() -{ - while ( current_ != end_ ) - { - Char c = getNextChar(); - if ( c == '*' && *current_ == '/' ) - break; - } - return getNextChar() == '/'; -} - - -bool -Reader::readCppStyleComment() -{ - while ( current_ != end_ ) - { - Char c = getNextChar(); - if ( c == '\r' || c == '\n' ) - break; - } - return true; -} - - -void -Reader::readNumber() -{ - while ( current_ != end_ ) - { - if ( !(*current_ >= '0' && *current_ <= '9') && - !in( *current_, '.', 'e', 'E', '+', '-' ) ) - break; - ++current_; - } -} - -bool -Reader::readString() -{ - Char c = 0; - while ( current_ != end_ ) - { - c = getNextChar(); - if ( c == '\\' ) - getNextChar(); - else if ( c == '"' ) - break; - } - return c == '"'; -} - - -bool -Reader::readObject( Token &/*tokenStart*/ ) -{ - Token tokenName; - std::string name; - currentValue() = Value( objectValue ); - while ( readToken( tokenName ) ) - { - bool initialTokenOk = true; - while ( tokenName.type_ == tokenComment && initialTokenOk ) - initialTokenOk = readToken( tokenName ); - if ( !initialTokenOk ) - break; - if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object - return true; - if ( tokenName.type_ != tokenString ) - break; - - name = ""; - if ( !decodeString( tokenName, name ) ) - return recoverFromError( tokenObjectEnd ); - - Token colon; - if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator ) - { - return addErrorAndRecover( "Missing ':' after object member name", - colon, - tokenObjectEnd ); - } - Value &value = currentValue()[ name ]; - nodes_.push( &value ); - bool ok = readValue(); - nodes_.pop(); - if ( !ok ) // error already set - return recoverFromError( tokenObjectEnd ); - - Token comma; - if ( !readToken( comma ) - || ( comma.type_ != tokenObjectEnd && - comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment ) ) - { - return addErrorAndRecover( "Missing ',' or '}' in object declaration", - comma, - tokenObjectEnd ); - } - bool finalizeTokenOk = true; - while ( comma.type_ == tokenComment && - finalizeTokenOk ) - finalizeTokenOk = readToken( comma ); - if ( comma.type_ == tokenObjectEnd ) - return true; - } - return addErrorAndRecover( "Missing '}' or object member name", - tokenName, - tokenObjectEnd ); -} - - -bool -Reader::readArray( Token &/*tokenStart*/ ) -{ - currentValue() = Value( arrayValue ); - skipSpaces(); - if ( *current_ == ']' ) // empty array - { - Token endArray; - readToken( endArray ); - return true; - } - int index = 0; - for (;;) - { - Value &value = currentValue()[ index++ ]; - nodes_.push( &value ); - bool ok = readValue(); - nodes_.pop(); - if ( !ok ) // error already set - return recoverFromError( tokenArrayEnd ); - - Token token; - // Accept Comment after last item in the array. - ok = readToken( token ); - while ( token.type_ == tokenComment && ok ) - { - ok = readToken( token ); - } - bool badTokenType = ( token.type_ != tokenArraySeparator && - token.type_ != tokenArrayEnd ); - if ( !ok || badTokenType ) - { - return addErrorAndRecover( "Missing ',' or ']' in array declaration", - token, - tokenArrayEnd ); - } - if ( token.type_ == tokenArrayEnd ) - break; - } - return true; -} - - -bool -Reader::decodeNumber( Token &token ) -{ - bool isDouble = false; - for ( Location inspect = token.start_; inspect != token.end_; ++inspect ) - { - isDouble = isDouble - || in( *inspect, '.', 'e', 'E', '+' ) - || ( *inspect == '-' && inspect != token.start_ ); - } - if ( isDouble ) - return decodeDouble( token ); - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - bool isNegative = *current == '-'; - if ( isNegative ) - ++current; - Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(-Value::minLargestInt) - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; - Value::UInt lastDigitThreshold = Value::UInt( maxIntegerValue % 10 ); - assert( lastDigitThreshold >=0 && lastDigitThreshold <= 9 ); - Value::LargestUInt value = 0; - while ( current < token.end_ ) - { - Char c = *current++; - if ( c < '0' || c > '9' ) - return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); - Value::UInt digit(c - '0'); - if ( value >= threshold ) - { - // If the current digit is not the last one, or if it is - // greater than the last digit of the maximum integer value, - // the parse the number as a double. - if ( current != token.end_ || digit > lastDigitThreshold ) - { - return decodeDouble( token ); - } - } - value = value * 10 + digit; - } - if ( isNegative ) - currentValue() = -Value::LargestInt( value ); - else if ( value <= Value::LargestUInt(Value::maxInt) ) - currentValue() = Value::LargestInt( value ); - else - currentValue() = value; - return true; -} - - -bool -Reader::decodeDouble( Token &token ) -{ - double value = 0; - const int bufferSize = 32; - int count; - int length = int(token.end_ - token.start_); - if ( length <= bufferSize ) - { - Char buffer[bufferSize+1]; - memcpy( buffer, token.start_, length ); - buffer[length] = 0; - count = sscanf( buffer, "%lf", &value ); - } - else - { - std::string buffer( token.start_, token.end_ ); - count = sscanf( buffer.c_str(), "%lf", &value ); - } - - if ( count != 1 ) - return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); - currentValue() = value; - return true; -} - - -bool -Reader::decodeString( Token &token ) -{ - std::string decoded; - if ( !decodeString( token, decoded ) ) - return false; - currentValue() = decoded; - return true; -} - - -bool -Reader::decodeString( Token &token, std::string &decoded ) -{ - decoded.reserve( token.end_ - token.start_ - 2 ); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while ( current != end ) - { - Char c = *current++; - if ( c == '"' ) - break; - else if ( c == '\\' ) - { - if ( current == end ) - return addError( "Empty escape sequence in string", token, current ); - Char escape = *current++; - switch ( escape ) - { - case '"': decoded += '"'; break; - case '/': decoded += '/'; break; - case '\\': decoded += '\\'; break; - case 'b': decoded += '\b'; break; - case 'f': decoded += '\f'; break; - case 'n': decoded += '\n'; break; - case 'r': decoded += '\r'; break; - case 't': decoded += '\t'; break; - case 'u': - { - unsigned int unicode; - if ( !decodeUnicodeCodePoint( token, current, end, unicode ) ) - return false; - decoded += codePointToUTF8(unicode); - } - break; - default: - return addError( "Bad escape sequence in string", token, current ); - } - } - else - { - decoded += c; - } - } - return true; -} - -bool -Reader::decodeUnicodeCodePoint( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ) -{ - - if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) ) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) - { - // surrogate pairs - if (end - current < 6) - return addError( "additional six characters expected to parse unicode surrogate pair.", token, current ); - unsigned int surrogatePair; - if (*(current++) == '\\' && *(current++)== 'u') - { - if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair )) - { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } - else - return false; - } - else - return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current ); - } - return true; -} - -bool -Reader::decodeUnicodeEscapeSequence( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ) -{ - if ( end - current < 4 ) - return addError( "Bad unicode escape sequence in string: four digits expected.", token, current ); - unicode = 0; - for ( int index =0; index < 4; ++index ) - { - Char c = *current++; - unicode *= 16; - if ( c >= '0' && c <= '9' ) - unicode += c - '0'; - else if ( c >= 'a' && c <= 'f' ) - unicode += c - 'a' + 10; - else if ( c >= 'A' && c <= 'F' ) - unicode += c - 'A' + 10; - else - return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current ); - } - return true; -} - - -bool -Reader::addError( const std::string &message, - Token &token, - Location extra ) -{ - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back( info ); - return false; -} - - -bool -Reader::recoverFromError( TokenType skipUntilToken ) -{ - int errorCount = int(errors_.size()); - Token skip; - for (;;) - { - if ( !readToken(skip) ) - errors_.resize( errorCount ); // discard errors caused by recovery - if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream ) - break; - } - errors_.resize( errorCount ); - return false; -} - - -bool -Reader::addErrorAndRecover( const std::string &message, - Token &token, - TokenType skipUntilToken ) -{ - addError( message, token ); - return recoverFromError( skipUntilToken ); -} - - -Value & -Reader::currentValue() -{ - return *(nodes_.top()); -} - - -Reader::Char -Reader::getNextChar() -{ - if ( current_ == end_ ) - return 0; - return *current_++; -} - - -void -Reader::getLocationLineAndColumn( Location location, - int &line, - int &column ) const -{ - Location current = begin_; - Location lastLineStart = current; - line = 0; - while ( current < location && current != end_ ) - { - Char c = *current++; - if ( c == '\r' ) - { - if ( *current == '\n' ) - ++current; - lastLineStart = current; - ++line; - } - else if ( c == '\n' ) - { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - - -std::string -Reader::getLocationLineAndColumn( Location location ) const -{ - int line, column; - getLocationLineAndColumn( location, line, column ); - char buffer[18+16+16+1]; - sprintf( buffer, "Line %d, Column %d", line, column ); - return buffer; -} - - -// Deprecated. Preserved for backward compatibility -std::string -Reader::getFormatedErrorMessages() const -{ - return getFormattedErrorMessages(); -} - - -std::string -Reader::getFormattedErrorMessages() const -{ - std::string formattedMessage; - for ( Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError ) - { - const ErrorInfo &error = *itError; - formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if ( error.extra_ ) - formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n"; - } - return formattedMessage; -} - - -std::istream& operator>>( std::istream &sin, Value &root ) -{ - Json::Reader reader; - bool ok = reader.parse(sin, root, true); - //JSON_ASSERT( ok ); - if (!ok) throw std::runtime_error(reader.getFormattedErrorMessages()); - return sin; -} - - -} // namespace Json diff --git a/ext/jsoncpp/src/json_tool.h b/ext/jsoncpp/src/json_tool.h deleted file mode 100644 index 658031bb..00000000 --- a/ext/jsoncpp/src/json_tool.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED -# define LIB_JSONCPP_JSON_TOOL_H_INCLUDED - -/* This header provides common string manipulation support, such as UTF-8, - * portable conversion from/to string... - * - * It is an internal header that must not be exposed. - */ - -namespace Json { - -/// Converts a unicode code-point to UTF-8. -static inline std::string -codePointToUTF8(unsigned int cp) -{ - std::string result; - - // based on description from http://en.wikipedia.org/wiki/UTF-8 - - if (cp <= 0x7f) - { - result.resize(1); - result[0] = static_cast<char>(cp); - } - else if (cp <= 0x7FF) - { - result.resize(2); - result[1] = static_cast<char>(0x80 | (0x3f & cp)); - result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6))); - } - else if (cp <= 0xFFFF) - { - result.resize(3); - result[2] = static_cast<char>(0x80 | (0x3f & cp)); - result[1] = 0x80 | static_cast<char>((0x3f & (cp >> 6))); - result[0] = 0xE0 | static_cast<char>((0xf & (cp >> 12))); - } - else if (cp <= 0x10FFFF) - { - result.resize(4); - result[3] = static_cast<char>(0x80 | (0x3f & cp)); - result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6))); - result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12))); - result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18))); - } - - return result; -} - - -/// Returns true if ch is a control character (in range [0,32[). -static inline bool -isControlCharacter(char ch) -{ - return ch > 0 && ch <= 0x1F; -} - - -enum { - /// Constant that specify the size of the buffer that must be passed to uintToString. - uintToStringBufferSize = 3*sizeof(LargestUInt)+1 -}; - -// Defines a char buffer for use with uintToString(). -typedef char UIntToStringBuffer[uintToStringBufferSize]; - - -/** Converts an unsigned integer to string. - * @param value Unsigned interger to convert to string - * @param current Input/Output string buffer. - * Must have at least uintToStringBufferSize chars free. - */ -static inline void -uintToString( LargestUInt value, - char *¤t ) -{ - *--current = 0; - do - { - *--current = char(value % 10) + '0'; - value /= 10; - } - while ( value != 0 ); -} - -} // namespace Json { - -#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED diff --git a/ext/jsoncpp/src/json_value.cpp b/ext/jsoncpp/src/json_value.cpp deleted file mode 100644 index ff98f635..00000000 --- a/ext/jsoncpp/src/json_value.cpp +++ /dev/null @@ -1,1829 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -# include <json/value.h> -# include <json/writer.h> -# ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -# include "json_batchallocator.h" -# endif // #ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -#endif // if !defined(JSON_IS_AMALGAMATION) -#include <iostream> -#include <utility> -#include <stdexcept> -#include <cstring> -#include <cassert> -#ifdef JSON_USE_CPPTL -# include <cpptl/conststring.h> -#endif -#include <cstddef> // size_t - -#define JSON_ASSERT_UNREACHABLE assert( false ) -#define JSON_ASSERT( condition ) assert( condition ); // @todo <= change this into an exception throw -#define JSON_FAIL_MESSAGE( message ) throw std::runtime_error( message ); -#define JSON_ASSERT_MESSAGE( condition, message ) if (!( condition )) JSON_FAIL_MESSAGE( message ) - -namespace Json { - -const Value Value::null; -const Int Value::minInt = Int( ~(UInt(-1)/2) ); -const Int Value::maxInt = Int( UInt(-1)/2 ); -const UInt Value::maxUInt = UInt(-1); -const Int64 Value::minInt64 = Int64( ~(UInt64(-1)/2) ); -const Int64 Value::maxInt64 = Int64( UInt64(-1)/2 ); -const UInt64 Value::maxUInt64 = UInt64(-1); -const LargestInt Value::minLargestInt = LargestInt( ~(LargestUInt(-1)/2) ); -const LargestInt Value::maxLargestInt = LargestInt( LargestUInt(-1)/2 ); -const LargestUInt Value::maxLargestUInt = LargestUInt(-1); - - -/// Unknown size marker -static const unsigned int unknown = (unsigned)-1; - - -/** Duplicates the specified string value. - * @param value Pointer to the string to duplicate. Must be zero-terminated if - * length is "unknown". - * @param length Length of the value. if equals to unknown, then it will be - * computed using strlen(value). - * @return Pointer on the duplicate instance of string. - */ -static inline char * -duplicateStringValue( const char *value, - unsigned int length = unknown ) -{ - if ( length == unknown ) - length = (unsigned int)strlen(value); - char *newString = static_cast<char *>( malloc( length + 1 ) ); - JSON_ASSERT_MESSAGE( newString != 0, "Failed to allocate string value buffer" ); - memcpy( newString, value, length ); - newString[length] = 0; - return newString; -} - - -/** Free the string duplicated by duplicateStringValue(). - */ -static inline void -releaseStringValue( char *value ) -{ - if ( value ) - free( value ); -} - -} // namespace Json - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ValueInternals... -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -#if !defined(JSON_IS_AMALGAMATION) -# ifdef JSON_VALUE_USE_INTERNAL_MAP -# include "json_internalarray.inl" -# include "json_internalmap.inl" -# endif // JSON_VALUE_USE_INTERNAL_MAP - -# include "json_valueiterator.inl" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CommentInfo -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - - -Value::CommentInfo::CommentInfo() - : comment_( 0 ) -{ -} - -Value::CommentInfo::~CommentInfo() -{ - if ( comment_ ) - releaseStringValue( comment_ ); -} - - -void -Value::CommentInfo::setComment( const char *text ) -{ - if ( comment_ ) - releaseStringValue( comment_ ); - JSON_ASSERT( text != 0 ); - JSON_ASSERT_MESSAGE( text[0]=='\0' || text[0]=='/', "Comments must start with /"); - // It seems that /**/ style comments are acceptable as well. - comment_ = duplicateStringValue( text ); -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CZString -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -# ifndef JSON_VALUE_USE_INTERNAL_MAP - -// Notes: index_ indicates if the string was allocated when -// a string is stored. - -Value::CZString::CZString( ArrayIndex index ) - : cstr_( 0 ) - , index_( index ) -{ -} - -Value::CZString::CZString( const char *cstr, DuplicationPolicy allocate ) - : cstr_( allocate == duplicate ? duplicateStringValue(cstr) - : cstr ) - , index_( allocate ) -{ -} - -Value::CZString::CZString( const CZString &other ) -: cstr_( other.index_ != noDuplication && other.cstr_ != 0 - ? duplicateStringValue( other.cstr_ ) - : other.cstr_ ) - , index_( other.cstr_ ? (other.index_ == noDuplication ? noDuplication : duplicate) - : other.index_ ) -{ -} - -Value::CZString::~CZString() -{ - if ( cstr_ && index_ == duplicate ) - releaseStringValue( const_cast<char *>( cstr_ ) ); -} - -void -Value::CZString::swap( CZString &other ) -{ - std::swap( cstr_, other.cstr_ ); - std::swap( index_, other.index_ ); -} - -Value::CZString & -Value::CZString::operator =( const CZString &other ) -{ - CZString temp( other ); - swap( temp ); - return *this; -} - -bool -Value::CZString::operator<( const CZString &other ) const -{ - if ( cstr_ ) - return strcmp( cstr_, other.cstr_ ) < 0; - return index_ < other.index_; -} - -bool -Value::CZString::operator==( const CZString &other ) const -{ - if ( cstr_ ) - return strcmp( cstr_, other.cstr_ ) == 0; - return index_ == other.index_; -} - - -ArrayIndex -Value::CZString::index() const -{ - return index_; -} - - -const char * -Value::CZString::c_str() const -{ - return cstr_; -} - -bool -Value::CZString::isStaticString() const -{ - return index_ == noDuplication; -} - -#endif // ifndef JSON_VALUE_USE_INTERNAL_MAP - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::Value -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/*! \internal Default constructor initialization must be equivalent to: - * memset( this, 0, sizeof(Value) ) - * This optimization is used in ValueInternalMap fast allocator. - */ -Value::Value( ValueType type ) - : type_( type ) - , allocated_( 0 ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - switch ( type ) - { - case nullValue: - break; - case intValue: - case uintValue: - value_.int_ = 0; - break; - case realValue: - value_.real_ = 0.0; - break; - case stringValue: - value_.string_ = 0; - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(); - break; -#else - case arrayValue: - value_.array_ = arrayAllocator()->newArray(); - break; - case objectValue: - value_.map_ = mapAllocator()->newMap(); - break; -#endif - case booleanValue: - value_.bool_ = false; - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - - -#if defined(JSON_HAS_INT64) -Value::Value( UInt value ) - : type_( uintValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.uint_ = value; -} - -Value::Value( Int value ) - : type_( intValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.int_ = value; -} - -#endif // if defined(JSON_HAS_INT64) - - -Value::Value( Int64 value ) - : type_( intValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.int_ = value; -} - - -Value::Value( UInt64 value ) - : type_( uintValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.uint_ = value; -} - -Value::Value( double value ) - : type_( realValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.real_ = value; -} - -Value::Value( const char *value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = duplicateStringValue( value ); -} - - -Value::Value( const char *beginValue, - const char *endValue ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = duplicateStringValue( beginValue, - (unsigned int)(endValue - beginValue) ); -} - - -Value::Value( const std::string &value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = duplicateStringValue( value.c_str(), - (unsigned int)value.length() ); - -} - -Value::Value( const StaticString &value ) - : type_( stringValue ) - , allocated_( false ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = const_cast<char *>( value.c_str() ); -} - - -# ifdef JSON_USE_CPPTL -Value::Value( const CppTL::ConstString &value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = duplicateStringValue( value, value.length() ); -} -# endif - -Value::Value( bool value ) - : type_( booleanValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.bool_ = value; -} - - -Value::Value( const Value &other ) - : type_( other.type_ ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if ( other.value_.string_ ) - { - value_.string_ = duplicateStringValue( other.value_.string_ ); - allocated_ = true; - } - else - value_.string_ = 0; - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues( *other.value_.map_ ); - break; -#else - case arrayValue: - value_.array_ = arrayAllocator()->newArrayCopy( *other.value_.array_ ); - break; - case objectValue: - value_.map_ = mapAllocator()->newMapCopy( *other.value_.map_ ); - break; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - if ( other.comments_ ) - { - comments_ = new CommentInfo[numberOfCommentPlacement]; - for ( int comment =0; comment < numberOfCommentPlacement; ++comment ) - { - const CommentInfo &otherComment = other.comments_[comment]; - if ( otherComment.comment_ ) - comments_[comment].setComment( otherComment.comment_ ); - } - } -} - - -Value::~Value() -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if ( allocated_ ) - releaseStringValue( value_.string_ ); - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - delete value_.map_; - break; -#else - case arrayValue: - arrayAllocator()->destructArray( value_.array_ ); - break; - case objectValue: - mapAllocator()->destructMap( value_.map_ ); - break; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - - if ( comments_ ) - delete[] comments_; -} - -Value & -Value::operator=( const Value &other ) -{ - Value temp( other ); - swap( temp ); - return *this; -} - -void -Value::swap( Value &other ) -{ - ValueType temp = type_; - type_ = other.type_; - other.type_ = temp; - std::swap( value_, other.value_ ); - int temp2 = allocated_; - allocated_ = other.allocated_; - other.allocated_ = temp2; -} - -ValueType -Value::type() const -{ - return type_; -} - - -int -Value::compare( const Value &other ) const -{ - if ( *this < other ) - return -1; - if ( *this > other ) - return 1; - return 0; -} - - -bool -Value::operator <( const Value &other ) const -{ - int typeDelta = type_ - other.type_; - if ( typeDelta ) - return typeDelta < 0 ? true : false; - switch ( type_ ) - { - case nullValue: - return false; - case intValue: - return value_.int_ < other.value_.int_; - case uintValue: - return value_.uint_ < other.value_.uint_; - case realValue: - return value_.real_ < other.value_.real_; - case booleanValue: - return value_.bool_ < other.value_.bool_; - case stringValue: - return ( value_.string_ == 0 && other.value_.string_ ) - || ( other.value_.string_ - && value_.string_ - && strcmp( value_.string_, other.value_.string_ ) < 0 ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - { - int delta = int( value_.map_->size() - other.value_.map_->size() ); - if ( delta ) - return delta < 0; - return (*value_.map_) < (*other.value_.map_); - } -#else - case arrayValue: - return value_.array_->compare( *(other.value_.array_) ) < 0; - case objectValue: - return value_.map_->compare( *(other.value_.map_) ) < 0; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable -} - -bool -Value::operator <=( const Value &other ) const -{ - return !(other < *this); -} - -bool -Value::operator >=( const Value &other ) const -{ - return !(*this < other); -} - -bool -Value::operator >( const Value &other ) const -{ - return other < *this; -} - -bool -Value::operator ==( const Value &other ) const -{ - //if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; - if ( type_ != temp ) - return false; - switch ( type_ ) - { - case nullValue: - return true; - case intValue: - return value_.int_ == other.value_.int_; - case uintValue: - return value_.uint_ == other.value_.uint_; - case realValue: - return value_.real_ == other.value_.real_; - case booleanValue: - return value_.bool_ == other.value_.bool_; - case stringValue: - return ( value_.string_ == other.value_.string_ ) - || ( other.value_.string_ - && value_.string_ - && strcmp( value_.string_, other.value_.string_ ) == 0 ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - return value_.map_->size() == other.value_.map_->size() - && (*value_.map_) == (*other.value_.map_); -#else - case arrayValue: - return value_.array_->compare( *(other.value_.array_) ) == 0; - case objectValue: - return value_.map_->compare( *(other.value_.map_) ) == 0; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable -} - -bool -Value::operator !=( const Value &other ) const -{ - return !( *this == other ); -} - -const char * -Value::asCString() const -{ - JSON_ASSERT( type_ == stringValue ); - return value_.string_; -} - - -std::string -Value::asString() const -{ - switch ( type_ ) - { - case nullValue: - return ""; - case stringValue: - return value_.string_ ? value_.string_ : ""; - case booleanValue: - return value_.bool_ ? "true" : "false"; - case intValue: - case uintValue: - case realValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to string" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return ""; // unreachable -} - -# ifdef JSON_USE_CPPTL -CppTL::ConstString -Value::asConstString() const -{ - return CppTL::ConstString( asString().c_str() ); -} -# endif - - -Value::Int -Value::asInt() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - JSON_ASSERT_MESSAGE( value_.int_ >= minInt && value_.int_ <= maxInt, "unsigned integer out of signed int range" ); - return Int(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE( value_.uint_ <= UInt(maxInt), "unsigned integer out of signed int range" ); - return Int(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= minInt && value_.real_ <= maxInt, "Real out of signed integer range" ); - return Int( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to int" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - - -Value::UInt -Value::asUInt() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - JSON_ASSERT_MESSAGE( value_.int_ >= 0, "Negative integer can not be converted to unsigned integer" ); - JSON_ASSERT_MESSAGE( value_.int_ <= maxUInt, "signed integer out of UInt range" ); - return UInt(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE( value_.uint_ <= maxUInt, "unsigned integer out of UInt range" ); - return UInt(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= 0 && value_.real_ <= maxUInt, "Real out of unsigned integer range" ); - return UInt( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to uint" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - - -# if defined(JSON_HAS_INT64) - -Value::Int64 -Value::asInt64() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - return value_.int_; - case uintValue: - JSON_ASSERT_MESSAGE( value_.uint_ <= UInt64(maxInt64), "unsigned integer out of Int64 range" ); - return value_.uint_; - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= minInt64 && value_.real_ <= maxInt64, "Real out of Int64 range" ); - return Int( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to Int64" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - - -Value::UInt64 -Value::asUInt64() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - JSON_ASSERT_MESSAGE( value_.int_ >= 0, "Negative integer can not be converted to UInt64" ); - return value_.int_; - case uintValue: - return value_.uint_; - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= 0 && value_.real_ <= maxUInt64, "Real out of UInt64 range" ); - return UInt( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to UInt64" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} -# endif // if defined(JSON_HAS_INT64) - - -LargestInt -Value::asLargestInt() const -{ -#if defined(JSON_NO_INT64) - return asInt(); -#else - return asInt64(); -#endif -} - - -LargestUInt -Value::asLargestUInt() const -{ -#if defined(JSON_NO_INT64) - return asUInt(); -#else - return asUInt64(); -#endif -} - - -double -Value::asDouble() const -{ - switch ( type_ ) - { - case nullValue: - return 0.0; - case intValue: - return static_cast<double>( value_.int_ ); - case uintValue: -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast<double>( value_.uint_ ); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast<double>( Int(value_.uint_/2) ) * 2 + Int(value_.uint_ & 1); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return value_.real_; - case booleanValue: - return value_.bool_ ? 1.0 : 0.0; - case stringValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to double" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -float -Value::asFloat() const -{ - switch ( type_ ) - { - case nullValue: - return 0.0f; - case intValue: - return static_cast<float>( value_.int_ ); - case uintValue: -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast<float>( value_.uint_ ); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast<float>( Int(value_.uint_/2) ) * 2 + Int(value_.uint_ & 1); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return static_cast<float>( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1.0f : 0.0f; - case stringValue: - case arrayValue: - case objectValue: - JSON_FAIL_MESSAGE( "Type is not convertible to float" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0.0f; // unreachable; -} - -bool -Value::asBool() const -{ - switch ( type_ ) - { - case nullValue: - return false; - case intValue: - case uintValue: - return value_.int_ != 0; - case realValue: - return value_.real_ != 0.0; - case booleanValue: - return value_.bool_; - case stringValue: - return value_.string_ && value_.string_[0] != 0; - case arrayValue: - case objectValue: - return value_.map_->size() != 0; - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable; -} - - -bool -Value::isConvertibleTo( ValueType other ) const -{ - switch ( type_ ) - { - case nullValue: - return true; - case intValue: - return ( other == nullValue && value_.int_ == 0 ) - || other == intValue - || ( other == uintValue && value_.int_ >= 0 ) - || other == realValue - || other == stringValue - || other == booleanValue; - case uintValue: - return ( other == nullValue && value_.uint_ == 0 ) - || ( other == intValue && value_.uint_ <= (unsigned)maxInt ) - || other == uintValue - || other == realValue - || other == stringValue - || other == booleanValue; - case realValue: - return ( other == nullValue && value_.real_ == 0.0 ) - || ( other == intValue && value_.real_ >= minInt && value_.real_ <= maxInt ) - || ( other == uintValue && value_.real_ >= 0 && value_.real_ <= maxUInt ) - || other == realValue - || other == stringValue - || other == booleanValue; - case booleanValue: - return ( other == nullValue && value_.bool_ == false ) - || other == intValue - || other == uintValue - || other == realValue - || other == stringValue - || other == booleanValue; - case stringValue: - return other == stringValue - || ( other == nullValue && (!value_.string_ || value_.string_[0] == 0) ); - case arrayValue: - return other == arrayValue - || ( other == nullValue && value_.map_->size() == 0 ); - case objectValue: - return other == objectValue - || ( other == nullValue && value_.map_->size() == 0 ); - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable; -} - - -/// Number of values in array or object -ArrayIndex -Value::size() const -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - case stringValue: - return 0; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: // size of the array is highest index + 1 - if ( !value_.map_->empty() ) - { - ObjectValues::const_iterator itLast = value_.map_->end(); - --itLast; - return (*itLast).first.index()+1; - } - return 0; - case objectValue: - return ArrayIndex( value_.map_->size() ); -#else - case arrayValue: - return Int( value_.array_->size() ); - case objectValue: - return Int( value_.map_->size() ); -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - - -bool -Value::empty() const -{ - if ( isNull() || isArray() || isObject() ) - return size() == 0u; - else - return false; -} - - -bool -Value::operator!() const -{ - return isNull(); -} - - -void -Value::clear() -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue || type_ == objectValue ); - - switch ( type_ ) - { -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_->clear(); - break; -#else - case arrayValue: - value_.array_->clear(); - break; - case objectValue: - value_.map_->clear(); - break; -#endif - default: - break; - } -} - -void -Value::resize( ArrayIndex newSize ) -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - *this = Value( arrayValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ArrayIndex oldSize = size(); - if ( newSize == 0 ) - clear(); - else if ( newSize > oldSize ) - (*this)[ newSize - 1 ]; - else - { - for ( ArrayIndex index = newSize; index < oldSize; ++index ) - { - value_.map_->erase( index ); - } - assert( size() == newSize ); - } -#else - value_.array_->resize( newSize ); -#endif -} - - -Value & -Value::operator[]( ArrayIndex index ) -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - *this = Value( arrayValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString key( index ); - ObjectValues::iterator it = value_.map_->lower_bound( key ); - if ( it != value_.map_->end() && (*it).first == key ) - return (*it).second; - - ObjectValues::value_type defaultValue( key, null ); - it = value_.map_->insert( it, defaultValue ); - return (*it).second; -#else - return value_.array_->resolveReference( index ); -#endif -} - - -Value & -Value::operator[]( int index ) -{ - JSON_ASSERT( index >= 0 ); - return (*this)[ ArrayIndex(index) ]; -} - - -const Value & -Value::operator[]( ArrayIndex index ) const -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString key( index ); - ObjectValues::const_iterator it = value_.map_->find( key ); - if ( it == value_.map_->end() ) - return null; - return (*it).second; -#else - Value *value = value_.array_->find( index ); - return value ? *value : null; -#endif -} - - -const Value & -Value::operator[]( int index ) const -{ - JSON_ASSERT( index >= 0 ); - return (*this)[ ArrayIndex(index) ]; -} - - -Value & -Value::operator[]( const char *key ) -{ - return resolveReference( key, false ); -} - - -Value & -Value::resolveReference( const char *key, - bool isStatic ) -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - *this = Value( objectValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, isStatic ? CZString::noDuplication - : CZString::duplicateOnCopy ); - ObjectValues::iterator it = value_.map_->lower_bound( actualKey ); - if ( it != value_.map_->end() && (*it).first == actualKey ) - return (*it).second; - - ObjectValues::value_type defaultValue( actualKey, null ); - it = value_.map_->insert( it, defaultValue ); - Value &value = (*it).second; - return value; -#else - return value_.map_->resolveReference( key, isStatic ); -#endif -} - - -Value -Value::get( ArrayIndex index, - const Value &defaultValue ) const -{ - const Value *value = &((*this)[index]); - return value == &null ? defaultValue : *value; -} - - -bool -Value::isValidIndex( ArrayIndex index ) const -{ - return index < size(); -} - - - -const Value & -Value::operator[]( const char *key ) const -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, CZString::noDuplication ); - ObjectValues::const_iterator it = value_.map_->find( actualKey ); - if ( it == value_.map_->end() ) - return null; - return (*it).second; -#else - const Value *value = value_.map_->find( key ); - return value ? *value : null; -#endif -} - - -Value & -Value::operator[]( const std::string &key ) -{ - return (*this)[ key.c_str() ]; -} - - -const Value & -Value::operator[]( const std::string &key ) const -{ - return (*this)[ key.c_str() ]; -} - -Value & -Value::operator[]( const StaticString &key ) -{ - return resolveReference( key, true ); -} - - -# ifdef JSON_USE_CPPTL -Value & -Value::operator[]( const CppTL::ConstString &key ) -{ - return (*this)[ key.c_str() ]; -} - - -const Value & -Value::operator[]( const CppTL::ConstString &key ) const -{ - return (*this)[ key.c_str() ]; -} -# endif - - -Value & -Value::append( const Value &value ) -{ - return (*this)[size()] = value; -} - - -Value -Value::get( const char *key, - const Value &defaultValue ) const -{ - const Value *value = &((*this)[key]); - return value == &null ? defaultValue : *value; -} - - -Value -Value::get( const std::string &key, - const Value &defaultValue ) const -{ - return get( key.c_str(), defaultValue ); -} - -Value -Value::removeMember( const char* key ) -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, CZString::noDuplication ); - ObjectValues::iterator it = value_.map_->find( actualKey ); - if ( it == value_.map_->end() ) - return null; - Value old(it->second); - value_.map_->erase(it); - return old; -#else - Value *value = value_.map_->find( key ); - if (value){ - Value old(*value); - value_.map_.remove( key ); - return old; - } else { - return null; - } -#endif -} - -Value -Value::removeMember( const std::string &key ) -{ - return removeMember( key.c_str() ); -} - -# ifdef JSON_USE_CPPTL -Value -Value::get( const CppTL::ConstString &key, - const Value &defaultValue ) const -{ - return get( key.c_str(), defaultValue ); -} -# endif - -bool -Value::isMember( const char *key ) const -{ - const Value *value = &((*this)[key]); - return value != &null; -} - - -bool -Value::isMember( const std::string &key ) const -{ - return isMember( key.c_str() ); -} - - -# ifdef JSON_USE_CPPTL -bool -Value::isMember( const CppTL::ConstString &key ) const -{ - return isMember( key.c_str() ); -} -#endif - -Value::Members -Value::getMemberNames() const -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return Value::Members(); - Members members; - members.reserve( value_.map_->size() ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ObjectValues::const_iterator it = value_.map_->begin(); - ObjectValues::const_iterator itEnd = value_.map_->end(); - for ( ; it != itEnd; ++it ) - members.push_back( std::string( (*it).first.c_str() ) ); -#else - ValueInternalMap::IteratorState it; - ValueInternalMap::IteratorState itEnd; - value_.map_->makeBeginIterator( it ); - value_.map_->makeEndIterator( itEnd ); - for ( ; !ValueInternalMap::equals( it, itEnd ); ValueInternalMap::increment(it) ) - members.push_back( std::string( ValueInternalMap::key( it ) ) ); -#endif - return members; -} -// -//# ifdef JSON_USE_CPPTL -//EnumMemberNames -//Value::enumMemberNames() const -//{ -// if ( type_ == objectValue ) -// { -// return CppTL::Enum::any( CppTL::Enum::transform( -// CppTL::Enum::keys( *(value_.map_), CppTL::Type<const CZString &>() ), -// MemberNamesTransform() ) ); -// } -// return EnumMemberNames(); -//} -// -// -//EnumValues -//Value::enumValues() const -//{ -// if ( type_ == objectValue || type_ == arrayValue ) -// return CppTL::Enum::anyValues( *(value_.map_), -// CppTL::Type<const Value &>() ); -// return EnumValues(); -//} -// -//# endif - - -bool -Value::isNull() const -{ - return type_ == nullValue; -} - - -bool -Value::isBool() const -{ - return type_ == booleanValue; -} - - -bool -Value::isInt() const -{ - return type_ == intValue; -} - - -bool -Value::isUInt() const -{ - return type_ == uintValue; -} - - -bool -Value::isIntegral() const -{ - return type_ == intValue - || type_ == uintValue - || type_ == booleanValue; -} - - -bool -Value::isDouble() const -{ - return type_ == realValue; -} - - -bool -Value::isNumeric() const -{ - return isIntegral() || isDouble(); -} - - -bool -Value::isString() const -{ - return type_ == stringValue; -} - - -bool -Value::isArray() const -{ - return type_ == nullValue || type_ == arrayValue; -} - - -bool -Value::isObject() const -{ - return type_ == nullValue || type_ == objectValue; -} - - -void -Value::setComment( const char *comment, - CommentPlacement placement ) -{ - if ( !comments_ ) - comments_ = new CommentInfo[numberOfCommentPlacement]; - comments_[placement].setComment( comment ); -} - - -void -Value::setComment( const std::string &comment, - CommentPlacement placement ) -{ - setComment( comment.c_str(), placement ); -} - - -bool -Value::hasComment( CommentPlacement placement ) const -{ - return comments_ != 0 && comments_[placement].comment_ != 0; -} - -std::string -Value::getComment( CommentPlacement placement ) const -{ - if ( hasComment(placement) ) - return comments_[placement].comment_; - return ""; -} - - -std::string -Value::toStyledString() const -{ - StyledWriter writer; - return writer.write( *this ); -} - - -Value::const_iterator -Value::begin() const -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeBeginIterator( it ); - return const_iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeBeginIterator( it ); - return const_iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return const_iterator( value_.map_->begin() ); - break; -#endif - default: - break; - } - return const_iterator(); -} - -Value::const_iterator -Value::end() const -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeEndIterator( it ); - return const_iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeEndIterator( it ); - return const_iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return const_iterator( value_.map_->end() ); - break; -#endif - default: - break; - } - return const_iterator(); -} - - -Value::iterator -Value::begin() -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeBeginIterator( it ); - return iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeBeginIterator( it ); - return iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return iterator( value_.map_->begin() ); - break; -#endif - default: - break; - } - return iterator(); -} - -Value::iterator -Value::end() -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeEndIterator( it ); - return iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeEndIterator( it ); - return iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return iterator( value_.map_->end() ); - break; -#endif - default: - break; - } - return iterator(); -} - - -// class PathArgument -// ////////////////////////////////////////////////////////////////// - -PathArgument::PathArgument() - : kind_( kindNone ) -{ -} - - -PathArgument::PathArgument( ArrayIndex index ) - : index_( index ) - , kind_( kindIndex ) -{ -} - - -PathArgument::PathArgument( const char *key ) - : key_( key ) - , kind_( kindKey ) -{ -} - - -PathArgument::PathArgument( const std::string &key ) - : key_( key.c_str() ) - , kind_( kindKey ) -{ -} - -// class Path -// ////////////////////////////////////////////////////////////////// - -Path::Path( const std::string &path, - const PathArgument &a1, - const PathArgument &a2, - const PathArgument &a3, - const PathArgument &a4, - const PathArgument &a5 ) -{ - InArgs in; - in.push_back( &a1 ); - in.push_back( &a2 ); - in.push_back( &a3 ); - in.push_back( &a4 ); - in.push_back( &a5 ); - makePath( path, in ); -} - - -void -Path::makePath( const std::string &path, - const InArgs &in ) -{ - const char *current = path.c_str(); - const char *end = current + path.length(); - InArgs::const_iterator itInArg = in.begin(); - while ( current != end ) - { - if ( *current == '[' ) - { - ++current; - if ( *current == '%' ) - addPathInArg( path, in, itInArg, PathArgument::kindIndex ); - else - { - ArrayIndex index = 0; - for ( ; current != end && *current >= '0' && *current <= '9'; ++current ) - index = index * 10 + ArrayIndex(*current - '0'); - args_.push_back( index ); - } - if ( current == end || *current++ != ']' ) - invalidPath( path, int(current - path.c_str()) ); - } - else if ( *current == '%' ) - { - addPathInArg( path, in, itInArg, PathArgument::kindKey ); - ++current; - } - else if ( *current == '.' ) - { - ++current; - } - else - { - const char *beginName = current; - while ( current != end && !strchr( "[.", *current ) ) - ++current; - args_.push_back( std::string( beginName, current ) ); - } - } -} - - -void -Path::addPathInArg( const std::string &path, - const InArgs &in, - InArgs::const_iterator &itInArg, - PathArgument::Kind kind ) -{ - if ( itInArg == in.end() ) - { - // Error: missing argument %d - } - else if ( (*itInArg)->kind_ != kind ) - { - // Error: bad argument type - } - else - { - args_.push_back( **itInArg ); - } -} - - -void -Path::invalidPath( const std::string &path, - int location ) -{ - // Error: invalid path. -} - - -const Value & -Path::resolve( const Value &root ) const -{ - const Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) - { - // Error: unable to resolve path (array value expected at position... - } - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - { - // Error: unable to resolve path (object value expected at position...) - } - node = &((*node)[arg.key_]); - if ( node == &Value::null ) - { - // Error: unable to resolve path (object has no member named '' at position...) - } - } - } - return *node; -} - - -Value -Path::resolve( const Value &root, - const Value &defaultValue ) const -{ - const Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) - return defaultValue; - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - return defaultValue; - node = &((*node)[arg.key_]); - if ( node == &Value::null ) - return defaultValue; - } - } - return *node; -} - - -Value & -Path::make( Value &root ) const -{ - Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() ) - { - // Error: node is not an array at position ... - } - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - { - // Error: node is not an object at position... - } - node = &((*node)[arg.key_]); - } - } - return *node; -} - - -} // namespace Json diff --git a/ext/jsoncpp/src/json_valueiterator.inl b/ext/jsoncpp/src/json_valueiterator.inl deleted file mode 100644 index 7457ca38..00000000 --- a/ext/jsoncpp/src/json_valueiterator.inl +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -// included by json_value.cpp - -namespace Json { - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIteratorBase -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIteratorBase::ValueIteratorBase() -#ifndef JSON_VALUE_USE_INTERNAL_MAP - : current_() - , isNull_( true ) -{ -} -#else - : isArray_( true ) - , isNull_( true ) -{ - iterator_.array_ = ValueInternalArray::IteratorState(); -} -#endif - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator ¤t ) - : current_( current ) - , isNull_( false ) -{ -} -#else -ValueIteratorBase::ValueIteratorBase( const ValueInternalArray::IteratorState &state ) - : isArray_( true ) -{ - iterator_.array_ = state; -} - - -ValueIteratorBase::ValueIteratorBase( const ValueInternalMap::IteratorState &state ) - : isArray_( false ) -{ - iterator_.map_ = state; -} -#endif - -Value & -ValueIteratorBase::deref() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - return current_->second; -#else - if ( isArray_ ) - return ValueInternalArray::dereference( iterator_.array_ ); - return ValueInternalMap::value( iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::increment() -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ++current_; -#else - if ( isArray_ ) - ValueInternalArray::increment( iterator_.array_ ); - ValueInternalMap::increment( iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::decrement() -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - --current_; -#else - if ( isArray_ ) - ValueInternalArray::decrement( iterator_.array_ ); - ValueInternalMap::decrement( iterator_.map_ ); -#endif -} - - -ValueIteratorBase::difference_type -ValueIteratorBase::computeDistance( const SelfType &other ) const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP -# ifdef JSON_USE_CPPTL_SMALLMAP - return current_ - other.current_; -# else - // Iterator for null value are initialized using the default - // constructor, which initialize current_ to the default - // std::map::iterator. As begin() and end() are two instance - // of the default std::map::iterator, they can not be compared. - // To allow this, we handle this comparison specifically. - if ( isNull_ && other.isNull_ ) - { - return 0; - } - - - // Usage of std::distance is not portable (does not compile with Sun Studio 12 RogueWave STL, - // which is the one used by default). - // Using a portable hand-made version for non random iterator instead: - // return difference_type( std::distance( current_, other.current_ ) ); - difference_type myDistance = 0; - for ( Value::ObjectValues::iterator it = current_; it != other.current_; ++it ) - { - ++myDistance; - } - return myDistance; -# endif -#else - if ( isArray_ ) - return ValueInternalArray::distance( iterator_.array_, other.iterator_.array_ ); - return ValueInternalMap::distance( iterator_.map_, other.iterator_.map_ ); -#endif -} - - -bool -ValueIteratorBase::isEqual( const SelfType &other ) const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - if ( isNull_ ) - { - return other.isNull_; - } - return current_ == other.current_; -#else - if ( isArray_ ) - return ValueInternalArray::equals( iterator_.array_, other.iterator_.array_ ); - return ValueInternalMap::equals( iterator_.map_, other.iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::copy( const SelfType &other ) -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - current_ = other.current_; -#else - if ( isArray_ ) - iterator_.array_ = other.iterator_.array_; - iterator_.map_ = other.iterator_.map_; -#endif -} - - -Value -ValueIteratorBase::key() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const Value::CZString czstring = (*current_).first; - if ( czstring.c_str() ) - { - if ( czstring.isStaticString() ) - return Value( StaticString( czstring.c_str() ) ); - return Value( czstring.c_str() ); - } - return Value( czstring.index() ); -#else - if ( isArray_ ) - return Value( ValueInternalArray::indexOf( iterator_.array_ ) ); - bool isStatic; - const char *memberName = ValueInternalMap::key( iterator_.map_, isStatic ); - if ( isStatic ) - return Value( StaticString( memberName ) ); - return Value( memberName ); -#endif -} - - -UInt -ValueIteratorBase::index() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const Value::CZString czstring = (*current_).first; - if ( !czstring.c_str() ) - return czstring.index(); - return Value::UInt( -1 ); -#else - if ( isArray_ ) - return Value::UInt( ValueInternalArray::indexOf( iterator_.array_ ) ); - return Value::UInt( -1 ); -#endif -} - - -const char * -ValueIteratorBase::memberName() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const char *name = (*current_).first.c_str(); - return name ? name : ""; -#else - if ( !isArray_ ) - return ValueInternalMap::key( iterator_.map_ ); - return ""; -#endif -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueConstIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueConstIterator::ValueConstIterator() -{ -} - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator ¤t ) - : ValueIteratorBase( current ) -{ -} -#else -ValueConstIterator::ValueConstIterator( const ValueInternalArray::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} - -ValueConstIterator::ValueConstIterator( const ValueInternalMap::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} -#endif - -ValueConstIterator & -ValueConstIterator::operator =( const ValueIteratorBase &other ) -{ - copy( other ); - return *this; -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIterator::ValueIterator() -{ -} - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueIterator::ValueIterator( const Value::ObjectValues::iterator ¤t ) - : ValueIteratorBase( current ) -{ -} -#else -ValueIterator::ValueIterator( const ValueInternalArray::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} - -ValueIterator::ValueIterator( const ValueInternalMap::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} -#endif - -ValueIterator::ValueIterator( const ValueConstIterator &other ) - : ValueIteratorBase( other ) -{ -} - -ValueIterator::ValueIterator( const ValueIterator &other ) - : ValueIteratorBase( other ) -{ -} - -ValueIterator & -ValueIterator::operator =( const SelfType &other ) -{ - copy( other ); - return *this; -} - -} // namespace Json diff --git a/ext/jsoncpp/src/json_writer.cpp b/ext/jsoncpp/src/json_writer.cpp deleted file mode 100644 index 1bda183f..00000000 --- a/ext/jsoncpp/src/json_writer.cpp +++ /dev/null @@ -1,838 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -# include <json/writer.h> -# include "json_tool.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include <utility> -#include <assert.h> -#include <stdio.h> -#include <string.h> -#include <iostream> -#include <sstream> -#include <iomanip> - -#if _MSC_VER >= 1400 // VC++ 8.0 -#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. -#endif - -namespace Json { - -static bool containsControlCharacter( const char* str ) -{ - while ( *str ) - { - if ( isControlCharacter( *(str++) ) ) - return true; - } - return false; -} - - -std::string valueToString( LargestInt value ) -{ - UIntToStringBuffer buffer; - char *current = buffer + sizeof(buffer); - bool isNegative = value < 0; - if ( isNegative ) - value = -value; - uintToString( LargestUInt(value), current ); - if ( isNegative ) - *--current = '-'; - assert( current >= buffer ); - return current; -} - - -std::string valueToString( LargestUInt value ) -{ - UIntToStringBuffer buffer; - char *current = buffer + sizeof(buffer); - uintToString( value, current ); - assert( current >= buffer ); - return current; -} - -#if defined(JSON_HAS_INT64) - -std::string valueToString( Int value ) -{ - return valueToString( LargestInt(value) ); -} - - -std::string valueToString( UInt value ) -{ - return valueToString( LargestUInt(value) ); -} - -#endif // # if defined(JSON_HAS_INT64) - - -std::string valueToString( double value ) -{ - char buffer[32]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. - sprintf_s(buffer, sizeof(buffer), "%#.16g", value); -#else - sprintf(buffer, "%#.16g", value); -#endif - char* ch = buffer + strlen(buffer) - 1; - if (*ch != '0') return buffer; // nothing to truncate, so save time - while(ch > buffer && *ch == '0'){ - --ch; - } - char* last_nonzero = ch; - while(ch >= buffer){ - switch(*ch){ - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - --ch; - continue; - case '.': - // Truncate zeroes to save bytes in output, but keep one. - *(last_nonzero+2) = '\0'; - return buffer; - default: - return buffer; - } - } - return buffer; -} - - -std::string valueToString( bool value ) -{ - return value ? "true" : "false"; -} - -std::string valueToQuotedString( const char *value ) -{ - // Not sure how to handle unicode... - if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value )) - return std::string("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to std::string is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - std::string::size_type maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL - std::string result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - for (const char* c=value; *c != 0; ++c) - { - switch(*c) - { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - //case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something. - // blep notes: actually escaping \/ may be useful in javascript to avoid </ - // sequence. - // Should add a flag to allow this compatibility mode and prevent this - // sequence from occurring. - default: - if ( isControlCharacter( *c ) ) - { - std::ostringstream oss; - oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int>(*c); - result += oss.str(); - } - else - { - result += *c; - } - break; - } - } - result += "\""; - return result; -} - -// Class Writer -// ////////////////////////////////////////////////////////////////// -Writer::~Writer() -{ -} - - -// Class FastWriter -// ////////////////////////////////////////////////////////////////// - -FastWriter::FastWriter() - : yamlCompatiblityEnabled_( false ) -{ -} - - -void -FastWriter::enableYAMLCompatibility() -{ - yamlCompatiblityEnabled_ = true; -} - - -std::string -FastWriter::write( const Value &root ) -{ - document_ = ""; - writeValue( root ); - document_ += "\n"; - return document_; -} - - -void -FastWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - document_ += "null"; - break; - case intValue: - document_ += valueToString( value.asLargestInt() ); - break; - case uintValue: - document_ += valueToString( value.asLargestUInt() ); - break; - case realValue: - document_ += valueToString( value.asDouble() ); - break; - case stringValue: - document_ += valueToQuotedString( value.asCString() ); - break; - case booleanValue: - document_ += valueToString( value.asBool() ); - break; - case arrayValue: - { - document_ += "["; - int size = value.size(); - for ( int index =0; index < size; ++index ) - { - if ( index > 0 ) - document_ += ","; - writeValue( value[index] ); - } - document_ += "]"; - } - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - document_ += "{"; - for ( Value::Members::iterator it = members.begin(); - it != members.end(); - ++it ) - { - const std::string &name = *it; - if ( it != members.begin() ) - document_ += ","; - document_ += valueToQuotedString( name.c_str() ); - document_ += yamlCompatiblityEnabled_ ? ": " - : ":"; - writeValue( value[name] ); - } - document_ += "}"; - } - break; - } -} - - -// Class StyledWriter -// ////////////////////////////////////////////////////////////////// - -StyledWriter::StyledWriter() - : rightMargin_( 74 ) - , indentSize_( 3 ) -{ -} - - -std::string -StyledWriter::write( const Value &root ) -{ - document_ = ""; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue( root ); - writeValue( root ); - writeCommentAfterValueOnSameLine( root ); - document_ += "\n"; - return document_; -} - - -void -StyledWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - pushValue( "null" ); - break; - case intValue: - pushValue( valueToString( value.asLargestInt() ) ); - break; - case uintValue: - pushValue( valueToString( value.asLargestUInt() ) ); - break; - case realValue: - pushValue( valueToString( value.asDouble() ) ); - break; - case stringValue: - pushValue( valueToQuotedString( value.asCString() ) ); - break; - case booleanValue: - pushValue( valueToString( value.asBool() ) ); - break; - case arrayValue: - writeArrayValue( value); - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - if ( members.empty() ) - pushValue( "{}" ); - else - { - writeWithIndent( "{" ); - indent(); - Value::Members::iterator it = members.begin(); - for (;;) - { - const std::string &name = *it; - const Value &childValue = value[name]; - writeCommentBeforeValue( childValue ); - writeWithIndent( valueToQuotedString( name.c_str() ) ); - document_ += " : "; - writeValue( childValue ); - if ( ++it == members.end() ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - document_ += ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "}" ); - } - } - break; - } -} - - -void -StyledWriter::writeArrayValue( const Value &value ) -{ - unsigned size = value.size(); - if ( size == 0 ) - pushValue( "[]" ); - else - { - bool isArrayMultiLine = isMultineArray( value ); - if ( isArrayMultiLine ) - { - writeWithIndent( "[" ); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index =0; - for (;;) - { - const Value &childValue = value[index]; - writeCommentBeforeValue( childValue ); - if ( hasChildValue ) - writeWithIndent( childValues_[index] ); - else - { - writeIndent(); - writeValue( childValue ); - } - if ( ++index == size ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - document_ += ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "]" ); - } - else // output on a single line - { - assert( childValues_.size() == size ); - document_ += "[ "; - for ( unsigned index =0; index < size; ++index ) - { - if ( index > 0 ) - document_ += ", "; - document_ += childValues_[index]; - } - document_ += " ]"; - } - } -} - - -bool -StyledWriter::isMultineArray( const Value &value ) -{ - int size = value.size(); - bool isMultiLine = size*3 >= rightMargin_ ; - childValues_.clear(); - for ( int index =0; index < size && !isMultiLine; ++index ) - { - const Value &childValue = value[index]; - isMultiLine = isMultiLine || - ( (childValue.isArray() || childValue.isObject()) && - childValue.size() > 0 ); - } - if ( !isMultiLine ) // check if line length > max line length - { - childValues_.reserve( size ); - addChildValues_ = true; - int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' - for ( int index =0; index < size && !isMultiLine; ++index ) - { - writeValue( value[index] ); - lineLength += int( childValues_[index].length() ); - isMultiLine = isMultiLine && hasCommentForValue( value[index] ); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - - -void -StyledWriter::pushValue( const std::string &value ) -{ - if ( addChildValues_ ) - childValues_.push_back( value ); - else - document_ += value; -} - - -void -StyledWriter::writeIndent() -{ - if ( !document_.empty() ) - { - char last = document_[document_.length()-1]; - if ( last == ' ' ) // already indented - return; - if ( last != '\n' ) // Comments may add new-line - document_ += '\n'; - } - document_ += indentString_; -} - - -void -StyledWriter::writeWithIndent( const std::string &value ) -{ - writeIndent(); - document_ += value; -} - - -void -StyledWriter::indent() -{ - indentString_ += std::string( indentSize_, ' ' ); -} - - -void -StyledWriter::unindent() -{ - assert( int(indentString_.size()) >= indentSize_ ); - indentString_.resize( indentString_.size() - indentSize_ ); -} - - -void -StyledWriter::writeCommentBeforeValue( const Value &root ) -{ - if ( !root.hasComment( commentBefore ) ) - return; - document_ += normalizeEOL( root.getComment( commentBefore ) ); - document_ += "\n"; -} - - -void -StyledWriter::writeCommentAfterValueOnSameLine( const Value &root ) -{ - if ( root.hasComment( commentAfterOnSameLine ) ) - document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); - - if ( root.hasComment( commentAfter ) ) - { - document_ += "\n"; - document_ += normalizeEOL( root.getComment( commentAfter ) ); - document_ += "\n"; - } -} - - -bool -StyledWriter::hasCommentForValue( const Value &value ) -{ - return value.hasComment( commentBefore ) - || value.hasComment( commentAfterOnSameLine ) - || value.hasComment( commentAfter ); -} - - -std::string -StyledWriter::normalizeEOL( const std::string &text ) -{ - std::string normalized; - normalized.reserve( text.length() ); - const char *begin = text.c_str(); - const char *end = begin + text.length(); - const char *current = begin; - while ( current != end ) - { - char c = *current++; - if ( c == '\r' ) // mac or dos EOL - { - if ( *current == '\n' ) // convert dos EOL - ++current; - normalized += '\n'; - } - else // handle unix EOL & other char - normalized += c; - } - return normalized; -} - - -// Class StyledStreamWriter -// ////////////////////////////////////////////////////////////////// - -StyledStreamWriter::StyledStreamWriter( std::string indentation ) - : document_(NULL) - , rightMargin_( 74 ) - , indentation_( indentation ) -{ -} - - -void -StyledStreamWriter::write( std::ostream &out, const Value &root ) -{ - document_ = &out; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue( root ); - writeValue( root ); - writeCommentAfterValueOnSameLine( root ); - *document_ << "\n"; - document_ = NULL; // Forget the stream, for safety. -} - - -void -StyledStreamWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - pushValue( "null" ); - break; - case intValue: - pushValue( valueToString( value.asLargestInt() ) ); - break; - case uintValue: - pushValue( valueToString( value.asLargestUInt() ) ); - break; - case realValue: - pushValue( valueToString( value.asDouble() ) ); - break; - case stringValue: - pushValue( valueToQuotedString( value.asCString() ) ); - break; - case booleanValue: - pushValue( valueToString( value.asBool() ) ); - break; - case arrayValue: - writeArrayValue( value); - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - if ( members.empty() ) - pushValue( "{}" ); - else - { - writeWithIndent( "{" ); - indent(); - Value::Members::iterator it = members.begin(); - for (;;) - { - const std::string &name = *it; - const Value &childValue = value[name]; - writeCommentBeforeValue( childValue ); - writeWithIndent( valueToQuotedString( name.c_str() ) ); - *document_ << " : "; - writeValue( childValue ); - if ( ++it == members.end() ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "}" ); - } - } - break; - } -} - - -void -StyledStreamWriter::writeArrayValue( const Value &value ) -{ - unsigned size = value.size(); - if ( size == 0 ) - pushValue( "[]" ); - else - { - bool isArrayMultiLine = isMultineArray( value ); - if ( isArrayMultiLine ) - { - writeWithIndent( "[" ); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index =0; - for (;;) - { - const Value &childValue = value[index]; - writeCommentBeforeValue( childValue ); - if ( hasChildValue ) - writeWithIndent( childValues_[index] ); - else - { - writeIndent(); - writeValue( childValue ); - } - if ( ++index == size ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "]" ); - } - else // output on a single line - { - assert( childValues_.size() == size ); - *document_ << "[ "; - for ( unsigned index =0; index < size; ++index ) - { - if ( index > 0 ) - *document_ << ", "; - *document_ << childValues_[index]; - } - *document_ << " ]"; - } - } -} - - -bool -StyledStreamWriter::isMultineArray( const Value &value ) -{ - int size = value.size(); - bool isMultiLine = size*3 >= rightMargin_ ; - childValues_.clear(); - for ( int index =0; index < size && !isMultiLine; ++index ) - { - const Value &childValue = value[index]; - isMultiLine = isMultiLine || - ( (childValue.isArray() || childValue.isObject()) && - childValue.size() > 0 ); - } - if ( !isMultiLine ) // check if line length > max line length - { - childValues_.reserve( size ); - addChildValues_ = true; - int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' - for ( int index =0; index < size && !isMultiLine; ++index ) - { - writeValue( value[index] ); - lineLength += int( childValues_[index].length() ); - isMultiLine = isMultiLine && hasCommentForValue( value[index] ); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - - -void -StyledStreamWriter::pushValue( const std::string &value ) -{ - if ( addChildValues_ ) - childValues_.push_back( value ); - else - *document_ << value; -} - - -void -StyledStreamWriter::writeIndent() -{ - /* - Some comments in this method would have been nice. ;-) - - if ( !document_.empty() ) - { - char last = document_[document_.length()-1]; - if ( last == ' ' ) // already indented - return; - if ( last != '\n' ) // Comments may add new-line - *document_ << '\n'; - } - */ - *document_ << '\n' << indentString_; -} - - -void -StyledStreamWriter::writeWithIndent( const std::string &value ) -{ - writeIndent(); - *document_ << value; -} - - -void -StyledStreamWriter::indent() -{ - indentString_ += indentation_; -} - - -void -StyledStreamWriter::unindent() -{ - assert( indentString_.size() >= indentation_.size() ); - indentString_.resize( indentString_.size() - indentation_.size() ); -} - - -void -StyledStreamWriter::writeCommentBeforeValue( const Value &root ) -{ - if ( !root.hasComment( commentBefore ) ) - return; - *document_ << normalizeEOL( root.getComment( commentBefore ) ); - *document_ << "\n"; -} - - -void -StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root ) -{ - if ( root.hasComment( commentAfterOnSameLine ) ) - *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); - - if ( root.hasComment( commentAfter ) ) - { - *document_ << "\n"; - *document_ << normalizeEOL( root.getComment( commentAfter ) ); - *document_ << "\n"; - } -} - - -bool -StyledStreamWriter::hasCommentForValue( const Value &value ) -{ - return value.hasComment( commentBefore ) - || value.hasComment( commentAfterOnSameLine ) - || value.hasComment( commentAfter ); -} - - -std::string -StyledStreamWriter::normalizeEOL( const std::string &text ) -{ - std::string normalized; - normalized.reserve( text.length() ); - const char *begin = text.c_str(); - const char *end = begin + text.length(); - const char *current = begin; - while ( current != end ) - { - char c = *current++; - if ( c == '\r' ) // mac or dos EOL - { - if ( *current == '\n' ) // convert dos EOL - ++current; - normalized += '\n'; - } - else // handle unix EOL & other char - normalized += c; - } - return normalized; -} - - -std::ostream& operator<<( std::ostream &sout, const Value &root ) -{ - Json::StyledStreamWriter writer; - writer.write(sout, root); - return sout; -} - - -} // namespace Json @@ -159,7 +159,7 @@ int main(int argc,char **argv) } std::string signature(Utils::base64Decode(argv[4],strlen(argv[4]))); - if ((signature.length() > ZEROTIER_ADDRESS_LENGTH)&&(id.verifySignature(inf.data(),inf.length(),signature))) { + if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verifySignature(inf.data(),inf.length(),signature.data(),signature.length()))) { std::cout << argv[3] << " signature valid" << std::endl; } else { std::cerr << argv[3] << " signature check FAILED" << std::endl; @@ -46,7 +46,6 @@ #include "node/Node.hpp" #include "node/Utils.hpp" -#include "node/Defaults.hpp" #include "launcher.h" @@ -67,24 +66,15 @@ static void sighandlerQuit(int sig) n->terminate(); else exit(0); } -static void sighandlerUsr(int sig) -{ -} -static void sighandlerHup(int sig) -{ - Node *n = node; - if (n) - n->updateStatusNow(); -} #endif int main(int argc,char **argv) { #ifndef _WIN32 - signal(SIGHUP,&sighandlerHup); + signal(SIGHUP,SIG_IGN); signal(SIGPIPE,SIG_IGN); - signal(SIGUSR1,&sighandlerUsr); - signal(SIGUSR2,&sighandlerUsr); + signal(SIGUSR1,SIG_IGN); + signal(SIGUSR2,SIG_IGN); signal(SIGALRM,SIG_IGN); signal(SIGINT,&sighandlerQuit); signal(SIGTERM,&sighandlerQuit); @@ -124,13 +114,16 @@ int main(int argc,char **argv) int exitCode = ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION; - node = new Node(homeDir,ZT_DEFAULTS.configUrlPrefix.c_str(),ZT_DEFAULTS.configAuthority.c_str()); + node = new Node(homeDir); + const char *termReason = (char *)0; switch(node->run()) { case Node::NODE_RESTART_FOR_RECONFIGURATION: exitCode = ZT_EXEC_RETURN_VALUE_PLEASE_RESTART; break; case Node::NODE_UNRECOVERABLE_ERROR: exitCode = ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR; + termReason = node->reasonForTermination(); + fprintf(stderr,"%s: abnormal termination: %s\n",argv[0],(termReason) ? termReason : "(unknown reason)"); break; case Node::NODE_NEW_VERSION_AVAILABLE: exitCode = ZT_EXEC_RETURN_VALUE_TERMINATED_FOR_UPGRADE; diff --git a/makekeypair.cpp b/makekeypair.cpp new file mode 100644 index 00000000..b8736a4e --- /dev/null +++ b/makekeypair.cpp @@ -0,0 +1,47 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <iostream> +#include <string> +#include "node/EllipticCurveKeyPair.hpp" +#include "node/EllipticCurveKey.hpp" +#include "node/Utils.hpp" + +using namespace ZeroTier; + +int main(int argc,char **argv) +{ + std::cout << "[generating]" << std::endl; + + EllipticCurveKeyPair kp; + kp.generate(); + + std::cout << "PUBLIC: " << kp.pub().toHex() << std::endl; + std::cout << "PRIVATE: " << kp.priv().toHex() << std::endl; + + return 0; +} diff --git a/netconf-service/Makefile b/netconf-service/Makefile new file mode 100644 index 00000000..af737a97 --- /dev/null +++ b/netconf-service/Makefile @@ -0,0 +1,7 @@ +all: + gcc -O6 -c ../ext/lz4/lz4hc.c ../ext/lz4/lz4.c + g++ -DZT_OSNAME="linux" -DZT_ARCH="x86_64" -I/usr/include/mysql -I../ext/bin/libcrypto/include -O -pthread -o netconf.service netconf.cpp ../node/Utils.cpp ../node/Identity.cpp ../node/EllipticCurveKeyPair.cpp ../node/Salsa20.cpp ../node/HMAC.cpp lz4.o lz4hc.o ../ext/bin/libcrypto/linux-x86_64/libcrypto.a -lmysqlpp + g++ -DZT_OSNAME="linux" -DZT_ARCH="x86_64" -I/usr/include/mysql -I../ext/bin/libcrypto/include -O -pthread -o netconf-test netconf-test.cpp ../node/Utils.cpp ../node/Identity.cpp ../node/EllipticCurveKeyPair.cpp ../node/Salsa20.cpp ../node/HMAC.cpp ../node/Logger.cpp ../node/Service.cpp ../node/Thread.cpp lz4.o lz4hc.o ../ext/bin/libcrypto/linux-x86_64/libcrypto.a + +clean: + rm -f *.o netconf.service netconf-test diff --git a/netconf-service/netconf-test.cpp b/netconf-service/netconf-test.cpp new file mode 100644 index 00000000..6c7ef9f2 --- /dev/null +++ b/netconf-service/netconf-test.cpp @@ -0,0 +1,83 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +/* Self-tester that makes both new and repeated requests to netconf */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/time.h> + +#include <vector> +#include <string> +#include <iostream> + +#include "../node/Dictionary.hpp" +#include "../node/Service.hpp" +#include "../node/Identity.hpp" +#include "../node/RuntimeEnvironment.hpp" +#include "../node/Logger.hpp" +#include "../node/Thread.hpp" + +using namespace ZeroTier; + +static void svcHandler(void *arg,Service &svc,const Dictionary &msg) +{ + std::cout << msg.toString(); +} + +int main(int argc,char **argv) +{ + RuntimeEnvironment renv; + renv.log = new Logger((const char *)0,(const char *)0,0); + Service svc(&renv,"netconf","./netconf.service",&svcHandler,(void *)0); + + srand(time(0)); + + std::vector<Identity> population; + for(;;) { + Identity id; + if ((population.empty())||(rand() < (RAND_MAX / 4))) { + id.generate(); + population.push_back(id); + std::cout << "Testing with new identity: " << id.address().toString() << std::endl; + } else { + id = population[rand() % population.size()]; + Thread::sleep(1000); + std::cout << "Testing with existing identity: " << id.address().toString() << std::endl; + } + + Dictionary request; + request["type"] = "netconf-request"; + request["peerId"] = id.toString(false); + request["nwid"] = "6c92786fee000001"; + request["requestId"] = "12345"; + + svc.send(request); + } +} diff --git a/netconf-service/netconf.cpp b/netconf-service/netconf.cpp new file mode 100644 index 00000000..af6ed4e6 --- /dev/null +++ b/netconf-service/netconf.cpp @@ -0,0 +1,345 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +/* + * This is the netconf service. It's currently used only by netconf nodes that + * are run by ZeroTier itself. There is nothing to prevent you from running + * your own if you wanted to create your own networks outside our system. + * + * That being said, we'd like to charge for private networks to support + * ZeroTier One and future development efforts. So while this software is + * open source and we're not going to stop you from sidestepping this, we + * do ask -- honor system here -- that you pay for private networks if you + * are going to use them for any commercial purpose such as a business VPN + * alternative. + * + * This will at the moment only build on Linux and requires the mysql++ + * library, which is available here: + * + * http://tangentsoft.net/mysql++/ + * + * (Packages are available for CentOS via EPEL and for any Debian distro.) + * + * This program must be built and installed in the services.d subfolder of + * the ZeroTier One home folder of the node designated to act as a master + * for networks. Doing so will enable the NETWORK_CONFIG_REQUEST protocol + * verb. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <arpa/inet.h> + +#include <iostream> +#include <string> +#include <map> +#include <list> +#include <vector> +#include <algorithm> + +#include <mysql++/mysql++.h> + +#include "../node/Dictionary.hpp" +#include "../node/Identity.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" + +using namespace ZeroTier; +using namespace mysqlpp; + +static Mutex stdoutWriteLock; +static Connection *dbCon = (Connection *)0; +static char mysqlHost[64],mysqlPort[64],mysqlDatabase[64],mysqlUser[64],mysqlPassword[64]; + +static void connectOrReconnect() +{ + for(;;) { + delete dbCon; + try { + dbCon = new Connection(mysqlDatabase,mysqlHost,mysqlUser,mysqlPassword,(unsigned int)strtol(mysqlPort,(char **)0,10)); + if (dbCon->connected()) { + fprintf(stderr,"(re?)-connected to mysql server successfully\n"); + break; + } else { + fprintf(stderr,"unable to connect to database server (connection closed), trying again in 1s...\n"); + usleep(1000000); + } + } catch (std::exception &exc) { + fprintf(stderr,"unable to connect to database server (%s), trying again in 1s...\n",exc.what()); + usleep(1000000); + } catch ( ... ) { + fprintf(stderr,"unable to connect to database server (unknown exception), trying again in 1s...\n"); + usleep(1000000); + } + } +} + +int main(int argc,char **argv) +{ + { + char *ee = getenv("ZT_NETCONF_MYSQL_HOST"); + if (!ee) { + fprintf(stderr,"missing environment variable: ZT_NETCONF_MYSQL_HOST\n"); + return -1; + } + strcpy(mysqlHost,ee); + ee = getenv("ZT_NETCONF_MYSQL_PORT"); + if (!ee) + strcpy(mysqlPort,"3306"); + else strcpy(mysqlPort,ee); + ee = getenv("ZT_NETCONF_MYSQL_DATABASE"); + if (!ee) { + fprintf(stderr,"missing environment variable: ZT_NETCONF_MYSQL_DATABASE\n"); + return -1; + } + strcpy(mysqlDatabase,ee); + ee = getenv("ZT_NETCONF_MYSQL_USER"); + if (!ee) { + fprintf(stderr,"missing environment variable: ZT_NETCONF_MYSQL_USER\n"); + return -1; + } + strcpy(mysqlUser,ee); + ee = getenv("ZT_NETCONF_MYSQL_PASSWORD"); + if (!ee) { + fprintf(stderr,"missing environment variable: ZT_NETCONF_MYSQL_PASSWORD\n"); + return -1; + } + strcpy(mysqlPassword,ee); + } + + char buf[131072]; + std::string dictBuf; + + connectOrReconnect(); + for(;;) { + for(int l=0;l<4;) { + int n = (int)read(STDIN_FILENO,buf + l,4 - l); + if (n < 0) { + fprintf(stderr,"error reading frame size from stdin: %s\n",strerror(errno)); + return -1; + } + l += n; + } + unsigned int fsize = (unsigned int)ntohl(*((const uint32_t *)buf)); + + while (dictBuf.length() < fsize) { + int n = (int)read(STDIN_FILENO,buf,std::min((int)sizeof(buf),(int)(fsize - dictBuf.length()))); + if (n < 0) { + fprintf(stderr,"error reading frame from stdin: %s\n",strerror(errno)); + return -1; + } + for(int i=0;i<n;++i) + dictBuf.push_back(buf[i]); + } + Dictionary request(dictBuf); + dictBuf = ""; + + if (!dbCon->connected()) + connectOrReconnect(); + + try { + const std::string &reqType = request.get("type"); + if (reqType == "netconf-request") { // NETWORK_CONFIG_REQUEST packet + Identity peerIdentity(request.get("peerId")); + uint64_t nwid = strtoull(request.get("nwid").c_str(),(char **)0,16); + Dictionary meta; + if (request.contains("meta")) + meta.fromString(request.get("meta")); + + // Do quick signature check / sanity check + if (!peerIdentity.locallyValidate(false)) { + fprintf(stderr,"identity failed signature check: %s\n",peerIdentity.toString(false).c_str()); + continue; + } + + // Save identity if unknown + { + Query q = dbCon->query(); + q << "SELECT identity,identityValidated FROM Node WHERE id = " << peerIdentity.address().toInt(); + StoreQueryResult rs = q.store(); + if (rs.num_rows() > 0) { + if (rs[0]["identity"] != peerIdentity.toString(false)) { + // TODO: handle collisions... + continue; + } else if ((int)rs[0]["identityValidated"] == 0) { + // TODO: launch background validation + } + } else { + q = dbCon->query(); + q << "INSERT INTO Node (id,creationTime,lastSeen,identity) VALUES (" << peerIdentity.address().toInt() << "," << Utils::now() << ",0," << quote << peerIdentity.toString(false) << ")"; + if (!q.exec()) { + fprintf(stderr,"error inserting Node row for peer %s, aborting netconf request\n",peerIdentity.address().toString().c_str()); + continue; + } + // TODO: launch background validation + } + } + + // Update lastSeen + { + Query q = dbCon->query(); + q << "UPDATE Node SET lastSeen = " << Utils::now() << " WHERE id = " << peerIdentity.address().toInt(); + q.exec(); + } + + bool isOpen = false; + { + Query q = dbCon->query(); + q << "SELECT isOpen FROM Network WHERE id = " << nwid; + StoreQueryResult rs = q.store(); + if (rs.num_rows() > 0) + isOpen = ((int)rs[0]["isOpen"] > 0); + else { + Dictionary response; + response["peer"] = peerIdentity.address().toString(); + response["nwid"] = request.get("nwid"); + response["type"] = "netconf-response"; + response["requestId"] = request.get("requestId"); + response["error"] = "NOT_FOUND"; + std::string respm = response.toString(); + uint32_t respml = (uint32_t)htonl((uint32_t)respm.length()); + + stdoutWriteLock.lock(); + write(STDOUT_FILENO,&respml,4); + write(STDOUT_FILENO,respm.data(),respm.length()); + stdoutWriteLock.unlock(); + continue; + } + } + + Dictionary netconf; + + netconf["peer"] = peerIdentity.address().toString(); + sprintf(buf,"%.16llx",(unsigned long long)nwid); + netconf["nwid"] = buf; + netconf["isOpen"] = (isOpen ? "1" : "0"); + + if (!isOpen) { + // TODO: handle closed networks, look up private membership, + // generate signed cert. + } + + std::string ipv4Static,ipv6Static; + + { + // Check for IPv4 static assignments + Query q = dbCon->query(); + q << "SELECT INET_NTOA(ip) AS ip,netmaskBits FROM IPv4Static WHERE Node_id = " << peerIdentity.address().toInt() << " AND Network_id = " << nwid; + StoreQueryResult rs = q.store(); + if (rs.num_rows() > 0) { + for(int i=0;i<rs.num_rows();++i) { + if (ipv4Static.length()) + ipv4Static.push_back(','); + ipv4Static.append(rs[i]["ip"].c_str()); + ipv4Static.push_back('/'); + ipv4Static.append(rs[i]["netmaskBits"].c_str()); + } + } + + // Try to auto-assign if there's any auto-assign networks with space + // available. + if (!ipv4Static.length()) { + unsigned char addressBytes[5]; + peerIdentity.address().copyTo(addressBytes,5); + + q = dbCon->query(); + q << "SELECT ipNet,netmaskBits FROM IPv4AutoAssign WHERE Network_id = " << nwid; + rs = q.store(); + if (rs.num_rows() > 0) { + for(int aaRow=0;aaRow<rs.num_rows();++aaRow) { + uint32_t ipNet = (uint32_t)((unsigned long)rs[aaRow]["ipNet"]); + unsigned int netmaskBits = (unsigned int)rs[aaRow]["netmaskBits"]; + + uint32_t tryIp = (((uint32_t)addressBytes[1]) << 24) | + (((uint32_t)addressBytes[2]) << 16) | + (((uint32_t)addressBytes[3]) << 8) | + ((((uint32_t)addressBytes[4]) % 254) + 1); + tryIp &= (0xffffffff >> netmaskBits); + tryIp |= ipNet; + + for(int k=0;k<100000;++k) { + Query q2 = dbCon->query(); + q2 << "INSERT INTO IPv4Static (Network_id,Node_id,ip,netmaskBits) VALUES (" << nwid << "," << peerIdentity.address().toInt() << "," << tryIp << "," << netmaskBits << ")"; + if (q2.exec()) { + sprintf(buf,"%u.%u.%u.%u",(unsigned int)((tryIp >> 24) & 0xff),(unsigned int)((tryIp >> 16) & 0xff),(unsigned int)((tryIp >> 8) & 0xff),(unsigned int)(tryIp & 0xff)); + if (ipv4Static.length()) + ipv4Static.push_back(','); + ipv4Static.append(buf); + ipv4Static.push_back('/'); + sprintf(buf,"%u",netmaskBits); + ipv4Static.append(buf); + break; + } else { // insert will fail if IP is in use due to uniqueness constraints in DB + ++tryIp; + if ((tryIp & 0xff) == 0) + tryIp |= 1; + tryIp &= (0xffffffff >> netmaskBits); + tryIp |= ipNet; + } + } + + if (ipv4Static.length()) + break; + } + } + } + } + + if (ipv4Static.length()) + netconf["ipv4Static"] = ipv4Static; + if (ipv6Static.length()) + netconf["ipv6Static"] = ipv6Static; + + { + Dictionary response; + response["peer"] = peerIdentity.address().toString(); + response["nwid"] = request.get("nwid"); + response["type"] = "netconf-response"; + response["requestId"] = request.get("requestId"); + response["netconf"] = netconf.toString(); + std::string respm = response.toString(); + uint32_t respml = (uint32_t)htonl((uint32_t)respm.length()); + + stdoutWriteLock.lock(); + write(STDOUT_FILENO,&respml,4); + write(STDOUT_FILENO,respm.data(),respm.length()); + stdoutWriteLock.unlock(); + } + } + } catch (std::exception &exc) { + fprintf(stderr,"unexpected exception handling message: %s\n",exc.what()); + } catch ( ... ) { + fprintf(stderr,"unexpected exception handling message: unknown exception\n"); + } + } +} diff --git a/node/Address.hpp b/node/Address.hpp index 4c912540..fce4f20c 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -28,72 +28,156 @@ #ifndef _ZT_ADDRESS_HPP #define _ZT_ADDRESS_HPP +#include <stdio.h> +#include <stdlib.h> #include <stdint.h> +#include <string.h> #include <string> #include "Utils.hpp" #include "MAC.hpp" #include "Constants.hpp" +#include "Buffer.hpp" namespace ZeroTier { /** - * ZeroTier address, which doubles as the last 5 octets of the MAC on taps - * - * Natural sort order will differ on big vs. little endian machines, but that - * won't matter when it's used as a local map/set key. + * A ZeroTier address */ class Address { -private: - union { - unsigned char o[ZT_ADDRESS_LENGTH]; - uint64_t v; - } _a; - public: Address() - throw() + throw() : + _a(0) { - _a.v = 0; } Address(const Address &a) + throw() : + _a(a._a) + { + } + + Address(uint64_t a) + throw() : + _a(a & 0xffffffffffULL) + { + } + + Address(const char *s) throw() { - _a.v = a._a.v; + unsigned char foo[ZT_ADDRESS_LENGTH]; + setTo(foo,Utils::unhex(s,foo,ZT_ADDRESS_LENGTH)); } - /** - * Create from a ZeroTier MAC - * - * @param m MAC (assumed to be a ZeroTier MAC) - */ - Address(const MAC &m) + Address(const std::string &s) throw() { - _a.v = 0; - for(int i=0;i<ZT_ADDRESS_LENGTH;++i) - _a.o[i] = m.data[i + 1]; + unsigned char foo[ZT_ADDRESS_LENGTH]; + setTo(foo,Utils::unhex(s.c_str(),foo,ZT_ADDRESS_LENGTH)); } /** - * @param bits Raw address -- 5 bytes in length + * @param bits Raw address -- 5 bytes, big-endian byte order + * @param len Length of array */ - Address(const void *bits) + Address(const void *bits,unsigned int len) throw() { - _a.v = 0; - for(int i=0;i<ZT_ADDRESS_LENGTH;++i) - _a.o[i] = ((const unsigned char *)bits)[i]; + setTo(bits,len); } inline Address &operator=(const Address &a) throw() { - _a.v = a._a.v; + _a = a._a; return *this; } + inline Address &operator=(const uint64_t a) + throw() + { + _a = (a & 0xffffffffffULL); + return *this; + } + + /** + * @param bits Raw address -- 5 bytes, big-endian byte order + * @param len Length of array + */ + inline void setTo(const void *bits,unsigned int len) + throw() + { + if (len < ZT_ADDRESS_LENGTH) { + _a = 0; + return; + } + const unsigned char *b = (const unsigned char *)bits; + uint64_t a = ((uint64_t)*b++) << 32; + a |= ((uint64_t)*b++) << 24; + a |= ((uint64_t)*b++) << 16; + a |= ((uint64_t)*b++) << 8; + a |= ((uint64_t)*b); + _a = a; + } + + /** + * @param bits Buffer to hold 5-byte address in big-endian byte order + * @param len Length of array + */ + inline void copyTo(void *bits,unsigned int len) const + throw() + { + if (len < ZT_ADDRESS_LENGTH) + return; + unsigned char *b = (unsigned char *)bits; + *(b++) = (unsigned char)((_a >> 32) & 0xff); + *(b++) = (unsigned char)((_a >> 24) & 0xff); + *(b++) = (unsigned char)((_a >> 16) & 0xff); + *(b++) = (unsigned char)((_a >> 8) & 0xff); + *b = (unsigned char)(_a & 0xff); + } + + /** + * Append to a buffer in big-endian byte order + * + * @param b Buffer to append to + */ + template<unsigned int C> + inline void appendTo(Buffer<C> &b) const + throw(std::out_of_range) + { + b.append((unsigned char)((_a >> 32) & 0xff)); + b.append((unsigned char)((_a >> 24) & 0xff)); + b.append((unsigned char)((_a >> 16) & 0xff)); + b.append((unsigned char)((_a >> 8) & 0xff)); + b.append((unsigned char)(_a & 0xff)); + } + + /** + * @return String containing address as 5 binary bytes + */ + inline std::string toBinaryString() const + { + std::string b; + b.push_back((char)((_a >> 32) & 0xff)); + b.push_back((char)((_a >> 24) & 0xff)); + b.push_back((char)((_a >> 16) & 0xff)); + b.push_back((char)((_a >> 8) & 0xff)); + b.push_back((char)(_a & 0xff)); + return b; + } + + /** + * @return Integer containing address (0 to 2^40) + */ + inline uint64_t toInt() const + throw() + { + return _a; + } + /** * Derive a MAC whose first octet is the ZeroTier LAN standard * @@ -104,8 +188,7 @@ public: { MAC m; m.data[0] = ZT_MAC_FIRST_OCTET; - for(int i=1;i<6;++i) - m.data[i] = _a.o[i - 1]; + copyTo(m.data + 1,ZT_ADDRESS_LENGTH); return m; } @@ -114,18 +197,15 @@ public: */ inline std::string toString() const { - return Utils::hex(_a.o,ZT_ADDRESS_LENGTH); + char buf[16]; + sprintf(buf,"%.10llx",(unsigned long long)_a); + return std::string(buf); }; /** - * Set address to zero - */ - inline void zero() throw() { _a.v = 0; } - - /** * @return True if this address is not zero */ - inline operator bool() const throw() { return (_a.v); } + inline operator bool() const throw() { return (_a); } /** * @return Sum of all bytes in address @@ -133,10 +213,7 @@ public: inline unsigned int sum() const throw() { - unsigned int s = 0; - for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i) - s += _a.o[i]; - return s; + return (unsigned int)(((_a >> 32) & 0xff) + ((_a >> 24) & 0xff) + ((_a >> 16) & 0xff) + ((_a >> 8) & 0xff) + (_a & 0xff)); } /** @@ -151,23 +228,24 @@ public: inline bool isReserved() const throw() { - return ((!_a.v)||(_a.o[0] == ZT_ADDRESS_RESERVED_PREFIX)); + return ((!_a)||((_a >> 32) == ZT_ADDRESS_RESERVED_PREFIX)); } - inline unsigned char *data() throw() { return _a.o; } - inline const unsigned char *data() const throw() { return _a.o; } - - inline unsigned int size() const throw() { return ZT_ADDRESS_LENGTH; } + /** + * @param i Value from 0 to 4 (inclusive) + * @return Byte at said position (address interpreted in big-endian order) + */ + inline unsigned char operator[](unsigned int i) const throw() { return (unsigned char)((_a >> (32 - (i * 8))) & 0xff); } - inline unsigned char &operator[](unsigned int i) throw() { return _a.o[i]; } - inline unsigned char operator[](unsigned int i) const throw() { return _a.o[i]; } + inline bool operator==(const Address &a) const throw() { return (_a == a._a); } + inline bool operator!=(const Address &a) const throw() { return (_a != a._a); } + inline bool operator>(const Address &a) const throw() { return (_a > a._a); } + inline bool operator<(const Address &a) const throw() { return (_a < a._a); } + inline bool operator>=(const Address &a) const throw() { return (_a >= a._a); } + inline bool operator<=(const Address &a) const throw() { return (_a <= a._a); } - inline bool operator==(const Address &a) const throw() { return (_a.v == a._a.v); } - inline bool operator!=(const Address &a) const throw() { return (_a.v != a._a.v); } - inline bool operator<(const Address &a) const throw() { return (_a.v < a._a.v); } - inline bool operator>(const Address &a) const throw() { return (_a.v > a._a.v); } - inline bool operator<=(const Address &a) const throw() { return (_a.v <= a._a.v); } - inline bool operator>=(const Address &a) const throw() { return (_a.v >= a._a.v); } +private: + uint64_t _a; }; } // namespace ZeroTier diff --git a/node/BlobArray.hpp b/node/BlobArray.hpp deleted file mode 100644 index d78bcffa..00000000 --- a/node/BlobArray.hpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2012-2013 ZeroTier Networks LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#ifndef _ZT_BLOBARRAY_HPP -#define _ZT_BLOBARRAY_HPP - -#include <vector> -#include <string> -#include <algorithm> - -namespace ZeroTier { - -/** - * A vector of binary strings serializable in a packed format - * - * The format uses variable-length integers to indicate the length of each - * field. Each byte of the length has another byte with seven more significant - * bits if its 8th bit is set. Fields can be up to 2^28 in length. - */ -class BlobArray : public std::vector<std::string> -{ -public: - inline std::string serialize() const - { - std::string r; - for(BlobArray::const_iterator i=begin();i!=end();++i) { - unsigned int flen = (unsigned int)i->length(); - do { - unsigned char flenb = (unsigned char)(flen & 0x7f); - flen >>= 7; - flenb |= (flen) ? 0x80 : 0; - r.push_back((char)flenb); - } while (flen); - r.append(*i); - } - return r; - } - - /** - * Deserialize, replacing the current contents of this array - * - * @param data Serialized binary data - * @param len Length of serialized data - */ - inline void deserialize(const void *data,unsigned int len) - { - clear(); - for(unsigned int i=0;i<len;) { - unsigned int flen = 0; - unsigned int chunk = 0; - while (i < len) { - flen |= ((unsigned int)(((const unsigned char *)data)[i] & 0x7f)) << (7 * chunk++); - if (!(((const unsigned char *)data)[i++] & 0x80)) - break; - } - flen = std::min(flen,len - i); - push_back(std::string(((const char *)data) + i,flen)); - i += flen; - } - } - inline void deserialize(const std::string &data) - { - deserialize(data.data(),(unsigned int)data.length()); - } -}; - -} // namespace ZeroTier - -#endif - diff --git a/node/Constants.hpp b/node/Constants.hpp index fbb309e6..36973b38 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -118,6 +118,11 @@ error_no_ZT_ARCH_defined; #define ZT_DEFAULT_UDP_PORT 8993 /** + * Local control port, also used for multiple invocation check + */ +#define ZT_CONTROL_UDP_PORT 39393 + +/** * Default payload MTU for UDP packets * * In the future we might support UDP path MTU discovery, but for now we @@ -152,13 +157,6 @@ error_no_ZT_ARCH_defined; #define ZT_IF_MTU 2800 /** - * Maximum number of networks we can be a member of - * - * This is a safe value that's within the tap device limit on all known OSes. - */ -#define ZT_MAX_NETWORK_MEMBERSHIPS 16 - -/** * Maximum number of packet fragments we'll support * * The actual spec allows 16, but this is the most we'll support right @@ -185,9 +183,9 @@ error_no_ZT_ARCH_defined; #define ZT_MAC_FIRST_OCTET 0x32 /** - * How often Topology::clean() is called in ms + * How often Topology::clean() and Network::clean() are called in ms */ -#define ZT_TOPOLOGY_CLEAN_PERIOD 300000 +#define ZT_DB_CLEAN_PERIOD 300000 /** * Delay between WHOIS retries in ms @@ -233,21 +231,16 @@ error_no_ZT_ARCH_defined; #define ZT_MULTICAST_PROPAGATION_DEPTH 7 /** - * Length of circular ring buffer history of multicast packets + * Length of ring buffer history of recent multicast packets */ #define ZT_MULTICAST_DEDUP_HISTORY_LENGTH 1024 /** - * Expiration time in ms for multicast history items + * Expiration time in ms for multicast deduplication history items */ #define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 4000 /** - * Number of bits to randomly "decay" in bloom filter per hop - */ -#define ZT_MULTICAST_BLOOM_FILTER_DECAY_RATE 2 - -/** * Period between announcements of all multicast 'likes' in ms * * Announcement occurs when a multicast group is locally joined, but all @@ -282,23 +275,6 @@ error_no_ZT_ARCH_defined; #define ZT_PEER_DIRECT_PING_DELAY 120000 /** - * Period between rechecks of autoconfigure URL - * - * This is in the absence of an external message ordering a recheck. - */ -#define ZT_AUTOCONFIGURE_INTERVAL 3600000 - -/** - * Period between autoconfigure attempts if no successful autoconfig - */ -#define ZT_AUTOCONFIGURE_CHECK_DELAY 15000 - -/** - * Delay between updates of status file in home directory - */ -#define ZT_STATUS_OUTPUT_PERIOD 120000 - -/** * Minimum delay in Node service loop * * This is the shortest of the check delays/periods. @@ -348,9 +324,4 @@ error_no_ZT_ARCH_defined; */ #define ZT_RENDEZVOUS_NAT_T_DELAY 500 -/** - * Generate a new ownership verify secret on launch if older than this - */ -#define ZT_OVS_GENERATE_NEW_IF_OLDER_THAN 86400000 - #endif diff --git a/node/Defaults.cpp b/node/Defaults.cpp index f1454796..3e936b2d 100644 --- a/node/Defaults.cpp +++ b/node/Defaults.cpp @@ -68,9 +68,7 @@ static inline std::map< Identity,std::vector<InetAddress> > _mkSupernodeMap() Defaults::Defaults() throw(std::runtime_error) : - supernodes(_mkSupernodeMap()), - configUrlPrefix("http://api.zerotier.com/one/nc/"), - configAuthority("f9f34184ac:1:AwGgrWjb8dARXzruqxiy1+Qf+gz4iM5IMfQTCWrJXkwERdvbvxTPZvtIyitw4gS90TGIxW+e7uJxweg9Vyq5lZJBrg==:QeEQLm9ymLC3EcnIw2OUqufUwb2wgHSAg6wQOXKyhT779p/8Hz5485PZLJCbr/aVHjwzop8APJk9B45Zm0Mb/LEhQTBMH2jvc7qqoYnMCNCO9jpADeMJwMW5e1VFgIObWl9uNjhRbf5/m8dZcn0pKKGwjSoP1QTeVWOC8GkZhE25bUWj") + supernodes(_mkSupernodeMap()) { } diff --git a/node/Defaults.hpp b/node/Defaults.hpp index b9c8ecf5..3a350865 100644 --- a/node/Defaults.hpp +++ b/node/Defaults.hpp @@ -55,16 +55,6 @@ public: * Supernodes on the ZeroTier network */ const std::map< Identity,std::vector<InetAddress> > supernodes; - - /** - * URL prefix for autoconfiguration - */ - const std::string configUrlPrefix; - - /** - * Identity used to encrypt and authenticate configuration from URL - */ - const std::string configAuthority; }; extern const Defaults ZT_DEFAULTS; diff --git a/node/Demarc.cpp b/node/Demarc.cpp index 5efe55bc..b935ce53 100644 --- a/node/Demarc.cpp +++ b/node/Demarc.cpp @@ -100,7 +100,7 @@ bool Demarc::bindLocalUdp(unsigned int localPort) DemarcPortObj *v4r = &(_ports[(Port)v4p]); v4r->port = (Port)v4p; v4r->parent = this; - v4r->obj = v4 = new UdpSocket(localPort,false,&Demarc::_CBudpSocketPacketHandler,v4r); + v4r->obj = v4 = new UdpSocket(false,localPort,false,&Demarc::_CBudpSocketPacketHandler,v4r); v4r->type = PORT_TYPE_UDP_SOCKET_V4; } catch ( ... ) { _ports.erase((Port)v4p); @@ -112,7 +112,7 @@ bool Demarc::bindLocalUdp(unsigned int localPort) DemarcPortObj *v6r = &(_ports[(Port)v6p]); v6r->port = (Port)v6p; v6r->parent = this; - v6r->obj = v6 = new UdpSocket(localPort,true,&Demarc::_CBudpSocketPacketHandler,v6r); + v6r->obj = v6 = new UdpSocket(false,localPort,true,&Demarc::_CBudpSocketPacketHandler,v6r); v6r->type = PORT_TYPE_UDP_SOCKET_V6; } catch ( ... ) { _ports.erase((Port)v6p); diff --git a/node/Demarc.hpp b/node/Demarc.hpp index e13ed0cb..721dc0c8 100644 --- a/node/Demarc.hpp +++ b/node/Demarc.hpp @@ -49,6 +49,9 @@ class UdpSocket; * about what they actually are. * * All ports are closed when this class is destroyed. + * + * Its name "demarcation point" comes from the telco/cable terminology for + * the box where wires terminate at a customer's property. */ class Demarc { diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp new file mode 100644 index 00000000..f706168f --- /dev/null +++ b/node/Dictionary.hpp @@ -0,0 +1,207 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_DICTIONARY_HPP +#define _ZT_DICTIONARY_HPP + +#include <string> +#include <map> +#include <stdexcept> +#include "Constants.hpp" + +namespace ZeroTier { + +/** + * Simple key/value dictionary with string serialization + * + * The serialization format is a flat key=value with backslash escape. + * It does not support comments or other syntactic complexities. It is + * human-readable if the keys and values in the dictionary are also + * human-readable. Otherwise it might contain unprintable characters. + */ +class Dictionary : public std::map<std::string,std::string> +{ +public: + Dictionary() + { + } + + /** + * @param s String-serialized dictionary + */ + Dictionary(const char *s) + { + fromString(s); + } + + /** + * @param s String-serialized dictionary + */ + Dictionary(const std::string &s) + { + fromString(s.c_str()); + } + + /** + * Get a key, throwing an exception if it is not present + * + * @param key Key to look up + * @return Reference to value + * @throws std::invalid_argument Key not found + */ + inline const std::string &get(const std::string &key) const + throw(std::invalid_argument) + { + const_iterator e(find(key)); + if (e == end()) + throw std::invalid_argument(std::string("missing required field: ")+key); + return e->second; + } + + /** + * Get a key, returning a default if not present + * + * @param key Key to look up + * @param dfl Default if not present + * @return Value or default + */ + inline const std::string &get(const std::string &key,const std::string &dfl) const + { + const_iterator e(find(key)); + if (e == end()) + return dfl; + return e->second; + } + + /** + * @param key Key to check + * @return True if dictionary contains key + */ + inline bool contains(const std::string &key) const + { + return (find(key) != end()); + } + + /** + * @return String-serialized dictionary + */ + inline std::string toString() const + { + std::string s; + + for(const_iterator kv(begin());kv!=end();++kv) { + _appendEsc(kv->first.data(),kv->first.length(),s); + s.push_back('='); + _appendEsc(kv->second.data(),kv->second.length(),s); + s.append(ZT_EOL_S); + } + + return s; + } + + /** + * Clear and initialize from a string + * + * @param s String-serialized dictionary + */ + inline void fromString(const char *s) + { + clear(); + bool escapeState = false; + std::string keyBuf; + std::string *element = &keyBuf; + while (*s) { + if (escapeState) { + escapeState = false; + switch(*s) { + case '0': + element->push_back((char)0); + break; + case 'r': + element->push_back('\r'); + break; + case 'n': + element->push_back('\n'); + break; + default: + element->push_back(*s); + break; + } + } else { + if (*s == '\\') { + escapeState = true; + } else if (*s == '=') { + if (element == &keyBuf) + element = &((*this)[keyBuf]); + } else if ((*s == '\r')||(*s == '\n')) { + if ((element == &keyBuf)&&(keyBuf.length() > 0)) + (*this)[keyBuf]; + keyBuf = ""; + element = &keyBuf; + } else element->push_back(*s); + } + ++s; + } + if ((element == &keyBuf)&&(keyBuf.length() > 0)) + (*this)[keyBuf]; + } + inline void fromString(const std::string &s) + { + fromString(s.c_str()); + } + +private: + static inline void _appendEsc(const char *data,unsigned int len,std::string &to) + { + for(unsigned int i=0;i<len;++i) { + switch(data[i]) { + case 0: + to.append("\\0"); + break; + case '\r': + to.append("\\r"); + break; + case '\n': + to.append("\\n"); + break; + case '\\': + to.append("\\\\"); + break; + case '=': + to.append("\\="); + break; + default: + to.push_back(data[i]); + break; + } + } + } +}; + +} // namespace ZeroTier + +#endif diff --git a/node/EllipticCurveKey.hpp b/node/EllipticCurveKey.hpp index 5a7b895f..b32f702d 100644 --- a/node/EllipticCurveKey.hpp +++ b/node/EllipticCurveKey.hpp @@ -65,15 +65,19 @@ public: throw() : _bytes(0) { + memset(_key,0,sizeof(_key)); } EllipticCurveKey(const void *data,unsigned int len) throw() { - if (len <= ZT_EC_MAX_BYTES) { - _bytes = len; - memcpy(_key,data,len); - } else _bytes = 0; + set(data,len); + } + + EllipticCurveKey(const std::string &data) + throw() + { + set(data.data(),data.length()); } EllipticCurveKey(const EllipticCurveKey &k) diff --git a/node/EllipticCurveKeyPair.cpp b/node/EllipticCurveKeyPair.cpp index bed0725e..66acf320 100644 --- a/node/EllipticCurveKeyPair.cpp +++ b/node/EllipticCurveKeyPair.cpp @@ -55,7 +55,20 @@ public: }; static _EC_Group ZT_EC_GROUP; -/* Key derivation function */ +/** + * Key derivation function + * + * TODO: + * If/when we document the protocol, this will have to be documented as + * well. It's a fairly standard KDF that uses SHA-256 to transform the + * raw EC key. It's generally considered good crypto practice to do this + * to eliminate the possibility of leaking information from EC exchange to + * downstream algorithms. + * + * In our code it is used to produce a two 32-bit keys. One key is used + * for Salsa20 and the other for HMAC-SHA-256. They are generated together + * as a single 64-bit key. + */ static void *_zt_EC_KDF(const void *in,size_t inlen,void *out,size_t *outlen) { SHA256_CTX sha; @@ -130,9 +143,8 @@ bool EllipticCurveKeyPair::generate() fread(tmp,sizeof(tmp),1,rf); fclose(rf); } else { - fprintf(stderr,"WARNING: cannot open /dev/urandom\n"); - for(unsigned int i=0;i<sizeof(tmp);++i) - tmp[i] = (unsigned char)(rand() >> 3); + fprintf(stderr,"FATAL: could not open /dev/urandom\n"); + exit(-1); } RAND_seed(tmp,sizeof(tmp)); #else diff --git a/node/EllipticCurveKeyPair.hpp b/node/EllipticCurveKeyPair.hpp index 2649f4c4..dbe08cc9 100644 --- a/node/EllipticCurveKeyPair.hpp +++ b/node/EllipticCurveKeyPair.hpp @@ -35,6 +35,8 @@ namespace ZeroTier { /** * An elliptic curve key pair supporting generation and key agreement + * + * This is basically OpenSSL libcrypto glue. */ class EllipticCurveKeyPair { diff --git a/node/EthernetTap.cpp b/node/EthernetTap.cpp index 8fb5e788..c45d50c2 100644 --- a/node/EthernetTap.cpp +++ b/node/EthernetTap.cpp @@ -218,7 +218,7 @@ EthernetTap::EthernetTap( int kextpid; char tmp[4096]; strcpy(tmp,_r->homePath.c_str()); - if ((kextpid = (int)fork()) == 0) { + if ((kextpid = (int)vfork()) == 0) { chdir(tmp); execl(ZT_MAC_KEXTLOAD,ZT_MAC_KEXTLOAD,"-q","-repository",tmp,"tap.kext",(const char *)0); exit(-1); @@ -255,7 +255,7 @@ EthernetTap::EthernetTap( // Configure MAC address and MTU, bring interface up long cpid; - if ((cpid = (long)fork()) == 0) { + if ((cpid = (long)vfork()) == 0) { execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,"lladdr",ethaddr,"mtu",mtustr,"up",(const char *)0); exit(-1); } else { @@ -285,7 +285,7 @@ EthernetTap::~EthernetTap() #ifdef __APPLE__ void EthernetTap::whack() { - long cpid = (long)fork(); + long cpid = (long)vfork(); if (cpid == 0) { execl(ZT_MAC_IPCONFIG,ZT_MAC_IPCONFIG,"set",_dev,"AUTOMATIC-V6",(const char *)0); exit(-1); @@ -304,7 +304,7 @@ void EthernetTap::whack() {} #ifdef __LINUX__ static bool ___removeIp(const char *_dev,const InetAddress &ip) { - long cpid = (long)fork(); + long cpid = (long)vfork(); if (cpid == 0) { execl(ZT_ETHERTAP_IP_COMMAND,ZT_ETHERTAP_IP_COMMAND,"addr","del",ip.toString().c_str(),"dev",_dev,(const char *)0); exit(1); /* not reached unless exec fails */ @@ -337,7 +337,7 @@ bool EthernetTap::addIP(const InetAddress &ip) } long cpid; - if ((cpid = (long)fork()) == 0) { + if ((cpid = (long)vfork()) == 0) { execl(ZT_ETHERTAP_IP_COMMAND,ZT_ETHERTAP_IP_COMMAND,"addr","add",ip.toString().c_str(),"dev",_dev,(const char *)0); exit(-1); } else { @@ -357,7 +357,7 @@ bool EthernetTap::addIP(const InetAddress &ip) static bool ___removeIp(const char *_dev,const InetAddress &ip) { int cpid; - if ((cpid = (int)fork()) == 0) { + if ((cpid = (int)vfork()) == 0) { execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,"inet",ip.toIpString().c_str(),"-alias",(const char *)0); exit(-1); } else { @@ -390,7 +390,7 @@ bool EthernetTap::addIP(const InetAddress &ip) } int cpid; - if ((cpid = (int)fork()) == 0) { + if ((cpid = (int)vfork()) == 0) { execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); exit(-1); } else { diff --git a/node/Filter.cpp b/node/Filter.cpp index 5cd295c3..a0412173 100644 --- a/node/Filter.cpp +++ b/node/Filter.cpp @@ -25,6 +25,9 @@ * LLC. Start here: http://www.zerotier.com/ */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <stdint.h> #include "RuntimeEnvironment.hpp" @@ -34,21 +37,20 @@ namespace ZeroTier { +const char *const Filter::UNKNOWN_NAME = "(unknown)"; +const Range<unsigned int> Filter::ANY; + bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int len) const + throw(std::invalid_argument) { if ((!_etherType)||(_etherType(etype))) { // ethertype is ANY, or matches // Ethertype determines meaning of protocol and port switch(etype) { - default: - if ((!_protocol)&&(!_port)) - return true; // match other ethertypes if protocol and port are ANY, since we don't know what to do with them - break; - case ZT_ETHERTYPE_IPV4: if (len > 20) { - if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // IP protocol - if (!_port) - return true; // protocol matches or is ANY, port is ANY + if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // protocol is ANY or match + if (!_port) // port is ANY + return true; // Don't match on fragments beyond fragment 0. If we've blocked // fragment 0, further fragments will fall on deaf ears anyway. @@ -60,22 +62,27 @@ bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int l switch(((const uint8_t *)data)[9]) { // port's meaning depends on IP protocol case ZT_IPPROTO_ICMP: - return _port(((const uint8_t *)data)[ihl]); // port = ICMP type + // For ICMP, port is ICMP type + return _port(((const uint8_t *)data)[ihl]); case ZT_IPPROTO_TCP: case ZT_IPPROTO_UDP: case ZT_IPPROTO_SCTP: case ZT_IPPROTO_UDPLITE: - return _port(((const uint16_t *)data)[(ihl / 2) + 1]); // destination port + // For these, port is destination port. Protocol designers were + // nice enough to put the field in the same place. + return _port(((const uint16_t *)data)[(ihl / 2) + 1]); + default: + // port has no meaning for other IP types, so ignore it + return true; } return false; // no match on port } - } + } else throw std::invalid_argument("undersized IPv4 packet"); break; case ZT_ETHERTYPE_IPV6: if (len > 40) { - // see: http://stackoverflow.com/questions/17518951/is-the-ipv6-header-really-this-nutty int nextHeader = ((const uint8_t *)data)[6]; unsigned int pos = 40; while ((pos < len)&&(nextHeader >= 0)&&(nextHeader != 59)) { // 59 == no next header @@ -102,9 +109,11 @@ bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int l case ZT_IPPROTO_ESP: // ESP return _protocol(ZT_IPPROTO_ESP); // true if ESP is matched protocol, otherwise false since packet will be IPsec case ZT_IPPROTO_ICMPV6: - if (_protocol(ZT_IPPROTO_ICMPV6)) { // only match ICMPv6 if specified + // Only match ICMPv6 if we've selected it specifically + if (_protocol(ZT_IPPROTO_ICMPV6)) { + // Port is interpreted as ICMPv6 type if ((!_port)||(_port(((const uint8_t *)data)[pos]))) - return true; // protocol matches, port is ANY or matches ICMP type + return true; } break; case ZT_IPPROTO_TCP: @@ -118,25 +127,75 @@ bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int l return true; // protocol matches or is ANY, port is ANY or matches } break; + default: { + char foo[128]; + sprintf(foo,"unrecognized IPv6 header type %d",(int)nextHeader); + throw std::invalid_argument(foo); + } } fprintf(stderr,"[rule] V6: end header parse, next header %.2x, new pos %d\n",nextHeader,pos); } - } + } else throw std::invalid_argument("undersized IPv6 packet"); break; + + default: + // For other ethertypes, protocol and port are ignored. What would they mean? + return true; } } return false; } -Filter::Filter(const RuntimeEnvironment *renv) : - _r(renv) +std::string Filter::Rule::toString() const { -} + char buf[128]; + std::string s; -Filter::~Filter() -{ + switch(_etherType.magnitude()) { + case 0: + s.push_back('*'); + break; + case 1: + sprintf(buf,"%u",_etherType.start); + s.append(buf); + break; + default: + sprintf(buf,"%u-%u",_etherType.start,_etherType.end); + s.append(buf); + break; + } + s.push_back('/'); + switch(_protocol.magnitude()) { + case 0: + s.push_back('*'); + break; + case 1: + sprintf(buf,"%u",_protocol.start); + s.append(buf); + break; + default: + sprintf(buf,"%u-%u",_protocol.start,_protocol.end); + s.append(buf); + break; + } + s.push_back('/'); + switch(_port.magnitude()) { + case 0: + s.push_back('*'); + break; + case 1: + sprintf(buf,"%u",_port.start); + s.append(buf); + break; + default: + sprintf(buf,"%u-%u",_port.start,_port.end); + s.append(buf); + break; + } + + return s; } void Filter::add(const Rule &r,const Action &a) @@ -153,89 +212,161 @@ void Filter::add(const Rule &r,const Action &a) std::string Filter::toString(const char *sep) const { - char buf[256]; - if (!sep) sep = ","; std::string s; + bool first = true; Mutex::Lock _l(_chain_m); for(std::vector<Entry>::const_iterator i(_chain.begin());i!=_chain.end();++i) { - bool first = (i == _chain.begin()); + s.append(i->rule.toString()); + if (first) + first = false; + else s.append(sep); + } - s.push_back('['); + return s; +} - if (i->rule.etherType()) { - if (i->rule.etherType().magnitude() > 1) - sprintf(buf,"%u-%u",i->rule.etherType().start,i->rule.etherType().end); - else sprintf(buf,"%u",i->rule.etherType().start); - s.append(buf); - } else s.push_back('*'); +const char *Filter::etherTypeName(const unsigned int etherType) + throw() +{ + switch(etherType) { + case ZT_ETHERTYPE_IPV4: return "ETHERTYPE_IPV4"; + case ZT_ETHERTYPE_ARP: return "ETHERTYPE_ARP"; + case ZT_ETHERTYPE_RARP: return "ETHERTYPE_RARP"; + case ZT_ETHERTYPE_ATALK: return "ETHERTYPE_ATALK"; + case ZT_ETHERTYPE_AARP: return "ETHERTYPE_AARP"; + case ZT_ETHERTYPE_IPX_A: return "ETHERTYPE_IPX_A"; + case ZT_ETHERTYPE_IPX_B: return "ETHERTYPE_IPX_B"; + case ZT_ETHERTYPE_IPV6: return "ETHERTYPE_IPV6"; + } + return UNKNOWN_NAME; +} + +const char *Filter::ipProtocolName(const unsigned int ipp) + throw() +{ + switch(ipp) { + case ZT_IPPROTO_ICMP: return "IPPROTO_ICMP"; + case ZT_IPPROTO_IGMP: return "IPPROTO_IGMP"; + case ZT_IPPROTO_TCP: return "IPPROTO_TCP"; + case ZT_IPPROTO_UDP: return "IPPROTO_UDP"; + case ZT_IPPROTO_GRE: return "IPPROTO_GRE"; + case ZT_IPPROTO_ESP: return "IPPROTO_ESP"; + case ZT_IPPROTO_AH: return "IPPROTO_AH"; + case ZT_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6"; + case ZT_IPPROTO_OSPF: return "IPPROTO_OSPF"; + case ZT_IPPROTO_IPIP: return "IPPROTO_IPIP"; + case ZT_IPPROTO_IPCOMP: return "IPPROTO_IPCOMP"; + case ZT_IPPROTO_L2TP: return "IPPROTO_L2TP"; + case ZT_IPPROTO_SCTP: return "IPPROTO_SCTP"; + case ZT_IPPROTO_FC: return "IPPROTO_FC"; + case ZT_IPPROTO_UDPLITE: return "IPPROTO_UDPLITE"; + case ZT_IPPROTO_HIP: return "IPPROTO_HIP"; + } + return UNKNOWN_NAME; +} - s.push_back(';'); +const char *Filter::icmpTypeName(const unsigned int icmpType) + throw() +{ + switch(icmpType) { + case ZT_ICMP_ECHO_REPLY: return "ICMP_ECHO_REPLY"; + case ZT_ICMP_DESTINATION_UNREACHABLE: return "ICMP_DESTINATION_UNREACHABLE"; + case ZT_ICMP_SOURCE_QUENCH: return "ICMP_SOURCE_QUENCH"; + case ZT_ICMP_REDIRECT: return "ICMP_REDIRECT"; + case ZT_ICMP_ALTERNATE_HOST_ADDRESS: return "ICMP_ALTERNATE_HOST_ADDRESS"; + case ZT_ICMP_ECHO_REQUEST: return "ICMP_ECHO_REQUEST"; + case ZT_ICMP_ROUTER_ADVERTISEMENT: return "ICMP_ROUTER_ADVERTISEMENT"; + case ZT_ICMP_ROUTER_SOLICITATION: return "ICMP_ROUTER_SOLICITATION"; + case ZT_ICMP_TIME_EXCEEDED: return "ICMP_TIME_EXCEEDED"; + case ZT_ICMP_BAD_IP_HEADER: return "ICMP_BAD_IP_HEADER"; + case ZT_ICMP_TIMESTAMP: return "ICMP_TIMESTAMP"; + case ZT_ICMP_TIMESTAMP_REPLY: return "ICMP_TIMESTAMP_REPLY"; + case ZT_ICMP_INFORMATION_REQUEST: return "ICMP_INFORMATION_REQUEST"; + case ZT_ICMP_INFORMATION_REPLY: return "ICMP_INFORMATION_REPLY"; + case ZT_ICMP_ADDRESS_MASK_REQUEST: return "ICMP_ADDRESS_MASK_REQUEST"; + case ZT_ICMP_ADDRESS_MASK_REPLY: return "ICMP_ADDRESS_MASK_REPLY"; + case ZT_ICMP_TRACEROUTE: return "ICMP_TRACEROUTE"; + case ZT_ICMP_MOBILE_HOST_REDIRECT: return "ICMP_MOBILE_HOST_REDIRECT"; + case ZT_ICMP_MOBILE_REGISTRATION_REQUEST: return "ICMP_MOBILE_REGISTRATION_REQUEST"; + case ZT_ICMP_MOBILE_REGISTRATION_REPLY: return "ICMP_MOBILE_REGISTRATION_REPLY"; + } + return UNKNOWN_NAME; +} - if (i->rule.protocol()) { - if (i->rule.protocol().magnitude() > 1) - sprintf(buf,"%u-%u",i->rule.protocol().start,i->rule.protocol().end); - else sprintf(buf,"%u",i->rule.protocol().start); - s.append(buf); - } else s.push_back('*'); +const char *Filter::icmp6TypeName(const unsigned int icmp6Type) + throw() +{ + switch(icmp6Type) { + case ZT_ICMP6_DESTINATION_UNREACHABLE: return "ICMP6_DESTINATION_UNREACHABLE"; + case ZT_ICMP6_PACKET_TOO_BIG: return "ICMP6_PACKET_TOO_BIG"; + case ZT_ICMP6_TIME_EXCEEDED: return "ICMP6_TIME_EXCEEDED"; + case ZT_ICMP6_PARAMETER_PROBLEM: return "ICMP6_PARAMETER_PROBLEM"; + case ZT_ICMP6_ECHO_REQUEST: return "ICMP6_ECHO_REQUEST"; + case ZT_ICMP6_ECHO_REPLY: return "ICMP6_ECHO_REPLY"; + case ZT_ICMP6_MULTICAST_LISTENER_QUERY: return "ICMP6_MULTICAST_LISTENER_QUERY"; + case ZT_ICMP6_MULTICAST_LISTENER_REPORT: return "ICMP6_MULTICAST_LISTENER_REPORT"; + case ZT_ICMP6_MULTICAST_LISTENER_DONE: return "ICMP6_MULTICAST_LISTENER_DONE"; + case ZT_ICMP6_ROUTER_SOLICITATION: return "ICMP6_ROUTER_SOLICITATION"; + case ZT_ICMP6_ROUTER_ADVERTISEMENT: return "ICMP6_ROUTER_ADVERTISEMENT"; + case ZT_ICMP6_NEIGHBOR_SOLICITATION: return "ICMP6_NEIGHBOR_SOLICITATION"; + case ZT_ICMP6_NEIGHBOR_ADVERTISEMENT: return "ICMP6_NEIGHBOR_ADVERTISEMENT"; + case ZT_ICMP6_REDIRECT_MESSAGE: return "ICMP6_REDIRECT_MESSAGE"; + case ZT_ICMP6_ROUTER_RENUMBERING: return "ICMP6_ROUTER_RENUMBERING"; + case ZT_ICMP6_NODE_INFORMATION_QUERY: return "ICMP6_NODE_INFORMATION_QUERY"; + case ZT_ICMP6_NODE_INFORMATION_RESPONSE: return "ICMP6_NODE_INFORMATION_RESPONSE"; + case ZT_ICMP6_INV_NEIGHBOR_SOLICITATION: return "ICMP6_INV_NEIGHBOR_SOLICITATION"; + case ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT: return "ICMP6_INV_NEIGHBOR_ADVERTISEMENT"; + case ZT_ICMP6_MLDV2: return "ICMP6_MLDV2"; + case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST"; + case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY"; + case ZT_ICMP6_MOBILE_PREFIX_SOLICITATION: return "ICMP6_MOBILE_PREFIX_SOLICITATION"; + case ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT: return "ICMP6_MOBILE_PREFIX_ADVERTISEMENT"; + case ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION: return "ICMP6_CERTIFICATION_PATH_SOLICITATION"; + case ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT: return "ICMP6_CERTIFICATION_PATH_ADVERTISEMENT"; + case ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT: return "ICMP6_MULTICAST_ROUTER_ADVERTISEMENT"; + case ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION: return "ICMP6_MULTICAST_ROUTER_SOLICITATION"; + case ZT_ICMP6_MULTICAST_ROUTER_TERMINATION: return "ICMP6_MULTICAST_ROUTER_TERMINATION"; + case ZT_ICMP6_RPL_CONTROL_MESSAGE: return "ICMP6_RPL_CONTROL_MESSAGE"; + } + return UNKNOWN_NAME; +} - s.push_back(';'); +Filter::Action Filter::operator()(const RuntimeEnvironment *_r,unsigned int etherType,const void *frame,unsigned int len) const +{ + Mutex::Lock _l(_chain_m); - if (i->rule.port()) { - if (i->rule.port().magnitude() > 1) - sprintf(buf,"%u-%u",i->rule.port().start,i->rule.port().end); - else sprintf(buf,"%u",i->rule.port().start); - s.append(buf); - } else s.push_back('*'); + TRACE("starting match against %d rules",(int)_chain.size()); - s.append("]:"); + int ruleNo = 0; + for(std::vector<Entry>::const_iterator r(_chain.begin());r!=_chain.end();++r,++ruleNo) { + try { + if (r->rule(etherType,frame,len)) { + TRACE("match: %s",r->rule.toString().c_str()); - switch(i->action) { - case ACTION_DENY: - s.append("DENY"); - break; - case ACTION_ALLOW: - s.append("ALLOW"); - break; - case ACTION_LOG: - s.append("LOG"); - break; + switch(r->action) { + case ACTION_ALLOW: + case ACTION_DENY: + return r->action; + default: + break; + } + } else { + TRACE("no match: %s",r->rule.toString().c_str()); + } + } catch (std::invalid_argument &exc) { + LOG("filter: unable to parse packet on rule %s (%d): %s",r->rule.toString().c_str(),ruleNo,exc.what()); + return ACTION_UNPARSEABLE; + } catch ( ... ) { + LOG("filter: unable to parse packet on rule %s (%d): unknown exception",r->rule.toString().c_str(),ruleNo); + return ACTION_UNPARSEABLE; } - - if (!first) - s.append(sep); } - return s; -} - -const char *Filter::etherTypeName(const unsigned int etherType) - throw() -{ - static char tmp[6]; - switch(etherType) { - case ZT_ETHERTYPE_IPV4: - return "IPV4"; - case ZT_ETHERTYPE_ARP: - return "ARP"; - case ZT_ETHERTYPE_RARP: - return "RARP"; - case ZT_ETHERTYPE_ATALK: - return "ATALK"; - case ZT_ETHERTYPE_AARP: - return "AARP"; - case ZT_ETHERTYPE_IPX_A: - return "IPX_A"; - case ZT_ETHERTYPE_IPX_B: - return "IPX_B"; - case ZT_ETHERTYPE_IPV6: - return "IPV6"; - } - sprintf(tmp,"%.4x",etherType); - return tmp; // technically not thread safe, but we're only going to see this in debugging if ever + return ACTION_ALLOW; } } // namespace ZeroTier diff --git a/node/Filter.hpp b/node/Filter.hpp index 861603f0..8b86b48f 100644 --- a/node/Filter.hpp +++ b/node/Filter.hpp @@ -33,6 +33,7 @@ #include <string> #include <vector> #include <utility> +#include <stdexcept> #include "Mutex.hpp" #include "Range.hpp" @@ -130,6 +131,19 @@ class Filter { public: /** + * Value returned by etherTypeName, etc. on unknown + * + * These static methods return precisely this, so a pointer equality + * check will work. + */ + static const char *const UNKNOWN_NAME; + + /** + * An empty range as a more idiomatic way of specifying a wildcard match + */ + static const Range<unsigned int> ANY; + + /** * A filter rule * * This behaves as an immutable value object. @@ -171,8 +185,15 @@ public: * @param data Ethernet frame data * @param len Length of ethernet frame * @return True if rule matches + * @throws std::invalid_argument Frame invalid or not parseable */ - bool operator()(unsigned int etype,const void *data,unsigned int len) const; + bool operator()(unsigned int etype,const void *data,unsigned int len) const + throw(std::invalid_argument); + + /** + * @return Human readable representation of rule + */ + std::string toString() const; inline bool operator==(const Rule &r) const throw() { return ((_etherType == r._etherType)&&(_protocol == r._protocol)&&(_port == r._port)); } inline bool operator!=(const Rule &r) const throw() { return !(*this == r); } @@ -208,7 +229,7 @@ public: { ACTION_DENY = 0, ACTION_ALLOW = 1, - ACTION_LOG = 2 + ACTION_UNPARSEABLE = 2 }; /** @@ -227,8 +248,27 @@ public: Action action; }; - Filter(const RuntimeEnvironment *renv); - ~Filter(); + Filter() : + _chain(), + _chain_m() + { + } + + Filter(const Filter &f) : + _chain(), + _chain_m() + { + Mutex::Lock _l(f._chain_m); + _chain = f._chain; + } + + inline Filter &operator=(const Filter &f) + { + Mutex::Lock _l1(_chain_m); + Mutex::Lock _l2(f._chain_m); + _chain = f._chain; + return *this; + } /** * Remove all filter entries @@ -281,16 +321,27 @@ public: */ std::string toString(const char *sep = (const char *)0) const; - /** - * @param etherType Ethernet type ID - * @return Name of Ethernet protocol (e.g. ARP, IPV4) - */ static const char *etherTypeName(const unsigned int etherType) throw(); + static const char *ipProtocolName(const unsigned int ipp) + throw(); + static const char *icmpTypeName(const unsigned int icmpType) + throw(); + static const char *icmp6TypeName(const unsigned int icmp6Type) + throw(); -private: - const RuntimeEnvironment *_r; + /** + * Match against an Ethernet frame + * + * @param _r Runtime environment + * @param etherType Ethernet frame type + * @param frame Ethernet frame data + * @param len Length of frame in bytes + * @return Action if matched or ACTION_ALLOW if not matched + */ + Action operator()(const RuntimeEnvironment *_r,unsigned int etherType,const void *frame,unsigned int len) const; +private: std::vector<Entry> _chain; Mutex _chain_m; }; diff --git a/node/Http.cpp b/node/Http.cpp deleted file mode 100644 index 86ae2d25..00000000 --- a/node/Http.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2012-2013 ZeroTier Networks LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#include <vector> -#include <set> -#include <list> - -#ifndef _WIN32 -#include <unistd.h> -#endif - -#include "Http.hpp" -#include "Utils.hpp" -#include "InetAddress.hpp" - -static http_parser_settings _http_parser_settings; - -namespace ZeroTier { - -static bool _sendAll(int fd,const void *buf,unsigned int len) -{ - for(;;) { - int n = (int)::send(fd,buf,len,0); - if ((n < 0)&&(errno == EINTR)) - continue; - return (n == (int)len); - } -} - -const std::map<std::string,std::string> Http::EMPTY_HEADERS; - -Http::Request::Request( - Http::Method m, - const std::string &url, - const std::map<std::string,std::string> &rh, - const std::string &rb, - bool (*handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &), - void *arg) : - _url(url), - _requestHeaders(rh), - _handler(handler), - _arg(arg), - _method(m), - _fd(0) -{ - _http_parser_settings.on_message_begin = &Http::Request::_http_on_message_begin; - _http_parser_settings.on_url = &Http::Request::_http_on_url; - _http_parser_settings.on_status_complete = &Http::Request::_http_on_status_complete; - _http_parser_settings.on_header_field = &Http::Request::_http_on_header_field; - _http_parser_settings.on_header_value = &Http::Request::_http_on_header_value; - _http_parser_settings.on_headers_complete = &Http::Request::_http_on_headers_complete; - _http_parser_settings.on_body = &Http::Request::_http_on_body; - _http_parser_settings.on_message_complete = &Http::Request::_http_on_message_complete; - - start(); -} - -Http::Request::~Request() -{ - if (_fd > 0) - ::close(_fd); - join(); -} - -void Http::Request::main() - throw() -{ - char buf[131072]; - - try { - http_parser_init(&_parser,HTTP_RESPONSE); - _parser.data = this; - - http_parser_url urlParsed; - if (http_parser_parse_url(_url.c_str(),_url.length(),0,&urlParsed)) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL parse error"); - return; - } - if (!(urlParsed.field_set & (1 << UF_SCHEMA))) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL specifies no schema"); - return; - } - - std::string schema(_url.substr(urlParsed.field_data[UF_SCHEMA].off,urlParsed.field_data[UF_SCHEMA].len)); - - if (schema == "file") { - const std::string filePath(_url.substr(urlParsed.field_data[UF_PATH].off,urlParsed.field_data[UF_PATH].len)); - - uint64_t lm = Utils::getLastModified(filePath.c_str()); - if (lm) { - const std::map<std::string,std::string>::const_iterator ifModSince(_requestHeaders.find("If-Modified-Since")); - if ((ifModSince != _requestHeaders.end())&&(ifModSince->second.length())) { - uint64_t t64 = Utils::fromRfc1123(ifModSince->second); - if ((t64)&&(lm > t64)) { - suicidalThread = !_handler(this,_arg,_url,304,_responseHeaders,""); - return; - } - } - - if (Utils::readFile(filePath.c_str(),_responseBody)) { - _responseHeaders["Last-Modified"] = Utils::toRfc1123(lm); - suicidalThread = !_handler(this,_arg,_url,200,_responseHeaders,_responseBody); - return; - } - } - - suicidalThread = !_handler(this,_arg,_url,404,_responseHeaders,"file not found or not readable"); - return; - } else if (schema == "http") { - if (!(urlParsed.field_set & (1 << UF_HOST))) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL contains no host"); - return; - } - std::string host(_url.substr(urlParsed.field_data[UF_HOST].off,urlParsed.field_data[UF_HOST].len)); - - std::list<InetAddress> v4,v6; - { - struct addrinfo *res = (struct addrinfo *)0; - if (!getaddrinfo(host.c_str(),(const char *)0,(const struct addrinfo *)0,&res)) { - struct addrinfo *p = res; - do { - if (p->ai_family == AF_INET) - v4.push_back(InetAddress(p->ai_addr)); - else if (p->ai_family == AF_INET6) - v6.push_back(InetAddress(p->ai_addr)); - } while ((p = p->ai_next)); - freeaddrinfo(res); - } - } - - std::list<InetAddress> *addrList; - if (v4.empty()&&v6.empty()) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"could not find address for host in URL"); - return; - } else if (v4.empty()) { - addrList = &v6; - } else { - addrList = &v4; - } - InetAddress *addr; - { - addrList->sort(); - addrList->unique(); - unsigned int i = 0,k = 0; - k = rand() % addrList->size(); - std::list<InetAddress>::iterator a(addrList->begin()); - while (i++ != k) ++a; - addr = &(*a); - } - - int remotePort = ((urlParsed.field_set & (1 << UF_PORT))&&(urlParsed.port)) ? (int)urlParsed.port : (int)80; - if ((remotePort <= 0)||(remotePort > 0xffff)) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL port out of range"); - return; - } - addr->setPort(remotePort); - - _fd = socket(addr->isV6() ? AF_INET6 : AF_INET,SOCK_STREAM,0); - if (_fd <= 0) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"could not open socket"); - return; - } - - for(;;) { - if (connect(_fd,addr->saddr(),addr->saddrLen())) { - if (errno == EINTR) - continue; - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"connection failed to remote host"); - return; - } else break; - } - - const char *mstr = "GET"; - switch(_method) { - case HTTP_METHOD_HEAD: mstr = "HEAD"; break; - default: break; - } - int mlen = (int)snprintf(buf,sizeof(buf),"%s %s HTTP/1.1\r\nAccept-Encoding: \r\nHost: %s\r\n",mstr,_url.substr(urlParsed.field_data[UF_PATH].off,urlParsed.field_data[UF_PATH].len).c_str(),host.c_str()); - if (mlen >= (int)sizeof(buf)) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL too long"); - return; - } - if (!_sendAll(_fd,buf,mlen)) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error"); - return; - } - - for(std::map<std::string,std::string>::const_iterator rh(_requestHeaders.begin());rh!=_requestHeaders.end();++rh) { - mlen = (int)snprintf(buf,sizeof(buf),"%s: %s\r\n",rh->first.c_str(),rh->second.c_str()); - if (mlen >= (int)sizeof(buf)) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"header too long"); - return; - } - if (!_sendAll(_fd,buf,mlen)) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error"); - return; - } - } - - if (!_sendAll(_fd,"\r\n",2)) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error"); - return; - } - - _responseStatusCode = 0; - _messageComplete = false; - for(;;) { - mlen = (int)::recv(_fd,buf,sizeof(buf),0); - if (mlen < 0) { - if (errno != EINTR) - break; - else continue; - } - if (((int)http_parser_execute(&_parser,&_http_parser_settings,buf,mlen) != mlen)||(_parser.upgrade)) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"invalid HTTP response from server"); - return; - } - if (_messageComplete) { - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,_responseStatusCode,_responseHeaders,_responseBody); - return; - } - } - - ::close(_fd); _fd = 0; - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"empty HTTP response from server"); - return; - } else { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"only 'file' and 'http' methods are supported"); - return; - } - } catch ( ... ) { - suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"unexpected exception retrieving URL"); - return; - } -} - -int Http::Request::_http_on_message_begin(http_parser *parser) -{ - return 0; -} -int Http::Request::_http_on_url(http_parser *parser,const char *data,size_t length) -{ - return 0; -} -int Http::Request::_http_on_status_complete(http_parser *parser) -{ - Http::Request *r = (Http::Request *)parser->data; - r->_responseStatusCode = parser->status_code; - return 0; -} -int Http::Request::_http_on_header_field(http_parser *parser,const char *data,size_t length) -{ - Http::Request *r = (Http::Request *)parser->data; - if ((r->_currentHeaderField.length())&&(r->_responseHeaders.find(r->_currentHeaderField) != r->_responseHeaders.end())) - r->_currentHeaderField.assign(""); - r->_currentHeaderField.append(data,length); - return 0; -} -int Http::Request::_http_on_header_value(http_parser *parser,const char *data,size_t length) -{ - Http::Request *r = (Http::Request *)parser->data; - if (r->_currentHeaderField.length()) - r->_responseHeaders[r->_currentHeaderField].append(data,length); - return 0; -} -int Http::Request::_http_on_headers_complete(http_parser *parser) -{ - Http::Request *r = (Http::Request *)parser->data; - return ((r->_method == Http::HTTP_METHOD_HEAD) ? 1 : 0); -} -int Http::Request::_http_on_body(http_parser *parser,const char *data,size_t length) -{ - Http::Request *r = (Http::Request *)parser->data; - r->_responseBody.append(data,length); - return 0; -} -int Http::Request::_http_on_message_complete(http_parser *parser) -{ - Http::Request *r = (Http::Request *)parser->data; - r->_messageComplete = true; - return 0; -} - -} // namespace ZeroTier diff --git a/node/Http.hpp b/node/Http.hpp deleted file mode 100644 index a099b15d..00000000 --- a/node/Http.hpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2012-2013 ZeroTier Networks LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#ifndef _ZT_HTTP_HPP -#define _ZT_HTTP_HPP - -#include <map> -#include <string> -#include <stdexcept> -#include "Thread.hpp" - -#include "../ext/http-parser/http_parser.h" - -namespace ZeroTier { - -class Http -{ -public: - /** - * HTTP request methods - */ - enum Method - { - HTTP_METHOD_GET, - HTTP_METHOD_HEAD - }; - - /** - * An empty headers map for convenience - */ - static const std::map<std::string,std::string> EMPTY_HEADERS; - - /** - * HTTP request - */ - class Request : protected Thread - { - public: - /** - * Create and issue an HTTP request - * - * The supplied handler is called when the request is - * complete or if an error occurs. A code of zero indicates - * that the server could not be reached, and a description - * of the error will be in 'body'. If the handler returns - * false the Request object deletes itself. Otherwise the - * object must be deleted by other code. - * - * @param m Request method - * @param url Destination URL - * @param rh Request headers - * @param rb Request body or empty string for none (currently unused) - * @param handler Request handler function - * @param arg First argument to request handler - */ - Request( - Http::Method m, - const std::string &url, - const std::map<std::string,std::string> &rh, - const std::string &rb, - bool (*handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &), - void *arg); - - /** - * Destruction cancels any in-progress request - */ - virtual ~Request(); - - protected: - virtual void main() - throw(); - - private: - // HTTP parser handlers - static int _http_on_message_begin(http_parser *parser); - static int _http_on_url(http_parser *parser,const char *data,size_t length); - static int _http_on_status_complete(http_parser *parser); - static int _http_on_header_field(http_parser *parser,const char *data,size_t length); - static int _http_on_header_value(http_parser *parser,const char *data,size_t length); - static int _http_on_headers_complete(http_parser *parser); - static int _http_on_body(http_parser *parser,const char *data,size_t length); - static int _http_on_message_complete(http_parser *parser); - - http_parser _parser; - std::string _url; - - std::map<std::string,std::string> _requestHeaders; - std::map<std::string,std::string> _responseHeaders; - - std::string _currentHeaderField; - std::string _responseBody; - - bool (*_handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &); - void *_arg; - - Http::Method _method; - int _responseStatusCode; - bool _messageComplete; - volatile int _fd; - }; -}; - -} // namespace ZeroTier - -#endif diff --git a/node/Identity.cpp b/node/Identity.cpp index f16947a0..e9cbef3d 100644 --- a/node/Identity.cpp +++ b/node/Identity.cpp @@ -57,11 +57,13 @@ void Identity::generate() // the address of an identity will be detected as its signature will be // invalid. Of course, deep verification of address/key relationship is // required to cover the more elaborate address claim jump attempt case. + unsigned char atmp[ZT_ADDRESS_LENGTH]; + _address.copyTo(atmp,ZT_ADDRESS_LENGTH); SHA256_CTX sha; unsigned char dig[32]; unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0; SHA256_Init(&sha); - SHA256_Update(&sha,_address.data(),ZT_ADDRESS_LENGTH); + SHA256_Update(&sha,atmp,ZT_ADDRESS_LENGTH); SHA256_Update(&sha,&zero,1); SHA256_Update(&sha,&idtype,1); SHA256_Update(&sha,&zero,1); @@ -73,11 +75,13 @@ void Identity::generate() bool Identity::locallyValidate(bool doAddressDerivationCheck) const { + unsigned char atmp[ZT_ADDRESS_LENGTH]; + _address.copyTo(atmp,ZT_ADDRESS_LENGTH); SHA256_CTX sha; unsigned char dig[32]; unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0; SHA256_Init(&sha); - SHA256_Update(&sha,_address.data(),ZT_ADDRESS_LENGTH); + SHA256_Update(&sha,atmp,ZT_ADDRESS_LENGTH); SHA256_Update(&sha,&zero,1); SHA256_Update(&sha,&idtype,1); SHA256_Update(&sha,&zero,1); @@ -119,7 +123,7 @@ bool Identity::fromString(const char *str) std::string b(Utils::unhex(fields[0])); if (b.length() != ZT_ADDRESS_LENGTH) return false; - _address = b.data(); + _address.setTo(b.data(),ZT_ADDRESS_LENGTH); b = Utils::base64Decode(fields[2]); if ((!b.length())||(b.length() > ZT_EC_MAX_BYTES)) @@ -218,7 +222,7 @@ Address Identity::deriveAddress(const void *keyBytes,unsigned int keyLen) delete [] ram; - return Address(dig); // first 5 bytes of dig[] + return Address(dig,ZT_ADDRESS_LENGTH); // first 5 bytes of dig[] } std::string Identity::encrypt(const Identity &to,const void *data,unsigned int len) const diff --git a/node/Identity.hpp b/node/Identity.hpp index 5cdfe9f8..1cce4fb0 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -104,7 +104,7 @@ public: _keyPair((EllipticCurveKeyPair *)0) { if (!fromString(str)) - throw std::invalid_argument("invalid string-serialized identity"); + throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str); } Identity(const std::string &str) @@ -112,7 +112,7 @@ public: _keyPair((EllipticCurveKeyPair *)0) { if (!fromString(str)) - throw std::invalid_argument("invalid string-serialized identity"); + throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str); } template<unsigned int C> @@ -307,7 +307,7 @@ public: inline void serialize(Buffer<C> &b,bool includePrivate = false) const throw(std::out_of_range) { - b.append(_address.data(),ZT_ADDRESS_LENGTH); + _address.appendTo(b); b.append((unsigned char)IDENTITY_TYPE_NIST_P_521); b.append((unsigned char)(_publicKey.size() & 0xff)); b.append(_publicKey.data(),_publicKey.size()); @@ -340,7 +340,7 @@ public: unsigned int p = startAt; - _address = b.field(p,ZT_ADDRESS_LENGTH); + _address.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; if (b[p++] != IDENTITY_TYPE_NIST_P_521) diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index 1addf42d..ac89f84b 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -261,9 +261,21 @@ public: bf.set((peers[chosen++] = *i)->address().sum()); // Add a supernode if there are fewer than the desired - // number of recipients. + // number of recipients. Note that we do not use the bloom + // filter to track visits to supernodes, intentionally + // allowing multicasts to ping pong between supernodes. + // Supernodes propagate even messages they've already seen, + // while regular nodes do not. Thus this ping-ponging will + // cause the supernodes to pick new starting points for + // peer to peer graph traversal multiple times. It's a + // simple, stateless way to increase supernode-driven + // propagation of a multicast in the event that peer to + // peer connectivity for its group is sparse. if (chosen < max) { - P peer = topology.getBestSupernode(&originalSubmitter,1,true); + Address avoid[2]; + avoid[0] = originalSubmitter; + avoid[1] = upstream; + P peer = topology.getBestSupernode(avoid,2,true); if (peer) peers[chosen++] = peer; } diff --git a/node/Network.cpp b/node/Network.cpp index f34e07e0..a50d56dc 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -25,19 +25,87 @@ * LLC. Start here: http://www.zerotier.com/ */ +#include <stdlib.h> +#include <math.h> + +#include <openssl/sha.h> + +#include "RuntimeEnvironment.hpp" +#include "NodeConfig.hpp" #include "Network.hpp" #include "Switch.hpp" +#include "Packet.hpp" namespace ZeroTier { +void Network::Certificate::_shaForSignature(unsigned char *dig) const +{ + SHA256_CTX sha; + SHA256_Init(&sha); + unsigned char zero = 0; + for(const_iterator i(begin());i!=end();++i) { + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,(const unsigned char *)i->first.data(),i->first.length()); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,(const unsigned char *)i->second.data(),i->second.length()); + SHA256_Update(&sha,&zero,1); + } + SHA256_Final(dig,&sha); +} + +static const std::string _DELTA_PREFIX("~"); +bool Network::Certificate::qualifyMembership(const Network::Certificate &mc) const +{ + // Note: optimization probably needed here, probably via some kind of + // memoization / dynamic programming. + + for(const_iterator myField(begin());myField!=end();++myField) { + if (!((myField->first.length() > 1)&&(myField->first[0] == '~'))) { // ~fields are max delta range specs + // If they lack the same field, comparison fails. + const_iterator theirField(mc.find(myField->first)); + if (theirField == mc.end()) + return false; + + const_iterator deltaField(find(_DELTA_PREFIX + myField->first)); + if (deltaField == end()) { + // If there is no delta, compare on simple equality + if (myField->second != theirField->second) + return false; + } else { + // Otherwise compare range with max delta. Presence of a dot in delta + // indicates a floating point comparison. Otherwise an integer + // comparison occurs. + if (deltaField->second.find('.') != std::string::npos) { + double my = strtod(myField->second.c_str(),(char **)0); + double their = strtod(theirField->second.c_str(),(char **)0); + double delta = strtod(deltaField->second.c_str(),(char **)0); + if (fabs(my - their) > delta) + return false; + } else { + int64_t my = strtoll(myField->second.c_str(),(char **)0,10); + int64_t their = strtoll(theirField->second.c_str(),(char **)0,10); + int64_t delta = strtoll(deltaField->second.c_str(),(char **)0,10); + if (my > their) { + if ((my - their) > delta) + return false; + } else { + if ((their - my) > delta) + return false; + } + } + } + } + } + + return true; +} + Network::Network(const RuntimeEnvironment *renv,uint64_t id) throw(std::runtime_error) : _r(renv), - _id(id), _tap(renv,renv->identity.address().toMAC(),ZT_IF_MTU,&_CBhandleTapData,this), - _members(), - _open(false), - _lock() + _lastConfigUpdate(0), + _id(id) { } @@ -45,6 +113,53 @@ Network::~Network() { } +void Network::setConfiguration(const Network::Config &conf) +{ + Mutex::Lock _l(_lock); + if ((conf.networkId() == _id)&&(conf.peerAddress() == _r->identity.address())) { // sanity check + _configuration = conf; + _myCertificate = conf.certificateOfMembership(); + _lastConfigUpdate = Utils::now(); + } +} + +void Network::requestConfiguration() +{ + Packet outp(controller(),_r->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append((uint64_t)_id); + _r->sw->send(outp,true); +} + +bool Network::isAllowed(const Address &peer) const +{ + // Exceptions can occur if we do not yet have *our* configuration. + try { + Mutex::Lock _l(_lock); + if (_configuration.isOpen()) + return true; + std::map<Address,Certificate>::const_iterator pc(_membershipCertificates.find(peer)); + if (pc == _membershipCertificates.end()) + return false; + return _myCertificate.qualifyMembership(pc->second); + } catch (std::exception &exc) { + TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer.toString().c_str(),exc.what()); + return false; + } catch ( ... ) { + TRACE("isAllowed() check failed for peer %s: unexpected exception: unknown exception",peer.toString().c_str()); + return false; + } +} + +void Network::clean() +{ + Mutex::Lock _l(_lock); + for(std::map<Address,Certificate>::iterator i=(_membershipCertificates.begin());i!=_membershipCertificates.end();) { + if (_myCertificate.qualifyMembership(i->second)) + ++i; + else _membershipCertificates.erase(i++); + } +} + void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data) { const RuntimeEnvironment *_r = ((Network *)arg)->_r; diff --git a/node/Network.hpp b/node/Network.hpp index 417688f0..637c6664 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -30,33 +30,277 @@ #include <string> #include <set> +#include <map> #include <vector> #include <stdexcept> + +#include "Constants.hpp" +#include "Utils.hpp" #include "EthernetTap.hpp" #include "Address.hpp" #include "Mutex.hpp" -#include "InetAddress.hpp" -#include "Constants.hpp" #include "SharedPtr.hpp" #include "AtomicCounter.hpp" -#include "RuntimeEnvironment.hpp" #include "MulticastGroup.hpp" #include "NonCopyable.hpp" #include "MAC.hpp" +#include "Dictionary.hpp" +#include "Identity.hpp" +#include "InetAddress.hpp" namespace ZeroTier { +class RuntimeEnvironment; class NodeConfig; /** - * Local network endpoint + * A virtual LAN + * + * Networks can be open or closed. Each network has an ID whose most + * significant 40 bits are the ZeroTier address of the node that should + * be contacted for network configuration. The least significant 24 + * bits are arbitrary, allowing up to 2^24 networks per managing + * node. + * + * Open networks do not track membership. Anyone is allowed to communicate + * over them. + * + * Closed networks track membership by way of timestamped signatures. When + * the network requests its configuration, one of the fields returned is + * a signature for the identity of the peer on the network. This signature + * includes a timestamp. When a peer communicates with other peers on a + * closed network, it periodically (and pre-emptively) propagates this + * signature to the peers with which it is communicating. Peers reject + * packets with an error if no recent signature is on file. */ class Network : NonCopyable { friend class SharedPtr<Network>; friend class NodeConfig; +public: + /** + * A certificate of network membership + */ + class Certificate : private Dictionary + { + public: + Certificate() + { + } + + Certificate(const char *s) : + Dictionary(s) + { + } + + Certificate(const std::string &s) : + Dictionary(s) + { + } + + /** + * @return Read-only underlying dictionary + */ + inline const Dictionary &dictionary() const { return *this; } + + inline void setNetworkId(uint64_t id) + { + char buf[32]; + sprintf(buf,"%.16llx",(unsigned long long)id); + (*this)["nwid"] = buf; + } + + inline uint64_t networkId() const + throw(std::invalid_argument) + { + return strtoull(get("nwid").c_str(),(char **)0,16); + } + + inline void setPeerAddress(Address &a) + { + (*this)["peer"] = a.toString(); + } + + inline Address peerAddress() const + throw(std::invalid_argument) + { + return Address(get("peer")); + } + + /** + * Set the timestamp and timestamp max-delta + * + * @param ts Timestamp in ms since epoch + * @param maxDelta Maximum difference between two peers on the same network + */ + inline void setTimestamp(uint64_t ts,uint64_t maxDelta) + { + char foo[32]; + sprintf(foo,"%llu",(unsigned long long)ts); + (*this)["ts"] = foo; + sprintf(foo,"%llu",(unsigned long long)maxDelta); + (*this)["~ts"] = foo; + } + + /** + * Sign this certificate + * + * @param with Signing identity -- the identity of this network's controller + * @return Signature or empty string on failure + */ + inline std::string sign(const Identity &with) const + { + unsigned char dig[32]; + _shaForSignature(dig); + return with.sign(dig); + } + + /** + * Verify this certificate's signature + * + * @param with Signing identity -- the identity of this network's controller + * @param sig Signature + * @param siglen Length of signature in bytes + */ + inline bool verify(const Identity &with,const void *sig,unsigned int siglen) const + { + unsigned char dig[32]; + _shaForSignature(dig); + return with.verifySignature(dig,sig,siglen); + } + + /** + * Check if another peer is indeed a current member of this network + * + * Fields with companion ~fields are compared with the defined maximum + * delta in this certificate. Fields without ~fields are compared for + * equality. + * + * This does not verify the certificate's signature! + * + * @param mc Peer membership certificate + * @return True if mc's membership in this network is current + */ + bool qualifyMembership(const Certificate &mc) const; + + private: + void _shaForSignature(unsigned char *dig) const; + }; + + /** + * A network configuration for a given node + */ + class Config : private Dictionary + { + public: + Config() + { + } + + Config(const char *s) : + Dictionary(s) + { + } + + Config(const std::string &s) : + Dictionary(s) + { + } + + inline void setNetworkId(uint64_t id) + { + char buf[32]; + sprintf(buf,"%.16llx",(unsigned long long)id); + (*this)["nwid"] = buf; + } + + inline uint64_t networkId() const + throw(std::invalid_argument) + { + return strtoull(get("nwid").c_str(),(char **)0,16); + } + + inline void setPeerAddress(Address &a) + { + (*this)["peer"] = a.toString(); + } + + inline Address peerAddress() const + throw(std::invalid_argument) + { + return Address(get("peer")); + } + + /** + * @return Certificate of membership for this network, or empty cert if none + */ + inline Certificate certificateOfMembership() const + { + return Certificate(get("com","")); + } + + /** + * @return True if this is an open non-access-controlled network + */ + inline bool isOpen() const + { + return (get("isOpen") == "1"); + } + + /** + * @return All static addresses / netmasks, IPv4 or IPv6 + */ + inline std::set<InetAddress> staticAddresses() const + { + std::set<InetAddress> sa; + std::vector<std::string> ips(Utils::split(get("ipv4Static","").c_str(),",","","")); + for(std::vector<std::string>::const_iterator i(ips.begin());i!=ips.end();++i) + sa.insert(InetAddress(*i)); + ips = Utils::split(get("ipv6Static","").c_str(),",","",""); + for(std::vector<std::string>::const_iterator i(ips.begin());i!=ips.end();++i) + sa.insert(InetAddress(*i)); + return sa; + } + + /** + * Set static IPv4 and IPv6 addresses + * + * This sets the ipv4Static and ipv6Static fields to comma-delimited + * lists of assignments. The port field in InetAddress must be the + * number of bits in the netmask. + * + * @param begin Start of container or array of addresses (InetAddress) + * @param end End of container or array of addresses (InetAddress) + * @tparam I Type of container or array + */ + template<typename I> + inline void setStaticInetAddresses(const I &begin,const I &end) + { + std::string v4; + std::string v6; + for(I i(begin);i!=end;++i) { + if (i->isV4()) { + if (v4.length()) + v4.push_back(','); + v4.append(i->toString()); + } else if (i->isV6()) { + if (v6.length()) + v6.push_back(','); + v6.append(i->toString()); + } + } + if (v4.length()) + (*this)["ipv4Static"] = v4; + else erase("ipv4Static"); + if (v6.length()) + (*this)["ipv6Static"] = v6; + else erase("ipv6Static"); + } + }; + private: + // Only NodeConfig can create, only SharedPtr can delete Network(const RuntimeEnvironment *renv,uint64_t id) throw(std::runtime_error); @@ -74,83 +318,101 @@ public: inline EthernetTap &tap() throw() { return _tap; } /** - * Get this network's members - * - * If this is an open network, membership isn't relevant and this doesn't - * mean much. If it's a closed network, frames will only be exchanged to/from - * members. - * - * @return Members of this network + * @return Address of network's controlling node */ - inline std::set<Address> members() const - { - Mutex::Lock _l(_lock); - return _members; - } + inline Address controller() throw() { return Address(_id >> 24); } /** - * @param addr Address to check - * @return True if address is a member + * @return True if network is open (no membership required) */ - inline bool isMember(const Address &addr) const + inline bool isOpen() const throw() { - Mutex::Lock _l(_lock); - return (_members.count(addr) > 0); + try { + Mutex::Lock _l(_lock); + return _configuration.isOpen(); + } catch ( ... ) { + return false; + } } /** - * Shortcut to check open() and then isMember() - * - * @param addr Address to check - * @return True if network is open or if address is a member + * Update multicast groups for this network's tap + * + * @return True if internal multicast group set has changed */ - inline bool isAllowed(const Address &addr) const - throw() + inline bool updateMulticastGroups() { Mutex::Lock _l(_lock); - return ((_open)||(_members.count(addr) > 0)); + return _tap.updateMulticastGroups(_multicastGroups); } /** - * @return True if network is open (no membership required) + * @return Latest set of multicast groups for this network's tap */ - inline bool open() const - throw() + inline std::set<MulticastGroup> multicastGroups() const { Mutex::Lock _l(_lock); - return _open; + return _multicastGroups; } /** - * Update internal multicast group set and return true if changed + * Set or update this network's configuration * - * @return True if internal multicast group set has changed + * This is called by PacketDecoder when an update comes over the wire, or + * internally when an old config is reloaded from disk. + * + * @param conf Configuration in key/value dictionary form */ - inline bool updateMulticastGroups() + void setConfiguration(const Config &conf); + + /** + * Causes this network to request an updated configuration from its master node now + */ + void requestConfiguration(); + + /** + * Add or update a peer's membership certificate + * + * The certificate must already have been validated via signature checking. + * + * @param peer Peer that owns certificate + * @param cert Certificate itself + */ + inline void addMembershipCertificate(const Address &peer,const Certificate &cert) { Mutex::Lock _l(_lock); - return _tap.updateMulticastGroups(_multicastGroups); + _membershipCertificates[peer] = cert; } + bool isAllowed(const Address &peer) const; + /** - * @return Latest set of multicast groups + * Perform periodic database cleaning such as removing expired membership certificates */ - inline std::set<MulticastGroup> multicastGroups() const + void clean(); + + /** + * @return Time of last updated configuration or 0 if none + */ + inline uint64_t lastConfigUpdate() const + throw() { - Mutex::Lock _l(_lock); - return _multicastGroups; + return _lastConfigUpdate; } private: static void _CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data); const RuntimeEnvironment *_r; - uint64_t _id; + EthernetTap _tap; - std::set<Address> _members; std::set<MulticastGroup> _multicastGroups; - bool _open; + std::map<Address,Certificate> _membershipCertificates; + Config _configuration; + Certificate _myCertificate; + uint64_t _lastConfigUpdate; + uint64_t _id; Mutex _lock; AtomicCounter __refCount; diff --git a/node/Node.cpp b/node/Node.cpp index 3b18d7ba..8079e801 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -37,26 +37,28 @@ #include <vector> #include <string> -#ifndef _WIN32 +#ifdef _WIN32 +#include <Windows.h> +#else #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <sys/file.h> #endif -#include <openssl/sha.h> - #include "Condition.hpp" #include "Node.hpp" #include "Topology.hpp" #include "Demarc.hpp" +#include "Packet.hpp" #include "Switch.hpp" #include "Utils.hpp" #include "EthernetTap.hpp" #include "Logger.hpp" #include "Constants.hpp" #include "InetAddress.hpp" -#include "Pack.hpp" +#include "Salsa20.hpp" +#include "HMAC.hpp" #include "RuntimeEnvironment.hpp" #include "NodeConfig.hpp" #include "Defaults.hpp" @@ -66,11 +68,109 @@ #include "Mutex.hpp" #include "Multicaster.hpp" #include "CMWC4096.hpp" +#include "Service.hpp" #include "../version.h" namespace ZeroTier { +struct _LocalClientImpl +{ + unsigned char key[32]; + UdpSocket *sock; + void (*resultHandler)(void *,unsigned long,const char *); + void *arg; + InetAddress localDestAddr; + Mutex inUseLock; +}; + +static void _CBlocalClientHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) +{ + _LocalClientImpl *impl = (_LocalClientImpl *)arg; + if (!impl) + return; + if (!impl->resultHandler) + return; // sanity check + Mutex::Lock _l(impl->inUseLock); + + try { + unsigned long convId = 0; + std::vector<std::string> results; + if (!NodeConfig::decodeControlMessagePacket(impl->key,data,len,convId,results)) + return; + for(std::vector<std::string>::iterator r(results.begin());r!=results.end();++r) + impl->resultHandler(impl->arg,convId,r->c_str()); + } catch ( ... ) {} +} + +Node::LocalClient::LocalClient(const char *authToken,void (*resultHandler)(void *,unsigned long,const char *),void *arg) + throw() : + _impl((void *)0) +{ + _LocalClientImpl *impl = new _LocalClientImpl; + + UdpSocket *sock = (UdpSocket *)0; + for(unsigned int i=0;i<5000;++i) { + try { + sock = new UdpSocket(true,32768 + (rand() % 20000),false,&_CBlocalClientHandler,impl); + break; + } catch ( ... ) { + sock = (UdpSocket *)0; + } + } + + // If socket fails to bind, there's a big problem like missing IPv4 stack + if (sock) { + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha,authToken,strlen(authToken)); + SHA256_Final(impl->key,&sha); + + impl->sock = sock; + impl->resultHandler = resultHandler; + impl->arg = arg; + impl->localDestAddr = InetAddress::LO4; + impl->localDestAddr.setPort(ZT_CONTROL_UDP_PORT); + _impl = impl; + } else delete impl; +} + +Node::LocalClient::~LocalClient() +{ + if (_impl) { + ((_LocalClientImpl *)_impl)->inUseLock.lock(); + delete ((_LocalClientImpl *)_impl)->sock; + ((_LocalClientImpl *)_impl)->inUseLock.unlock(); + delete ((_LocalClientImpl *)_impl); + } +} + +unsigned long Node::LocalClient::send(const char *command) + throw() +{ + if (!_impl) + return 0; + _LocalClientImpl *impl = (_LocalClientImpl *)_impl; + Mutex::Lock _l(impl->inUseLock); + + try { + uint32_t convId = (uint32_t)rand(); + if (!convId) + convId = 1; + + std::vector<std::string> tmp; + tmp.push_back(std::string(command)); + std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets(NodeConfig::encodeControlMessage(impl->key,convId,tmp)); + + for(std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> >::iterator p(packets.begin());p!=packets.end();++p) + impl->sock->send(impl->localDestAddr,p->data(),p->size(),-1); + + return convId; + } catch ( ... ) { + return 0; + } +} + struct _NodeImpl { RuntimeEnvironment renv; @@ -78,7 +178,6 @@ struct _NodeImpl Node::ReasonForTermination reasonForTermination; volatile bool started; volatile bool running; - volatile bool updateStatusNow; volatile bool terminateNow; // Helper used to rapidly terminate from run() @@ -94,20 +193,66 @@ struct _NodeImpl } }; -Node::Node(const char *hp,const char *urlPrefix,const char *configAuthorityIdentity) +#ifndef __WINDOWS__ +static void _netconfServiceMessageHandler(void *renv,Service &svc,const Dictionary &msg) +{ + if (!renv) + return; // sanity check + const RuntimeEnvironment *_r = (const RuntimeEnvironment *)renv; + + try { + const std::string &type = msg.get("type"); + if (type == "netconf-response") { + uint64_t inRePacketId = strtoull(msg.get("requestId").c_str(),(char **)0,16); + SharedPtr<Network> network = _r->nc->network(strtoull(msg.get("nwid").c_str(),(char **)0,16)); + Address peerAddress(msg.get("peer").c_str()); + + if ((network)&&(peerAddress)) { + if (msg.contains("error")) { + Packet::ErrorCode errCode = Packet::ERROR_INVALID_REQUEST; + const std::string &err = msg.get("error"); + if (err == "NOT_FOUND") + errCode = Packet::ERROR_NOT_FOUND; + + Packet outp(peerAddress,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(inRePacketId); + outp.append((unsigned char)errCode); + outp.append(network->id()); + _r->sw->send(outp,true); + } else if (msg.contains("netconf")) { + const std::string &netconf = msg.get("netconf"); + if (netconf.length() < 2048) { // sanity check + Packet outp(peerAddress,_r->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(inRePacketId); + outp.append(network->id()); + outp.append((uint16_t)netconf.length()); + outp.append(netconf.data(),netconf.length()); + _r->sw->send(outp,true); + } + } + } + } + } catch (std::exception &exc) { + LOG("unexpected exception parsing response from netconf service: %s",exc.what()); + } catch ( ... ) { + LOG("unexpected exception parsing response from netconf service: unknown exception"); + } +} +#endif // !__WINDOWS__ + +Node::Node(const char *hp) throw() : _impl(new _NodeImpl) { _NodeImpl *impl = (_NodeImpl *)_impl; impl->renv.homePath = hp; - impl->renv.autoconfUrlPrefix = urlPrefix; - impl->renv.configAuthorityIdentityStr = configAuthorityIdentity; impl->reasonForTermination = Node::NODE_RUNNING; impl->started = false; impl->running = false; - impl->updateStatusNow = false; impl->terminateNow = false; } @@ -115,6 +260,10 @@ Node::~Node() { _NodeImpl *impl = (_NodeImpl *)_impl; +#ifndef __WINDOWS__ + delete impl->renv.netconfService; +#endif + delete impl->renv.sysEnv; delete impl->renv.topology; delete impl->renv.sw; @@ -155,11 +304,9 @@ Node::ReasonForTermination Node::run() TRACE("initializing..."); + // Create non-crypto PRNG right away in case other code in init wants to use it _r->prng = new CMWC4096(); - if (!_r->configAuthority.fromString(_r->configAuthorityIdentityStr)) - return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"configuration authority identity is not valid"); - bool gotId = false; std::string identitySecretPath(_r->homePath + ZT_PATH_SEPARATOR_S + "identity.secret"); std::string identityPublicPath(_r->homePath + ZT_PATH_SEPARATOR_S + "identity.public"); @@ -188,37 +335,35 @@ Node::ReasonForTermination Node::run() } Utils::lockDownFile(identitySecretPath.c_str(),false); - // Generate ownership verification secret, which can be presented to - // a controlling web site (like ours) to prove ownership of a node and - // permit its configuration to be centrally modified. When ZeroTier One - // requests its config it sends a hash of this secret, and so the - // config server can verify this hash to determine if the secret the - // user presents is correct. - std::string ovsPath(_r->homePath + ZT_PATH_SEPARATOR_S + "thisdeviceismine"); - if (((Utils::now() - Utils::getLastModified(ovsPath.c_str())) >= ZT_OVS_GENERATE_NEW_IF_OLDER_THAN)||(!Utils::readFile(ovsPath.c_str(),_r->ownershipVerificationSecret))) { - _r->ownershipVerificationSecret = ""; - unsigned int securern = 0; + // Clean up some obsolete files if present -- this will be removed later + unlink((_r->homePath + ZT_PATH_SEPARATOR_S + "status").c_str()); + unlink((_r->homePath + ZT_PATH_SEPARATOR_S + "thisdeviceismine").c_str()); + + // Load or generate config authentication secret + std::string configAuthTokenPath(_r->homePath + ZT_PATH_SEPARATOR_S + "authtoken.secret"); + std::string configAuthToken; + if (!Utils::readFile(configAuthTokenPath.c_str(),configAuthToken)) { + configAuthToken = ""; + unsigned int sr = 0; for(unsigned int i=0;i<24;++i) { - Utils::getSecureRandom(&securern,sizeof(securern)); - _r->ownershipVerificationSecret.push_back("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[securern % 62]); + Utils::getSecureRandom(&sr,sizeof(sr)); + configAuthToken.push_back("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[sr % 62]); } - _r->ownershipVerificationSecret.append(ZT_EOL_S); - if (!Utils::writeFile(ovsPath.c_str(),_r->ownershipVerificationSecret)) - return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write 'thisdeviceismine' (home path not writable?)"); + if (!Utils::writeFile(configAuthTokenPath.c_str(),configAuthToken)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write authtoken.secret (home path not writable?)"); } - Utils::lockDownFile(ovsPath.c_str(),false); - _r->ownershipVerificationSecret = Utils::trim(_r->ownershipVerificationSecret); // trim off CR file is saved with - unsigned char ovsDig[32]; - SHA256_CTX sha; - SHA256_Init(&sha); - SHA256_Update(&sha,_r->ownershipVerificationSecret.data(),_r->ownershipVerificationSecret.length()); - SHA256_Final(ovsDig,&sha); - _r->ownershipVerificationSecretHash = Utils::base64Encode(ovsDig,32); + Utils::lockDownFile(configAuthTokenPath.c_str(),false); // Create the core objects in RuntimeEnvironment: node config, demarcation // point, switch, network topology database, and system environment // watcher. - _r->nc = new NodeConfig(_r,_r->autoconfUrlPrefix + _r->identity.address().toString()); + try { + _r->nc = new NodeConfig(_r,configAuthToken.c_str()); + } catch ( ... ) { + // An exception here currently means that another instance of ZeroTier + // One is running. + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"another instance of ZeroTier One appears to be running, or local control UDP port cannot be bound"); + } _r->demarc = new Demarc(_r); _r->multicaster = new Multicaster(); _r->sw = new Switch(_r); @@ -247,17 +392,26 @@ Node::ReasonForTermination Node::run() return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"unknown exception during initialization"); } +#ifndef __WINDOWS__ try { - std::string statusPath(_r->homePath + ZT_PATH_SEPARATOR_S + "status"); + std::string netconfServicePath(_r->homePath + ZT_PATH_SEPARATOR_S + "services.d" + ZT_PATH_SEPARATOR_S + "netconf.service"); + if (Utils::fileExists(netconfServicePath.c_str())) { + LOG("netconf.d/netconfi.service appears to exist, starting..."); + _r->netconfService = new Service(_r,"netconf",netconfServicePath.c_str(),&_netconfServiceMessageHandler,_r); + } + } catch ( ... ) { + LOG("unexpected exception attempting to start services"); + } +#endif + try { uint64_t lastPingCheck = 0; - uint64_t lastTopologyClean = Utils::now(); // don't need to do this immediately + uint64_t lastClean = Utils::now(); // don't need to do this immediately uint64_t lastNetworkFingerprintCheck = 0; uint64_t lastAutoconfigureCheck = 0; uint64_t networkConfigurationFingerprint = _r->sysEnv->getNetworkConfigurationFingerprint(); uint64_t lastMulticastCheck = 0; uint64_t lastMulticastAnnounceAll = 0; - uint64_t lastStatusUpdate = 0; long lastDelayDelta = 0; LOG("%s starting version %s",_r->identity.address().toString().c_str(),versionString()); @@ -292,16 +446,6 @@ Node::ReasonForTermination Node::run() } } - if ((now - lastAutoconfigureCheck) >= ZT_AUTOCONFIGURE_CHECK_DELAY) { - // It seems odd to only do this simple check every so often, but the purpose is to - // delay between calls to refreshConfiguration() enough that the previous attempt - // has time to either succeed or fail. Otherwise we'll block the whole loop, since - // config update is guarded by a Mutex. - lastAutoconfigureCheck = now; - if ((now - _r->nc->lastAutoconfigure()) >= ZT_AUTOCONFIGURE_INTERVAL) - _r->nc->refreshConfiguration(); // happens in background - } - // Periodically check for changes in our local multicast subscriptions and broadcast // those changes to peers. if ((now - lastMulticastCheck) >= ZT_MULTICAST_LOCAL_POLL_PERIOD) { @@ -337,11 +481,9 @@ Node::ReasonForTermination Node::run() if ((now - lastPingCheck) >= ZT_PING_CHECK_DELAY) { lastPingCheck = now; try { - if (_r->topology->isSupernode(_r->identity.address())) { - // The only difference in how supernodes behave is here: they only - // actively ping each other and only passively listen for pings - // from anyone else. They also don't send firewall openers, since - // they're never firewalled. + if (_r->topology->amSupernode()) { + // Supernodes do not ping anyone but each other. They also don't + // send firewall openers, since they aren't ever firewalled. std::vector< SharedPtr<Peer> > sns(_r->topology->supernodePeers()); for(std::vector< SharedPtr<Peer> >::const_iterator p(sns.begin());p!=sns.end();++p) { if ((now - (*p)->lastDirectSend()) > ZT_PEER_DIRECT_PING_DELAY) @@ -384,23 +526,10 @@ Node::ReasonForTermination Node::run() } } - if ((now - lastTopologyClean) >= ZT_TOPOLOGY_CLEAN_PERIOD) { - lastTopologyClean = now; - _r->topology->clean(); // happens in background - } - - if (((now - lastStatusUpdate) >= ZT_STATUS_OUTPUT_PERIOD)||(impl->updateStatusNow)) { - lastStatusUpdate = now; - impl->updateStatusNow = false; - FILE *statusf = ::fopen(statusPath.c_str(),"w"); - if (statusf) { - try { - _r->topology->eachPeer(Topology::DumpPeerStatistics(statusf)); - } catch ( ... ) { - TRACE("unexpected exception updating status dump"); - } - ::fclose(statusf); - } + if ((now - lastClean) >= ZT_DB_CLEAN_PERIOD) { + lastClean = now; + _r->topology->clean(); + _r->nc->cleanAllNetworks(); } try { @@ -436,13 +565,6 @@ void Node::terminate() ((_NodeImpl *)_impl)->renv.mainLoopWaitCondition.signal(); } -void Node::updateStatusNow() - throw() -{ - ((_NodeImpl *)_impl)->updateStatusNow = true; - ((_NodeImpl *)_impl)->renv.mainLoopWaitCondition.signal(); -} - class _VersionStringMaker { public: diff --git a/node/Node.hpp b/node/Node.hpp index df6b946f..b716b556 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -41,6 +41,44 @@ class Node { public: /** + * Client for controlling a local ZeroTier One node + */ + class LocalClient + { + public: + /** + * Create a new node config client + * + * @param authToken Authentication token + * @param resultHandler Function to call when commands provide results + */ + LocalClient(const char *authToken,void (*resultHandler)(void *,unsigned long,const char *),void *arg) + throw(); + + ~LocalClient(); + + /** + * Send a command to the local node + * + * Note that the returned conversation ID will never be 0. A return value + * of 0 indicates a fatal error such as failure to bind to any local UDP + * port. + * + * @param command + * @return Conversation ID that will be provided to result handler when/if results are sent back + */ + unsigned long send(const char *command) + throw(); + + private: + // LocalClient is not copyable + LocalClient(const LocalClient&); + const LocalClient& operator=(const LocalClient&); + + void *_impl; + }; + + /** * Returned by node main if/when it terminates */ enum ReasonForTermination @@ -58,11 +96,8 @@ public: * The node is not executed until run() is called. * * @param hp Home directory path - * @param url URL prefix for autoconfiguration (http and file permitted) - * @param configAuthorityIdentity Public identity used to encrypt/authenticate configuration from this URL (ASCII string format) - * @throws std::invalid_argument Invalid argument supplied to constructor */ - Node(const char *hp,const char *urlPrefix,const char *configAuthorityIdentity) + Node(const char *hp) throw(); ~Node(); @@ -99,12 +134,6 @@ public: throw(); /** - * Update the status file in the home directory on next service loop - */ - void updateStatusNow() - throw(); - - /** * Get the ZeroTier version in major.minor.revision string format * * @return Version in string form @@ -117,6 +146,10 @@ public: static unsigned int versionRevision() throw(); private: + // Nodes are not copyable + Node(const Node&); + const Node& operator=(const Node&); + void *const _impl; // private implementation }; diff --git a/node/NodeConfig.cpp b/node/NodeConfig.cpp index fcbbc6bd..4a174535 100644 --- a/node/NodeConfig.cpp +++ b/node/NodeConfig.cpp @@ -27,180 +27,225 @@ #include <stdio.h> #include <string.h> +#include <stdlib.h> +#include <stdint.h> + #include <memory> #include <string> -#include <json/json.h> +#include <openssl/sha.h> #include "NodeConfig.hpp" #include "RuntimeEnvironment.hpp" #include "Defaults.hpp" #include "Utils.hpp" #include "Logger.hpp" +#include "Topology.hpp" +#include "Demarc.hpp" +#include "InetAddress.hpp" +#include "Peer.hpp" +#include "Salsa20.hpp" +#include "HMAC.hpp" namespace ZeroTier { -NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const std::string &url) : +NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const char *authToken) + throw(std::runtime_error) : _r(renv), - _lastAutoconfigure(0), - _lastAutoconfigureLastModified(), - _url(url), - _autoconfigureLock(), - _networks(), - _networks_m() + _controlSocket(true,ZT_CONTROL_UDP_PORT,false,&_CBcontrolPacketHandler,this) { + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha,authToken,strlen(authToken)); + SHA256_Final(_controlSocketKey,&sha); } NodeConfig::~NodeConfig() { - _autoconfigureLock.lock(); // wait for any autoconfs to finish - _autoconfigureLock.unlock(); } -void NodeConfig::refreshConfiguration() +void NodeConfig::whackAllTaps() +{ + std::vector< SharedPtr<Network> > nwlist; + Mutex::Lock _l(_networks_m); + for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n) + n->second->tap().whack(); +} + +void NodeConfig::cleanAllNetworks() +{ + Mutex::Lock _l(_networks_m); + for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n) + n->second->clean(); +} + +// Macro used in execute() +#undef _P +#define _P(f,...) { r.push_back(std::string()); Utils::stdsprintf(r.back(),(f),##__VA_ARGS__); } + +// Used with Topology::eachPeer to dump peer stats +class _DumpPeerStatistics { - _autoconfigureLock.lock(); // unlocked when handler gets called +public: + _DumpPeerStatistics(std::vector<std::string> &out) : + r(out), + _now(Utils::now()) + { + } + + inline void operator()(Topology &t,const SharedPtr<Peer> &p) + { + InetAddress v4(p->ipv4ActivePath(_now)); + InetAddress v6(p->ipv6ActivePath(_now)); + _P("200 listpeers %s %s %s %u", + p->address().toString().c_str(), + ((v4) ? v4.toString().c_str() : "(none)"), + ((v6) ? v6.toString().c_str() : "(none)"), + (((v4)||(v6)) ? p->latency() : 0)); + } - TRACE("refreshing autoconfigure URL %s (if modified since: '%s')",_url.c_str(),_lastAutoconfigureLastModified.c_str()); +private: + std::vector<std::string> &r; + uint64_t _now; +}; - std::map<std::string,std::string> reqHeaders; - reqHeaders["X-ZT-ID"] = _r->identity.toString(false); - reqHeaders["X-ZT-OVSH"] = _r->ownershipVerificationSecretHash; - if (_lastAutoconfigureLastModified.length()) - reqHeaders["If-Modified-Since"] = _lastAutoconfigureLastModified; +std::vector<std::string> NodeConfig::execute(const char *command) +{ + std::vector<std::string> r; + std::vector<std::string> cmd(Utils::split(command,"\r\n \t","\\","'")); + + // + // Not coincidentally, response type codes correspond with HTTP + // status codes. + // + + if ((cmd.empty())||(cmd[0] == "help")) { + _P("200 help help"); + _P("200 help listpeers"); + _P("200 help listnetworks"); + _P("200 help join <network ID> [<network invitation code>]"); + _P("200 help leave <network ID>"); + } else if (cmd[0] == "listpeers") { + _r->topology->eachPeer(_DumpPeerStatistics(r)); + } else if (cmd[0] == "listnetworks") { + Mutex::Lock _l(_networks_m); + for(std::map< uint64_t,SharedPtr<Network> >::const_iterator nw(_networks.begin());nw!=_networks.end();++nw) { + _P("200 listnetworks %llu %s %s", + nw->first, + nw->second->tap().deviceName().c_str(), + (nw->second->isOpen() ? "public" : "private")); + } + } else if (cmd[0] == "join") { + _P("404 join Not implemented yet."); + } else if (cmd[0] == "leave") { + _P("404 leave Not implemented yet."); + } else { + _P("404 %s No such command. Use 'help' for help.",cmd[0].c_str()); + } - new Http::Request(Http::HTTP_METHOD_GET,_url,reqHeaders,std::string(),&NodeConfig::_CBautoconfHandler,this); + return r; } -void NodeConfig::__CBautoconfHandler(const std::string &lastModified,const std::string &body) +std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > NodeConfig::encodeControlMessage(const void *key,unsigned long conversationId,const std::vector<std::string> &payload) + throw(std::out_of_range) { - try { - Json::Value root; - Json::Reader reader; + char hmac[32]; + char keytmp[32]; + std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets; + Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet; - std::string dec(_r->identity.decrypt(_r->configAuthority,body.data(),body.length())); - if (!dec.length()) { - LOG("autoconfigure from %s failed: data did not decrypt as from config authority %s",_url.c_str(),_r->configAuthority.address().toString().c_str()); - return; - } - TRACE("decrypted autoconf: %s",dec.c_str()); + packet.setSize(16); // HMAC and IV + packet.append((uint32_t)(conversationId & 0xffffffff)); + for(unsigned int i=0;i<payload.size();++i) { + packet.append(payload[i]); // will throw if too big + packet.append((unsigned char)0); - if (!reader.parse(dec,root,false)) { - LOG("autoconfigure from %s failed: JSON parse error: %s",_url.c_str(),reader.getFormattedErrorMessages().c_str()); - return; - } + if (((i + 1) >= payload.size())||((packet.size() + payload[i + 1].length() + 1) >= packet.capacity())) { + Utils::getSecureRandom(packet.field(8,8),8); - if (!root.isObject()) { - LOG("autoconfigure from %s failed: not a JSON object",_url.c_str()); - return; + Salsa20 s20(key,256,packet.field(8,8)); + s20.encrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16); + + memcpy(keytmp,key,32); + for(unsigned int i=0;i<32;++i) + keytmp[i] ^= 0x77; // use a different permutation of key for HMAC than for Salsa20 + HMAC::sha256(keytmp,32,packet.field(16,packet.size() - 16),packet.size() - 16,hmac); + memcpy(packet.field(0,8),hmac,8); + + packets.push_back(packet); + + packet.setSize(16); // HMAC and IV + packet.append((uint32_t)(conversationId & 0xffffffff)); } + } - // Configure networks - const Json::Value &networks = root["_networks"]; - if (networks.isArray()) { - Mutex::Lock _l(_networks_m); - for(unsigned int ni=0;ni<networks.size();++ni) { - if (networks[ni].isObject()) { - const Json::Value &nwid_ = networks[ni]["id"]; - uint64_t nwid = nwid_.isNumeric() ? (uint64_t)nwid_.asUInt64() : (uint64_t)strtoull(networks[ni]["id"].asString().c_str(),(char **)0,10); - - if (nwid) { - SharedPtr<Network> nw; - std::map< uint64_t,SharedPtr<Network> >::iterator nwent(_networks.find(nwid)); - if (nwent != _networks.end()) - nw = nwent->second; - else { - try { - nw = SharedPtr<Network>(new Network(_r,nwid)); - _networks[nwid] = nw; - } catch (std::exception &exc) { - LOG("unable to create network %llu: %s",nwid,exc.what()); - } catch ( ... ) { - LOG("unable to create network %llu: unknown exception",nwid); - } - } - - if (nw) { - Mutex::Lock _l2(nw->_lock); - nw->_open = networks[ni]["isOpen"].asBool(); - - // Ensure that TAP device has all the right IP addresses - // TODO: IPv6 might work a tad differently - std::set<InetAddress> allIps; - const Json::Value &addresses = networks[ni]["_addresses"]; - if (addresses.isArray()) { - for(unsigned int ai=0;ai<addresses.size();++ai) { - if (addresses[ai].isString()) { - InetAddress addr(addresses[ai].asString()); - if (addr) { - TRACE("network %llu IP/netmask: %s",nwid,addr.toString().c_str()); - allIps.insert(addr); - } - } - } - } - nw->_tap.setIps(allIps); - - // NOTE: the _members field is optional for open networks, - // since members of open nets do not need to check membership - // of packet senders and mutlicasters. - const Json::Value &members = networks[ni]["_members"]; - nw->_members.clear(); - if (members.isArray()) { - for(unsigned int mi=0;mi<members.size();++mi) { - std::string rawAddr(Utils::unhex(members[mi].asString())); - if (rawAddr.length() == ZT_ADDRESS_LENGTH) { - Address addr(rawAddr.data()); - if ((addr)&&(!addr.isReserved())) { - //TRACE("network %llu member: %s",nwid,addr.toString().c_str()); - nw->_members.insert(addr); - } - } - } - } - } - } else { - TRACE("ignored networks[%u], 'id' field missing"); - } - } else { - TRACE("ignored networks[%u], not a JSON object",ni); - } - } + return packets; +} + +bool NodeConfig::decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector<std::string> &payload) +{ + char hmac[32]; + char keytmp[32]; + + try { + if (len < 20) + return false; + + Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet(data,len); + + memcpy(keytmp,key,32); + for(unsigned int i=0;i<32;++i) + keytmp[i] ^= 0x77; // use a different permutation of key for HMAC than for Salsa20 + HMAC::sha256(keytmp,32,packet.field(16,packet.size() - 16),packet.size() - 16,hmac); + if (memcmp(packet.field(0,8),hmac,8)) + return false; + + Salsa20 s20(key,256,packet.field(8,8)); + s20.decrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16); + + conversationId = packet.at<uint32_t>(16); + + const char *pl = ((const char *)packet.data()) + 20; + unsigned int pll = packet.size() - 20; + for(unsigned int i=0;i<pll;) { + unsigned int eos = i; + while ((eos < pll)&&(pl[eos])) + ++eos; + if (eos > i) { + payload.push_back(std::string(pl + i,eos - i)); + i = eos + 1; + } else break; } - _lastAutoconfigure = Utils::now(); - _lastAutoconfigureLastModified = lastModified; - } catch (std::exception &exc) { - TRACE("exception parsing autoconf URL response: %s",exc.what()); + return true; } catch ( ... ) { - TRACE("unexpected exception parsing autoconf URL response"); + return false; } } -bool NodeConfig::_CBautoconfHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body) +void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) { -#ifdef ZT_TRACE - const RuntimeEnvironment *_r = ((NodeConfig *)arg)->_r; -#endif - - if (code == 200) { - TRACE("200 got autoconfigure response from %s: %u bytes",url.c_str(),(unsigned int)body.length()); - - std::map<std::string,std::string>::const_iterator lm(headers.find("Last-Modified")); - if (lm != headers.end()) - ((NodeConfig *)arg)->__CBautoconfHandler(lm->second,body); - else ((NodeConfig *)arg)->__CBautoconfHandler(std::string(),body); - } else if (code == 304) { - TRACE("304 autoconfigure deferred, remote URL %s not modified",url.c_str()); - ((NodeConfig *)arg)->_lastAutoconfigure = Utils::now(); // still considered a success - } else if (code == 409) { // conflict, ID address in use by another ID - TRACE("%d autoconfigure failed from %s",code,url.c_str()); - } else { - TRACE("%d autoconfigure failed from %s",code,url.c_str()); - } + NodeConfig *nc = (NodeConfig *)arg; + const RuntimeEnvironment *_r = nc->_r; + + try { + unsigned long convId = 0; + std::vector<std::string> commands; + + if (!decodeControlMessagePacket(nc->_controlSocketKey,data,len,convId,commands)) { + TRACE("control bus packet from %s failed decode, discarded",remoteAddr.toString().c_str()); + return; + } + TRACE("control bus packet from %s, contains %d commands",remoteAddr.toString().c_str(),(int)commands.size()); - ((NodeConfig *)arg)->_autoconfigureLock.unlock(); - return false; // causes Request to delete itself + for(std::vector<std::string>::iterator c(commands.begin());c!=commands.end();++c) { + std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > resultPackets(encodeControlMessage(nc->_controlSocketKey,convId,nc->execute(c->c_str()))); + for(std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> >::iterator p(resultPackets.begin());p!=resultPackets.end();++p) + sock->send(remoteAddr,p->data(),p->size(),-1); + } + } catch ( ... ) { + TRACE("exception handling control bus packet from %s",remoteAddr.toString().c_str()); + } } } // namespace ZeroTier diff --git a/node/NodeConfig.hpp b/node/NodeConfig.hpp index 5caf18ab..62b23609 100644 --- a/node/NodeConfig.hpp +++ b/node/NodeConfig.hpp @@ -31,27 +31,51 @@ #include <map> #include <set> #include <string> +#include <vector> +#include <stdexcept> + #include <stdint.h> #include "SharedPtr.hpp" #include "Network.hpp" #include "Utils.hpp" -#include "Http.hpp" +#include "UdpSocket.hpp" +#include "Buffer.hpp" namespace ZeroTier { class RuntimeEnvironment; /** - * Node configuration holder and fetcher + * Maximum size of a packet for node configuration + */ +#define ZT_NODECONFIG_MAX_PACKET_SIZE 4096 + +/** + * Node configuration endpoint + * + * Packet format for local UDP configuration packets: + * [16] first 16 bytes of HMAC-SHA-256 of payload + * [ -- begin HMAC'ed envelope -- ] + * [8] random initialization vector + * [ -- begin cryptographic envelope -- ] + * [4] arbitrary tag, echoed in response + * [...] payload + * + * For requests, the payload consists of a single ASCII command. For + * responses, the payload consists of one or more response lines delimited + * by NULL (0) characters. The tag field is replicated in the result + * packet. */ class NodeConfig { public: /** * @param renv Runtime environment - * @param url Autoconfiguration URL (http:// or file://) + * @param authToken Configuration authentication token + * @throws std::runtime_error Unable to bind to local control port */ - NodeConfig(const RuntimeEnvironment *renv,const std::string &url); + NodeConfig(const RuntimeEnvironment *renv,const char *authToken) + throw(std::runtime_error); ~NodeConfig(); @@ -81,13 +105,12 @@ public: /** * Call whack() on all networks' tap devices */ - inline void whackAllTaps() - { - std::vector< SharedPtr<Network> > nwlist; - Mutex::Lock _l(_networks_m); - for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n) - n->second->tap().whack(); - } + void whackAllTaps(); + + /** + * Call clean() on all networks + */ + void cleanAllNetworks(); /** * @param nwid Network ID @@ -112,32 +135,49 @@ public: } /** - * @return Time of last successful autoconfigure or refresh + * Execute a command + * + * @param command Command and arguments separated by whitespace (must already be trimmed of CR+LF, etc.) + * @return One or more command results (lines of output) */ - inline uint64_t lastAutoconfigure() const { return _lastAutoconfigure; } + std::vector<std::string> execute(const char *command); /** - * @return Autoconfiguration URL + * Armor payload for control bus + * + * Note that no single element of payload can be longer than the max packet + * size. If this occurs out_of_range is thrown. + * + * @param key 32 byte key + * @param conversationId 32-bit conversation ID (bits beyond 32 are ignored) + * @param payload One or more strings to encode in packet + * @return One or more transport armored packets (if payload too big) + * @throws std::out_of_range An element of payload is too big */ - inline const std::string &url() const { return _url; } + static std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > encodeControlMessage(const void *key,unsigned long conversationId,const std::vector<std::string> &payload) + throw(std::out_of_range); /** - * Refresh configuration from autoconf URL + * Decode a packet from the control bus + * + * Note that 'payload' is appended to. Existing data is not cleared. + * + * @param key 32 byte key + * @param data Packet data + * @param len Packet length + * @param conversationId Result parameter filled with conversation ID on success + * @param payload Result parameter to which results are appended + * @return True on success, false on invalid packet or packet that failed authentication */ - void refreshConfiguration(); + static bool decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector<std::string> &payload); private: - void __CBautoconfHandler(const std::string &lastModified,const std::string &body); - static bool _CBautoconfHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body); + static void _CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len); const RuntimeEnvironment *_r; - volatile uint64_t _lastAutoconfigure; - - std::string _lastAutoconfigureLastModified; - std::string _url; - Mutex _autoconfigureLock; - + unsigned char _controlSocketKey[32]; + UdpSocket _controlSocket; std::map< uint64_t,SharedPtr<Network> > _networks; Mutex _networks_m; }; diff --git a/node/Pack.cpp b/node/Pack.cpp deleted file mode 100644 index 8bac1300..00000000 --- a/node/Pack.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2012-2013 ZeroTier Networks LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#include <iostream> -#include <string.h> -#include <stdlib.h> -#include "Pack.hpp" -#include "BlobArray.hpp" -#include "Utils.hpp" - -#include <openssl/sha.h> - -namespace ZeroTier { - -std::vector<const Pack::Entry *> Pack::getAll() const -{ - std::vector<const Entry *> v; - for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) - v.push_back(&(e->second)); - return v; -} - -const Pack::Entry *Pack::get(const std::string &name) const -{ - std::map<std::string,Entry>::const_iterator e(_entries.find(name)); - return ((e == _entries.end()) ? (const Entry *)0 : &(e->second)); -} - -const Pack::Entry *Pack::put(const std::string &name,const std::string &content) -{ - SHA256_CTX sha; - - Pack::Entry &e = _entries[name]; - e.name = name; - e.content = content; - - SHA256_Init(&sha); - SHA256_Update(&sha,content.data(),content.length()); - SHA256_Final(e.sha256,&sha); - - e.signedBy.zero(); - e.signature.assign((const char *)0,0); - - return &e; -} - -void Pack::clear() -{ - _entries.clear(); -} - -std::string Pack::serialize() const -{ - BlobArray archive; - for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) { - BlobArray entry; - entry.push_back(e->second.name); - entry.push_back(e->second.content); - entry.push_back(std::string((const char *)e->second.sha256,sizeof(e->second.sha256))); - entry.push_back(std::string((const char *)e->second.signedBy.data(),e->second.signedBy.size())); - entry.push_back(e->second.signature); - archive.push_back(entry.serialize()); - } - - std::string ser(archive.serialize()); - std::string comp; - Utils::compress(ser.begin(),ser.end(),Utils::StringAppendOutput(comp)); - return comp; -} - -bool Pack::deserialize(const void *sd,unsigned int sdlen) -{ - unsigned char dig[32]; - SHA256_CTX sha; - - std::string decomp; - if (!Utils::decompress(((const char *)sd),((const char *)sd) + sdlen,Utils::StringAppendOutput(decomp))) - return false; - - BlobArray archive; - archive.deserialize(decomp.data(),decomp.length()); - clear(); - for(BlobArray::const_iterator i=archive.begin();i!=archive.end();++i) { - BlobArray entry; - entry.deserialize(i->data(),i->length()); - - if (entry.size() != 5) return false; - if (entry[2].length() != 32) return false; // SHA-256 - if (entry[3].length() != ZT_ADDRESS_LENGTH) return false; // Address - - Pack::Entry &e = _entries[entry[0]]; - e.name = entry[0]; - e.content = entry[1]; - - SHA256_Init(&sha); - SHA256_Update(&sha,e.content.data(),e.content.length()); - SHA256_Final(dig,&sha); - if (memcmp(dig,entry[2].data(),32)) return false; // integrity check failed - memcpy(e.sha256,dig,32); - - if (entry[3].length() == ZT_ADDRESS_LENGTH) - e.signedBy = entry[3].data(); - else e.signedBy.zero(); - e.signature = entry[4]; - } - return true; -} - -bool Pack::signAll(const Identity &id) -{ - for(std::map<std::string,Entry>::iterator e=_entries.begin();e!=_entries.end();++e) { - e->second.signedBy = id.address(); - e->second.signature = id.sign(e->second.sha256); - if (!e->second.signature.length()) - return false; - } - return true; -} - -std::vector<const Pack::Entry *> Pack::verifyAll(const Identity &id,bool mandatory) const -{ - std::vector<const Entry *> bad; - for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) { - if ((e->second.signedBy)&&(e->second.signature.length())) { - if (id.address() != e->second.signedBy) - bad.push_back(&(e->second)); - else if (!id.verifySignature(e->second.sha256,e->second.signature.data(),e->second.signature.length())) - bad.push_back(&(e->second)); - } else if (mandatory) - bad.push_back(&(e->second)); - } - return bad; -} - -} // namespace ZeroTier diff --git a/node/Pack.hpp b/node/Pack.hpp deleted file mode 100644 index a0aecd6e..00000000 --- a/node/Pack.hpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2012-2013 ZeroTier Networks LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#ifndef _ZT_PACK_HPP -#define _ZT_PACK_HPP - -#include <string> -#include <map> -#include <list> -#include <stdexcept> -#include "Address.hpp" -#include "Identity.hpp" - -namespace ZeroTier { - -/** - * A very simple archive format for distributing packs of files or resources - * - * This is used for things like the auto-updater. It's not suitable for huge - * files, since at present it must work in memory. Packs support signing with - * identities and signature verification. - */ -class Pack -{ -public: - /** - * Pack entry structure for looking up deserialized entries - */ - struct Entry - { - std::string name; - std::string content; - unsigned char sha256[32]; - Address signedBy; - std::string signature; - }; - - Pack() {} - ~Pack() {} - - /** - * @return Vector of all entries - */ - std::vector<const Entry *> getAll() const; - - /** - * Look up an entry - * - * @param name Name to look up - * @return Pointer to entry if it exists or NULL if not found - */ - const Entry *get(const std::string &name) const; - - /** - * Add an entry to this pack - * - * @param name Entry to add - * @param content Entry's contents - * @return The new entry - */ - const Entry *put(const std::string &name,const std::string &content); - - /** - * Remove all entries - */ - void clear(); - - /** - * @return Number of entries in pack - */ - inline unsigned int numEntries() const { return (unsigned int)_entries.size(); } - - /** - * Serialize this pack - * - * @return Serialized form (compressed with LZ4) - */ - std::string serialize() const; - - /** - * Deserialize this pack - * - * Any current contents are lost. This does not verify signatures, - * but does check SHA256 hashes for entry integrity. If the return - * value is false, the pack's contents are undefined. - * - * @param sd Serialized data - * @param sdlen Length of serialized data - * @return True on success, false on deserialization error - */ - bool deserialize(const void *sd,unsigned int sdlen); - inline bool deserialize(const std::string &sd) { return deserialize(sd.data(),sd.length()); } - - /** - * Sign all entries in this pack with a given identity - * - * @param id Identity to sign with - * @return True on signature success, false if error - */ - bool signAll(const Identity &id); - - /** - * Verify all signed entries - * - * @param id Identity to verify against - * @param mandatory If true, require that all entries be signed and fail if no signature - * @return Vector of entries that failed verification or empty vector if all passed - */ - std::vector<const Entry *> verifyAll(const Identity &id,bool mandatory) const; - -private: - std::map<std::string,Entry> _entries; -}; - -} // namespace ZeroTier - -#endif diff --git a/node/Packet.cpp b/node/Packet.cpp index d12f396d..94d9164b 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -42,6 +42,9 @@ const char *Packet::verbString(Verb v) case VERB_FRAME: return "FRAME"; case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME"; case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE"; + case VERB_NETWORK_MEMBERSHIP_CERTIFICATE: return "NETWORK_MEMBERSHIP_CERTIFICATE"; + case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST"; + case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH"; } return "(unknown)"; } @@ -57,6 +60,7 @@ const char *Packet::errorString(ErrorCode e) case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION"; case ERROR_IDENTITY_INVALID: return "IDENTITY_INVALID"; case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION"; + case ERROR_NO_MEMBER_CERTIFICATE: return "NO_MEMBER_CERTIFICATE"; } return "(unknown)"; } diff --git a/node/Packet.hpp b/node/Packet.hpp index a5c450fb..0e7ccea3 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -132,27 +132,34 @@ #define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BYTES 64 // Field incides for parsing verbs + #define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION + 1) #define ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION + 1) #define ZT_PROTO_VERB_HELLO_IDX_REVISION (ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION + 1) #define ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP (ZT_PROTO_VERB_HELLO_IDX_REVISION + 2) #define ZT_PROTO_VERB_HELLO_IDX_IDENTITY (ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP + 8) + #define ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB + 1) #define ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE (ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID + 8) #define ZT_PROTO_VERB_ERROR_IDX_PAYLOAD (ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE + 1) + #define ZT_PROTO_VERB_OK_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_OK_IDX_IN_RE_VERB + 1) #define ZT_PROTO_VERB_OK_IDX_PAYLOAD (ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID + 8) + #define ZT_PROTO_VERB_WHOIS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD) + #define ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT (ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS + 5) #define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN (ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT + 2) #define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS (ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN + 1) + #define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8) #define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2) + #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1) #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8) @@ -166,6 +173,12 @@ #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH + 2) #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH + 2) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2) + +#define ZT_PROTO_VERB_NETWORK_CONFIG_REFRESH_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) + // Field indices for parsing OK and ERROR payloads of replies #define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD) #define ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY (ZT_PROTO_VERB_OK_IDX_PAYLOAD) @@ -287,7 +300,7 @@ public: * * @return Destination ZT address */ - inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH)); } + inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } /** * @return True if fragment is of a valid length @@ -449,7 +462,7 @@ public: * <[2] 16-bit length of payload> * <[2] 16-bit length of signature> * <[...] ethernet payload> - * <[...] ECDSA signature> + * <[...] ECDSA signature of SHA-256 hash (see below)> * * The signature is made using the key of the original submitter, and * can be used to authenticate the submitter for security and rate @@ -463,7 +476,57 @@ public: * * No OK or ERROR is generated. */ - VERB_MULTICAST_FRAME = 9 + VERB_MULTICAST_FRAME = 9, + + /* Network member certificate for sending peer: + * <[8] 64-bit network ID> + * <[2] 16-bit length of certificate> + * <[2] 16-bit length of signature> + * <[...] string-serialized certificate dictionary> + * <[...] ECDSA signature of certificate> + * + * OK is generated on acceptance. ERROR is returned on failure. In both + * cases the payload is the network ID. + */ + VERB_NETWORK_MEMBERSHIP_CERTIFICATE = 10, + + /* Network configuration request: + * <[8] 64-bit network ID> + * <[2] 16-bit length of request meta-data dictionary> + * <[...] string-serialized request meta-data> + * + * This message requests network configuration from a node capable of + * providing it. Such nodes run the netconf service, which must be + * installed into the ZeroTier home directory. + * + * OK response payload: + * <[8] 64-bit network ID> + * <[2] 16-bit length of network configuration dictionary> + * <[...] network configuration dictionary> + * + * OK returns a Dictionary (string serialized) containing the network's + * configuration and IP address assignment information for the querying + * node. It also contains a membership certificate that the querying + * node can push to other peers to demonstrate its right to speak on + * a given network. + * + * ERROR may be NOT_FOUND if no such network is known, or + * UNSUPPORTED_OPERATION if the netconf service isn't available. The + * payload will be the network ID. + */ + VERB_NETWORK_CONFIG_REQUEST = 11, + + /* Network configuration refresh request: + * <[8] 64-bit network ID> + * + * This message can be sent by the network configuration master node + * to request that nodes refresh their network configuration. It can + * thus be used to "push" updates. + * + * It does not generate an OK or ERROR message, and is treated only as + * a hint to refresh now. + */ + VERB_NETWORK_CONFIG_REFRESH = 12 }; /** @@ -490,7 +553,10 @@ public: ERROR_IDENTITY_INVALID = 5, /* Verb or use case not supported/enabled by this node */ - ERROR_UNSUPPORTED_OPERATION = 6 + ERROR_UNSUPPORTED_OPERATION = 6, + + /* Message to private network rejected -- no unexpired certificate on file */ + ERROR_NO_MEMBER_CERTIFICATE = 7 }; /** @@ -603,14 +669,14 @@ public: * * @return Destination ZT address */ - inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH)); } + inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } /** * Get this packet's source * * @return Source ZT address */ - inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH)); } + inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } /** * @return True if packet is of valid length diff --git a/node/PacketDecoder.cpp b/node/PacketDecoder.cpp index ba1a9001..518ed9e7 100644 --- a/node/PacketDecoder.cpp +++ b/node/PacketDecoder.cpp @@ -25,6 +25,7 @@ * LLC. Start here: http://www.zerotier.com/ */ +#include "Constants.hpp" #include "RuntimeEnvironment.hpp" #include "Topology.hpp" #include "PacketDecoder.hpp" @@ -32,6 +33,7 @@ #include "Peer.hpp" #include "NodeConfig.hpp" #include "Filter.hpp" +#include "Service.hpp" namespace ZeroTier { @@ -102,6 +104,12 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r) return _doMULTICAST_LIKE(_r,peer); case Packet::VERB_MULTICAST_FRAME: return _doMULTICAST_FRAME(_r,peer); + case Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE: + return _doNETWORK_MEMBERSHIP_CERTIFICATE(_r,peer); + case Packet::VERB_NETWORK_CONFIG_REQUEST: + return _doNETWORK_CONFIG_REQUEST(_r,peer); + case Packet::VERB_NETWORK_CONFIG_REFRESH: + return _doNETWORK_CONFIG_REFRESH(_r,peer); default: // This might be something from a new or old version of the protocol. // Technically it passed HMAC so the packet is still valid, but we @@ -305,7 +313,7 @@ bool PacketDecoder::_doOK(const RuntimeEnvironment *_r,const SharedPtr<Peer> &pe bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer) { if (payloadLength() == ZT_ADDRESS_LENGTH) { - SharedPtr<Peer> p(_r->topology->getPeer(Address(payload()))); + SharedPtr<Peer> p(_r->topology->getPeer(Address(payload(),ZT_ADDRESS_LENGTH))); if (p) { Packet outp(source(),_r->identity.address(),Packet::VERB_OK); outp.append((unsigned char)Packet::VERB_WHOIS); @@ -314,7 +322,7 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer> outp.encrypt(peer->cryptKey()); outp.hmacSet(peer->macKey()); _r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1); - TRACE("sent WHOIS response to %s for %s",source().toString().c_str(),Address(payload()).toString().c_str()); + TRACE("sent WHOIS response to %s for %s",source().toString().c_str(),Address(payload(),ZT_ADDRESS_LENGTH).toString().c_str()); } else { Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR); outp.append((unsigned char)Packet::VERB_WHOIS); @@ -324,7 +332,7 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer> outp.encrypt(peer->cryptKey()); outp.hmacSet(peer->macKey()); _r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1); - TRACE("sent WHOIS ERROR to %s for %s (not found)",source().toString().c_str(),Address(payload()).toString().c_str()); + TRACE("sent WHOIS ERROR to %s for %s (not found)",source().toString().c_str(),Address(payload(),ZT_ADDRESS_LENGTH).toString().c_str()); } } else { TRACE("dropped WHOIS from %s(%s): missing or invalid address",source().toString().c_str(),_remoteAddress.toString().c_str()); @@ -335,7 +343,7 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer> bool PacketDecoder::_doRENDEZVOUS(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer) { try { - Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH)); + Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); SharedPtr<Peer> withPeer(_r->topology->getPeer(with)); if (withPeer) { unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT); @@ -433,7 +441,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared if (network->isAllowed(source())) { if (size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) { - Address originalSubmitterAddress(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS,ZT_ADDRESS_LENGTH)); + Address originalSubmitterAddress(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); MAC fromMac(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC,6)); MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC,6)),at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI)); unsigned int hops = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT]; @@ -450,19 +458,28 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared // Technically should not happen, since the original submitter is // excluded from consideration as a propagation recipient. TRACE("dropped boomerang MULTICAST_FRAME received from %s(%s)",source().toString().c_str(),_remoteAddress.toString().c_str()); - } else if ((!isDuplicate)||(_r->topology->isSupernode(_r->identity.address()))) { + } else if ((!isDuplicate)||(_r->topology->amSupernode())) { + // // If I am a supernode, I will repeatedly propagate duplicates. That's // because supernodes are used to bridge sparse multicast groups. Non- // supernodes will ignore duplicates completely. + // + // TODO: supernodes should keep a local bloom filter too and OR it with + // the bloom from the packet in order to pick different recipients each + // time a multicast returns to them for repropagation. + // + SharedPtr<Peer> originalSubmitter(_r->topology->getPeer(originalSubmitterAddress)); if (!originalSubmitter) { TRACE("requesting WHOIS on original multicast frame submitter %s",originalSubmitterAddress.toString().c_str()); _r->sw->requestWhois(originalSubmitterAddress); _step = DECODE_STEP_WAITING_FOR_ORIGINAL_SUBMITTER_LOOKUP; - return false; + return false; // try again if/when we get OK(WHOIS) } else if (Multicaster::verifyMulticastPacket(originalSubmitter->identity(),network->id(),fromMac,mg,etherType,dataAndSignature,datalen,dataAndSignature + datalen,signaturelen)) { _r->multicaster->addToDedupHistory(mccrc,now); + // Even if we are a supernode, we still don't repeatedly inject + // duplicates into our own tap. if (!isDuplicate) network->tap().put(fromMac,mg.mac(),etherType,dataAndSignature,datalen); @@ -494,7 +511,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared compress(); for(unsigned int i=0;i<np;++i) { - TRACE("propagating multicast from original node %s: %s -> %s",originalSubmitterAddress.toString().c_str(),upstream.toString().c_str(),propPeers[i]->address().toString().c_str()); + //TRACE("propagating multicast from original node %s: %s -> %s",originalSubmitterAddress.toString().c_str(),upstream.toString().c_str(),propPeers[i]->address().toString().c_str()); // Re-use this packet to re-send multicast frame to everyone // downstream from us. newInitializationVector(); @@ -504,7 +521,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared return true; } else { - TRACE("terminating MULTICAST_FRAME propagation from %s(%s): max depth reached",source().toString().c_str(),_remoteAddress.toString().c_str()); + //TRACE("terminating MULTICAST_FRAME propagation from %s(%s): max depth reached",source().toString().c_str(),_remoteAddress.toString().c_str()); } } else { LOG("rejected MULTICAST_FRAME from %s(%s) due to failed signature check (claims original sender %s)",source().toString().c_str(),_remoteAddress.toString().c_str(),originalSubmitterAddress.toString().c_str()); @@ -529,4 +546,66 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared return true; } +bool PacketDecoder::_doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer) +{ + // TODO: not implemented yet, will be needed for private networks. + + return true; +} + +bool PacketDecoder::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer) +{ + char tmp[128]; + try { + uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); +#ifndef __WINDOWS__ + if (_r->netconfService) { + unsigned int dictLen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN); + std::string dict((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,dictLen),dictLen); + + Dictionary request; + request["type"] = "netconf-request"; + request["peerId"] = peer->identity().toString(false); + sprintf(tmp,"%llx",(unsigned long long)nwid); + request["nwid"] = tmp; + sprintf(tmp,"%llx",(unsigned long long)packetId()); + request["requestId"] = tmp; + _r->netconfService->send(request); + } else { +#endif // !__WINDOWS__ + Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(packetId()); + outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); + outp.append(nwid); + outp.encrypt(peer->cryptKey()); + outp.hmacSet(peer->macKey()); + _r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1); + TRACE("sent ERROR(NETWORK_CONFIG_REQUEST,UNSUPPORTED_OPERATION) to %s(%s)",peer->address().toString().c_str(),_remoteAddress.toString().c_str()); +#ifndef __WINDOWS__ + } +#endif // !__WINDOWS__ + } catch (std::exception &exc) { + TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what()); + } catch ( ... ) { + TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str()); + } + return true; +} + +bool PacketDecoder::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer) +{ + try { + uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REFRESH_IDX_NETWORK_ID); + SharedPtr<Network> nw(_r->nc->network(nwid)); + if ((nw)&&(source() == nw->controller())) // only respond to requests from controller + nw->requestConfiguration(); + } catch (std::exception &exc) { + TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what()); + } catch ( ... ) { + TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str()); + } + return true; +} + } // namespace ZeroTier diff --git a/node/PacketDecoder.hpp b/node/PacketDecoder.hpp index e595d326..51408ba5 100644 --- a/node/PacketDecoder.hpp +++ b/node/PacketDecoder.hpp @@ -122,6 +122,9 @@ private: bool _doFRAME(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer); bool _doMULTICAST_LIKE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer); bool _doMULTICAST_FRAME(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer); + bool _doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer); + bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer); + bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer); uint64_t _receiveTime; Demarc::Port _localPort; diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 5f76c8ac..00d393af 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -29,6 +29,7 @@ #define _ZT_RUNTIMEENVIRONMENT_HPP #include <string> +#include "Constants.hpp" #include "Identity.hpp" #include "Condition.hpp" @@ -42,6 +43,7 @@ class Topology; class SysEnv; class Multicaster; class CMWC4096; +class Service; /** * Holds global state for an instance of ZeroTier::Node @@ -59,29 +61,30 @@ class RuntimeEnvironment { public: RuntimeEnvironment() : - identity(), log((Logger *)0), prng((CMWC4096 *)0), nc((NodeConfig *)0), demarc((Demarc *)0), multicaster((Multicaster *)0), sw((Switch *)0), - topology((Topology *)0) + topology((Topology *)0), + sysEnv((SysEnv *)0) +#ifndef __WINDOWS__ + ,netconfService((Service *)0) +#endif { } std::string homePath; - std::string autoconfUrlPrefix; - std::string configAuthorityIdentityStr; - std::string ownershipVerificationSecret; - std::string ownershipVerificationSecretHash; // base64 of SHA-256 X16 rounds // signal() to prematurely interrupt main loop wait Condition mainLoopWaitCondition; - Identity configAuthority; Identity identity; + // Order matters a bit here. These are constructed in this order + // and then deleted in the opposite order on Node exit. + Logger *log; // may be null CMWC4096 *prng; NodeConfig *nc; @@ -90,6 +93,10 @@ public: Switch *sw; Topology *topology; SysEnv *sysEnv; + +#ifndef __WINDOWS__ + Service *netconfService; // may be null +#endif }; } // namespace ZeroTier diff --git a/node/Service.cpp b/node/Service.cpp new file mode 100644 index 00000000..c614e4e4 --- /dev/null +++ b/node/Service.cpp @@ -0,0 +1,241 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Constants.hpp" + +#ifndef __WINDOWS__ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <time.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/select.h> +#include <sys/wait.h> + +#include "Service.hpp" +#include "RuntimeEnvironment.hpp" +#include "Utils.hpp" +#include "Logger.hpp" + +namespace ZeroTier { + +Service::Service(const RuntimeEnvironment *renv,const char *name,const char *path,void (*handler)(void *,Service &,const Dictionary &),void *arg) : + _r(renv), + _path(path), + _name(name), + _arg(arg), + _handler(handler), + _pid(-1), + _childStdin(0), + _childStdout(0), + _childStderr(0), + _run(true) +{ + start(); +} + +Service::~Service() +{ + _run = false; + long pid = _pid; + if (pid > 0) { + int st = 0; + ::kill(pid,SIGTERM); + for(int i=0;i<20;++i) { + if (waitpid(pid,&st,WNOHANG) == pid) { + pid = 0; + break; + } + Thread::sleep(100); + } + if (pid > 0) { + ::kill(pid,SIGKILL); + waitpid(pid,&st,0); + } + } + join(); +} + +bool Service::send(const Dictionary &msg) +{ + if (_childStdin <= 0) + return false; + + std::string mser = msg.toString(); + if (mser.length() > ZT_SERVICE_MAX_MESSAGE_SIZE) + return false; + + // This can technically block. We'll fix this if it ends up being a + // problem. + uint32_t len = Utils::hton((uint32_t)mser.length()); + if (write(_childStdin,&len,4) != 4) + return false; + if ((int)write(_childStdin,mser.data(),mser.length()) != (int)mser.length()) + return false; + + return true; +} + +void Service::main() + throw() +{ + char buf[131072]; + fd_set readfds,writefds,exceptfds; + struct timeval tv; + + std::string stderrBuf; + std::string stdoutBuf; + unsigned int stdoutExpecting = 0; + + while (_run) { + if (_pid <= 0) { + LOG("launching service %s...",_name.c_str()); + + int in[2],out[2],err[2]; + pipe(in); + pipe(out); + pipe(err); + + long pid = vfork(); + if (pid < 0) { + LOG("service %s terminating: could not fork!",_name.c_str()); + return; + } else if (pid) { + // Parent + close(in[0]); + close(out[1]); + close(err[1]); + Thread::sleep(500); // give child time to start + _childStdin = in[1]; + _childStdout = out[0]; + _childStderr = err[0]; + fcntl(_childStdout,F_SETFL,O_NONBLOCK); + fcntl(_childStderr,F_SETFL,O_NONBLOCK); + _pid = pid; + } else { + // Child + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0],STDIN_FILENO); + dup2(out[1],STDOUT_FILENO); + dup2(err[1],STDERR_FILENO); + execl(_path.c_str(),_path.c_str(),_r->homePath.c_str(),(const char *)0); + exit(-1); + } + } else { + int st = 0; + if (waitpid(_pid,&st,WNOHANG) == _pid) { + if (_childStdin > 0) close(_childStdin); + _childStdin = 0; + if (_childStdout > 0) close(_childStdout); + if (_childStderr > 0) close(_childStderr); + _pid = 0; + + if (!_run) + return; + + LOG("service %s exited with exit code: %d, delaying 1s to attempt relaunch",_name.c_str(),st); + + Thread::sleep(1000); // wait to relaunch + continue; + } + } + + // If we've made it here, _pid is running last we checked. + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + FD_SET(_childStdout,&readfds); + FD_SET(_childStderr,&readfds); + + tv.tv_sec = 1; + tv.tv_usec = 0; + select(std::max(_childStdout,_childStderr)+1,&readfds,&writefds,&exceptfds,&tv); + + if (!_run) { + if (_childStdin > 0) close(_childStdin); + _childStdin = 0; + if (_childStdout > 0) close(_childStdout); + if (_childStderr > 0) close(_childStderr); + return; + } + + if ((_childStderr > 0)&&(FD_ISSET(_childStderr,&readfds))) { + int n = (int)read(_childStderr,buf,sizeof(buf)); + for(int i=0;i<n;++i) { + if ((buf[i] == '\r')||(buf[i] == '\n')) { + stderrBuf = Utils::trim(stderrBuf); + if (stderrBuf.length()) + LOG("service %s: %s",_name.c_str(),stderrBuf.c_str()); + stderrBuf = ""; + } else stderrBuf.push_back(buf[i]); + } + } + + if ((_childStdout > 0)&&(FD_ISSET(_childStdout,&readfds))) { + int n = (int)read(_childStdout,buf,sizeof(buf)); + for(int i=0;i<n;++i) { + stdoutBuf.push_back(buf[i]); + if (stdoutExpecting) { + if (stdoutBuf.length() == stdoutExpecting) { + try { + _handler(_arg,*this,Dictionary(stdoutBuf)); + } catch ( ... ) { + LOG("unexpected exception handling message from service %s",_name.c_str()); + } + stdoutBuf = ""; + stdoutExpecting = 0; + } + } else if (stdoutBuf.length() == 4) { + stdoutExpecting = Utils::ntoh(*((const uint32_t *)stdoutBuf.data())); + stdoutBuf = ""; + if (stdoutExpecting > ZT_SERVICE_MAX_MESSAGE_SIZE) { + LOG("message size overrun from service %s: %u bytes -- restarting service",_name.c_str(),stdoutExpecting); + stdoutExpecting = 0; + kill(_pid,SIGKILL); + break; + } + } + } + } + } +} + +} // namespace ZeroTier + +#endif // __WINDOWS__ + diff --git a/node/Service.hpp b/node/Service.hpp new file mode 100644 index 00000000..c8b729c7 --- /dev/null +++ b/node/Service.hpp @@ -0,0 +1,129 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_SERVICE_HPP +#define _ZT_SERVICE_HPP + +#include <string> +#include <stdexcept> + +#include "Constants.hpp" +#include "Dictionary.hpp" +#include "Thread.hpp" +#include "Mutex.hpp" + +/** + * Maximum size of a service message in bytes (sanity limit) + */ +#define ZT_SERVICE_MAX_MESSAGE_SIZE 131072 + +namespace ZeroTier { + +class RuntimeEnvironment; + +#ifndef __WINDOWS__ +/** + * A subprocess that communicates with the host via a simple protocol + * + * This is currently only supported on *nix systems, and is used to implement + * special plugins that are used by supernodes and network configuration + * master nodes. Users will probably have no use for it. + * + * The simple binary protocol consists of a bidirectional stream of string- + * serialized Dictionaries prefixed by a 32-bit message length. Input + * messages are sent to the subprocess via its stdin, and output is read + * from its stdout. Messages printed by the subprocess on its stderr are + * logged via the standard Logger instance. If the subprocess dies, an + * attempt is made to restart it every second. + */ +class Service : protected Thread +{ +public: + /** + * Create and launch a new service + * + * @param renv Runtime environment + * @param name Name of service + * @param path Path to service binary + * @param handler Handler function to call when service generates output + * @param arg First argument to service + */ + Service(const RuntimeEnvironment *renv, + const char *name, + const char *path, + void (*handler)(void *,Service &,const Dictionary &), + void *arg); + + virtual ~Service(); + + /** + * Send a message to service subprocess + * + * @param msg Message in key/value dictionary form + * @return True if message was sent + */ + bool send(const Dictionary &msg); + + /** + * @return Name of service + */ + inline const char *name() const + throw() + { + return _name.c_str(); + } + + /** + * @return True if subprocess is running + */ + inline bool running() const + throw() + { + return (_pid > 0); + } + +protected: + virtual void main() + throw(); + +private: + const RuntimeEnvironment *_r; + std::string _path; + std::string _name; + void *_arg; + void (*_handler)(void *,Service &,const Dictionary &); + long _pid; + int _childStdin; + int _childStdout; + int _childStderr; + volatile bool _run; +}; +#endif // __WINDOWS__ + +} // namespace ZeroTier + +#endif diff --git a/node/Switch.cpp b/node/Switch.cpp index 5275f5ad..bb10b412 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -124,7 +124,7 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c Packet outpTmpl(propPeers[0]->address(),_r->identity.address(),Packet::VERB_MULTICAST_FRAME); outpTmpl.append((uint8_t)0); outpTmpl.append((uint64_t)network->id()); - outpTmpl.append(_r->identity.address().data(),ZT_ADDRESS_LENGTH); + _r->identity.address().appendTo(outpTmpl); outpTmpl.append(from.data,6); outpTmpl.append(mg.mac().data,6); outpTmpl.append((uint32_t)mg.adi()); @@ -246,7 +246,7 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force) { // tell p1 where to find p2 Packet outp(p1,_r->identity.address(),Packet::VERB_RENDEZVOUS); - outp.append(p2.data(),ZT_ADDRESS_LENGTH); + p2.appendTo(outp); outp.append((uint16_t)cg.first.port()); if (cg.first.isV6()) { outp.append((unsigned char)16); @@ -262,7 +262,7 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force) } { // tell p2 where to find p1 Packet outp(p2,_r->identity.address(),Packet::VERB_RENDEZVOUS); - outp.append(p1.data(),ZT_ADDRESS_LENGTH); + p1.appendTo(outp); outp.append((uint16_t)cg.second.port()); if (cg.second.isV6()) { outp.append((unsigned char)16); @@ -386,7 +386,7 @@ void Switch::announceMulticastGroups(const std::map< SharedPtr<Network>,std::set Packet outp((*p)->address(),_r->identity.address(),Packet::VERB_MULTICAST_LIKE); for(std::map< SharedPtr<Network>,std::set<MulticastGroup> >::const_iterator nwmgs(allMemberships.begin());nwmgs!=allMemberships.end();++nwmgs) { - if ((nwmgs->first->open())||(_r->topology->isSupernode((*p)->address()))||(nwmgs->first->isMember((*p)->address()))) { + if ((_r->topology->isSupernode((*p)->address()))||(nwmgs->first->isAllowed((*p)->address()))) { for(std::set<MulticastGroup>::iterator mg(nwmgs->second.begin());mg!=nwmgs->second.end();++mg) { if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) { send(outp,true); @@ -592,7 +592,7 @@ Address Switch::_sendWhoisRequest(const Address &addr,const Address *peersAlread SharedPtr<Peer> supernode(_r->topology->getBestSupernode(peersAlreadyConsulted,numPeersAlreadyConsulted,false)); if (supernode) { Packet outp(supernode->address(),_r->identity.address(),Packet::VERB_WHOIS); - outp.append(addr.data(),ZT_ADDRESS_LENGTH); + addr.appendTo(outp); outp.encrypt(supernode->cryptKey()); outp.hmacSet(supernode->macKey()); diff --git a/node/Thread.cpp b/node/Thread.cpp index 37a1a5a5..f650f6fc 100644 --- a/node/Thread.cpp +++ b/node/Thread.cpp @@ -47,7 +47,6 @@ static void *__m_thread_main(void *ptr) namespace ZeroTier {
Thread::Thread() :
- suicidalThread(false),
_impl(malloc(sizeof(pthread_t))),
_running()
{
@@ -76,7 +75,7 @@ void Thread::join() void Thread::sleep(unsigned long ms)
{
- usleep(ms);
+ usleep(ms * 1000);
}
void Thread::__intl_run()
@@ -84,10 +83,6 @@ void Thread::__intl_run() for(;;) {
_notInit = false;
this->main();
- if (suicidalThread) {
- delete this;
- return;
- }
if (_notInit) // UGLY ASS HACK: see main()
usleep(50);
else break;
@@ -127,7 +122,6 @@ struct __m_thread_info namespace ZeroTier {
Thread::Thread() :
- suicidalThread(false),
_impl(malloc(sizeof(__m_thread_info))),
_running()
{
@@ -162,10 +156,6 @@ void Thread::__intl_run() for(;;) {
_notInit = false;
this->main();
- if (suicidalThread) {
- delete this;
- return;
- }
if (_notInit)
Thread::sleep(50);
else break;
diff --git a/node/Thread.hpp b/node/Thread.hpp index b023fbae..9b399a00 100644 --- a/node/Thread.hpp +++ b/node/Thread.hpp @@ -78,11 +78,6 @@ protected: virtual void main()
throw();
- /**
- * Subclasses can set to true to cause Thread to delete itself on exit
- */
- volatile bool suicidalThread;
-
private:
void *_impl;
AtomicCounter _running;
diff --git a/node/Topology.cpp b/node/Topology.cpp index e627e767..8487684e 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -39,7 +39,8 @@ namespace ZeroTier { Topology::Topology(const RuntimeEnvironment *renv,const char *dbpath) throw(std::runtime_error) : Thread(), - _r(renv) + _r(renv), + _amSupernode(false) { if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWCREAT,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE)) { if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWREPLACE,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE)) @@ -77,9 +78,11 @@ Topology::~Topology() void Topology::setSupernodes(const std::map< Identity,std::vector<InetAddress> > &sn) { Mutex::Lock _l(_supernodes_m); + _supernodes = sn; _supernodeAddresses.clear(); _supernodePeers.clear(); + for(std::map< Identity,std::vector<InetAddress> >::const_iterator i(sn.begin());i!=sn.end();++i) { if (i->first != _r->identity) { SharedPtr<Peer> p(getPeer(i->first.address())); @@ -93,6 +96,8 @@ void Topology::setSupernodes(const std::map< Identity,std::vector<InetAddress> > } _supernodeAddresses.insert(i->first.address()); } + + _amSupernode = (_supernodes.find(_r->identity) != _supernodes.end()); } void Topology::addPeer(const SharedPtr<Peer> &candidate,void (*callback)(void *,const SharedPtr<Peer> &,Topology::PeerVerifyResult),void *arg) @@ -127,9 +132,12 @@ SharedPtr<Peer> Topology::getPeer(const Address &zta) return ap->second; } + unsigned char ztatmp[ZT_ADDRESS_LENGTH]; + zta.copyTo(ztatmp,ZT_ADDRESS_LENGTH); + Buffer<ZT_KISSDB_VALUE_SIZE> b(ZT_KISSDB_VALUE_SIZE); _dbm_m.lock(); - if (!KISSDB_get(&_dbm,zta.data(),b.data())) { + if (!KISSDB_get(&_dbm,ztatmp,b.data())) { _dbm_m.unlock(); SharedPtr<Peer> p(new Peer()); @@ -300,11 +308,13 @@ void Topology::main() for(std::map< Address,SharedPtr<Peer> >::iterator p(_activePeers.begin());p!=_activePeers.end();++p) { if (p->second->getAndResetDirty()) { try { + uint64_t atmp[ZT_ADDRESS_LENGTH]; + p->second->identity().address().copyTo(atmp,ZT_ADDRESS_LENGTH); Buffer<ZT_PEER_MAX_SERIALIZED_LENGTH> b; p->second->serialize(b); b.zeroUnused(); _dbm_m.lock(); - if (KISSDB_put(&_dbm,p->second->identity().address().data(),b.data())) { + if (KISSDB_put(&_dbm,atmp,b.data())) { TRACE("error writing %s to peer.db",p->second->identity().address().toString().c_str()); } _dbm_m.unlock(); @@ -329,11 +339,13 @@ void Topology::_reallyAddPeer(const SharedPtr<Peer> &p) _activePeers[p->identity().address()] = p; } try { + uint64_t atmp[ZT_ADDRESS_LENGTH]; + p->address().copyTo(atmp,ZT_ADDRESS_LENGTH); Buffer<ZT_PEER_MAX_SERIALIZED_LENGTH> b; p->serialize(b); b.zeroUnused(); _dbm_m.lock(); - if (KISSDB_put(&_dbm,p->identity().address().data(),b.data())) { + if (KISSDB_put(&_dbm,atmp,b.data())) { TRACE("error writing %s to peerdb",p->address().toString().c_str()); } else p->getAndResetDirty(); _dbm_m.unlock(); diff --git a/node/Topology.hpp b/node/Topology.hpp index ae1a15b4..104ad344 100644 --- a/node/Topology.hpp +++ b/node/Topology.hpp @@ -163,6 +163,11 @@ public: } /** + * @return True if this node's identity is in the supernode set + */ + inline bool amSupernode() const { return _amSupernode; } + + /** * Clean and flush database now (runs in the background) */ void clean(); @@ -271,35 +276,6 @@ public: std::vector< SharedPtr<Peer> > &_v; }; - /** - * Dump peer I/O statistics to an open FILE (for status reporting and debug) - */ - class DumpPeerStatistics - { - public: - DumpPeerStatistics(FILE *out) : - _out(out), - _now(Utils::now()) - { - fprintf(_out,"Peer Direct IPv4 Direct IPv6 Latency(ms)"ZT_EOL_S); - } - - inline void operator()(Topology &t,const SharedPtr<Peer> &p) - { - InetAddress v4(p->ipv4ActivePath(_now)); - InetAddress v6(p->ipv6ActivePath(_now)); - fprintf(_out,"%-10s %-21s %-51s %u"ZT_EOL_S, - p->address().toString().c_str(), - ((v4) ? v4.toString().c_str() : "(none)"), - ((v6) ? v6.toString().c_str() : "(none)"), - p->latency()); - } - - private: - FILE *_out; - uint64_t _now; - }; - protected: virtual void main() throw(); @@ -334,6 +310,9 @@ private: std::vector< SharedPtr<Peer> > _supernodePeers; Mutex _supernodes_m; + // Set to true if my identity is in _supernodes + volatile bool _amSupernode; + KISSDB _dbm; Mutex _dbm_m; }; diff --git a/node/UdpSocket.cpp b/node/UdpSocket.cpp index 95156fcc..6178d16e 100644 --- a/node/UdpSocket.cpp +++ b/node/UdpSocket.cpp @@ -49,6 +49,7 @@ namespace ZeroTier { UdpSocket::UdpSocket( + bool localOnly, int localPort, bool ipv6, void (*packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int), @@ -87,7 +88,9 @@ UdpSocket::UdpSocket( memset(&sin6,0,sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(localPort); - memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr)); + if (localOnly) + memcpy(&(sin6.sin6_addr.s6_addr),InetAddress::LO6.rawIpData(),16); + else memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr)); if (::bind(_sock,(const struct sockaddr *)&sin6,sizeof(sin6))) { ::close(_sock); throw std::runtime_error("unable to bind to port"); @@ -109,7 +112,9 @@ UdpSocket::UdpSocket( memset(&sin,0,sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(localPort); - sin.sin_addr.s_addr = INADDR_ANY; + if (localOnly) + memcpy(&(sin.sin_addr.s_addr),InetAddress::LO4.rawIpData(),4); + else sin.sin_addr.s_addr = INADDR_ANY; if (::bind(_sock,(const struct sockaddr *)&sin,sizeof(sin))) { ::close(_sock); throw std::runtime_error("unable to bind to port"); diff --git a/node/UdpSocket.hpp b/node/UdpSocket.hpp index be407d56..a3223f1f 100644 --- a/node/UdpSocket.hpp +++ b/node/UdpSocket.hpp @@ -46,6 +46,7 @@ public: /** * Create and bind a local UDP socket * + * @param localOnly If true, bind to loopback address only * @param localPort Local port to listen to * @param ipv6 If true, bind this as an IPv6 socket, otherwise IPv4 * @param packetHandler Function to call when packets are read @@ -53,6 +54,7 @@ public: * @throws std::runtime_error Unable to bind */ UdpSocket( + bool localOnly, int localPort, bool ipv6, void (*packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int), diff --git a/node/Utils.cpp b/node/Utils.cpp index b9db07b9..7a4d51ba 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -28,14 +28,14 @@ #include <stdio.h> #include <string.h> #include <stdlib.h> -#include "Utils.hpp" -#include "Mutex.hpp" +#include <stdarg.h> #if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> +#include <dirent.h> #endif #ifdef _WIN32 @@ -45,6 +45,9 @@ #include <sys/stat.h> #include <openssl/rand.h> +#include "Utils.hpp" +#include "Mutex.hpp" + namespace ZeroTier { const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; @@ -213,6 +216,29 @@ const char Utils::base64DecMap[128] = { static const char *DAY_NAMES[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static const char *MONTH_NAMES[12] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; +std::map<std::string,bool> Utils::listDirectory(const char *path) +{ + struct dirent de; + struct dirent *dptr; + std::map<std::string,bool> r; + + DIR *d = opendir(path); + if (!d) + return r; + + dptr = (struct dirent *)0; + for(;;) { + if (readdir_r(d,&de,&dptr)) + break; + if (dptr) { + if ((!strcmp(dptr->d_name,"."))&&(!strcmp(dptr->d_name,".."))) + r[std::string(dptr->d_name)] = (dptr->d_type == DT_DIR); + } else break; + } + + return r; +} + std::string Utils::base64Encode(const void *data,unsigned int len) { if (!len) @@ -530,4 +556,20 @@ std::string Utils::trim(const std::string &s) return s.substr(start,end - start); } +void Utils::stdsprintf(std::string &s,const char *fmt,...) + throw(std::bad_alloc,std::length_error) +{ + char buf[65536]; + va_list ap; + + va_start(ap,fmt); + int n = vsnprintf(buf,sizeof(buf),fmt,ap); + va_end(ap); + + if ((n >= (int)sizeof(buf))||(n < 0)) + throw std::length_error("printf result too large"); + + s.append(buf); +} + } // namespace ZeroTier diff --git a/node/Utils.hpp b/node/Utils.hpp index e6bef5e5..a222ca4f 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -34,13 +34,14 @@ #include <time.h> #include <sys/time.h> #include <arpa/inet.h> + #include <string> #include <stdexcept> #include <vector> +#include <map> #include "../ext/lz4/lz4.h" #include "../ext/lz4/lz4hc.h" -#include "../ext/huffandpuff/huffman.h" #include "Constants.hpp" @@ -58,6 +59,16 @@ class Utils { public: /** + * List a directory's contents + * + * @param path Path to list + * @param files Set to fill with files + * @param directories Set to fill with directories + * @return Map of entries and whether or not they are also directories (empty on failure) + */ + static std::map<std::string,bool> listDirectory(const char *path); + + /** * @param data Data to convert to hex * @param len Length of data * @return Hexadecimal string @@ -109,6 +120,15 @@ public: static uint64_t getLastModified(const char *path); /** + * @param path Path to check + * @return True if file or directory exists at path location + */ + static inline bool fileExists(const char *path) + { + return (getLastModified(path) != 0); + } + + /** * @param t64 Time in ms since epoch * @return RFC1123 date string */ @@ -164,7 +184,6 @@ public: template<typename I,typename O> static inline void compress(I begin,I end,O out) { - char huffheap[HUFFHEAP_SIZE]; unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE); char *buf = new char[bufLen * 2]; char *buf2 = buf + bufLen; @@ -198,16 +217,9 @@ public: continue; } - unsigned long huffCompressedLen = huffman_compress((const unsigned char *)buf2,lz4CompressedLen,(unsigned char *)buf,bufLen,huffheap); - if ((!huffCompressedLen)||((int)huffCompressedLen >= lz4CompressedLen)) { - l = hton((uint32_t)lz4CompressedLen); // lz4 only - out((const void *)&l,4); - out((const void *)buf2,(unsigned int)lz4CompressedLen); - } else { - l = hton((uint32_t)0x80000000 | (uint32_t)huffCompressedLen); // lz4 with huffman - out((const void *)&l,4); - out((const void *)buf,(unsigned int)huffCompressedLen); - } + l = hton((uint32_t)lz4CompressedLen); // lz4 only + out((const void *)&l,4); + out((const void *)buf2,(unsigned int)lz4CompressedLen); } delete [] buf; @@ -230,7 +242,6 @@ public: template<typename I,typename O> static inline bool decompress(I begin,I end,O out) { - char huffheap[HUFFHEAP_SIZE]; volatile char i32c[4]; void *const i32cp = (void *)i32c; unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE); @@ -267,23 +278,10 @@ public: return false; } - if ((_compressedSize & 0x80000000)) { // lz4 and huffman - unsigned long lz4CompressedSize = huffman_decompress((const unsigned char *)buf,compressedSize,(unsigned char *)buf2,bufLen,huffheap); - if (lz4CompressedSize) { - if (LZ4_uncompress_unknownOutputSize(buf2,buf,lz4CompressedSize,bufLen) != (int)originalSize) { - delete [] buf; - return false; - } else out((const void *)buf,(unsigned int)originalSize); - } else { - delete [] buf; - return false; - } - } else { // lz4 only - if (LZ4_uncompress_unknownOutputSize(buf,buf2,compressedSize,bufLen) != (int)originalSize) { - delete [] buf; - return false; - } else out((const void *)buf2,(unsigned int)originalSize); - } + if (LZ4_uncompress_unknownOutputSize(buf,buf2,compressedSize,bufLen) != (int)originalSize) { + delete [] buf; + return false; + } else out((const void *)buf2,(unsigned int)originalSize); } else { // stored if (originalSize > bufLen) { delete [] buf; @@ -392,6 +390,18 @@ public: static std::string trim(const std::string &s); /** + * Like sprintf, but appends to std::string + * + * @param s String to append to + * @param fmt Printf format string + * @param ... Format arguments + * @throws std::bad_alloc Memory allocation failure + * @throws std::length_error Format + args exceeds internal buffer maximum + */ + static void stdsprintf(std::string &s,const char *fmt,...) + throw(std::bad_alloc,std::length_error); + + /** * Count the number of bits set in an integer * * @param v 32-bit integer @@ -1,9 +1,4 @@ OBJS=\ - ext/http-parser/http_parser.o \ - ext/huffandpuff/huffman.o \ - ext/jsoncpp/src/json_reader.o \ - ext/jsoncpp/src/json_value.o \ - ext/jsoncpp/src/json_writer.o \ ext/kissdb/kissdb.o \ ext/lz4/lz4hc.o \ ext/lz4/lz4.o \ @@ -13,7 +8,6 @@ OBJS=\ node/EthernetTap.o \ node/Filter.o \ node/HMAC.o \ - node/Http.o \ node/Identity.o \ node/InetAddress.o \ node/Logger.o \ @@ -22,9 +16,9 @@ OBJS=\ node/NodeConfig.o \ node/Packet.o \ node/PacketDecoder.o \ - node/Pack.o \ node/Peer.o \ node/Salsa20.o \ + node/Service.o \ node/Switch.o \ node/SysEnv.o \ node/Thread.o \ diff --git a/packtool.cpp b/packtool.cpp deleted file mode 100644 index 6726c8d7..00000000 --- a/packtool.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* - * ZeroTier One - Global Peer to Peer Ethernet - * Copyright (C) 2012-2013 ZeroTier Networks LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <iostream> -#include <string> - -#include "node/Identity.hpp" -#include "node/Pack.hpp" -#include "node/Utils.hpp" - -#include <unistd.h> - -using namespace ZeroTier; - -static void printHelp(const char *pn) -{ - std::cout << "Usage: " << pn << " <command> [<args>]" << std::endl << std::endl; - std::cout << "Commands:" << std::endl; - std::cout << " list <packfile> [<identity.secret/public>]" << std::endl; - std::cout << " create <packfile> <identity.secret> <file> [<file> ...]" << std::endl; - std::cout << " extract <packfile> <destination directory>" << std::endl; - std::cout << std::endl << "To check signatures, use 'list' with an identity argument." << std::endl; -} - -static Pack *readPack(const char *path) -{ - std::string tmp; - if (!Utils::readFile(path,tmp)) - return (Pack *)0; - Pack *p = new Pack(); - if (!p->deserialize(tmp)) { - delete p; - return (Pack *)0; - } - return p; -} - -int main(int argc,char **argv) -{ - if (argc < 2) { - printHelp(argv[0]); - return -1; - } - - if (!strcmp(argv[1],"list")) { - if (argc < 3) { - printHelp(argv[0]); - return -1; - } - - Pack *pack = readPack(argv[2]); - if (!pack) { - std::cout << "Could not read " << argv[2] << std::endl; - return -1; - } - - std::vector<const Pack::Entry *> entries = pack->getAll(); - for(std::vector<const Pack::Entry *>::iterator e=entries.begin();e!=entries.end();++e) { - std::cout << (*e)->name << '\t' << (*e)->content.length() << '\t' << Utils::hex((*e)->sha256,32) << "\tsigned by: " << (*e)->signedBy.toString() << std::endl; - } - - if (argc >= 4) { - std::string idser; - if (!Utils::readFile(argv[3],idser)) { - std::cout << "Unable to read identity from " << argv[3] << std::endl; - return -1; - } - Identity id; - if (!id.fromString(idser)) { - std::cout << "Invalid identity" << std::endl; - return -1; - } - - entries = pack->verifyAll(id,true); - for(std::vector<const Pack::Entry *>::iterator e=entries.begin();e!=entries.end();++e) { - std::cout << "!!! Signature verification FAILED for: " << (*e)->name << std::endl; - } - if (!entries.size()) - std::cout << "Signature for all entries verified OK" << std::endl; - } - - delete pack; - } else if (!strcmp(argv[1],"create")) { - if (argc < 5) { - printHelp(argv[0]); - return -1; - } - - std::string idser; - if (!Utils::readFile(argv[3],idser)) { - std::cout << "Unable to read identity from " << argv[3] << std::endl; - return -1; - } - Identity id; - if (!id.fromString(idser)) { - std::cout << "Invalid identity" << std::endl; - return -1; - } - - if (!id.hasPrivate()) { - std::cout << "Identity must include private key to sign" << std::endl; - return -1; - } - - Pack pack; - for(int i=4;i<argc;++i) { - std::string fdata; - if (!Utils::readFile(argv[i],fdata)) { - std::cout << "Unable to read " << argv[i] << std::endl; - return -1; - } - pack.put(std::string(argv[i]),fdata); - std::cout << "Added " << argv[i] << std::endl; - } - if (!pack.signAll(id)) { - std::cout << "Unable to sign with identity" << std::endl; - return -1; - } else std::cout << "Signed all entries with identity " << id.address().toString() << std::endl; - std::string packser = pack.serialize(); - - if (!Utils::writeFile(argv[2],packser)) { - std::cout << "Unable to write " << argv[2] << std::endl; - return -1; - } - std::cout << "Wrote " << packser.length() << " bytes to " << argv[2] << std::endl; - } else if (!strcmp(argv[1],"extract")) { - if (argc < 4) { - printHelp(argv[0]); - return -1; - } - - Pack *pack = readPack(argv[2]); - if (!pack) { - std::cout << "Could not read " << argv[2] << std::endl; - return -1; - } - - if (chdir(argv[3])) { - std::cout << "Unable to change to " << argv[3] << " for output." << std::endl; - return -1; - } - - std::vector<const Pack::Entry *> entries = pack->getAll(); - for(std::vector<const Pack::Entry *>::iterator e=entries.begin();e!=entries.end();++e) { - if (!Utils::writeFile((*e)->name.c_str(),(*e)->content)) - std::cout << "Error writing " << (*e)->name << std::endl; - else std::cout << "Wrote " << (*e)->name << " (" << (*e)->content.length() << ")" << std::endl; - } - } else { - printHelp(argv[0]); - return -1; - } - - return 0; -} diff --git a/selftest.cpp b/selftest.cpp index d5a2bc45..edbdcaf1 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -43,8 +43,9 @@ #include "node/HMAC.hpp" #include "node/MAC.hpp" #include "node/Peer.hpp" -#include "node/Http.hpp" #include "node/Condition.hpp" +#include "node/NodeConfig.hpp" +#include "node/Dictionary.hpp" using namespace ZeroTier; @@ -266,31 +267,60 @@ static int testOther() } std::cout << "PASS" << std::endl; - return 0; -} - -static Condition testHttpDoneCondition; - -static bool testHttpHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body) -{ - if (code) - std::cout << "[net] " << url << " " << code << " bytes: " << body.length() << std::endl; - else std::cout << "[net] " << url << " FAILED: " << body << std::endl; - testHttpDoneCondition.signal(); - return false; -} + std::cout << "[other] Testing command bus encode/decode... "; std::cout.flush(); + try { + static char key[32] = { 0 }; + for(unsigned int k=0;k<1000;++k) { + std::vector<std::string> original; + for(unsigned int i=0,j=rand() % 256,l=(rand() % 1024)+1;i<j;++i) + original.push_back(std::string(l,'x')); + std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets(NodeConfig::encodeControlMessage(key,1,original)); + //std::cout << packets.size() << ' '; std::cout.flush(); + std::vector<std::string> after; + for(std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> >::iterator i(packets.begin());i!=packets.end();++i) { + unsigned long convId = 9999; + if (!NodeConfig::decodeControlMessagePacket(key,i->data(),i->size(),convId,after)) { + std::cout << "FAIL (decode)" << std::endl; + return -1; + } + if (convId != 1) { + std::cout << "FAIL (conversation ID)" << std::endl; + return -1; + } + } + if (after != original) { + std::cout << "FAIL (compare)" << std::endl; + return -1; + } + } + } catch (std::exception &exc) { + std::cout << "FAIL (" << exc.what() << ")" << std::endl; + return -1; + } + std::cout << "PASS" << std::endl; -static int testNet() -{ - std::cout << "[net] GET http://www.uc.edu/" << std::endl; - new Http::Request(Http::HTTP_METHOD_GET,"http://www.uc.edu/",Http::EMPTY_HEADERS,std::string(),&testHttpHandler,(void *)0); - testHttpDoneCondition.wait(); - std::cout << "[net] GET http://zerotier.com/" << std::endl; - new Http::Request(Http::HTTP_METHOD_GET,"http://zerotier.com/",Http::EMPTY_HEADERS,std::string(),&testHttpHandler,(void *)0); - testHttpDoneCondition.wait(); - std::cout << "[net] GET http://www.google.com/" << std::endl; - new Http::Request(Http::HTTP_METHOD_GET,"http://www.google.com/",Http::EMPTY_HEADERS,std::string(),&testHttpHandler,(void *)0); - testHttpDoneCondition.wait(); + std::cout << "[other] Testing Dictionary... "; std::cout.flush(); + for(int k=0;k<10000;++k) { + Dictionary a,b; + int nk = rand() % 32; + for(int q=0;q<nk;++q) { + std::string k,v; + int kl = (rand() % 512); + int vl = (rand() % 512); + for(int i=0;i<kl;++i) + k.push_back((char)rand()); + for(int i=0;i<vl;++i) + v.push_back((char)rand()); + a[k] = v; + } + std::string aser = a.toString(); + b.fromString(aser); + if (a != b) { + std::cout << "FAIL!" << std::endl; + return -1; + } + } + std::cout << "PASS" << std::endl; return 0; } @@ -301,7 +331,6 @@ int main(int argc,char **argv) srand(time(0)); - r |= testNet(); r |= testCrypto(); r |= testPacket(); r |= testOther(); |