From 0f2c765f45dc068825d1f320d58bcb8e091e7029 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Tue, 5 Feb 2019 23:04:45 -0800 Subject: [PATCH 01/16] Update formatting in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cf002213..938041ff 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,7 @@ A ping message can be sent to the server, with an optional data string. ``` websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long"); +``` ### Heartbeat. From 3f8027b65c55ace0e735c599c4a2c97209a5f3cf Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Sat, 16 Feb 2019 10:31:55 -0800 Subject: [PATCH 02/16] unittest for sending large messages --- ixwebsocket/IXSocketServer.cpp | 4 +-- makefile | 2 +- test/cmd_websocket_chat.cpp | 62 +++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/ixwebsocket/IXSocketServer.cpp b/ixwebsocket/IXSocketServer.cpp index 56012e88..de67c052 100644 --- a/ixwebsocket/IXSocketServer.cpp +++ b/ixwebsocket/IXSocketServer.cpp @@ -42,13 +42,13 @@ namespace ix void SocketServer::logError(const std::string& str) { std::lock_guard lock(_logMutex); - std::cerr << str << std::endl; + fprintf(stderr, "%s\n", str.c_str()); } void SocketServer::logInfo(const std::string& str) { std::lock_guard lock(_logMutex); - std::cout << str << std::endl; + fprintf(stderr, "%s\n", str.c_str()); } std::pair SocketServer::listen() diff --git a/makefile b/makefile index 04e384f8..3f1df944 100644 --- a/makefile +++ b/makefile @@ -24,7 +24,7 @@ test_server: (cd test && npm i ws && node broadcast-server.js) # env TEST=Websocket_server make test -# env TEST=websocket_server make test +# env TEST=Websocket_chat make test # env TEST=heartbeat make test test: python test/run.py diff --git a/test/cmd_websocket_chat.cpp b/test/cmd_websocket_chat.cpp index 5a91cde2..7b3e9d59 100644 --- a/test/cmd_websocket_chat.cpp +++ b/test/cmd_websocket_chat.cpp @@ -11,7 +11,8 @@ #include #include -#include +#include +#include #include #include #include "msgpack11.hpp" @@ -39,9 +40,11 @@ namespace void sendMessage(const std::string& text); size_t getReceivedMessagesCount() const; + const std::vector& getReceivedMessages() const; std::string encodeMessage(const std::string& text); std::pair decodeMessage(const std::string& str); + void appendMessage(const std::string& message); private: std::string _user; @@ -50,7 +53,8 @@ namespace ix::WebSocket _webSocket; - std::queue _receivedQueue; + std::vector _receivedMessages; + mutable std::mutex _mutex; }; WebSocketChat::WebSocketChat(const std::string& user, @@ -65,7 +69,20 @@ namespace size_t WebSocketChat::getReceivedMessagesCount() const { - return _receivedQueue.size(); + std::lock_guard lock(_mutex); + return _receivedMessages.size(); + } + + const std::vector& WebSocketChat::getReceivedMessages() const + { + std::lock_guard lock(_mutex); + return _receivedMessages; + } + + void WebSocketChat::appendMessage(const std::string& message) + { + std::lock_guard lock(_mutex); + _receivedMessages.push_back(message); } bool WebSocketChat::isReady() const @@ -85,7 +102,8 @@ namespace std::stringstream ss; ss << "ws://localhost:" << _port - << "/"; + << "/" + << _user; url = ss.str(); } @@ -127,10 +145,16 @@ namespace // as we do for the satori chat example. // store text - _receivedQueue.push(result.second); + appendMessage(result.second); + + std::string payload = result.second; + if (payload.size() > 2000) + { + payload = ""; + } ss << std::endl - << result.first << " > " << result.second + << result.first << " > " << payload << std::endl << _user << " > "; log(ss.str()); @@ -269,15 +293,35 @@ TEST_CASE("Websocket_chat", "[websocket_chat]") chatB.sendMessage("from B1"); chatB.sendMessage("from B2"); - // Give us 1s for all messages to be received - ix::msleep(1000); + // FIXME: cannot handle large message, we need to break them down + // into small one at the websocket layer (using CONTINUATION opcode) + size_t size = 512 * 1000; // 512K is OK, larger is not !! + std::string bigMessage(size, 'a'); + chatB.sendMessage(bigMessage); + + // Wait until all messages are received. 10s timeout + int attempts = 0; + while (chatA.getReceivedMessagesCount() != 3 || + chatB.getReceivedMessagesCount() != 3) + { + REQUIRE(attempts++ < 10); + ix::msleep(1000); + } chatA.stop(); chatB.stop(); - REQUIRE(chatA.getReceivedMessagesCount() == 2); + REQUIRE(chatA.getReceivedMessagesCount() == 3); REQUIRE(chatB.getReceivedMessagesCount() == 3); + REQUIRE(chatB.getReceivedMessages()[0] == "from A1"); + REQUIRE(chatB.getReceivedMessages()[1] == "from A2"); + REQUIRE(chatB.getReceivedMessages()[2] == "from A3"); + + REQUIRE(chatA.getReceivedMessages()[0] == "from B1"); + REQUIRE(chatA.getReceivedMessages()[1] == "from B2"); + REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size()); + // Give us 500ms for the server to notice that clients went away ix::msleep(500); REQUIRE(server.getClients().size() == 0); From 505dd6d50f3282124435809fefb2753d64f072b7 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Sat, 16 Feb 2019 10:33:37 -0800 Subject: [PATCH 03/16] document bug --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 938041ff..b671e052 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ If the remote end (server) breaks the connection, the code will try to perpetual ## Limitations +* Sending large messages are not supported yet (see feature/send_large_message). This is a bug and will be fixed. * There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well. * Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that. * The server code is using select to detect incoming data, and creates one OS thread per connection. This isn't as scalable as strategies using epoll or kqueue. From 3a77e96a0506500aba806bdf61a70371e0b85549 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 20 Feb 2019 18:59:07 -0800 Subject: [PATCH 04/16] Feature/send large message (#14) * introduce send fragment * can pass a fin frame * can send messages which are a perfect multiple of the chunk size * set fin only for last fragment * cleanup * last fragment should be of type CONTINUATION * Add simple send and receive programs * speedups receiving + better way to wait for thing * receive speedup by using linked list of chunks instead of large array * document bug * use chunks to receive data * trailing spaces --- CMakeLists.txt | 1 + examples/chat/cmd_websocket_chat.cpp | 2 +- .../cobra_publisher/IXCobraConnection.cpp | 22 +- examples/cobra_publisher/IXCobraConnection.h | 10 +- .../cobra_publisher/ixcrypto/IXBase64.cpp | 91 +- examples/cobra_publisher/ixcrypto/IXBase64.h | 1 + examples/cobra_publisher/ixcrypto/IXHash.cpp | 22 + examples/cobra_publisher/ixcrypto/IXHash.h | 15 + examples/cobra_publisher/ixcrypto/IXUuid.cpp | 75 + examples/cobra_publisher/ixcrypto/IXUuid.h | 17 + .../jsoncpp/json/json-forwards.h | 14 +- examples/cobra_publisher/jsoncpp/json/json.h | 16 +- examples/cobra_publisher/jsoncpp/jsoncpp.cpp | 22 +- examples/ws_receive/.gitignore | 1 + examples/ws_receive/CMakeLists.txt | 30 + examples/ws_receive/README.md | 1 + examples/ws_receive/ixcrypto | 1 + .../ws_receive/jsoncpp/json/json-forwards.h | 333 + examples/ws_receive/jsoncpp/json/json.h | 2186 +++++++ examples/ws_receive/jsoncpp/jsoncpp.cpp | 5386 +++++++++++++++++ examples/ws_receive/package-lock.json | 29 + examples/ws_receive/ws_receive.cpp | 153 + examples/ws_receive/ws_receive.js | 43 + examples/ws_send/.gitignore | 1 + examples/ws_send/CMakeLists.txt | 31 + examples/ws_send/README.md | 1 + examples/ws_send/ixcrypto | 1 + examples/ws_send/jsoncpp | 1 + examples/ws_send/ws_send.cpp | 306 + ixwebsocket/IXCancellationRequest.cpp | 4 +- ixwebsocket/IXCancellationRequest.h | 2 +- ixwebsocket/IXDNSLookup.cpp | 12 +- ixwebsocket/IXDNSLookup.h | 6 +- ixwebsocket/IXEventFd.cpp | 8 +- ixwebsocket/IXEventFd.h | 2 +- ixwebsocket/IXProgressCallback.h | 14 + ixwebsocket/IXSocket.cpp | 33 +- ixwebsocket/IXSocket.h | 4 +- ixwebsocket/IXSocketAppleSSL.cpp | 14 +- ixwebsocket/IXSocketAppleSSL.h | 6 +- ixwebsocket/IXSocketConnect.cpp | 8 +- ixwebsocket/IXSocketConnect.h | 2 +- ixwebsocket/IXSocketOpenSSL.cpp | 18 +- ixwebsocket/IXSocketOpenSSL.h | 6 +- ixwebsocket/IXSocketSChannel.cpp | 8 +- ixwebsocket/IXSocketSChannel.h | 6 +- ixwebsocket/IXSocketServer.cpp | 10 +- ixwebsocket/IXSocketServer.h | 2 +- ixwebsocket/IXWebSocket.cpp | 27 +- ixwebsocket/IXWebSocket.h | 12 +- ixwebsocket/IXWebSocketErrorInfo.h | 2 +- ixwebsocket/IXWebSocketHandshake.cpp | 14 +- ixwebsocket/IXWebSocketHandshake.h | 2 +- ixwebsocket/IXWebSocketHttpHeaders.h | 2 +- ixwebsocket/IXWebSocketPerMessageDeflate.cpp | 6 +- ixwebsocket/IXWebSocketPerMessageDeflate.h | 2 +- .../IXWebSocketPerMessageDeflateCodec.cpp | 18 +- .../IXWebSocketPerMessageDeflateCodec.h | 2 +- .../IXWebSocketPerMessageDeflateOptions.cpp | 12 +- .../IXWebSocketPerMessageDeflateOptions.h | 2 +- ixwebsocket/IXWebSocketSendInfo.h | 2 +- ixwebsocket/IXWebSocketServer.cpp | 4 +- ixwebsocket/IXWebSocketServer.h | 2 +- ixwebsocket/IXWebSocketTransport.cpp | 210 +- ixwebsocket/IXWebSocketTransport.h | 46 +- makefile | 4 + test/IXSocketTest.cpp | 2 +- test/IXTest.cpp | 4 +- test/IXWebSocketHeartBeatTest.cpp | 6 +- test/cmd_websocket_chat.cpp | 15 +- test/run.py | 2 +- test/test_runner.cpp | 2 +- 72 files changed, 9117 insertions(+), 260 deletions(-) create mode 100644 examples/cobra_publisher/ixcrypto/IXHash.cpp create mode 100644 examples/cobra_publisher/ixcrypto/IXHash.h create mode 100644 examples/cobra_publisher/ixcrypto/IXUuid.cpp create mode 100644 examples/cobra_publisher/ixcrypto/IXUuid.h create mode 100644 examples/ws_receive/.gitignore create mode 100644 examples/ws_receive/CMakeLists.txt create mode 100644 examples/ws_receive/README.md create mode 120000 examples/ws_receive/ixcrypto create mode 100644 examples/ws_receive/jsoncpp/json/json-forwards.h create mode 100644 examples/ws_receive/jsoncpp/json/json.h create mode 100644 examples/ws_receive/jsoncpp/jsoncpp.cpp create mode 100644 examples/ws_receive/package-lock.json create mode 100644 examples/ws_receive/ws_receive.cpp create mode 100644 examples/ws_receive/ws_receive.js create mode 100644 examples/ws_send/.gitignore create mode 100644 examples/ws_send/CMakeLists.txt create mode 100644 examples/ws_send/README.md create mode 120000 examples/ws_send/ixcrypto create mode 120000 examples/ws_send/jsoncpp create mode 100644 examples/ws_send/ws_send.cpp create mode 100644 ixwebsocket/IXProgressCallback.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 62c7bc08..8c85d9ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXSetThreadName.h ixwebsocket/IXDNSLookup.h ixwebsocket/IXCancellationRequest.h + ixwebsocket/IXProgressCallback.h ixwebsocket/IXWebSocket.h ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketTransport.h diff --git a/examples/chat/cmd_websocket_chat.cpp b/examples/chat/cmd_websocket_chat.cpp index 11cb340f..8af42141 100644 --- a/examples/chat/cmd_websocket_chat.cpp +++ b/examples/chat/cmd_websocket_chat.cpp @@ -115,7 +115,7 @@ namespace // store text _receivedQueue.push(result.second); - ss << std::endl + ss << std::endl << result.first << " > " << result.second << std::endl << _user << " > "; diff --git a/examples/cobra_publisher/IXCobraConnection.cpp b/examples/cobra_publisher/IXCobraConnection.cpp index 3fc4f4c6..2afc7378 100644 --- a/examples/cobra_publisher/IXCobraConnection.cpp +++ b/examples/cobra_publisher/IXCobraConnection.cpp @@ -59,8 +59,8 @@ namespace ix } void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType, - const std::string& errorMsg, - const WebSocketHttpHeaders& headers) + const std::string& errorMsg, + const WebSocketHttpHeaders& headers) { std::lock_guard lock(_eventCallbackMutex); if (_eventCallback) @@ -176,10 +176,10 @@ namespace ix } void CobraConnection::configure(const std::string& appkey, - const std::string& endpoint, - const std::string& rolename, - const std::string& rolesecret, - WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions) + const std::string& endpoint, + const std::string& rolename, + const std::string& rolesecret, + WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions) { _appkey = appkey; _endpoint = endpoint; @@ -229,7 +229,7 @@ namespace ix return _webSocket.send(serializedJson).success; } - // + // // Extract the nonce from the handshake response // use it to compute a hash during authentication // @@ -297,7 +297,7 @@ namespace ix if (!pdu.isMember("body")) return false; Json::Value body = pdu["body"]; - // Identify subscription_id, so that we can find + // Identify subscription_id, so that we can find // which callback to execute if (!body.isMember("subscription_id")) return false; Json::Value subscriptionId = body["subscription_id"]; @@ -339,7 +339,7 @@ namespace ix // publish is not thread safe as we are trying to reuse some Json objects. // bool CobraConnection::publish(const Json::Value& channels, - const Json::Value& msg) + const Json::Value& msg) { _body["channels"] = channels; _body["message"] = msg; @@ -371,7 +371,7 @@ namespace ix } void CobraConnection::subscribe(const std::string& channel, - SubscriptionCallback cb) + SubscriptionCallback cb) { // Create and send a subscribe pdu Json::Value body; @@ -471,5 +471,5 @@ namespace ix { connect(); } - + } // namespace ix diff --git a/examples/cobra_publisher/IXCobraConnection.h b/examples/cobra_publisher/IXCobraConnection.h index 623f7de0..388d08fe 100644 --- a/examples/cobra_publisher/IXCobraConnection.h +++ b/examples/cobra_publisher/IXCobraConnection.h @@ -84,7 +84,7 @@ namespace ix /// Returns true only if we're connected bool isConnected() const; - + /// Flush the publish queue bool flushQueue(); @@ -118,7 +118,7 @@ namespace ix /// /// Member variables - /// + /// WebSocket _webSocket; /// Configuration data @@ -148,10 +148,10 @@ namespace ix std::unordered_map _cbs; mutable std::mutex _cbsMutex; - // Message Queue can be touched on control+background thread, + // Message Queue can be touched on control+background thread, // protecting with a mutex. // - // Message queue is used when there are problems sending messages so + // Message queue is used when there are problems sending messages so // that sending can be retried later. std::deque _messageQueue; mutable std::mutex _queueMutex; @@ -159,5 +159,5 @@ namespace ix // Cap the queue size (100 elems so far -> ~100k) static constexpr size_t kQueueMaxSize = 256; }; - + } // namespace ix diff --git a/examples/cobra_publisher/ixcrypto/IXBase64.cpp b/examples/cobra_publisher/ixcrypto/IXBase64.cpp index abe9c8ba..2cee0f69 100644 --- a/examples/cobra_publisher/ixcrypto/IXBase64.cpp +++ b/examples/cobra_publisher/ixcrypto/IXBase64.cpp @@ -1,39 +1,39 @@ /* base64.cpp and base64.h - + Copyright (C) 2004-2008 RenĂ© Nyffenegger - + This source code is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. - + Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - + 1. The origin of this source code must not be misrepresented; you must not claim that you wrote the original source code. If you use this source code in a product, an acknowledgment in the product documentation would be appreciated but is not required. - + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original source code. - + 3. This notice may not be removed or altered from any source distribution. - + RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch - + */ #include "IXBase64.h" - + namespace ix { static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; - + std::string base64_encode(const std::string& data, size_t len) { std::string ret; @@ -41,9 +41,9 @@ namespace ix int j = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; - + const char* bytes_to_encode = data.c_str(); - + while(len--) { char_array_3[i++] = *(bytes_to_encode++); @@ -53,32 +53,83 @@ namespace ix char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; - + for(i = 0; (i <4) ; i++) ret += base64_chars[char_array_4[i]]; - + i = 0; } } - + if(i) { for(j = i; j < 3; j++) char_array_3[j] = '\0'; - + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; - + for(j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; - + while((i++ < 3)) ret += '='; - + } - + + return ret; + } + + static inline bool is_base64(unsigned char c) + { + return (isalnum(c) || (c == '+') || (c == '/')); + } + + std::string base64_decode(const std::string& encoded_string) + { + int in_len = (int)encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) + { + char_array_4[i++] = encoded_string[in_]; in_++; + if(i ==4) + { + for(i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for(i = 0; (i < 3); i++) + ret += char_array_3[i]; + + i = 0; + } + } + + if(i) + { + for(j = i; j <4; j++) + char_array_4[j] = 0; + + for(j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for(j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + return ret; } } diff --git a/examples/cobra_publisher/ixcrypto/IXBase64.h b/examples/cobra_publisher/ixcrypto/IXBase64.h index 3c8e2660..524e8128 100644 --- a/examples/cobra_publisher/ixcrypto/IXBase64.h +++ b/examples/cobra_publisher/ixcrypto/IXBase64.h @@ -11,4 +11,5 @@ namespace ix { std::string base64_encode(const std::string& data, size_t len); + std::string base64_decode(const std::string& encoded_string); } diff --git a/examples/cobra_publisher/ixcrypto/IXHash.cpp b/examples/cobra_publisher/ixcrypto/IXHash.cpp new file mode 100644 index 00000000..6eb7f5e1 --- /dev/null +++ b/examples/cobra_publisher/ixcrypto/IXHash.cpp @@ -0,0 +1,22 @@ +/* + * IXHash.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#include + +namespace ix +{ + uint64_t djb2Hash(const std::string& data) + { + uint64_t hashAddress = 5381; + + for (auto& c : data) + { + hashAddress = ((hashAddress << 5) + hashAddress) + c; + } + + return hashAddress; + } +} diff --git a/examples/cobra_publisher/ixcrypto/IXHash.h b/examples/cobra_publisher/ixcrypto/IXHash.h new file mode 100644 index 00000000..0f7eecc9 --- /dev/null +++ b/examples/cobra_publisher/ixcrypto/IXHash.h @@ -0,0 +1,15 @@ +/* + * IXHash.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + uint64_t djb2Hash(const std::string& data); +} + diff --git a/examples/cobra_publisher/ixcrypto/IXUuid.cpp b/examples/cobra_publisher/ixcrypto/IXUuid.cpp new file mode 100644 index 00000000..4a276494 --- /dev/null +++ b/examples/cobra_publisher/ixcrypto/IXUuid.cpp @@ -0,0 +1,75 @@ +/* + * IXUuid.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone. All rights reserved. + */ + +/** + * Generate a random uuid similar to the uuid python module + * + * >>> import uuid + * >>> uuid.uuid4().hex + * 'bec08155b37d4050a1f3c3fa0276bf12' + * + * Code adapted from https://github.com/r-lyeh-archived/sole + */ + +#include "IXUuid.h" + +#include +#include +#include +#include + + +namespace ix +{ + class Uuid + { + public: + Uuid(); + std::string toString() const; + + private: + uint64_t _ab; + uint64_t _cd; + }; + + Uuid::Uuid() + { + static std::random_device rd; + static std::uniform_int_distribution dist(0, (uint64_t)(~0)); + + _ab = dist(rd); + _cd = dist(rd); + + _ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL; + _cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL; + } + + std::string Uuid::toString() const + { + std::stringstream ss; + ss << std::hex << std::nouppercase << std::setfill('0'); + + uint32_t a = (_ab >> 32); + uint32_t b = (_ab & 0xFFFFFFFF); + uint32_t c = (_cd >> 32); + uint32_t d = (_cd & 0xFFFFFFFF); + + ss << std::setw(8) << (a); + ss << std::setw(4) << (b >> 16); + ss << std::setw(4) << (b & 0xFFFF); + ss << std::setw(4) << (c >> 16 ); + ss << std::setw(4) << (c & 0xFFFF); + ss << std::setw(8) << d; + + return ss.str(); + } + + std::string uuid4() + { + Uuid id; + return id.toString(); + } +} diff --git a/examples/cobra_publisher/ixcrypto/IXUuid.h b/examples/cobra_publisher/ixcrypto/IXUuid.h new file mode 100644 index 00000000..cd248f9f --- /dev/null +++ b/examples/cobra_publisher/ixcrypto/IXUuid.h @@ -0,0 +1,17 @@ +/* + * IXUuid.h + * Author: Benjamin Sergeant + * Copyright (c) 2017 Machine Zone. All rights reserved. + */ +#pragma once + +#include + +namespace ix +{ + /** + * Generate a random uuid + */ + std::string uuid4(); + +} diff --git a/examples/cobra_publisher/jsoncpp/json/json-forwards.h b/examples/cobra_publisher/jsoncpp/json/json-forwards.h index 60d3bd0b..7f0e8082 100644 --- a/examples/cobra_publisher/jsoncpp/json/json-forwards.h +++ b/examples/cobra_publisher/jsoncpp/json/json-forwards.h @@ -7,28 +7,28 @@ // ////////////////////////////////////////////////////////////////////// /* -The JsonCpp library's source code, including accompanying documentation, +The JsonCpp library's source code, including accompanying documentation, tests and demonstration applications, are licensed under the following conditions... -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors, and is released under the terms of the MIT License (see below). -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License - + The full text of the MIT License follows: ======================================================================== diff --git a/examples/cobra_publisher/jsoncpp/json/json.h b/examples/cobra_publisher/jsoncpp/json/json.h index 3d7fb636..4311e90e 100644 --- a/examples/cobra_publisher/jsoncpp/json/json.h +++ b/examples/cobra_publisher/jsoncpp/json/json.h @@ -6,28 +6,28 @@ // ////////////////////////////////////////////////////////////////////// /* -The JsonCpp library's source code, including accompanying documentation, +The JsonCpp library's source code, including accompanying documentation, tests and demonstration applications, are licensed under the following conditions... -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors, and is released under the terms of the MIT License (see below). -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License - + The full text of the MIT License follows: ======================================================================== @@ -1673,7 +1673,7 @@ public: - `"rejectDupKeys": false or true` - If true, `parse()` returns false when a key is duplicated within an object. - `"allowSpecialFloats": false or true` - - If true, special float values (NaNs and infinities) are allowed + - If true, special float values (NaNs and infinities) are allowed and their values are lossfree restorable. You can examine 'settings_` yourself diff --git a/examples/cobra_publisher/jsoncpp/jsoncpp.cpp b/examples/cobra_publisher/jsoncpp/jsoncpp.cpp index be0f9ec7..dafdba3e 100644 --- a/examples/cobra_publisher/jsoncpp/jsoncpp.cpp +++ b/examples/cobra_publisher/jsoncpp/jsoncpp.cpp @@ -6,28 +6,28 @@ // ////////////////////////////////////////////////////////////////////// /* -The JsonCpp library's source code, including accompanying documentation, +The JsonCpp library's source code, including accompanying documentation, tests and demonstration applications, are licensed under the following conditions... -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors, and is released under the terms of the MIT License (see below). -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License - + The full text of the MIT License follows: ======================================================================== @@ -238,7 +238,7 @@ static inline void fixNumericLocaleInput(char* begin, char* end) { #include #if defined(_MSC_VER) -#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above +#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above #define snprintf sprintf_s #elif _MSC_VER >= 1900 // VC++ 14.0 and above #define snprintf std::snprintf @@ -383,7 +383,7 @@ bool Reader::parse(const char* beginDoc, bool Reader::readValue() { // readValue() may call itself only if it calls readObject() or ReadArray(). - // These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue(). + // These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue(). // parse() executes one nodes_.push(), so > instead of >=. if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); @@ -4215,7 +4215,7 @@ Value& Path::make(Value& root) const { #endif #endif -#if defined(__BORLANDC__) +#if defined(__BORLANDC__) #include #define isfinite _finite #define snprintf _snprintf @@ -5290,7 +5290,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const JSONCPP_STRING cs_str = settings_["commentStyle"].asString(); bool eyc = settings_["enableYAMLCompatibility"].asBool(); bool dnp = settings_["dropNullPlaceholders"].asBool(); - bool usf = settings_["useSpecialFloats"].asBool(); + bool usf = settings_["useSpecialFloats"].asBool(); unsigned int pre = settings_["precision"].asUInt(); CommentStyle::Enum cs = CommentStyle::All; if (cs_str == "All") { diff --git a/examples/ws_receive/.gitignore b/examples/ws_receive/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/examples/ws_receive/.gitignore @@ -0,0 +1 @@ +build diff --git a/examples/ws_receive/CMakeLists.txt b/examples/ws_receive/CMakeLists.txt new file mode 100644 index 00000000..797076dc --- /dev/null +++ b/examples/ws_receive/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Author: Benjamin Sergeant +# Copyright (c) 2019 Machine Zone, Inc. All rights reserved. +# + +cmake_minimum_required (VERSION 3.4.1) +project (ws_receive) + +# There's -Weverything too for clang +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32") + +set (CMAKE_CXX_STANDARD 14) + +option(USE_TLS "Add TLS support" ON) + +add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket) + +include_directories(ws_receive .) + +add_executable(ws_receive + jsoncpp/jsoncpp.cpp + ixcrypto/IXBase64.cpp + ixcrypto/IXHash.cpp + ws_receive.cpp) + +if (APPLE AND USE_TLS) + target_link_libraries(ws_receive "-framework foundation" "-framework security") +endif() + +target_link_libraries(ws_receive ixwebsocket) diff --git a/examples/ws_receive/README.md b/examples/ws_receive/README.md new file mode 100644 index 00000000..2b48e07c --- /dev/null +++ b/examples/ws_receive/README.md @@ -0,0 +1 @@ +ws_receive is a simple server upload program. It needs to be used in conjonction with ws_send. diff --git a/examples/ws_receive/ixcrypto b/examples/ws_receive/ixcrypto new file mode 120000 index 00000000..1b13e4a5 --- /dev/null +++ b/examples/ws_receive/ixcrypto @@ -0,0 +1 @@ +../cobra_publisher/ixcrypto \ No newline at end of file diff --git a/examples/ws_receive/jsoncpp/json/json-forwards.h b/examples/ws_receive/jsoncpp/json/json-forwards.h new file mode 100644 index 00000000..7f0e8082 --- /dev/null +++ b/examples/ws_receive/jsoncpp/json/json-forwards.h @@ -0,0 +1,333 @@ +/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json-forwards.h" +/// This header provides forward declaration for all JsonCpp types. + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED +# define JSON_FORWARD_AMALGATED_H_INCLUDED +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include +#include //typedef String +#include //typedef int64_t, uint64_t + +/// 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 non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// 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 +#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) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#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) // MSVC +# if _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 + // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' + // characters in the debug information) + // All projects I've ever seen with VS6 were using this globally (not bothering + // with pragma push/pop). +# pragma warning(disable : 4786) +# endif // MSVC 6 + +# if _MSC_VER >= 1500 // MSVC 2008 + /// Indicates that the following function is deprecated. +# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +# endif + +#endif // defined(_MSC_VER) + +// In c++11 the override keyword allows you to explicity define that a function +// is intended to override the base-class version. This makes the code more +// managable and fixes a set of common hard-to-find bugs. +#if __cplusplus >= 201103L +# define JSONCPP_OVERRIDE override +# define JSONCPP_NOEXCEPT noexcept +#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900 +# define JSONCPP_OVERRIDE override +# define JSONCPP_NOEXCEPT throw() +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +# define JSONCPP_OVERRIDE override +# define JSONCPP_NOEXCEPT noexcept +#else +# define JSONCPP_OVERRIDE +# define JSONCPP_NOEXCEPT throw() +#endif + +#ifndef JSON_HAS_RVALUE_REFERENCES + +#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // MSVC >= 2010 + +#ifdef __clang__ +#if __has_feature(cxx_rvalue_references) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // has_feature + +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // GXX_EXPERIMENTAL + +#endif // __clang__ || __GNUC__ + +#endif // not defined JSON_HAS_RVALUE_REFERENCES + +#ifndef JSON_HAS_RVALUE_REFERENCES +#define JSON_HAS_RVALUE_REFERENCES 0 +#endif + +#ifdef __clang__ +# if __has_extension(attribute_deprecated_with_message) +# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +# endif +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +# endif // GNUC version +#endif // __clang__ || __GNUC__ + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if __GNUC__ >= 6 +# define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +# include "version.h" + +# if JSONCPP_USING_SECURE_MEMORY +# include "allocator.h" //typedef Allocator +# endif + +#endif // if !defined(JSON_IS_AMALGAMATION) + +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 int64_t Int64; +typedef uint64_t UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) +#if JSONCPP_USING_SECURE_MEMORY +#define JSONCPP_STRING std::basic_string, Json::SecureAllocator > +#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator > +#define JSONCPP_OSTREAM std::basic_ostream> +#define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator > +#define JSONCPP_ISTREAM std::istream +#else +#define JSONCPP_STRING std::string +#define JSONCPP_OSTRINGSTREAM std::ostringstream +#define JSONCPP_OSTREAM std::ostream +#define JSONCPP_ISTRINGSTREAM std::istringstream +#define JSONCPP_ISTREAM std::istream +#endif // if JSONCPP_USING_SECURE_MEMORY +} // end namespace Json + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED diff --git a/examples/ws_receive/jsoncpp/json/json.h b/examples/ws_receive/jsoncpp/json/json.h new file mode 100644 index 00000000..4311e90e --- /dev/null +++ b/examples/ws_receive/jsoncpp/json/json.h @@ -0,0 +1,2186 @@ +/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGATED_H_INCLUDED +# define JSON_AMALGATED_H_INCLUDED +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +// DO NOT EDIT. This file (and "version") is generated by CMake. +// Run CMake configure step to update it. +#ifndef JSON_VERSION_H_INCLUDED +# define JSON_VERSION_H_INCLUDED + +# define JSONCPP_VERSION_STRING "1.8.1" +# define JSONCPP_VERSION_MAJOR 1 +# define JSONCPP_VERSION_MINOR 8 +# define JSONCPP_VERSION_PATCH 1 +# define JSONCPP_VERSION_QUALIFIER +# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) + +#ifdef JSONCPP_USING_SECURE_MEMORY +#undef JSONCPP_USING_SECURE_MEMORY +#endif +#define JSONCPP_USING_SECURE_MEMORY 0 +// If non-zero, the library zeroes any memory that it has allocated before +// it frees its memory. + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include +#include //typedef String +#include //typedef int64_t, uint64_t + +/// 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 non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// 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 +#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) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#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) // MSVC +# if _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 + // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' + // characters in the debug information) + // All projects I've ever seen with VS6 were using this globally (not bothering + // with pragma push/pop). +# pragma warning(disable : 4786) +# endif // MSVC 6 + +# if _MSC_VER >= 1500 // MSVC 2008 + /// Indicates that the following function is deprecated. +# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +# endif + +#endif // defined(_MSC_VER) + +// In c++11 the override keyword allows you to explicity define that a function +// is intended to override the base-class version. This makes the code more +// managable and fixes a set of common hard-to-find bugs. +#if __cplusplus >= 201103L +# define JSONCPP_OVERRIDE override +# define JSONCPP_NOEXCEPT noexcept +#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900 +# define JSONCPP_OVERRIDE override +# define JSONCPP_NOEXCEPT throw() +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +# define JSONCPP_OVERRIDE override +# define JSONCPP_NOEXCEPT noexcept +#else +# define JSONCPP_OVERRIDE +# define JSONCPP_NOEXCEPT throw() +#endif + +#ifndef JSON_HAS_RVALUE_REFERENCES + +#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // MSVC >= 2010 + +#ifdef __clang__ +#if __has_feature(cxx_rvalue_references) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // has_feature + +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) +#define JSON_HAS_RVALUE_REFERENCES 1 +#endif // GXX_EXPERIMENTAL + +#endif // __clang__ || __GNUC__ + +#endif // not defined JSON_HAS_RVALUE_REFERENCES + +#ifndef JSON_HAS_RVALUE_REFERENCES +#define JSON_HAS_RVALUE_REFERENCES 0 +#endif + +#ifdef __clang__ +# if __has_extension(attribute_deprecated_with_message) +# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +# endif +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +# endif // GNUC version +#endif // __clang__ || __GNUC__ + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if __GNUC__ >= 6 +# define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +# include "version.h" + +# if JSONCPP_USING_SECURE_MEMORY +# include "allocator.h" //typedef Allocator +# endif + +#endif // if !defined(JSON_IS_AMALGAMATION) + +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 int64_t Int64; +typedef uint64_t UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) +#if JSONCPP_USING_SECURE_MEMORY +#define JSONCPP_STRING std::basic_string, Json::SecureAllocator > +#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator > +#define JSONCPP_OSTREAM std::basic_ostream> +#define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator > +#define JSONCPP_ISTREAM std::istream +#else +#define JSONCPP_STRING std::string +#define JSONCPP_OSTRINGSTREAM std::ostringstream +#define JSONCPP_OSTREAM std::ostream +#define JSONCPP_ISTRINGSTREAM std::istringstream +#define JSONCPP_ISTREAM std::istream +#endif // if JSONCPP_USING_SECURE_MEMORY +} // end namespace Json + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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) + +#pragma pack(push, 8) + +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_; + + /// \c true if dropped null placeholders are allowed. Default: \c false. + bool allowDroppedNullPlaceholders_; + + /// \c true if numeric object key are allowed. Default: \c false. + bool allowNumericKeys_; +}; + +} // namespace Json + +#pragma pack(pop) + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include +#include + +#ifndef JSON_USE_CPPTL_SMALLMAP +#include +#else +#include +#endif +#ifdef JSON_USE_CPPTL +#include +#endif + +//Conditional NORETURN attribute on the throw functions would: +// a) suppress false positives from static code analysis +// b) possibly improve optimization opportunities. +#if !defined(JSONCPP_NORETURN) +# if defined(_MSC_VER) +# define JSONCPP_NORETURN __declspec(noreturn) +# elif defined(__GNUC__) +# define JSONCPP_NORETURN __attribute__ ((__noreturn__)) +# else +# define JSONCPP_NORETURN +# endif +#endif + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +/** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ +class JSON_API Exception : public std::exception { +public: + Exception(JSONCPP_STRING const& msg); + ~Exception() JSONCPP_NOEXCEPT JSONCPP_OVERRIDE; + char const* what() const JSONCPP_NOEXCEPT JSONCPP_OVERRIDE; +protected: + JSONCPP_STRING msg_; +}; + +/** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ +class JSON_API RuntimeError : public Exception { +public: + RuntimeError(JSONCPP_STRING const& msg); +}; + +/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ +class JSON_API LogicError : public Exception { +public: + LogicError(JSONCPP_STRING const& msg); +}; + +/// used internally +JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg); +/// used internally +JSONCPP_NORETURN void throwLogicError(JSONCPP_STRING const& msg); + +/** \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 EnumMemberNames; +// typedef CppTL::AnyEnumerator 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) : c_str_(czstring) {} + + operator const char*() const { return c_str_; } + + const char* c_str() const { return c_str_; } + +private: + const char* c_str_; +}; + +/** \brief Represents a JSON 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 resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain 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. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ +class JSON_API Value { + friend class ValueIteratorBase; +public: + typedef std::vector 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; ///< We regret this reference to a global instance; prefer the simpler Value(). + static const Value& nullRef; ///< just a kludge for binary-compatibility; same as null + static Value const& nullSingleton(); ///< Prefer this to null or nullRef. + + /// 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; + +#if defined(JSON_HAS_INT64) + /// 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; +#endif // defined(JSON_HAS_INT64) + +private: +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { + noDuplication = 0, + duplicate, + duplicateOnCopy + }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); +#if JSON_HAS_RVALUE_REFERENCES + CZString(CZString&& other); +#endif + ~CZString(); + CZString& operator=(const CZString& other); + +#if JSON_HAS_RVALUE_REFERENCES + CZString& operator=(CZString&& other); +#endif + + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + //const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_: 2; + unsigned length_: 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + +public: +#ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +#else + typedef CppTL::SmallMap ObjectValues; +#endif // ifndef JSON_USE_CPPTL_SMALLMAP +#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); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** \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. + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, + * which might be computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const JSONCPP_STRING& value); ///< Copy data() til size(). Embedded zeroes too. +#ifdef JSON_USE_CPPTL + Value(const CppTL::ConstString& value); +#endif + Value(bool value); + /// Deep copy. + Value(const Value& other); +#if JSON_HAS_RVALUE_REFERENCES + /// Move constructor + Value(Value&& other); +#endif + ~Value(); + + /// Deep copy, then swap(other). + /// \note Over-write existing comments. To preserve comments, use #swapPayload(). + Value& operator=(Value other); + + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + /// copy everything. + void copy(const Value& other); + /// copy values but leave comments and source offsets in place. + void copyPayload(const Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + 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; ///< Embedded zeroes could cause you trouble! +#if JSONCPP_USING_SECURE_MEMORY + unsigned getCStringLength() const; //Allows you to understand the length of the CString +#endif + JSONCPP_STRING asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString( + char const** begin, char const** end) const; +#ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +#endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + 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 isInt64() const; + bool isUInt() const; + bool isUInt64() 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); + +#if JSON_HAS_RVALUE_REFERENCES + Value& append(Value&& value); +#endif + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + 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. + /// \param key may contain embedded nulls. + Value& operator[](const JSONCPP_STRING& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const JSONCPP_STRING& key) const; + /** \brief Access an object value by name, create a null member if it does not + exist. + + * If the object has 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. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const JSONCPP_STRING& key, const Value& defaultValue) const; +#ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const CppTL::ConstString& key, const Value& defaultValue) const; +#endif + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value const* demand(char const* begin, char const* end); + /// \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 + /// \deprecated + JSONCPP_DEPRECATED("") + Value removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + /// \deprecated + JSONCPP_DEPRECATED("") + Value removeMember(const JSONCPP_STRING& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + + Update 'removed' iff removed. + \param key may contain embedded nulls. + \return true iff removed (no exceptions) + */ + bool removeMember(JSONCPP_STRING const& key, Value* removed); + /// Same as removeMember(JSONCPP_STRING const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + + O(n) expensive operations. + Update 'removed' iff removed. + \return true iff removed (no exceptions) + */ + bool removeIndex(ArrayIndex i, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const JSONCPP_STRING& key) const; + /// Same as isMember(JSONCPP_STRING const& key)const + bool isMember(const char* begin, const char* end) 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 + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(JSONCPP_STRING const&) instead.") + void setComment(const char* comment, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const JSONCPP_STRING& comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + JSONCPP_STRING getComment(CommentPlacement placement) const; + + JSONCPP_STRING toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart(ptrdiff_t start); + void setOffsetLimit(ptrdiff_t limit); + ptrdiff_t getOffsetStart() const; + ptrdiff_t getOffsetLimit() const; + +private: + void initBasic(ValueType type, bool allocated = false); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + struct CommentInfo { + CommentInfo(); + ~CommentInfo(); + + void setComment(const char* text, size_t len); + + 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_; // actually ptr to unsigned, followed by str, unless !allocated_ + ObjectValues* map_; + } value_; + ValueType type_ : 8; + unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. + // If not allocated_, string_ must be null-terminated. + CommentInfo* comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + ptrdiff_t start_; + ptrdiff_t limit_; +}; + +/** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ +class JSON_API PathArgument { +public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(const JSONCPP_STRING& key); + +private: + enum Kind { + kindNone = 0, + kindIndex, + kindKey + }; + JSONCPP_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 JSON_API Path { +public: + Path(const JSONCPP_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 InArgs; + typedef std::vector Args; + + void makePath(const JSONCPP_STRING& path, const InArgs& in); + void addPathInArg(const JSONCPP_STRING& path, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind); + void invalidPath(const JSONCPP_STRING& path, int location); + + Args args_; +}; + +/** \brief base class for Value iterators. + * + */ +class JSON_API ValueIteratorBase { +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + 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 other.computeDistance(*this); + } + + /// 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, or -1 if it is not an arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + JSONCPP_STRING name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) 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: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_; + +public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); +}; + +/** \brief const iterator for object and array value. + * + */ +class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef const Value value_type; + //typedef unsigned int size_t; + //typedef int difference_type; + typedef const Value& reference; + typedef const Value* pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + ValueConstIterator(ValueIterator const& other); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); +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(); } + + pointer operator->() const { return &deref(); } +}; + +/** \brief Iterator for object and array value. + */ +class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef Value value_type; + typedef unsigned int size_t; + typedef int difference_type; + typedef Value& reference; + typedef Value* pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + explicit ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); +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(); } + + pointer operator->() const { return &deref(); } +}; + +} // namespace Json + + +namespace std { +/// Specialize std::swap() for Json::Value. +template<> +inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } +} + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + +/** \brief Unserialize a JSON document into a + *Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ +class JSON_API Reader { +public: + typedef char Char; + typedef const Char* Location; + + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + * + */ + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + JSONCPP_STRING message; + }; + + /** \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 JSON + * 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 JSON + 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(JSONCPP_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.") + JSONCPP_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. + */ + JSONCPP_STRING getFormattedErrorMessages() const; + + /** \brief Returns a vector of structured erros encounted while parsing. + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate + * multiple + * errors. This can occur if the parser recovers from a non-fatal + * parse error and then encounters additional errors. + */ + std::vector getStructuredErrors() const; + + /** \brief Add a semantic error message. + * \param value JSON Value location associated with the error + * \param message The error message. + * \return \c true if the error was successfully added, \c false if the + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const JSONCPP_STRING& message); + + /** \brief Add a semantic error message with extra context. + * \param value JSON Value location associated with the error + * \param message The error message. + * \param extra Additional JSON Value location to contextualize the error + * \return \c true if the error was successfully added, \c false if either + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); + + /** \brief Return whether there are any errors. + * \return \c true if there are no errors to report \c false if + * errors have occurred. + */ + bool good() 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_; + JSONCPP_STRING message_; + Location extra_; + }; + + typedef std::deque Errors; + + 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 decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, JSONCPP_STRING& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const JSONCPP_STRING& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + JSONCPP_STRING getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static bool containsNewLine(Location begin, Location end); + static JSONCPP_STRING normalizeEOL(Location begin, Location end); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + JSONCPP_STRING document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + JSONCPP_STRING commentsBefore_; + Features features_; + bool collectComments_; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() {} + /** \brief Read a Value from a JSON + document. + * The document must be a UTF-8 encoded string containing the document to read. + * + * \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 errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, JSONCPP_STRING* errs) = 0; + + class JSON_API Factory { + public: + virtual ~Factory() {} + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +/** \brief Build a CharReader implementation. + +Usage: +\code + using namespace Json; + CharReaderBuilder builder; + builder["collectComments"] = false; + Value value; + JSONCPP_STRING errs; + bool ok = parseFromStream(builder, std::cin, &value, &errs); +\endcode +*/ +class JSON_API CharReaderBuilder : public CharReader::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - `"collectComments": false or true` + - true to collect comment and allow writing them + back during serialization, false to discard comments. + This parameter is ignored if allowComments is false. + - `"allowComments": false or true` + - true if comments are allowed. + - `"strictRoot": false or true` + - true if root must be either an array or an object value + - `"allowDroppedNullPlaceholders": false or true` + - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) + - `"allowNumericKeys": false or true` + - true if numeric object keys are allowed. + - `"allowSingleQuotes": false or true` + - true if '' are allowed for strings (both keys and values) + - `"stackLimit": integer` + - Exceeding stackLimit (recursive depth of `readValue()`) will + cause an exception. + - This is a security issue (seg-faults caused by deeply nested JSON), + so the default is low. + - `"failIfExtra": false or true` + - If true, `parse()` returns false when extra non-whitespace trails + the JSON value in the input string. + - `"rejectDupKeys": false or true` + - If true, `parse()` returns false when a key is duplicated within an object. + - `"allowSpecialFloats": false or true` + - If true, special float values (NaNs and infinities) are allowed + and their values are lossfree restorable. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + ~CharReaderBuilder() JSONCPP_OVERRIDE; + + CharReader* newCharReader() const JSONCPP_OVERRIDE; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](JSONCPP_STRING key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); +}; + +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream( + CharReader::Factory const&, + JSONCPP_ISTREAM&, + Value* root, std::string* errs); + +/** \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<<() +*/ +JSON_API JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM&, Value&); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + +class Value; + +/** + +Usage: +\code + using namespace Json; + void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { + std::unique_ptr const writer( + factory.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush + } +\endcode +*/ +class JSON_API StreamWriter { +protected: + JSONCPP_OSTREAM* sout_; // not owned; will not delete +public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success (For now, we always return zero, so check the stream instead.) + \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, JSONCPP_OSTREAM* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory +}; // StreamWriter + +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +JSONCPP_STRING JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); + + +/** \brief Build a StreamWriter implementation. + +Usage: +\code + using namespace Json; + Value value = ...; + StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; // or whatever you like + std::unique_ptr writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush +\endcode +*/ +class JSON_API StreamWriterBuilder : public StreamWriter::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None" or "All" + - "indentation": "" + - "enableYAMLCompatibility": false or true + - slightly change the whitespace around colons + - "dropNullPlaceholders": false or true + - Drop the "null" string from the writer's output for nullValues. + Strictly speaking, this is not valid JSON. But when the output is being + fed to a browser's Javascript, it makes for smaller output and the + browser can handle the output just fine. + - "useSpecialFloats": false or true + - If true, outputs non-finite floating point values in the following way: + NaN values as "NaN", positive infinity as "Infinity", and negative infinity + as "-Infinity". + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + ~StreamWriterBuilder() JSONCPP_OVERRIDE; + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + StreamWriter* newStreamWriter() const JSONCPP_OVERRIDE; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](JSONCPP_STRING key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); +}; + +/** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ +class JSON_API Writer { +public: + virtual ~Writer(); + + virtual JSONCPP_STRING write(const Value& root) = 0; +}; + +/** \brief Outputs a Value in JSON 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 + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API FastWriter : public Writer { + +public: + FastWriter(); + ~FastWriter() JSONCPP_OVERRIDE {} + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's Javascript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + void omitEndingLineFeed(); + +public: // overridden from Writer + JSONCPP_STRING write(const Value& root) JSONCPP_OVERRIDE; + +private: + void writeValue(const Value& value); + + JSONCPP_STRING document_; + bool yamlCompatiblityEnabled_; + bool dropNullPlaceholders_; + bool omitEndingLineFeed_; +}; + +/** \brief Writes a Value in JSON 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() + * \deprecated Use StreamWriterBuilder. + */ +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API StyledWriter : public Writer { +public: + StyledWriter(); + ~StyledWriter() JSONCPP_OVERRIDE {} + +public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + JSONCPP_STRING write(const Value& root) JSONCPP_OVERRIDE; + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const JSONCPP_STRING& value); + void writeIndent(); + void writeWithIndent(const JSONCPP_STRING& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static JSONCPP_STRING normalizeEOL(const JSONCPP_STRING& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + JSONCPP_STRING document_; + JSONCPP_STRING indentString_; + unsigned int rightMargin_; + unsigned int indentSize_; + bool addChildValues_; +}; + +/** \brief Writes a Value in JSON 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. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API StyledStreamWriter { +public: +/** + * \param indentation Each level will be indented by this amount extra. + */ + StyledStreamWriter(JSONCPP_STRING indentation = "\t"); + ~StyledStreamWriter() {} + +public: + /** \brief Serialize a Value in JSON 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(JSONCPP_OSTREAM& out, const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const JSONCPP_STRING& value); + void writeIndent(); + void writeWithIndent(const JSONCPP_STRING& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static JSONCPP_STRING normalizeEOL(const JSONCPP_STRING& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + JSONCPP_OSTREAM* document_; + JSONCPP_STRING indentString_; + unsigned int rightMargin_; + JSONCPP_STRING indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; + +#if defined(JSON_HAS_INT64) +JSONCPP_STRING JSON_API valueToString(Int value); +JSONCPP_STRING JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) +JSONCPP_STRING JSON_API valueToString(LargestInt value); +JSONCPP_STRING JSON_API valueToString(LargestUInt value); +JSONCPP_STRING JSON_API valueToString(double value); +JSONCPP_STRING JSON_API valueToString(bool value); +JSONCPP_STRING JSON_API valueToQuotedString(const char* value); + +/// \brief Output using the StyledStreamWriter. +/// \see Json::operator>>() +JSON_API JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM&, const Value& root); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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_ASSERTIONS_H_INCLUDED +#define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include +#include + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + +// @todo <= add detail about condition in exception +# define JSON_ASSERT(condition) \ + {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} + +# define JSON_FAIL_MESSAGE(message) \ + { \ + JSONCPP_OSTRINGSTREAM oss; oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } + +#else // JSON_USE_EXCEPTION + +# define JSON_ASSERT(condition) assert(condition) + +// The call to assert() will show the failure message in debug builds. In +// release builds we abort, for a core-dump or debugger. +# define JSON_FAIL_MESSAGE(message) \ + { \ + JSONCPP_OSTRINGSTREAM oss; oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGATED_H_INCLUDED diff --git a/examples/ws_receive/jsoncpp/jsoncpp.cpp b/examples/ws_receive/jsoncpp/jsoncpp.cpp new file mode 100644 index 00000000..dafdba3e --- /dev/null +++ b/examples/ws_receive/jsoncpp/jsoncpp.cpp @@ -0,0 +1,5386 @@ +/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +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. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "json/json.h" + +#ifndef JSON_IS_AMALGAMATION +#error "Compile with -I PATH_TO_JSON_DIRECTORY" +#endif + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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 + + +// Also support old flag NO_LOCALE_SUPPORT +#ifdef NO_LOCALE_SUPPORT +#define JSONCPP_NO_LOCALE_SUPPORT +#endif + +#ifndef JSONCPP_NO_LOCALE_SUPPORT +#include +#endif + +/* 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 { +static char getDecimalPoint() { +#ifdef JSONCPP_NO_LOCALE_SUPPORT + return '\0'; +#else + struct lconv* lc = localeconv(); + return lc ? *(lc->decimal_point) : '\0'; +#endif +} + +/// Converts a unicode code-point to UTF-8. +static inline JSONCPP_STRING codePointToUTF8(unsigned int cp) { + JSONCPP_STRING result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) { + result.resize(1); + result[0] = static_cast(cp); + } else if (cp <= 0x7FF) { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } else if (cp <= 0xFFFF) { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); + } else if (cp <= 0x10FFFF) { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + +/// Returns true if ch is a control character (in range [1,31]). +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*& current) { + *--current = 0; + do { + *--current = static_cast(value % 10U + static_cast('0')); + value /= 10; + } while (value != 0); +} + +/** Change ',' to '.' everywhere in buffer. + * + * We had a sophisticated way, but it did not work in WinCE. + * @see https://github.com/open-source-parsers/jsoncpp/pull/9 + */ +static inline void fixNumericLocale(char* begin, char* end) { + while (begin < end) { + if (*begin == ',') { + *begin = '.'; + } + ++begin; + } +} + +static inline void fixNumericLocaleInput(char* begin, char* end) { + char decimalPoint = getDecimalPoint(); + if (decimalPoint != '\0' && decimalPoint != '.') { + while (begin < end) { + if (*begin == '.') { + *begin = decimalPoint; + } + ++begin; + } + } +} + +} // namespace Json { + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors +// Copyright (C) 2016 InfoTeCS JSC. All rights reserved. +// 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 +#include +#include +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above +#define snprintf sprintf_s +#elif _MSC_VER >= 1900 // VC++ 14.0 and above +#define snprintf std::snprintf +#else +#define snprintf _snprintf +#endif +#elif defined(__ANDROID__) || defined(__QNXNTO__) +#define snprintf snprintf +#elif __cplusplus >= 201103L +#if !defined(__MINGW32__) && !defined(__CYGWIN__) +#define snprintf std::snprintf +#endif +#endif + +#if defined(__QNXNTO__) +#define sscanf std::sscanf +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile time to change the stack limit +#if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) +#define JSONCPP_DEPRECATED_STACK_LIMIT 1000 +#endif + +static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) +typedef std::unique_ptr CharReaderPtr; +#else +typedef std::auto_ptr CharReaderPtr; +#endif + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() + : allowComments_(true), strictRoot_(false), + allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) {} + +Features Features::all() { return Features(); } + +Features Features::strictMode() { + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + +bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(Features::strictMode()), + collectComments_() {} + +Reader::Reader(const Features& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool +Reader::parse(const std::string& document, Value& root, bool collectComments) { + JSONCPP_STRING documentCopy(document.data(), document.data() + document.capacity()); + std::swap(documentCopy, 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 begin(sin); + // std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since JSONCPP_STRING is reference-counted, this at least does not + // create an extra copy. + JSONCPP_STRING doc; + std::getline(sin, doc, (char)EOF); + return parse(doc.data(), doc.data() + doc.size(), 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_.clear(); + 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() { + // readValue() may call itself only if it calls readObject() or ReadArray(). + // These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue(). + // parse() executes one nodes_.push(), so > instead of >=. + if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + 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::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; +} + +JSONCPP_STRING Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { + JSONCPP_STRING normalized; + normalized.reserve(static_cast(end - begin)); + Reader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void +Reader::addComment(Location begin, Location end, CommentPlacement placement) { + assert(collectComments_); + const JSONCPP_STRING& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != 0); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool Reader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool Reader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void Reader::readNumber() { + const char *p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } +} + +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; + JSONCPP_STRING name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + 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; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = JSONCPP_STRING(numberName.asCString()); + } else { + break; + } + + 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) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *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) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeNumber(Token& token, Value& decoded) { + // 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; + // TODO: Help the compiler do the div and mod at compile time or get rid of them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + Value::UInt digit(static_cast(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool Reader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + JSONCPP_STRING buffer(token.start_, token.end_); + JSONCPP_ISTRINGSTREAM is(buffer); + if (!(is >> value)) + return addError("'" + JSONCPP_STRING(token.start_, token.end_) + + "' is not a number.", + token); + decoded = value; + return true; +} + +bool Reader::decodeString(Token& token) { + JSONCPP_STRING decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeString(Token& token, JSONCPP_STRING& decoded) { + decoded.reserve(static_cast(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& current, + 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& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", + token, + current); + int 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); + } + ret_unicode = static_cast(unicode); + return true; +} + +bool +Reader::addError(const JSONCPP_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) { + size_t const errorCount = 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 JSONCPP_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; +} + +JSONCPP_STRING Reader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; +} + +// Deprecated. Preserved for backward compatibility +JSONCPP_STRING Reader::getFormatedErrorMessages() const { + return getFormattedErrorMessages(); +} + +JSONCPP_STRING Reader::getFormattedErrorMessages() const { + JSONCPP_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::vector Reader::getStructuredErrors() const { + std::vector allErrors; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool Reader::pushError(const Value& value, const JSONCPP_STRING& message) { + ptrdiff_t const length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = 0; + errors_.push_back(info); + return true; +} + +bool Reader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { + ptrdiff_t const length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length + || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool Reader::good() const { + return !errors_.size(); +} + +// exact copy of Features +class OurFeatures { +public: + static OurFeatures all(); + bool allowComments_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + bool allowSpecialFloats_; + int stackLimit_; +}; // OurFeatures + +// exact copy of Implementation of class Features +// //////////////////////////////// + +OurFeatures OurFeatures::all() { return OurFeatures(); } + +// Implementation of class Reader +// //////////////////////////////// + +// exact copy of Reader, renamed to OurReader +class OurReader { +public: + typedef char Char; + typedef const Char* Location; + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + JSONCPP_STRING message; + }; + + OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + JSONCPP_STRING getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; + bool pushError(const Value& value, const JSONCPP_STRING& message); + bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); + bool good() const; + +private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl + + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenNaN, + tokenPosInf, + tokenNegInf, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + JSONCPP_STRING message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + bool readNumber(bool checkInf); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, JSONCPP_STRING& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const JSONCPP_STRING& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + JSONCPP_STRING getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static JSONCPP_STRING normalizeEOL(Location begin, Location end); + static bool containsNewLine(Location begin, Location end); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + JSONCPP_STRING document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + JSONCPP_STRING commentsBefore_; + + OurFeatures const features_; + bool collectComments_; +}; // OurReader + +// complete copy of Read impl, for OurReader + +bool OurReader::containsNewLine(OurReader::Location begin, OurReader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +OurReader::OurReader(OurFeatures const& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), + features_(features), collectComments_() { +} + +bool OurReader::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_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_) { + if ((features_.strictRoot_ || token.type_ != tokenError) && token.type_ != tokenEndOfStream) { + addError("Extra non-whitespace after JSON value.", token); + return false; + } + } + 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 OurReader::readValue() { + // To preserve the old behaviour we cast size_t to int. + if (static_cast(nodes_.size()) > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNaN: + { + Value v(std::numeric_limits::quiet_NaN()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenPosInf: + { + Value v(std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNegInf: + { + Value v(-std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + +void OurReader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool OurReader::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 '\'': + if (features_.allowSingleQuotes_) { + token.type_ = tokenString; + ok = readStringSingleQuote(); + break; + } // else continue + 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': + token.type_ = tokenNumber; + readNumber(false); + break; + case '-': + if (readNumber(true)) { + token.type_ = tokenNumber; + } else { + token.type_ = tokenNegInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + 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 'N': + if (features_.allowSpecialFloats_) { + token.type_ = tokenNaN; + ok = match("aN", 2); + } else { + ok = false; + } + break; + case 'I': + if (features_.allowSpecialFloats_) { + token.type_ = tokenPosInf; + ok = match("nfinity", 7); + } else { + ok = false; + } + 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 OurReader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool OurReader::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 OurReader::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; +} + +JSONCPP_STRING OurReader::normalizeEOL(OurReader::Location begin, OurReader::Location end) { + JSONCPP_STRING normalized; + normalized.reserve(static_cast(end - begin)); + OurReader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void +OurReader::addComment(Location begin, Location end, CommentPlacement placement) { + assert(collectComments_); + const JSONCPP_STRING& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != 0); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool OurReader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool OurReader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +bool OurReader::readNumber(bool checkInf) { + const char *p = current_; + if (checkInf && p != end_ && *p == 'I') { + current_ = ++p; + return false; + } + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + return true; +} +bool OurReader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + + +bool OurReader::readStringSingleQuote() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; +} + +bool OurReader::readObject(Token& tokenStart) { + Token tokenName; + JSONCPP_STRING name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + 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; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover( + "Missing ':' after object member name", colon, tokenObjectEnd); + } + if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + JSONCPP_STRING msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover( + msg, tokenName, 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 OurReader::readArray(Token& tokenStart) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *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 OurReader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeNumber(Token& token, Value& decoded) { + // 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; + // TODO: Help the compiler do the div and mod at compile time or get rid of them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(-Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + Value::UInt digit(static_cast(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool OurReader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + const int bufferSize = 32; + int count; + ptrdiff_t const length = token.end_ - token.start_; + + // Sanity check to avoid buffer overflow exploits. + if (length < 0) { + return addError("Unable to parse token length", token); + } + size_t const ulength = static_cast(length); + + // Avoid using a string constant for the format control string given to + // sscanf, as this can cause hard to debug crashes on OS X. See here for more + // info: + // + // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html + char format[] = "%lf"; + + if (length <= bufferSize) { + Char buffer[bufferSize + 1]; + memcpy(buffer, token.start_, ulength); + buffer[length] = 0; + fixNumericLocaleInput(buffer, buffer + length); + count = sscanf(buffer, format, &value); + } else { + JSONCPP_STRING buffer(token.start_, token.end_); + count = sscanf(buffer.c_str(), format, &value); + } + + if (count != 1) + return addError("'" + JSONCPP_STRING(token.start_, token.end_) + + "' is not a number.", + token); + decoded = value; + return true; +} + +bool OurReader::decodeString(Token& token) { + JSONCPP_STRING decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeString(Token& token, JSONCPP_STRING& decoded) { + decoded.reserve(static_cast(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 OurReader::decodeUnicodeCodePoint(Token& token, + Location& current, + 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 OurReader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", + token, + current); + int 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); + } + ret_unicode = static_cast(unicode); + return true; +} + +bool +OurReader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool OurReader::recoverFromError(TokenType skipUntilToken) { + size_t errorCount = 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 OurReader::addErrorAndRecover(const JSONCPP_STRING& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& OurReader::currentValue() { return *(nodes_.top()); } + +OurReader::Char OurReader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void OurReader::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; +} + +JSONCPP_STRING OurReader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; +} + +JSONCPP_STRING OurReader::getFormattedErrorMessages() const { + JSONCPP_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::vector OurReader::getStructuredErrors() const { + std::vector allErrors; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool OurReader::pushError(const Value& value, const JSONCPP_STRING& message) { + ptrdiff_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = 0; + errors_.push_back(info); + return true; +} + +bool OurReader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { + ptrdiff_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length + || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool OurReader::good() const { + return !errors_.size(); +} + + +class OurCharReader : public CharReader { + bool const collectComments_; + OurReader reader_; +public: + OurCharReader( + bool collectComments, + OurFeatures const& features) + : collectComments_(collectComments) + , reader_(features) + {} + bool parse( + char const* beginDoc, char const* endDoc, + Value* root, JSONCPP_STRING* errs) JSONCPP_OVERRIDE { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } +}; + +CharReaderBuilder::CharReaderBuilder() +{ + setDefaults(&settings_); +} +CharReaderBuilder::~CharReaderBuilder() +{} +CharReader* CharReaderBuilder::newCharReader() const +{ + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); + features.stackLimit_ = settings_["stackLimit"].asInt(); + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + return new OurCharReader(collectComments, features); +} +static void getValidReaderKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("collectComments"); + valid_keys->insert("allowComments"); + valid_keys->insert("strictRoot"); + valid_keys->insert("allowDroppedNullPlaceholders"); + valid_keys->insert("allowNumericKeys"); + valid_keys->insert("allowSingleQuotes"); + valid_keys->insert("stackLimit"); + valid_keys->insert("failIfExtra"); + valid_keys->insert("rejectDupKeys"); + valid_keys->insert("allowSpecialFloats"); +} +bool CharReaderBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidReaderKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + JSONCPP_STRING const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return 0u == inv.size(); +} +Value& CharReaderBuilder::operator[](JSONCPP_STRING key) +{ + return settings_[key]; +} +// static +void CharReaderBuilder::strictMode(Json::Value* settings) +{ +//! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; + (*settings)["allowSpecialFloats"] = false; +//! [CharReaderBuilderStrictMode] +} +// static +void CharReaderBuilder::setDefaults(Json::Value* settings) +{ +//! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; +//! [CharReaderBuilderDefaults] +} + +////////////////////////////////// +// global functions + +bool parseFromStream( + CharReader::Factory const& fact, JSONCPP_ISTREAM& sin, + Value* root, JSONCPP_STRING* errs) +{ + JSONCPP_OSTRINGSTREAM ssin; + ssin << sin.rdbuf(); + JSONCPP_STRING doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); +} + +JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM& sin, Value& root) { + CharReaderBuilder b; + JSONCPP_STRING errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) { + fprintf(stderr, + "Error from reader: %s", + errs.c_str()); + + throwRuntimeError(errs); + } + return sin; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// 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() + : current_(), isNull_(true) { +} + +ValueIteratorBase::ValueIteratorBase( + const Value::ObjectValues::iterator& current) + : current_(current), isNull_(false) {} + +Value& ValueIteratorBase::deref() const { + return current_->second; +} + +void ValueIteratorBase::increment() { + ++current_; +} + +void ValueIteratorBase::decrement() { + --current_; +} + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance(const SelfType& other) const { +#ifdef JSON_USE_CPPTL_SMALLMAP + return other.current_ - 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 +} + +bool ValueIteratorBase::isEqual(const SelfType& other) const { + if (isNull_) { + return other.isNull_; + } + return current_ == other.current_; +} + +void ValueIteratorBase::copy(const SelfType& other) { + current_ = other.current_; + isNull_ = other.isNull_; +} + +Value ValueIteratorBase::key() const { + const Value::CZString czstring = (*current_).first; + if (czstring.data()) { + if (czstring.isStaticString()) + return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); +} + +UInt ValueIteratorBase::index() const { + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) + return czstring.index(); + return Value::UInt(-1); +} + +JSONCPP_STRING ValueIteratorBase::name() const { + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) return JSONCPP_STRING(); + return JSONCPP_STRING(keey, end); +} + +char const* ValueIteratorBase::memberName() const { + const char* cname = (*current_).first.data(); + return cname ? cname : ""; +} + +char const* ValueIteratorBase::memberName(char const** end) const { + const char* cname = (*current_).first.data(); + if (!cname) { + *end = NULL; + return NULL; + } + *end = cname + (*current_).first.length(); + return cname; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() {} + +ValueConstIterator::ValueConstIterator( + const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueConstIterator::ValueConstIterator(ValueIterator const& other) + : ValueIteratorBase(other) {} + +ValueConstIterator& ValueConstIterator:: +operator=(const ValueIteratorBase& other) { + copy(other); + return *this; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() {} + +ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueIterator::ValueIterator(const ValueConstIterator& other) + : ValueIteratorBase(other) { + throwRuntimeError("ConstIterator to Iterator should never be allowed."); +} + +ValueIterator::ValueIterator(const ValueIterator& other) + : ValueIteratorBase(other) {} + +ValueIterator& ValueIterator::operator=(const SelfType& other) { + copy(other); + return *this; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#ifdef JSON_USE_CPPTL +#include +#endif +#include // size_t +#include // min() + +#define JSON_ASSERT_UNREACHABLE assert(false) + +namespace Json { + +// This is a walkaround to avoid the static initialization of Value::null. +// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of +// 8 (instead of 4) as a bit of future-proofing. +#if defined(__ARMEL__) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#else +#define ALIGNAS(byte_alignment) +#endif +//static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; +//const unsigned char& kNullRef = kNull[0]; +//const Value& Value::null = reinterpret_cast(kNullRef); +//const Value& Value::nullRef = null; + +// static +Value const& Value::nullSingleton() +{ + static Value const nullStatic; + return nullStatic; +} + +// for backwards compatibility, we'll leave these global references around, but DO NOT +// use them in JSONCPP library code any more! +Value const& Value::null = Value::nullSingleton(); +Value const& Value::nullRef = Value::nullSingleton(); + +const Int Value::minInt = Int(~(UInt(-1) / 2)); +const Int Value::maxInt = Int(UInt(-1) / 2); +const UInt Value::maxUInt = UInt(-1); +#if defined(JSON_HAS_INT64) +const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); +const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); +const UInt64 Value::maxUInt64 = UInt64(-1); +// The constant is hard-coded because some compiler have trouble +// converting Value::maxUInt64 to a double correctly (AIX/xlC). +// Assumes that UInt64 is a 64 bits integer. +static const double maxUInt64AsDouble = 18446744073709551615.0; +#endif // defined(JSON_HAS_INT64) +const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); +const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +template +static inline bool InRange(double d, T min, U max) { + // The casts can lose precision, but we are looking only for + // an approximate range. Might fail on edge cases though. ~cdunn + //return d >= static_cast(min) && d <= static_cast(max); + return d >= min && d <= max; +} +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble(Json::UInt64 value) { + return static_cast(Int64(value / 2)) * 2.0 + static_cast(Int64(value & 1)); +} + +template static inline double integerToDouble(T value) { + return static_cast(value); +} + +template +static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); +} +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + +/** 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, + size_t length) +{ + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= static_cast(Value::maxInt)) + length = Value::maxInt - 1; + + char* newString = static_cast(malloc(length + 1)); + if (newString == NULL) { + throwRuntimeError( + "in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; +} + +/* Record the length as a prefix. + */ +static inline char* duplicateAndPrefixStringValue( + const char* value, + unsigned int length) +{ + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + unsigned actualLength = length + static_cast(sizeof(unsigned)) + 1U; + char* newString = static_cast(malloc(actualLength)); + if (newString == 0) { + throwRuntimeError( + "in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later + return newString; +} +inline static void decodePrefixedString( + bool isPrefixed, char const* prefixed, + unsigned* length, char const** value) +{ + if (!isPrefixed) { + *length = static_cast(strlen(prefixed)); + *value = prefixed; + } else { + *length = *reinterpret_cast(prefixed); + *value = prefixed + sizeof(unsigned); + } +} +/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue(). + */ +#if JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { + unsigned length = 0; + char const* valueDecoded; + decodePrefixedString(true, value, &length, &valueDecoded); + size_t const size = sizeof(unsigned) + length + 1U; + memset(value, 0, size); + free(value); +} +static inline void releaseStringValue(char* value, unsigned length) { + // length==0 => we allocated the strings memory + size_t size = (length==0) ? strlen(value) : length; + memset(value, 0, size); + free(value); +} +#else // !JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { + free(value); +} +static inline void releaseStringValue(char* value, unsigned) { + free(value); +} +#endif // JSONCPP_USING_SECURE_MEMORY + +} // namespace Json + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) + +#include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +Exception::Exception(JSONCPP_STRING const& msg) + : msg_(msg) +{} +Exception::~Exception() JSONCPP_NOEXCEPT +{} +char const* Exception::what() const JSONCPP_NOEXCEPT +{ + return msg_.c_str(); +} +RuntimeError::RuntimeError(JSONCPP_STRING const& msg) + : Exception(msg) +{} +LogicError::LogicError(JSONCPP_STRING const& msg) + : Exception(msg) +{} +JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg) +{ + throw RuntimeError(msg); +} +JSONCPP_NORETURN void throwLogicError(JSONCPP_STRING const& msg) +{ + throw LogicError(msg); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CommentInfo +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +Value::CommentInfo::CommentInfo() : comment_(0) +{} + +Value::CommentInfo::~CommentInfo() { + if (comment_) + releaseStringValue(comment_, 0u); +} + +void Value::CommentInfo::setComment(const char* text, size_t len) { + if (comment_) { + releaseStringValue(comment_, 0u); + comment_ = 0; + } + JSON_ASSERT(text != 0); + JSON_ASSERT_MESSAGE( + text[0] == '\0' || text[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + // It seems that /**/ style comments are acceptable as well. + comment_ = duplicateStringValue(text, len); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +// Notes: policy_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {} + +Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate) + : cstr_(str) { + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = ulength & 0x3FFFFFFF; +} + +Value::CZString::CZString(const CZString& other) { + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != 0 + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = static_cast(other.cstr_ + ? (static_cast(other.storage_.policy_) == noDuplication + ? noDuplication : duplicate) + : static_cast(other.storage_.policy_)) & 3U; + storage_.length_ = other.storage_.length_; +} + +#if JSON_HAS_RVALUE_REFERENCES +Value::CZString::CZString(CZString&& other) + : cstr_(other.cstr_), index_(other.index_) { + other.cstr_ = nullptr; +} +#endif + +Value::CZString::~CZString() { + if (cstr_ && storage_.policy_ == duplicate) { + releaseStringValue(const_cast(cstr_), storage_.length_ + 1u); //+1 for null terminating character for sake of completeness but not actually necessary + } +} + +void Value::CZString::swap(CZString& other) { + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); +} + +Value::CZString& Value::CZString::operator=(const CZString& other) { + cstr_ = other.cstr_; + index_ = other.index_; + return *this; +} + +#if JSON_HAS_RVALUE_REFERENCES +Value::CZString& Value::CZString::operator=(CZString&& other) { + cstr_ = other.cstr_; + index_ = other.index_; + other.cstr_ = nullptr; + return *this; +} +#endif + +bool Value::CZString::operator<(const CZString& other) const { + if (!cstr_) return index_ < other.index_; + //return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); +} + +bool Value::CZString::operator==(const CZString& other) const { + if (!cstr_) return index_ == other.index_; + //return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) return false; + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; +} + +ArrayIndex Value::CZString::index() const { return index_; } + +//const char* Value::CZString::c_str() const { return cstr_; } +const char* Value::CZString::data() const { return cstr_; } +unsigned Value::CZString::length() const { return storage_.length_; } +bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; } + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// 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 vtype) { + static char const emptyString[] = ""; + initBasic(vtype); + switch (vtype) { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + // allocated_ == false, so this is safe. + value_.string_ = const_cast(static_cast(emptyString)); + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +Value::Value(Int value) { + initBasic(intValue); + value_.int_ = value; +} + +Value::Value(UInt value) { + initBasic(uintValue); + value_.uint_ = value; +} +#if defined(JSON_HAS_INT64) +Value::Value(Int64 value) { + initBasic(intValue); + value_.int_ = value; +} +Value::Value(UInt64 value) { + initBasic(uintValue); + value_.uint_ = value; +} +#endif // defined(JSON_HAS_INT64) + +Value::Value(double value) { + initBasic(realValue); + value_.real_ = value; +} + +Value::Value(const char* value) { + initBasic(stringValue, true); + JSON_ASSERT_MESSAGE(value != NULL, "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(strlen(value))); +} + +Value::Value(const char* beginValue, const char* endValue) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(beginValue, static_cast(endValue - beginValue)); +} + +Value::Value(const JSONCPP_STRING& value) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(value.data(), static_cast(value.length())); +} + +Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value::Value(const CppTL::ConstString& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(value.length())); +} +#endif + +Value::Value(bool value) { + initBasic(booleanValue); + value_.bool_ = value; +} + +Value::Value(Value const& other) + : type_(other.type_), allocated_(false) + , + comments_(0), start_(other.start_), limit_(other.limit_) +{ + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.allocated_) { + unsigned len; + char const* str; + decodePrefixedString(other.allocated_, other.value_.string_, + &len, &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + allocated_ = true; + } else { + value_.string_ = other.value_.string_; + allocated_ = false; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + 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_, strlen(otherComment.comment_)); + } + } +} + +#if JSON_HAS_RVALUE_REFERENCES +// Move constructor +Value::Value(Value&& other) { + initBasic(nullValue); + swap(other); +} +#endif + +Value::~Value() { + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (allocated_) + releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } + + delete[] comments_; + + value_.uint_ = 0; +} + +Value& Value::operator=(Value other) { + swap(other); + return *this; +} + +void Value::swapPayload(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 & 0x1; +} + +void Value::copyPayload(const Value& other) { + type_ = other.type_; + value_ = other.value_; + allocated_ = other.allocated_; +} + +void Value::swap(Value& other) { + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); +} + +void Value::copy(const Value& other) { + copyPayload(other); + comments_ = other.comments_; + start_ = other.start_; + limit_ = other.limit_; +} + +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: + { + if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + if (other.value_.string_) return true; + else return false; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); + } + 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_); + } + 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: + { + if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + if (this_len != other_len) return false; + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && + (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator!=(const Value& other) const { return !(*this == other); } + +const char* Value::asCString() const { + JSON_ASSERT_MESSAGE(type_ == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return this_str; +} + +#if JSONCPP_USING_SECURE_MEMORY +unsigned Value::getCStringLength() const { + JSON_ASSERT_MESSAGE(type_ == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return this_len; +} +#endif + +bool Value::getString(char const** str, char const** cend) const { + if (type_ != stringValue) return false; + if (value_.string_ == 0) return false; + unsigned length; + decodePrefixedString(this->allocated_, this->value_.string_, &length, str); + *cend = *str + length; + return true; +} + +JSONCPP_STRING Value::asString() const { + switch (type_) { + case nullValue: + return ""; + case stringValue: + { + if (value_.string_ == 0) return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return JSONCPP_STRING(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } +} + +#ifdef JSON_USE_CPPTL +CppTL::ConstString Value::asConstString() const { + unsigned len; + char const* str; + decodePrefixedString(allocated_, value_.string_, + &len, &str); + return CppTL::ConstString(str, len); +} +#endif + +Value::Int Value::asInt() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), + "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); +} + +Value::UInt Value::asUInt() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +} + +#if defined(JSON_HAS_INT64) + +Value::Int64 Value::asInt64() const { + switch (type_) { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), + "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +} + +Value::UInt64 Value::asUInt64() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +} +#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 intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); +} + +float Value::asFloat() const { + switch (type_) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + // This can fail (silently?) if the value is bigger than MAX_FLOAT. + return static_cast(integerToDouble(value_.uint_)); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); +} + +bool Value::asBool() const { + switch (type_) { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ ? true : false; + case uintValue: + return value_.uint_ ? true : false; + case realValue: + // This is kind of strange. Not recommended. + return (value_.real_ != 0.0) ? true : false; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + +bool Value::isConvertibleTo(ValueType other) const { + switch (other) { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || + (type_ == booleanValue && value_.bool_ == false) || + (type_ == stringValue && asString().empty()) || + (type_ == arrayValue && value_.map_->size() == 0) || + (type_ == objectValue && value_.map_->size() == 0) || + type_ == nullValue; + case intValue: + return isInt() || + (type_ == realValue && InRange(value_.real_, minInt, maxInt)) || + type_ == booleanValue || type_ == nullValue; + case uintValue: + return isUInt() || + (type_ == realValue && InRange(value_.real_, 0, maxUInt)) || + type_ == booleanValue || type_ == nullValue; + case realValue: + return isNumeric() || type_ == booleanValue || type_ == nullValue; + case booleanValue: + return isNumeric() || type_ == booleanValue || type_ == nullValue; + case stringValue: + return isNumeric() || type_ == booleanValue || type_ == stringValue || + type_ == nullValue; + case arrayValue: + return type_ == arrayValue || type_ == nullValue; + case objectValue: + return type_ == objectValue || type_ == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; +} + +/// 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; + 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()); + } + 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_MESSAGE(type_ == nullValue || type_ == arrayValue || + type_ == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type_) { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; + } +} + +void Value::resize(ArrayIndex newSize) { + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, + "in Json::Value::resize(): requires arrayValue"); + if (type_ == nullValue) + *this = Value(arrayValue); + 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); + } + JSON_ASSERT(size() == newSize); + } +} + +Value& Value::operator[](ArrayIndex index) { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type_ == nullValue) + *this = Value(arrayValue); + 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, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + return (*it).second; +} + +Value& Value::operator[](int index) { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +const Value& Value::operator[](ArrayIndex index) const { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type_ == nullValue) + return nullSingleton(); + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) + return nullSingleton(); + return (*it).second; +} + +const Value& Value::operator[](int index) const { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +void Value::initBasic(ValueType vtype, bool allocated) { + type_ = vtype; + allocated_ = allocated; + comments_ = 0; + start_ = 0; + limit_ = 0; +} + +// Access an object value by name, create a null member if it does not exist. +// @pre Type of '*this' is object or null. +// @param key is null-terminated. +Value& Value::resolveReference(const char* key) { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type_ == nullValue) + *this = Value(objectValue); + CZString actualKey( + key, static_cast(strlen(key)), CZString::noDuplication); // NOTE! + ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +// @param key is not null-terminated. +Value& Value::resolveReference(char const* key, char const* cend) +{ + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type_ == nullValue) + *this = Value(objectValue); + CZString actualKey( + key, static_cast(cend-key), 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, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +Value Value::get(ArrayIndex index, const Value& defaultValue) const { + const Value* value = &((*this)[index]); + return value == &nullSingleton() ? defaultValue : *value; +} + +bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } + +Value const* Value::find(char const* key, char const* cend) const +{ + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::find(key, end, found): requires objectValue or nullValue"); + if (type_ == nullValue) return NULL; + CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) return NULL; + return &(*it).second; +} +const Value& Value::operator[](const char* key) const +{ + Value const* found = find(key, key + strlen(key)); + if (!found) return nullSingleton(); + return *found; +} +Value const& Value::operator[](JSONCPP_STRING const& key) const +{ + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) return nullSingleton(); + return *found; +} + +Value& Value::operator[](const char* key) { + return resolveReference(key, key + strlen(key)); +} + +Value& Value::operator[](const JSONCPP_STRING& key) { + return resolveReference(key.data(), key.data() + key.length()); +} + +Value& Value::operator[](const StaticString& key) { + return resolveReference(key.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value& Value::operator[](const CppTL::ConstString& key) { + return resolveReference(key.c_str(), key.end_c_str()); +} +Value const& Value::operator[](CppTL::ConstString const& key) const +{ + Value const* found = find(key.c_str(), key.end_c_str()); + if (!found) return nullSingleton(); + return *found; +} +#endif + +Value& Value::append(const Value& value) { return (*this)[size()] = value; } + +#if JSON_HAS_RVALUE_REFERENCES + Value& Value::append(Value&& value) { return (*this)[size()] = value; } +#endif + +Value Value::get(char const* key, char const* cend, Value const& defaultValue) const +{ + Value const* found = find(key, cend); + return !found ? defaultValue : *found; +} +Value Value::get(char const* key, Value const& defaultValue) const +{ + return get(key, key + strlen(key), defaultValue); +} +Value Value::get(JSONCPP_STRING const& key, Value const& defaultValue) const +{ + return get(key.data(), key.data() + key.length(), defaultValue); +} + + +bool Value::removeMember(const char* key, const char* cend, Value* removed) +{ + if (type_ != objectValue) { + return false; + } + CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); + ObjectValues::iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return false; + *removed = it->second; + value_.map_->erase(it); + return true; +} +bool Value::removeMember(const char* key, Value* removed) +{ + return removeMember(key, key + strlen(key), removed); +} +bool Value::removeMember(JSONCPP_STRING const& key, Value* removed) +{ + return removeMember(key.data(), key.data() + key.length(), removed); +} +Value Value::removeMember(const char* key) +{ + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type_ == nullValue) + return nullSingleton(); + + Value removed; // null + removeMember(key, key + strlen(key), &removed); + return removed; // still null if removeMember() did nothing +} +Value Value::removeMember(const JSONCPP_STRING& key) +{ + return removeMember(key.c_str()); +} + +bool Value::removeIndex(ArrayIndex index, Value* removed) { + if (type_ != arrayValue) { + return false; + } + CZString key(index); + ObjectValues::iterator it = value_.map_->find(key); + if (it == value_.map_->end()) { + return false; + } + *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i){ + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + ObjectValues::iterator itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; +} + +#ifdef JSON_USE_CPPTL +Value Value::get(const CppTL::ConstString& key, + const Value& defaultValue) const { + return get(key.c_str(), key.end_c_str(), defaultValue); +} +#endif + +bool Value::isMember(char const* key, char const* cend) const +{ + Value const* value = find(key, cend); + return NULL != value; +} +bool Value::isMember(char const* key) const +{ + return isMember(key, key + strlen(key)); +} +bool Value::isMember(JSONCPP_STRING const& key) const +{ + return isMember(key.data(), key.data() + key.length()); +} + +#ifdef JSON_USE_CPPTL +bool Value::isMember(const CppTL::ConstString& key) const { + return isMember(key.c_str(), key.end_c_str()); +} +#endif + +Value::Members Value::getMemberNames() const { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type_ == nullValue) + return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) { + members.push_back(JSONCPP_STRING((*it).first.data(), + (*it).first.length())); + } + 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() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +// EnumValues +// Value::enumValues() const +//{ +// if ( type_ == objectValue || type_ == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type() ); +// return EnumValues(); +//} +// +//# endif + +static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; +} + +bool Value::isNull() const { return type_ == nullValue; } + +bool Value::isBool() const { return type_ == booleanValue; } + +bool Value::isInt() const { + switch (type_) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= minInt && value_.int_ <= maxInt; +#else + return true; +#endif + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isUInt() const { + switch (type_) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); +#else + return value_.int_ >= 0; +#endif + case uintValue: +#if defined(JSON_HAS_INT64) + return value_.uint_ <= maxUInt; +#else + return true; +#endif + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isInt64() const { +#if defined(JSON_HAS_INT64) + switch (type_) { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isUInt64() const { +#if defined(JSON_HAS_INT64) + switch (type_) { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && + IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isIntegral() const { + switch (type_) { + case intValue: + case uintValue: + return true; + case realValue: +#if defined(JSON_HAS_INT64) + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); +#else + return value_.real_ >= minInt && value_.real_ <= maxUInt && IsIntegral(value_.real_); +#endif // JSON_HAS_INT64 + default: + break; + } + return false; +} + +bool Value::isDouble() const { return type_ == intValue || type_ == uintValue || type_ == realValue; } + +bool Value::isNumeric() const { return isDouble(); } + +bool Value::isString() const { return type_ == stringValue; } + +bool Value::isArray() const { return type_ == arrayValue; } + +bool Value::isObject() const { return type_ == objectValue; } + +void Value::setComment(const char* comment, size_t len, CommentPlacement placement) { + if (!comments_) + comments_ = new CommentInfo[numberOfCommentPlacement]; + if ((len > 0) && (comment[len-1] == '\n')) { + // Always discard trailing newline, to aid indentation. + len -= 1; + } + comments_[placement].setComment(comment, len); +} + +void Value::setComment(const char* comment, CommentPlacement placement) { + setComment(comment, strlen(comment), placement); +} + +void Value::setComment(const JSONCPP_STRING& comment, CommentPlacement placement) { + setComment(comment.c_str(), comment.length(), placement); +} + +bool Value::hasComment(CommentPlacement placement) const { + return comments_ != 0 && comments_[placement].comment_ != 0; +} + +JSONCPP_STRING Value::getComment(CommentPlacement placement) const { + if (hasComment(placement)) + return comments_[placement].comment_; + return ""; +} + +void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } + +void Value::setOffsetLimit(ptrdiff_t limit) { limit_ = limit; } + +ptrdiff_t Value::getOffsetStart() const { return start_; } + +ptrdiff_t Value::getOffsetLimit() const { return limit_; } + +JSONCPP_STRING Value::toStyledString() const { + StreamWriterBuilder builder; + + JSONCPP_STRING out = this->hasComment(commentBefore) ? "\n" : ""; + out += Json::writeString(builder, *this); + out += "\n"; + + return out; +} + +Value::const_iterator Value::begin() const { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return const_iterator(); +} + +Value::const_iterator Value::end() const { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->end()); + break; + default: + break; + } + return const_iterator(); +} + +Value::iterator Value::begin() { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); +} + +Value::iterator Value::end() { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); +} + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {} + +PathArgument::PathArgument(ArrayIndex index) + : key_(), index_(index), kind_(kindIndex) {} + +PathArgument::PathArgument(const char* key) + : key_(key), index_(), kind_(kindKey) {} + +PathArgument::PathArgument(const JSONCPP_STRING& key) + : key_(key.c_str()), index_(), kind_(kindKey) {} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path(const JSONCPP_STRING& path, + const PathArgument& a1, + const PathArgument& a2, + const PathArgument& a3, + const PathArgument& a4, + const PathArgument& a5) { + InArgs in; + in.reserve(5); + 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 JSONCPP_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 == ']') { + ++current; + } else { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) + ++current; + args_.push_back(JSONCPP_STRING(beginName, current)); + } + } +} + +void Path::addPathInArg(const JSONCPP_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 JSONCPP_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... + return Value::null; + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: unable to resolve path (object value expected at position...) + return Value::null; + } + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) { + // Error: unable to resolve path (object has no member named '' at + // position...) + return Value::null; + } + } + } + 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::nullSingleton()) + 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 + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// 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 +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0 +#include +#define isfinite _finite +#elif defined(__sun) && defined(__SVR4) //Solaris +#if !defined(isfinite) +#include +#define isfinite finite +#endif +#elif defined(_AIX) +#if !defined(isfinite) +#include +#define isfinite finite +#endif +#elif defined(__hpux) +#if !defined(isfinite) +#if defined(__ia64) && !defined(finite) +#define isfinite(x) ((sizeof(x) == sizeof(float) ? \ + _Isfinitef(x) : _IsFinite(x))) +#else +#include +#define isfinite finite +#endif +#endif +#else +#include +#if !(defined(__QNXNTO__)) // QNX already defines isfinite +#define isfinite std::isfinite +#endif +#endif + +#if defined(_MSC_VER) +#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above +#define snprintf sprintf_s +#elif _MSC_VER >= 1900 // VC++ 14.0 and above +#define snprintf std::snprintf +#else +#define snprintf _snprintf +#endif +#elif defined(__ANDROID__) || defined(__QNXNTO__) +#define snprintf snprintf +#elif __cplusplus >= 201103L +#if !defined(__MINGW32__) && !defined(__CYGWIN__) +#define snprintf std::snprintf +#endif +#endif + +#if defined(__BORLANDC__) +#include +#define isfinite _finite +#define snprintf _snprintf +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) +typedef std::unique_ptr StreamWriterPtr; +#else +typedef std::auto_ptr StreamWriterPtr; +#endif + +static bool containsControlCharacter(const char* str) { + while (*str) { + if (isControlCharacter(*(str++))) + return true; + } + return false; +} + +static bool containsControlCharacter0(const char* str, unsigned len) { + char const* end = str + len; + while (end != str) { + if (isControlCharacter(*str) || 0==*str) + return true; + ++str; + } + return false; +} + +JSONCPP_STRING valueToString(LargestInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } else if (value < 0) { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } else { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; +} + +JSONCPP_STRING valueToString(LargestUInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; +} + +#if defined(JSON_HAS_INT64) + +JSONCPP_STRING valueToString(Int value) { + return valueToString(LargestInt(value)); +} + +JSONCPP_STRING valueToString(UInt value) { + return valueToString(LargestUInt(value)); +} + +#endif // # if defined(JSON_HAS_INT64) + +namespace { +JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision) { + // Allocate a buffer that is more than large enough to store the 16 digits of + // precision requested below. + char buffer[36]; + __attribute__((unused)) + int len = -1; + + char formatString[15]; + snprintf(formatString, sizeof(formatString), "%%.%dg", precision); + + // Print into the buffer. We need not request the alternative representation + // that always has a decimal point because JSON doesn't distingish the + // concepts of reals and integers. + if (isfinite(value)) { + len = snprintf(buffer, sizeof(buffer), formatString, value); + fixNumericLocale(buffer, buffer + len); + + // try to ensure we preserve the fact that this was given to us as a double on input + if (!strchr(buffer, '.') && !strchr(buffer, 'e')) { + strcat(buffer, ".0"); + } + + } else { + // IEEE standard states that NaN values will not compare to themselves + if (value != value) { + len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null"); + } else if (value < 0) { + len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999"); + } else { + len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999"); + } + } + assert(len >= 0); + return buffer; +} +} + +JSONCPP_STRING valueToString(double value) { return valueToString(value, false, 17); } + +JSONCPP_STRING valueToString(bool value) { return value ? "true" : "false"; } + +JSONCPP_STRING valueToQuotedString(const char* value) { + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && + !containsControlCharacter(value)) + return JSONCPP_STRING("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to JSONCPP_STRING is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + JSONCPP_STRING::size_type maxsize = + strlen(value) * 2 + 3; // allescaped+quotes+NULL + JSONCPP_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 (*c); + result += oss.str(); + } else { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp +static char const* strnpbrk(char const* s, char const* accept, size_t n) { + assert((s || !n) && accept); + + char const* const end = s + n; + for (char const* cur = s; cur < end; ++cur) { + int const c = *cur; + for (char const* a = accept; *a; ++a) { + if (*a == c) { + return cur; + } + } + } + return NULL; +} +static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) { + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL && + !containsControlCharacter0(value, length)) + return JSONCPP_STRING("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to JSONCPP_STRING is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + JSONCPP_STRING::size_type maxsize = + length * 2 + 3; // allescaped+quotes+NULL + JSONCPP_STRING result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++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 (*c); + result += oss.str(); + } else { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() {} + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + : yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false), + omitEndingLineFeed_(false) {} + +void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; } + +void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } + +void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } + +JSONCPP_STRING FastWriter::write(const Value& root) { + document_.clear(); + writeValue(root); + if (!omitEndingLineFeed_) + document_ += "\n"; + return document_; +} + +void FastWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + if (!dropNullPlaceholders_) + 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: + { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) document_ += valueToQuotedStringN(str, static_cast(end-str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: { + document_ += '['; + ArrayIndex size = value.size(); + for (ArrayIndex 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 JSONCPP_STRING& name = *it; + if (it != members.begin()) + document_ += ','; + document_ += valueToQuotedStringN(name.data(), static_cast(name.length())); + document_ += yamlCompatiblityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } break; + } +} + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() + : rightMargin_(74), indentSize_(3), addChildValues_() {} + +JSONCPP_STRING StyledWriter::write(const Value& root) { + document_.clear(); + addChildValues_ = false; + indentString_.clear(); + 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: + { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + 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 JSONCPP_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) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledWriter::pushValue(const JSONCPP_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 JSONCPP_STRING& value) { + writeIndent(); + document_ += value; +} + +void StyledWriter::indent() { indentString_ += JSONCPP_STRING(indentSize_, ' '); } + +void StyledWriter::unindent() { + assert(indentString_.size() >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); +} + +void StyledWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + document_ += "\n"; + writeIndent(); + const JSONCPP_STRING& comment = root.getComment(commentBefore); + JSONCPP_STRING::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + document_ += *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here + document_ += "\n"; +} + +void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + document_ += "\n"; + document_ += root.getComment(commentAfter); + document_ += "\n"; + } +} + +bool StyledWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter(JSONCPP_STRING indentation) + : document_(NULL), rightMargin_(74), indentation_(indentation), + addChildValues_() {} + +void StyledStreamWriter::write(JSONCPP_OSTREAM& out, const Value& root) { + document_ = &out; + addChildValues_ = false; + indentString_.clear(); + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + 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: + { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + 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 JSONCPP_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 { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + 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) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledStreamWriter::pushValue(const JSONCPP_STRING& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; +} + +void StyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; +} + +void StyledStreamWriter::writeWithIndent(const JSONCPP_STRING& value) { + if (!indented_) writeIndent(); + *document_ << value; + indented_ = false; +} + +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; + + if (!indented_) writeIndent(); + const JSONCPP_STRING& comment = root.getComment(commentBefore); + JSONCPP_STRING::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *document_ << *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; +} + +void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + *document_ << ' ' << root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; +} + +bool StyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +////////////////////////// +// BuiltStyledStreamWriter + +/// Scoped enums are not available until C++11. +struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; + +struct BuiltStyledStreamWriter : public StreamWriter +{ + BuiltStyledStreamWriter( + JSONCPP_STRING const& indentation, + CommentStyle::Enum cs, + JSONCPP_STRING const& colonSymbol, + JSONCPP_STRING const& nullSymbol, + JSONCPP_STRING const& endingLineFeedSymbol, + bool useSpecialFloats, + unsigned int precision); + int write(Value const& root, JSONCPP_OSTREAM* sout) JSONCPP_OVERRIDE; +private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultineArray(Value const& value); + void pushValue(JSONCPP_STRING const& value); + void writeIndent(); + void writeWithIndent(JSONCPP_STRING const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + typedef std::vector ChildValues; + + ChildValues childValues_; + JSONCPP_STRING indentString_; + unsigned int rightMargin_; + JSONCPP_STRING indentation_; + CommentStyle::Enum cs_; + JSONCPP_STRING colonSymbol_; + JSONCPP_STRING nullSymbol_; + JSONCPP_STRING endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; + bool useSpecialFloats_ : 1; + unsigned int precision_; +}; +BuiltStyledStreamWriter::BuiltStyledStreamWriter( + JSONCPP_STRING const& indentation, + CommentStyle::Enum cs, + JSONCPP_STRING const& colonSymbol, + JSONCPP_STRING const& nullSymbol, + JSONCPP_STRING const& endingLineFeedSymbol, + bool useSpecialFloats, + unsigned int precision) + : rightMargin_(74) + , indentation_(indentation) + , cs_(cs) + , colonSymbol_(colonSymbol) + , nullSymbol_(nullSymbol) + , endingLineFeedSymbol_(endingLineFeedSymbol) + , addChildValues_(false) + , indented_(false) + , useSpecialFloats_(useSpecialFloats) + , precision_(precision) +{ +} +int BuiltStyledStreamWriter::write(Value const& root, JSONCPP_OSTREAM* sout) +{ + sout_ = sout; + addChildValues_ = false; + indented_ = true; + indentString_.clear(); + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = NULL; + return 0; +} +void BuiltStyledStreamWriter::writeValue(Value const& value) { + switch (value.type()) { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_)); + break; + case stringValue: + { + // Is NULL is possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + 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 (;;) { + JSONCPP_STRING const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN(name.data(), static_cast(name.length()))); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value); + if (isMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) *sout_ << " "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *sout_ << ((!indentation_.empty()) ? ", " : ","); + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) *sout_ << " "; + *sout_ << "]"; + } + } +} + +bool BuiltStyledStreamWriter::isMultineArray(Value const& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + Value const& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void BuiltStyledStreamWriter::pushValue(JSONCPP_STRING const& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; +} + +void BuiltStyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } +} + +void BuiltStyledStreamWriter::writeWithIndent(JSONCPP_STRING const& value) { + if (!indented_) writeIndent(); + *sout_ << value; + indented_ = false; +} + +void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } + +void BuiltStyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { + if (cs_ == CommentStyle::None) return; + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) writeIndent(); + const JSONCPP_STRING& comment = root.getComment(commentBefore); + JSONCPP_STRING::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *sout_ << *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; +} + +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { + if (cs_ == CommentStyle::None) return; + if (root.hasComment(commentAfterOnSameLine)) + *sout_ << " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } +} + +// static +bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +/////////////// +// StreamWriter + +StreamWriter::StreamWriter() + : sout_(NULL) +{ +} +StreamWriter::~StreamWriter() +{ +} +StreamWriter::Factory::~Factory() +{} +StreamWriterBuilder::StreamWriterBuilder() +{ + setDefaults(&settings_); +} +StreamWriterBuilder::~StreamWriterBuilder() +{} +StreamWriter* StreamWriterBuilder::newStreamWriter() const +{ + JSONCPP_STRING indentation = settings_["indentation"].asString(); + JSONCPP_STRING cs_str = settings_["commentStyle"].asString(); + bool eyc = settings_["enableYAMLCompatibility"].asBool(); + bool dnp = settings_["dropNullPlaceholders"].asBool(); + bool usf = settings_["useSpecialFloats"].asBool(); + unsigned int pre = settings_["precision"].asUInt(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } else if (cs_str == "None") { + cs = CommentStyle::None; + } else { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + JSONCPP_STRING colonSymbol = " : "; + if (eyc) { + colonSymbol = ": "; + } else if (indentation.empty()) { + colonSymbol = ":"; + } + JSONCPP_STRING nullSymbol = "null"; + if (dnp) { + nullSymbol.clear(); + } + if (pre > 17) pre = 17; + JSONCPP_STRING endingLineFeedSymbol; + return new BuiltStyledStreamWriter( + indentation, cs, + colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre); +} +static void getValidWriterKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("indentation"); + valid_keys->insert("commentStyle"); + valid_keys->insert("enableYAMLCompatibility"); + valid_keys->insert("dropNullPlaceholders"); + valid_keys->insert("useSpecialFloats"); + valid_keys->insert("precision"); +} +bool StreamWriterBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidWriterKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + JSONCPP_STRING const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return 0u == inv.size(); +} +Value& StreamWriterBuilder::operator[](JSONCPP_STRING key) +{ + return settings_[key]; +} +// static +void StreamWriterBuilder::setDefaults(Json::Value* settings) +{ + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + (*settings)["useSpecialFloats"] = false; + (*settings)["precision"] = 17; + //! [StreamWriterBuilderDefaults] +} + +JSONCPP_STRING writeString(StreamWriter::Factory const& builder, Value const& root) { + JSONCPP_OSTRINGSTREAM sout; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); +} + +JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM& sout, Value const& root) { + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + diff --git a/examples/ws_receive/package-lock.json b/examples/ws_receive/package-lock.json new file mode 100644 index 00000000..b0870d1b --- /dev/null +++ b/examples/ws_receive/package-lock.json @@ -0,0 +1,29 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" + }, + "djb2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/djb2/-/djb2-0.0.2.tgz", + "integrity": "sha1-RAs4kao6uBQrVNRpsXe66p6W5O8=" + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "1.0.0" + } + } + } +} diff --git a/examples/ws_receive/ws_receive.cpp b/examples/ws_receive/ws_receive.cpp new file mode 100644 index 00000000..323d550e --- /dev/null +++ b/examples/ws_receive/ws_receive.cpp @@ -0,0 +1,153 @@ +/* + * ws_receive.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + + +namespace +{ + // We should cleanup the file name and full path further to remove .. as well + std::string extractFilename(const std::string& path) + { + std::string filename("filename.conf"); + std::string::size_type idx; + + idx = path.rfind('/'); + if (idx != std::string::npos) + { + std::string filename = path.substr(idx+1); + return filename; + } + else + { + return std::string(); + } + } +} + +namespace ix +{ + void errorHandler(const std::string& errMsg, + const std::string& id, + std::shared_ptr webSocket) + { + Json::Value pdu; + pdu["kind"] = "error"; + pdu["id"] = id; + pdu["message"] = errMsg; + webSocket->send(pdu.toStyledString()); + } + + void messageHandler(const std::string& str, + std::shared_ptr webSocket) + { + std::cerr << "Received message: " << str.size() << std::endl; + + Json::Value data; + Json::Reader reader; + if (!reader.parse(str, data)) + { + errorHandler("Invalid JSON", std::string(), webSocket); + return; + } + + std::cout << "id: " << data["id"].asString() << std::endl; + + std::string content = ix::base64_decode(data["content"].asString()); + std::cout << "Content size: " << content.size() << std::endl; + + // Validate checksum + uint64_t cksum = ix::djb2Hash(data["content"].asString()); + uint64_t cksumRef = data["djb2_hash"].asUInt64(); + + std::cout << "Computed hash: " << cksum << std::endl; + std::cout << "Reference hash: " << cksumRef << std::endl; + + if (cksum != cksumRef) + { + errorHandler("Hash mismatch.", std::string(), webSocket); + return; + } + + std::string filename = data["filename"].asString(); + filename = extractFilename(filename); + + std::ofstream out(filename); + out << content; + out.close(); + + Json::Value pdu; + pdu["ack"] = true; + pdu["id"] = data["id"]; + pdu["filename"] = data["filename"]; + webSocket->send(pdu.toStyledString()); + } +} + +int main(int argc, char** argv) +{ + int port = 8080; + if (argc == 2) + { + std::stringstream ss; + ss << argv[1]; + ss >> port; + } + + ix::WebSocketServer server(port); + + server.setOnConnectionCallback( + [&server](std::shared_ptr webSocket) + { + webSocket->setOnMessageCallback( + [webSocket, &server](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + if (messageType == ix::WebSocket_MessageType_Open) + { + std::cerr << "New connection" << std::endl; + std::cerr << "Uri: " << openInfo.uri << std::endl; + std::cerr << "Headers:" << std::endl; + for (auto it : openInfo.headers) + { + std::cerr << it.first << ": " << it.second << std::endl; + } + } + else if (messageType == ix::WebSocket_MessageType_Close) + { + std::cerr << "Closed connection" << std::endl; + } + else if (messageType == ix::WebSocket_MessageType_Message) + { + messageHandler(str, webSocket); + } + } + ); + } + ); + + auto res = server.listen(); + if (!res.first) + { + std::cerr << res.second << std::endl; + return 1; + } + + server.start(); + server.wait(); + + return 0; +} diff --git a/examples/ws_receive/ws_receive.js b/examples/ws_receive/ws_receive.js new file mode 100644 index 00000000..465ff11e --- /dev/null +++ b/examples/ws_receive/ws_receive.js @@ -0,0 +1,43 @@ +/* + * ws_receive.js + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ +const WebSocket = require('ws') +const djb2 = require('djb2') +const fs = require('fs') + +const wss = new WebSocket.Server({ port: 8080, + perMessageDeflate: false, + maxPayload: 1024 * 1024 * 1024 * 1024}); + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(data) { + console.log('Received message') + + let str = data.toString() + let obj = JSON.parse(str) + + console.log(obj.id) + console.log(obj.djb2_hash) + console.log(djb2(obj.content)) + + var content = Buffer.from(obj.content, 'base64') + // let bytes = base64.decode(obj.content) + + let path = obj.filename + fs.writeFile(path, content, function(err) { + if (err) { + throw err + } else { + console.log('wrote data to disk') + } + }); + + let response = { + id: obj.id + } + + ws.send(JSON.stringify(response)) + }); +}); diff --git a/examples/ws_send/.gitignore b/examples/ws_send/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/examples/ws_send/.gitignore @@ -0,0 +1 @@ +build diff --git a/examples/ws_send/CMakeLists.txt b/examples/ws_send/CMakeLists.txt new file mode 100644 index 00000000..316434e5 --- /dev/null +++ b/examples/ws_send/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Author: Benjamin Sergeant +# Copyright (c) 2019 Machine Zone, Inc. All rights reserved. +# + +cmake_minimum_required (VERSION 3.4.1) +project (ws_send) + +# There's -Weverything too for clang +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32") + +set (CMAKE_CXX_STANDARD 14) + +option(USE_TLS "Add TLS support" ON) + +add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket) + +include_directories(ws_send .) + +add_executable(ws_send + jsoncpp/jsoncpp.cpp + ixcrypto/IXBase64.cpp + ixcrypto/IXUuid.cpp + ixcrypto/IXHash.cpp + ws_send.cpp) + +if (APPLE AND USE_TLS) + target_link_libraries(ws_send "-framework foundation" "-framework security") +endif() + +target_link_libraries(ws_send ixwebsocket) diff --git a/examples/ws_send/README.md b/examples/ws_send/README.md new file mode 100644 index 00000000..7bd97c52 --- /dev/null +++ b/examples/ws_send/README.md @@ -0,0 +1 @@ +ws_send is a simple upload program. It needs to be used in conjonction with ws_receive. diff --git a/examples/ws_send/ixcrypto b/examples/ws_send/ixcrypto new file mode 120000 index 00000000..1b13e4a5 --- /dev/null +++ b/examples/ws_send/ixcrypto @@ -0,0 +1 @@ +../cobra_publisher/ixcrypto \ No newline at end of file diff --git a/examples/ws_send/jsoncpp b/examples/ws_send/jsoncpp new file mode 120000 index 00000000..00ead268 --- /dev/null +++ b/examples/ws_send/jsoncpp @@ -0,0 +1 @@ +../cobra_publisher/jsoncpp \ No newline at end of file diff --git a/examples/ws_send/ws_send.cpp b/examples/ws_send/ws_send.cpp new file mode 100644 index 00000000..df114fd2 --- /dev/null +++ b/examples/ws_send/ws_send.cpp @@ -0,0 +1,306 @@ +/* + * ws_send.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ix; + +namespace +{ + void log(const std::string& msg) + { + std::cout << msg << std::endl; + } + + class WebSocketSender + { + public: + WebSocketSender(const std::string& _url, + bool enablePerMessageDeflate); + + void subscribe(const std::string& channel); + void start(); + void stop(); + + void waitForConnection(); + void waitForAck(); + + void sendMessage(const std::string& filename, bool throttle); + + private: + std::string _url; + std::string _id; + ix::WebSocket _webSocket; + bool _enablePerMessageDeflate; + + std::mutex _conditionVariableMutex; + std::condition_variable _condition; + }; + + WebSocketSender::WebSocketSender(const std::string& url, + bool enablePerMessageDeflate) : + _url(url), + _enablePerMessageDeflate(enablePerMessageDeflate) + { + ; + } + + void WebSocketSender::stop() + { + _webSocket.stop(); + } + + void WebSocketSender::waitForConnection() + { + std::cout << "Connecting..." << std::endl; + + std::unique_lock lock(_conditionVariableMutex); + _condition.wait(lock); + } + + void WebSocketSender::waitForAck() + { + std::cout << "Waiting for ack..." << std::endl; + + std::unique_lock lock(_conditionVariableMutex); + _condition.wait(lock); + } + + std::string load(const std::string& path) + { + // std::vector memblock; + std::string str; + + std::ifstream file(path); + if (!file.is_open()) return std::string(); + + file.seekg(0, file.end); + std::streamoff size = file.tellg(); + file.seekg(0, file.beg); + + str.resize(size); + file.read((char*)&str.front(), static_cast(size)); + + return str; + } + + void WebSocketSender::start() + { + _webSocket.setUrl(_url); + + ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions( + _enablePerMessageDeflate, false, false, 15, 15); + _webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions); + + std::stringstream ss; + log(std::string("Connecting to url: ") + _url); + + _webSocket.setOnMessageCallback( + [this](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + std::stringstream ss; + if (messageType == ix::WebSocket_MessageType_Open) + { + _condition.notify_one(); + + log("ws_send: connected"); + std::cout << "Uri: " << openInfo.uri << std::endl; + std::cout << "Handshake Headers:" << std::endl; + for (auto it : openInfo.headers) + { + std::cout << it.first << ": " << it.second << std::endl; + } + } + else if (messageType == ix::WebSocket_MessageType_Close) + { + ss << "ws_send: connection closed:"; + ss << " code " << closeInfo.code; + ss << " reason " << closeInfo.reason << std::endl; + log(ss.str()); + } + else if (messageType == ix::WebSocket_MessageType_Message) + { + _condition.notify_one(); + + ss << "ws_send: received message: " + << str; + log(ss.str()); + + Json::Value data; + Json::Reader reader; + if (!reader.parse(str, data)) + { + std::cerr << "Invalid JSON response" << std::endl; + return; + } + + std::string id = data["id"].asString(); + if (_id != id) + { + std::cerr << "Invalid id" << std::endl; + } + } + else if (messageType == ix::WebSocket_MessageType_Error) + { + ss << "Connection error: " << error.reason << std::endl; + ss << "#retries: " << error.retries << std::endl; + ss << "Wait time(ms): " << error.wait_time << std::endl; + ss << "HTTP Status: " << error.http_status << std::endl; + log(ss.str()); + } + else + { + ss << "Invalid ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + class Bench + { + public: + Bench(const std::string& description) : + _description(description), + _start(std::chrono::system_clock::now()), + _reported(false) + { + ; + } + + ~Bench() + { + if (!_reported) + { + report(); + } + } + + void report() + { + auto now = std::chrono::system_clock::now(); + auto milliseconds = std::chrono::duration_cast(now - _start); + + _ms = milliseconds.count(); + std::cout << _description << " completed in " + << _ms << "ms" << std::endl; + + _reported = true; + } + + uint64_t getDuration() const + { + return _ms; + } + + private: + std::string _description; + std::chrono::time_point _start; + uint64_t _ms; + bool _reported; + }; + + void WebSocketSender::sendMessage(const std::string& filename, + bool throttle) + { + std::string content; + { + Bench bench("load file from disk"); + content = load(filename); + } + + _id = uuid4(); + + std::string b64Content; + { + Bench bench("base 64 encode file"); + b64Content = base64_encode(content, content.size()); + } + + Json::Value pdu; + pdu["kind"] = "send"; + pdu["id"] = _id; + pdu["content"] = b64Content; + pdu["djb2_hash"] = djb2Hash(b64Content); + pdu["filename"] = filename; + + Bench bench("Sending file through websocket"); + _webSocket.send(pdu.toStyledString(), + [throttle](int current, int total) -> bool + { + std::cout << "Step " << current << " out of " << total << std::endl; + + if (throttle) + { + std::chrono::duration duration(10); + std::this_thread::sleep_for(duration); + } + + return true; + }); + + bench.report(); + auto duration = bench.getDuration(); + auto transferRate = 1000 * b64Content.size() / duration; + transferRate /= (1024 * 1024); + std::cout << "Send transfer rate: " << transferRate << "MB/s" << std::endl; + } + + void wsSend(const std::string& url, + const std::string& path, + bool enablePerMessageDeflate, + bool throttle) + { + WebSocketSender webSocketSender(url, enablePerMessageDeflate); + webSocketSender.start(); + + webSocketSender.waitForConnection(); + + std::cout << "Sending..." << std::endl; + webSocketSender.sendMessage(path, throttle); + + webSocketSender.waitForAck(); + + std::cout << "Done !" << std::endl; + webSocketSender.stop(); + } +} + +int main(int argc, char** argv) +{ + if (argc != 3) + { + std::cerr << "Usage: ws_send " << std::endl; + return 1; + } + std::string url = argv[1]; + std::string path = argv[2]; + + bool throttle = false; + bool enablePerMessageDeflate = false; + + Socket::init(); + wsSend(url, path, enablePerMessageDeflate, throttle); + return 0; +} diff --git a/ixwebsocket/IXCancellationRequest.cpp b/ixwebsocket/IXCancellationRequest.cpp index a1ec9b5c..0af9424d 100644 --- a/ixwebsocket/IXCancellationRequest.cpp +++ b/ixwebsocket/IXCancellationRequest.cpp @@ -8,7 +8,7 @@ #include -namespace ix +namespace ix { CancellationRequest makeCancellationRequestWithTimeout(int secs, std::atomic& requestInitCancellation) @@ -20,7 +20,7 @@ namespace ix { // Was an explicit cancellation requested ? if (requestInitCancellation) return true; - + auto now = std::chrono::system_clock::now(); if ((now - start) > timeout) return true; diff --git a/ixwebsocket/IXCancellationRequest.h b/ixwebsocket/IXCancellationRequest.h index 8b9d11d1..ed7eb013 100644 --- a/ixwebsocket/IXCancellationRequest.h +++ b/ixwebsocket/IXCancellationRequest.h @@ -9,7 +9,7 @@ #include #include -namespace ix +namespace ix { using CancellationRequest = std::function; diff --git a/ixwebsocket/IXDNSLookup.cpp b/ixwebsocket/IXDNSLookup.cpp index a5608b07..624e1093 100644 --- a/ixwebsocket/IXDNSLookup.cpp +++ b/ixwebsocket/IXDNSLookup.cpp @@ -10,7 +10,7 @@ #include #include -namespace ix +namespace ix { const int64_t DNSLookup::kDefaultWait = 10; // ms @@ -26,7 +26,7 @@ namespace ix _done(false), _id(_nextId++) { - + } DNSLookup::~DNSLookup() @@ -36,7 +36,7 @@ namespace ix _activeJobs.erase(_id); } - struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname, + struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname, int port, std::string& errMsg) { @@ -49,7 +49,7 @@ namespace ix std::string sport = std::to_string(port); struct addrinfo* res; - int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), + int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res); if (getaddrinfo_result) { @@ -101,7 +101,7 @@ namespace ix _activeJobs.insert(_id); } - // + // // Good resource on thread forced termination // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread // @@ -141,7 +141,7 @@ namespace ix void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner { // We don't want to read or write into members variables of an object that could be - // gone, so we use temporary variables (res) or we pass in by copy everything that + // gone, so we use temporary variables (res) or we pass in by copy everything that // getAddrInfo needs to work. std::string errMsg; struct addrinfo* res = getAddrInfo(hostname, port, errMsg); diff --git a/ixwebsocket/IXDNSLookup.h b/ixwebsocket/IXDNSLookup.h index 7887cc2a..9c83c92d 100644 --- a/ixwebsocket/IXDNSLookup.h +++ b/ixwebsocket/IXDNSLookup.h @@ -3,7 +3,7 @@ * Author: Benjamin Sergeant * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. * - * Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo + * Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo * Does this in a background thread so that it can be cancelled, since * getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile. */ @@ -20,7 +20,7 @@ struct addrinfo; -namespace ix +namespace ix { class DNSLookup { public: @@ -39,7 +39,7 @@ namespace ix struct addrinfo* resolveBlocking(std::string& errMsg, const CancellationRequest& isCancellationRequested); - static struct addrinfo* getAddrInfo(const std::string& hostname, + static struct addrinfo* getAddrInfo(const std::string& hostname, int port, std::string& errMsg); diff --git a/ixwebsocket/IXEventFd.cpp b/ixwebsocket/IXEventFd.cpp index 607d1cba..1e13e826 100644 --- a/ixwebsocket/IXEventFd.cpp +++ b/ixwebsocket/IXEventFd.cpp @@ -14,7 +14,7 @@ // eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4) // is on Kernel 3.x // -// cf Android/Kernel table here +// cf Android/Kernel table here // https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel // @@ -28,9 +28,9 @@ #include // for write #endif -namespace ix +namespace ix { - EventFd::EventFd() : + EventFd::EventFd() : _eventfd(-1) { #ifdef __linux__ @@ -65,7 +65,7 @@ namespace ix #if defined(__linux__) if (_eventfd == -1) return false; - // 0 is a special value ; select will not wake up + // 0 is a special value ; select will not wake up uint64_t value = 0; // we should write 8 bytes for an uint64_t diff --git a/ixwebsocket/IXEventFd.h b/ixwebsocket/IXEventFd.h index 3c263a83..4986b6f5 100644 --- a/ixwebsocket/IXEventFd.h +++ b/ixwebsocket/IXEventFd.h @@ -6,7 +6,7 @@ #pragma once -namespace ix +namespace ix { class EventFd { public: diff --git a/ixwebsocket/IXProgressCallback.h b/ixwebsocket/IXProgressCallback.h new file mode 100644 index 00000000..879f6a89 --- /dev/null +++ b/ixwebsocket/IXProgressCallback.h @@ -0,0 +1,14 @@ +/* + * IXProgressCallback.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include + +namespace ix +{ + using OnProgressCallback = std::function; +} diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index 40fb0b31..ef5f1e3f 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -15,17 +15,16 @@ #include #include #include -#include #include #include -namespace ix +namespace ix { const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout; - Socket::Socket(int fd) : + Socket::Socket(int fd) : _sockfd(fd) { @@ -44,21 +43,22 @@ namespace ix return; } -#ifdef __linux__ - constexpr int nfds = 2; -#else - constexpr int nfds = 1; -#endif - - struct pollfd fds[nfds]; - fds[0].fd = _sockfd; - fds[0].events = POLLIN; + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(_sockfd, &rfds); #ifdef __linux__ - fds[1].fd = _eventfd.getFd(); - fds[1].events = POLLIN; + FD_SET(_eventfd.getFd(), &rfds); #endif - int ret = ::poll(fds, nfds, timeoutSecs * 1000); + + struct timeval timeout; + timeout.tv_sec = timeoutSecs; + timeout.tv_usec = 0; + + int sockfd = _sockfd; + int nfds = (std::max)(sockfd, _eventfd.getFd()); + int ret = select(nfds + 1, &rfds, nullptr, nullptr, + (timeoutSecs < 0) ? nullptr : &timeout); PollResultType pollResult = PollResultType_ReadyForRead; if (ret < 0) @@ -71,7 +71,6 @@ namespace ix } onPollCallback(pollResult); - } void Socket::wakeUpFromPoll() @@ -151,7 +150,7 @@ namespace ix #ifdef _WIN32 INT rc; WSADATA wsaData; - + rc = WSAStartup(MAKEWORD(2, 2), &wsaData); return rc != 0; #else diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index fc72a291..80b62cca 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -19,7 +19,7 @@ typedef SSIZE_T ssize_t; #include "IXEventFd.h" #include "IXCancellationRequest.h" -namespace ix +namespace ix { enum PollResultType { @@ -42,7 +42,7 @@ namespace ix virtual void wakeUpFromPoll(); // Virtual methods - virtual bool connect(const std::string& url, + virtual bool connect(const std::string& url, int port, std::string& errMsg, const CancellationRequest& isCancellationRequested); diff --git a/ixwebsocket/IXSocketAppleSSL.cpp b/ixwebsocket/IXSocketAppleSSL.cpp index 7cd2faff..c0a934ff 100644 --- a/ixwebsocket/IXSocketAppleSSL.cpp +++ b/ixwebsocket/IXSocketAppleSSL.cpp @@ -50,7 +50,7 @@ OSStatus read_from_socket(SSLConnectionRef connection, void *data, size_t *len) else return noErr; } - else if (0 == status) + else if (0 == status) { *len = 0; return errSSLClosedGraceful; @@ -102,7 +102,7 @@ OSStatus write_to_socket(SSLConnectionRef connection, const void *data, size_t * else { *len = 0; - if (EAGAIN == errno) + if (EAGAIN == errno) { return errSSLWouldBlock; } @@ -141,7 +141,7 @@ std::string getSSLErrorDescription(OSStatus status) } // anonymous namespace -namespace ix +namespace ix { SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd), _sslContext(nullptr) @@ -176,11 +176,11 @@ namespace ix do { status = SSLHandshake(_sslContext); - } while (errSSLWouldBlock == status || + } while (errSSLWouldBlock == status || errSSLServerAuthCompleted == status); } - if (noErr != status) + if (noErr != status) { errMsg = getSSLErrorDescription(status); close(); @@ -230,7 +230,7 @@ namespace ix ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte) { OSStatus status = errSSLWouldBlock; - while (errSSLWouldBlock == status) + while (errSSLWouldBlock == status) { size_t processed = 0; std::lock_guard lock(_mutex); @@ -239,7 +239,7 @@ namespace ix if (processed > 0) return (ssize_t) processed; - // The connection was reset, inform the caller that this + // The connection was reset, inform the caller that this // Socket should close if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify || diff --git a/ixwebsocket/IXSocketAppleSSL.h b/ixwebsocket/IXSocketAppleSSL.h index 6eac39d4..72dacf1d 100644 --- a/ixwebsocket/IXSocketAppleSSL.h +++ b/ixwebsocket/IXSocketAppleSSL.h @@ -14,15 +14,15 @@ #include -namespace ix +namespace ix { - class SocketAppleSSL : public Socket + class SocketAppleSSL : public Socket { public: SocketAppleSSL(int fd = -1); ~SocketAppleSSL(); - virtual bool connect(const std::string& host, + virtual bool connect(const std::string& host, int port, std::string& errMsg, const CancellationRequest& isCancellationRequested) final; diff --git a/ixwebsocket/IXSocketConnect.cpp b/ixwebsocket/IXSocketConnect.cpp index 8cf9aa22..6d3be10a 100644 --- a/ixwebsocket/IXSocketConnect.cpp +++ b/ixwebsocket/IXSocketConnect.cpp @@ -30,7 +30,7 @@ namespace } } -namespace ix +namespace ix { // // This function can be cancelled every 50 ms @@ -42,7 +42,7 @@ namespace ix const CancellationRequest& isCancellationRequested) { errMsg = "no error"; - + int fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); @@ -72,7 +72,7 @@ namespace ix errMsg = "Cancelled"; return -1; } - + // Use select to check the status of the new connection struct timeval timeout; timeout.tv_sec = 0; @@ -179,7 +179,7 @@ namespace ix // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect #ifdef SO_NOSIGPIPE int value = 1; - setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, + setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&value, sizeof(value)); #endif } diff --git a/ixwebsocket/IXSocketConnect.h b/ixwebsocket/IXSocketConnect.h index 61de312d..fa09eb9a 100644 --- a/ixwebsocket/IXSocketConnect.h +++ b/ixwebsocket/IXSocketConnect.h @@ -12,7 +12,7 @@ struct addrinfo; -namespace ix +namespace ix { class SocketConnect { public: diff --git a/ixwebsocket/IXSocketOpenSSL.cpp b/ixwebsocket/IXSocketOpenSSL.cpp index 332d9e3e..bc3d9774 100644 --- a/ixwebsocket/IXSocketOpenSSL.cpp +++ b/ixwebsocket/IXSocketOpenSSL.cpp @@ -18,12 +18,12 @@ #include #define socketerrno errno -namespace ix +namespace ix { std::atomic SocketOpenSSL::_openSSLInitializationSuccessful(false); SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd), - _ssl_connection(nullptr), + _ssl_connection(nullptr), _ssl_context(nullptr) { std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this); @@ -80,7 +80,7 @@ namespace ix return "OpenSSL failed - underlying BIO reported an I/O error"; } } - else if (err == SSL_ERROR_SSL) + else if (err == SSL_ERROR_SSL) { e = ERR_get_error(); std::string errMsg("OpenSSL failed - "); @@ -149,7 +149,7 @@ namespace ix #if OPENSSL_VERSION_NUMBER < 0x10100000L // Check server name bool hostname_verifies_ok = false; - STACK_OF(GENERAL_NAME) *san_names = + STACK_OF(GENERAL_NAME) *san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert, NID_subject_alt_name, NULL, NULL); if (san_names) @@ -160,8 +160,8 @@ namespace ix if (sk_name->type == GEN_DNS) { char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName); - if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) && - checkHost(hostname, name)) + if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) && + checkHost(hostname, name)) { hostname_verifies_ok = true; break; @@ -185,8 +185,8 @@ namespace ix ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); char *cn = (char *)ASN1_STRING_data(cn_asn1); - if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) && - checkHost(hostname, cn)) + if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) && + checkHost(hostname, cn)) { hostname_verifies_ok = true; } @@ -205,7 +205,7 @@ namespace ix return true; } - bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg) + bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg) { while (true) { diff --git a/ixwebsocket/IXSocketOpenSSL.h b/ixwebsocket/IXSocketOpenSSL.h index 4fdfd48b..ac0d18a6 100644 --- a/ixwebsocket/IXSocketOpenSSL.h +++ b/ixwebsocket/IXSocketOpenSSL.h @@ -17,15 +17,15 @@ #include -namespace ix +namespace ix { - class SocketOpenSSL : public Socket + class SocketOpenSSL : public Socket { public: SocketOpenSSL(int fd = -1); ~SocketOpenSSL(); - virtual bool connect(const std::string& host, + virtual bool connect(const std::string& host, int port, std::string& errMsg, const CancellationRequest& isCancellationRequested) final; diff --git a/ixwebsocket/IXSocketSChannel.cpp b/ixwebsocket/IXSocketSChannel.cpp index 9715f647..2dd2dee0 100644 --- a/ixwebsocket/IXSocketSChannel.cpp +++ b/ixwebsocket/IXSocketSChannel.cpp @@ -47,7 +47,7 @@ // link with ntdsapi.lib for DsMakeSpn function #pragma comment(lib, "ntdsapi.lib") -// The following function assumes that Winsock +// The following function assumes that Winsock // has already been initialized @@ -59,7 +59,7 @@ # error("This file should only be built on Windows") #endif -namespace ix +namespace ix { SocketSChannel::SocketSChannel() { @@ -68,7 +68,7 @@ namespace ix SocketSChannel::~SocketSChannel() { - + } bool SocketSChannel::connect(const std::string& host, @@ -78,7 +78,7 @@ namespace ix return Socket::connect(host, port, errMsg); } - + void SocketSChannel::secureSocket() { // there will be a lot to do here ... diff --git a/ixwebsocket/IXSocketSChannel.h b/ixwebsocket/IXSocketSChannel.h index abceafcb..4da6c881 100644 --- a/ixwebsocket/IXSocketSChannel.h +++ b/ixwebsocket/IXSocketSChannel.h @@ -8,15 +8,15 @@ #include "IXSocket.h" -namespace ix +namespace ix { - class SocketSChannel : public Socket + class SocketSChannel : public Socket { public: SocketSChannel(); ~SocketSChannel(); - virtual bool connect(const std::string& host, + virtual bool connect(const std::string& host, int port, std::string& errMsg) final; virtual void close() final; diff --git a/ixwebsocket/IXSocketServer.cpp b/ixwebsocket/IXSocketServer.cpp index de67c052..3f8beef4 100644 --- a/ixwebsocket/IXSocketServer.cpp +++ b/ixwebsocket/IXSocketServer.cpp @@ -14,7 +14,7 @@ #include #include -namespace ix +namespace ix { const int SocketServer::kDefaultPort(8080); const std::string SocketServer::kDefaultHost("127.0.0.1"); @@ -42,13 +42,13 @@ namespace ix void SocketServer::logError(const std::string& str) { std::lock_guard lock(_logMutex); - fprintf(stderr, "%s\n", str.c_str()); + std::cerr << str << std::endl; } void SocketServer::logInfo(const std::string& str) { std::lock_guard lock(_logMutex); - fprintf(stderr, "%s\n", str.c_str()); + std::cout << str << std::endl; } std::pair SocketServer::listen() @@ -83,7 +83,7 @@ namespace ix server.sin_family = AF_INET; server.sin_port = htons(_port); - // Using INADDR_ANY trigger a pop-up box as binding to any address is detected + // Using INADDR_ANY trigger a pop-up box as binding to any address is detected // by the osx firewall. We need to codesign the binary with a self-signed cert // to allow that, but this is a bit of a pain. (this is what node or python would do). // @@ -216,7 +216,7 @@ namespace ix // Launch the handleConnection work asynchronously in its own thread. // - // the destructor of a future returned by std::async blocks, + // the destructor of a future returned by std::async blocks, // so we need to declare it outside of this loop f = std::async(std::launch::async, &SocketServer::handleConnection, diff --git a/ixwebsocket/IXSocketServer.h b/ixwebsocket/IXSocketServer.h index 2de83f62..5fea37d9 100644 --- a/ixwebsocket/IXSocketServer.h +++ b/ixwebsocket/IXSocketServer.h @@ -16,7 +16,7 @@ #include #include -namespace ix +namespace ix { class SocketServer { public: diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index d3463cf3..cb7a4a76 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -50,7 +50,7 @@ namespace ix ); } - WebSocket::~WebSocket() + WebSocket::~WebSocket() { stop(); } @@ -135,7 +135,7 @@ namespace ix } _onMessageCallback(WebSocket_MessageType_Open, "", 0, - WebSocketErrorInfo(), + WebSocketErrorInfo(), WebSocketOpenInfo(status.uri, status.headers), WebSocketCloseInfo()); return status; @@ -155,7 +155,7 @@ namespace ix } _onMessageCallback(WebSocket_MessageType_Open, "", 0, - WebSocketErrorInfo(), + WebSocketErrorInfo(), WebSocketOpenInfo(status.uri, status.headers), WebSocketCloseInfo()); return status; @@ -184,7 +184,7 @@ namespace ix using millis = std::chrono::duration; millis duration; - while (true) + while (true) { if (isConnected() || isClosing() || _stop || !_automaticReconnection) { @@ -214,7 +214,7 @@ namespace ix { setThreadName(_url); - while (true) + while (true) { if (_stop) return; @@ -223,7 +223,7 @@ namespace ix if (_stop) return; - // 2. Poll to see if there's any new data available + // 2. Poll to see if there's any new data available _ws.poll(); if (_stop) return; @@ -273,7 +273,7 @@ namespace ix void WebSocket::setOnMessageCallback(const OnMessageCallback& callback) { - _onMessageCallback = callback; + _onMessageCallback = callback; } void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback) @@ -294,9 +294,10 @@ namespace ix } } - WebSocketSendInfo WebSocket::send(const std::string& text) + WebSocketSendInfo WebSocket::send(const std::string& text, + const OnProgressCallback& onProgressCallback) { - return sendMessage(text, false); + return sendMessage(text, false, onProgressCallback); } WebSocketSendInfo WebSocket::ping(const std::string& text) @@ -308,7 +309,9 @@ namespace ix return sendMessage(text, true); } - WebSocketSendInfo WebSocket::sendMessage(const std::string& text, bool ping) + WebSocketSendInfo WebSocket::sendMessage(const std::string& text, + bool ping, + const OnProgressCallback& onProgressCallback) { if (!isConnected()) return WebSocketSendInfo(false); @@ -330,7 +333,7 @@ namespace ix } else { - webSocketSendInfo = _ws.sendBinary(text); + webSocketSendInfo = _ws.sendBinary(text, onProgressCallback); } WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false); @@ -340,7 +343,7 @@ namespace ix ReadyState WebSocket::getReadyState() const { - switch (_ws.getReadyState()) + switch (_ws.getReadyState()) { case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open; case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting; diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 081b0cc4..c752df8f 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -19,11 +19,12 @@ #include "IXWebSocketSendInfo.h" #include "IXWebSocketPerMessageDeflateOptions.h" #include "IXWebSocketHttpHeaders.h" +#include "IXProgressCallback.h" namespace ix { // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants - enum ReadyState + enum ReadyState { WebSocket_ReadyState_Connecting = 0, WebSocket_ReadyState_Open = 1, @@ -78,7 +79,7 @@ namespace ix using OnTrafficTrackerCallback = std::function; - class WebSocket + class WebSocket { public: WebSocket(); @@ -97,7 +98,8 @@ namespace ix WebSocketInitResult connect(int timeoutSecs); void run(); - WebSocketSendInfo send(const std::string& text); + WebSocketSendInfo send(const std::string& text, + const OnProgressCallback& onProgressCallback = nullptr); WebSocketSendInfo ping(const std::string& text); void close(); @@ -115,7 +117,9 @@ namespace ix private: - WebSocketSendInfo sendMessage(const std::string& text, bool ping); + WebSocketSendInfo sendMessage(const std::string& text, + bool ping, + const OnProgressCallback& callback = nullptr); bool isConnected() const; bool isClosing() const; diff --git a/ixwebsocket/IXWebSocketErrorInfo.h b/ixwebsocket/IXWebSocketErrorInfo.h index 1ff50b6d..a8a02864 100644 --- a/ixwebsocket/IXWebSocketErrorInfo.h +++ b/ixwebsocket/IXWebSocketErrorInfo.h @@ -8,7 +8,7 @@ #include -namespace ix +namespace ix { struct WebSocketErrorInfo { diff --git a/ixwebsocket/IXWebSocketHandshake.cpp b/ixwebsocket/IXWebSocketHandshake.cpp index 54105ef1..ebe18bbf 100644 --- a/ixwebsocket/IXWebSocketHandshake.cpp +++ b/ixwebsocket/IXWebSocketHandshake.cpp @@ -16,7 +16,7 @@ #include -namespace ix +namespace ix { WebSocketHandshake::WebSocketHandshake(std::atomic& requestInitCancellation, std::shared_ptr socket, @@ -171,7 +171,7 @@ namespace ix std::string WebSocketHandshake::genRandomString(const int len) { - std::string alphanum = + std::string alphanum = "0123456789" "ABCDEFGH" "abcdefgh"; @@ -201,7 +201,7 @@ namespace ix char line[256]; int i; - while (true) + while (true) { int colon = 0; @@ -277,7 +277,7 @@ namespace ix { _requestInitCancellation = false; - auto isCancellationRequested = + auto isCancellationRequested = makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); std::string errMsg; @@ -372,7 +372,7 @@ namespace ix } // Check the value of the connection field - // Some websocket servers (Go/Gorilla?) send lowercase values for the + // Some websocket servers (Go/Gorilla?) send lowercase values for the // connection header, so do a case insensitive comparison if (!insensitiveStringCompare(headers["connection"], "Upgrade")) { @@ -418,7 +418,7 @@ namespace ix // Set the socket to non blocking mode + other tweaks SocketConnect::configure(fd); - auto isCancellationRequested = + auto isCancellationRequested = makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); std::string remote = std::string("remote fd ") + std::to_string(fd); @@ -432,7 +432,7 @@ namespace ix { return sendErrorResponse(400, "Error reading HTTP request line"); } - + // Validate request line (GET /foo HTTP/1.1\r\n) auto requestLine = parseRequestLine(line); auto method = std::get<0>(requestLine); diff --git a/ixwebsocket/IXWebSocketHandshake.h b/ixwebsocket/IXWebSocketHandshake.h index 29814fd6..4191e887 100644 --- a/ixwebsocket/IXWebSocketHandshake.h +++ b/ixwebsocket/IXWebSocketHandshake.h @@ -18,7 +18,7 @@ #include #include -namespace ix +namespace ix { struct WebSocketInitResult { diff --git a/ixwebsocket/IXWebSocketHttpHeaders.h b/ixwebsocket/IXWebSocketHttpHeaders.h index 97570950..be544a57 100644 --- a/ixwebsocket/IXWebSocketHttpHeaders.h +++ b/ixwebsocket/IXWebSocketHttpHeaders.h @@ -9,7 +9,7 @@ #include #include -namespace ix +namespace ix { using WebSocketHttpHeaders = std::unordered_map; } diff --git a/ixwebsocket/IXWebSocketPerMessageDeflate.cpp b/ixwebsocket/IXWebSocketPerMessageDeflate.cpp index 25376c11..d825aaa6 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflate.cpp +++ b/ixwebsocket/IXWebSocketPerMessageDeflate.cpp @@ -34,7 +34,7 @@ * - Reused zlib compression + decompression bits. * - Refactored to have 2 class for compression and decompression, to allow multi-threading * and make sure that _compressBuffer is not shared between threads. - * - Original code wasn't working for some reason, I had to add checks + * - Original code wasn't working for some reason, I had to add checks * for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers * would start accepting receiving/decoding compressed messages. Original code was probably * modifying the passed in buffers before processing in enabled.hpp ? @@ -65,13 +65,13 @@ namespace ix bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions) { - bool clientNoContextTakeover = + bool clientNoContextTakeover = perMessageDeflateOptions.getClientNoContextTakeover(); uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits(); uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits(); - return _compressor->init(deflateBits, clientNoContextTakeover) && + return _compressor->init(deflateBits, clientNoContextTakeover) && _decompressor->init(inflateBits, clientNoContextTakeover); } diff --git a/ixwebsocket/IXWebSocketPerMessageDeflate.h b/ixwebsocket/IXWebSocketPerMessageDeflate.h index 779b0524..936714c4 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflate.h +++ b/ixwebsocket/IXWebSocketPerMessageDeflate.h @@ -37,7 +37,7 @@ #include #include -namespace ix +namespace ix { class WebSocketPerMessageDeflateOptions; class WebSocketPerMessageDeflateCompressor; diff --git a/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp b/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp index 9f399923..3723b391 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp +++ b/ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp @@ -14,7 +14,7 @@ namespace { // The passed in size (4) is important, without it the string litteral - // is treated as a char* and the null termination (\x00) makes it + // is treated as a char* and the null termination (\x00) makes it // look like an empty string. const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4); @@ -76,16 +76,16 @@ namespace ix { // // 7.2.1. Compression - // + // // An endpoint uses the following algorithm to compress a message. - // + // // 1. Compress all the octets of the payload of the message using // DEFLATE. - // + // // 2. If the resulting data does not end with an empty DEFLATE block // with no compression (the "BTYPE" bits are set to 00), append an // empty DEFLATE block with no compression to the tail end. - // + // // 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. // After this step, the last octet of the compressed data contains // (possibly part of) the DEFLATE header bits with the "BTYPE" bits @@ -168,14 +168,14 @@ namespace ix { // // 7.2.2. Decompression - // + // // An endpoint uses the following algorithm to decompress a message. - // + // // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the // payload of the message. - // + // // 2. Decompress the resulting data using DEFLATE. - // + // std::string inFixed(in); inFixed += kEmptyUncompressedBlock; diff --git a/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h b/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h index 23cfc21c..78521c62 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h +++ b/ixwebsocket/IXWebSocketPerMessageDeflateCodec.h @@ -10,7 +10,7 @@ #include #include -namespace ix +namespace ix { class WebSocketPerMessageDeflateCompressor { diff --git a/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp b/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp index b14e1994..90dfe72f 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp +++ b/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp @@ -36,7 +36,7 @@ namespace ix _serverMaxWindowBits = serverMaxWindowBits; } - // + // // Four extension parameters are defined for "permessage-deflate" to // help endpoints manage per-connection resource usage. // @@ -88,9 +88,9 @@ namespace ix int x; ss >> x; - // Sanitize values to be in the proper range [8, 15] in + // Sanitize values to be in the proper range [8, 15] in // case a server would give us bogus values - _serverMaxWindowBits = + _serverMaxWindowBits = std::min(maxServerMaxWindowBits, std::max(x, minServerMaxWindowBits)); } @@ -103,9 +103,9 @@ namespace ix int x; ss >> x; - // Sanitize values to be in the proper range [8, 15] in + // Sanitize values to be in the proper range [8, 15] in // case a server would give us bogus values - _clientMaxWindowBits = + _clientMaxWindowBits = std::min(maxClientMaxWindowBits, std::max(x, minClientMaxWindowBits)); } @@ -162,7 +162,7 @@ namespace ix std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str) { std::string out(str); - out.erase(std::remove_if(out.begin(), + out.erase(std::remove_if(out.begin(), out.end(), [](unsigned char x){ return std::isspace(x); }), out.end()); diff --git a/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h b/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h index cfb8e16f..f536f477 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h +++ b/ixwebsocket/IXWebSocketPerMessageDeflateOptions.h @@ -8,7 +8,7 @@ #include -namespace ix +namespace ix { class WebSocketPerMessageDeflateOptions { diff --git a/ixwebsocket/IXWebSocketSendInfo.h b/ixwebsocket/IXWebSocketSendInfo.h index ef2bca42..1e684f0e 100644 --- a/ixwebsocket/IXWebSocketSendInfo.h +++ b/ixwebsocket/IXWebSocketSendInfo.h @@ -9,7 +9,7 @@ #include #include -namespace ix +namespace ix { struct WebSocketSendInfo { diff --git a/ixwebsocket/IXWebSocketServer.cpp b/ixwebsocket/IXWebSocketServer.cpp index c33f274e..3ffad19b 100644 --- a/ixwebsocket/IXWebSocketServer.cpp +++ b/ixwebsocket/IXWebSocketServer.cpp @@ -14,7 +14,7 @@ #include #include -namespace ix +namespace ix { const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds @@ -65,7 +65,7 @@ namespace ix auto status = webSocket->connectToSocket(fd, _handshakeTimeoutSecs); if (status.success) { - // Process incoming messages and execute callbacks + // Process incoming messages and execute callbacks // until the connection is closed webSocket->run(); } diff --git a/ixwebsocket/IXWebSocketServer.h b/ixwebsocket/IXWebSocketServer.h index f4b161cb..3ad4976a 100644 --- a/ixwebsocket/IXWebSocketServer.h +++ b/ixwebsocket/IXWebSocketServer.h @@ -18,7 +18,7 @@ #include "IXWebSocket.h" #include "IXSocketServer.h" -namespace ix +namespace ix { using OnConnectionCallback = std::function)>; diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index bbdee50d..f4008868 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -29,12 +29,15 @@ #include #include #include +#include +#include namespace ix { const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat"); const int WebSocketTransport::kDefaultHeartBeatPeriod(-1); + constexpr size_t WebSocketTransport::kChunkSize; WebSocketTransport::WebSocketTransport() : _readyState(CLOSED), @@ -45,7 +48,7 @@ namespace ix _heartBeatPeriod(kDefaultHeartBeatPeriod), _lastSendTimePoint(std::chrono::steady_clock::now()) { - + _readbuf.resize(kChunkSize); } WebSocketTransport::~WebSocketTransport() @@ -129,7 +132,7 @@ namespace ix return result; } - WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const + WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const { return _readyState; } @@ -153,7 +156,7 @@ namespace ix void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback) { - _onCloseCallback = onCloseCallback; + _onCloseCallback = onCloseCallback; } // Only consider send time points for that computation. @@ -173,7 +176,7 @@ namespace ix // If (1) heartbeat is enabled, and (2) no data was received or // send for a duration exceeding our heart-beat period, send a // ping to the server. - if (pollResult == PollResultType_Timeout && + if (pollResult == PollResultType_Timeout && heartBeatPeriodExceeded()) { std::stringstream ss; @@ -182,33 +185,31 @@ namespace ix return; } - while (true) + while (true) { - int N = (int) _rxbuf.size(); + ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size()); - _rxbuf.resize(N + 1500); - ssize_t ret = _socket->recv((char*)&_rxbuf[0] + N, 1500); - - if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || - _socket->getErrno() == EAGAIN)) { - _rxbuf.resize(N); + if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || + _socket->getErrno() == EAGAIN)) + { break; } - else if (ret <= 0) + else if (ret <= 0) { - _rxbuf.resize(N); - + _rxbuf.clear(); _socket->close(); setReadyState(CLOSED); break; } - else + else { - _rxbuf.resize(N + ret); + _rxbuf.insert(_rxbuf.end(), + _readbuf.begin(), + _readbuf.begin() + ret); } } - if (isSendBufferEmpty() && _readyState == CLOSING) + if (isSendBufferEmpty() && _readyState == CLOSING) { _socket->close(); setReadyState(CLOSED); @@ -282,7 +283,7 @@ namespace ix // void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback) { - while (true) + while (true) { wsheader_type ws; if (_rxbuf.size() < 2) return; /* Need at least 2 */ @@ -294,7 +295,7 @@ namespace ix ws.N0 = (data[1] & 0x7f); ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */ - + // // Calculate payload length: // 0-125 mean the payload is that long. @@ -332,7 +333,7 @@ namespace ix // invalid payload length according to the spec. bail out return; } - + if (ws.mask) { ws.masking_key[0] = ((uint8_t) data[i+0]) << 0; @@ -355,22 +356,40 @@ namespace ix // We got a whole message, now do something with it: if ( - ws.opcode == wsheader_type::TEXT_FRAME + ws.opcode == wsheader_type::TEXT_FRAME || ws.opcode == wsheader_type::BINARY_FRAME || ws.opcode == wsheader_type::CONTINUATION ) { unmaskReceiveBuffer(ws); - _receivedData.insert(_receivedData.end(), - _rxbuf.begin()+ws.header_size, - _rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed - if (ws.fin) - { - // fire callback with a string message - std::string stringMessage(_receivedData.begin(), - _receivedData.end()); - emitMessage(MSG, stringMessage, ws, onMessageCallback); - _receivedData.clear(); + // + // Usual case. Small unfragmented messages + // + if (ws.fin && _chunks.empty()) + { + emitMessage(MSG, + std::string(_rxbuf.begin()+ws.header_size, + _rxbuf.begin()+ws.header_size+(size_t) ws.N), + ws, + onMessageCallback); + } + else + { + // + // Add intermediary message to our chunk list. + // We use a chunk list instead of a big buffer because resizing + // large buffer can be very costly when we need to re-allocate + // the internal buffer which is slow and can let the internal OS + // receive buffer fill out. + // + _chunks.emplace_back( + std::vector(_rxbuf.begin()+ws.header_size, + _rxbuf.begin()+ws.header_size+(size_t)ws.N)); + if (ws.fin) + { + emitMessage(MSG, getMergedChunks(), ws, onMessageCallback); + _chunks.clear(); + } } } else if (ws.opcode == wsheader_type::PING) @@ -420,12 +439,33 @@ namespace ix close(); } + // Erase the message that has been processed from the input/read buffer _rxbuf.erase(_rxbuf.begin(), _rxbuf.begin() + ws.header_size + (size_t) ws.N); } } - void WebSocketTransport::emitMessage(MessageKind messageKind, + std::string WebSocketTransport::getMergedChunks() const + { + size_t length = 0; + for (auto&& chunk : _chunks) + { + length += chunk.size(); + } + + std::string msg; + msg.reserve(length); + + for (auto&& chunk : _chunks) + { + std::string str(chunk.begin(), chunk.end()); + msg += str; + } + + return msg; + } + + void WebSocketTransport::emitMessage(MessageKind messageKind, const std::string& message, const wsheader_type& ws, const OnMessageCallback& onMessageCallback) @@ -448,15 +488,17 @@ namespace ix unsigned WebSocketTransport::getRandomUnsigned() { auto now = std::chrono::system_clock::now(); - auto seconds = + auto seconds = std::chrono::duration_cast( now.time_since_epoch()).count(); return static_cast(seconds); } - WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type, - const std::string& message, - bool compress) + WebSocketSendInfo WebSocketTransport::sendData( + wsheader_type::opcode_type type, + const std::string& message, + bool compress, + const OnProgressCallback& onProgressCallback) { if (_readyState == CLOSING || _readyState == CLOSED) { @@ -473,15 +515,81 @@ namespace ix if (compress) { - bool success = _perMessageDeflate.compress(message, compressedMessage); - compressionError = !success; + if (!_perMessageDeflate.compress(message, compressedMessage)) + { + bool success = false; + compressionError = true; + payloadSize = 0; + wireSize = 0; + return WebSocketSendInfo(success, compressionError, payloadSize, wireSize); + } + compressionError = false; wireSize = compressedMessage.size(); message_begin = compressedMessage.begin(); message_end = compressedMessage.end(); } - uint64_t message_size = wireSize; + // Common case for most message. No fragmentation required. + if (wireSize < kChunkSize) + { + sendFragment(type, true, message_begin, message_end, compress); + } + else + { + // + // Large messages need to be fragmented + // + // Rules: + // First message needs to specify a proper type (BINARY or TEXT) + // Intermediary and last messages need to be of type CONTINUATION + // Last message must set the fin byte. + // + auto steps = wireSize / kChunkSize; + + std::string::const_iterator begin = message_begin; + std::string::const_iterator end = message_end; + + for (uint64_t i = 0 ; i < steps; ++i) + { + bool firstStep = i == 0; + bool lastStep = (i+1) == steps; + bool fin = lastStep; + + end = begin + kChunkSize; + if (lastStep) + { + end = message_end; + } + + auto opcodeType = type; + if (!firstStep) + { + opcodeType = wsheader_type::CONTINUATION; + } + + // Send message + sendFragment(opcodeType, fin, begin, end, compress); + + if (onProgressCallback && !onProgressCallback(i, steps)) + { + break; + } + + begin += kChunkSize; + } + } + + return WebSocketSendInfo(true, compressionError, payloadSize, wireSize); + } + + void WebSocketTransport::sendFragment(wsheader_type::opcode_type type, + bool fin, + std::string::const_iterator message_begin, + std::string::const_iterator message_end, + bool compress) + { + auto message_size = message_end - message_begin; unsigned x = getRandomUnsigned(); uint8_t masking_key[4] = {}; @@ -494,7 +602,13 @@ namespace ix header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + 4, 0); - header[0] = 0x80 | type; + header[0] = type; + + // The fin bit indicate that this is the last fragment. Fin is French for end. + if (fin) + { + header[0] |= 0x80; + } // This bit indicate that the frame is compressed if (compress) @@ -511,7 +625,7 @@ namespace ix header[4] = masking_key[2]; header[5] = masking_key[3]; } - else if (message_size < 65536) + else if (message_size < 65536) { header[1] = 126 | 0x80; header[2] = (message_size >> 8) & 0xff; @@ -546,8 +660,6 @@ namespace ix // Now actually send this data sendOnSocket(); - - return WebSocketSendInfo(true, compressionError, payloadSize, wireSize); } WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message) @@ -556,9 +668,13 @@ namespace ix return sendData(wsheader_type::PING, message, compress); } - WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message) + WebSocketSendInfo WebSocketTransport::sendBinary( + const std::string& message, + const OnProgressCallback& onProgressCallback) + { - return sendData(wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate); + return sendData(wsheader_type::BINARY_FRAME, message, + _enablePerMessageDeflate, onProgressCallback); } void WebSocketTransport::sendOnSocket() @@ -569,7 +685,7 @@ namespace ix { ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size()); - if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || + if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || _socket->getErrno() == EAGAIN)) { break; diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index 86fba38a..351db919 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "IXWebSocketSendInfo.h" #include "IXWebSocketPerMessageDeflate.h" @@ -23,8 +24,9 @@ #include "IXWebSocketHttpHeaders.h" #include "IXCancellationRequest.h" #include "IXWebSocketHandshake.h" +#include "IXProgressCallback.h" -namespace ix +namespace ix { class Socket; @@ -66,7 +68,8 @@ namespace ix int timeoutSecs); void poll(); - WebSocketSendInfo sendBinary(const std::string& message); + WebSocketSendInfo sendBinary(const std::string& message, + const OnProgressCallback& onProgressCallback); WebSocketSendInfo sendPing(const std::string& message); void close(); ReadyStateValues getReadyState() const; @@ -76,7 +79,6 @@ namespace ix private: std::string _url; - std::string _origin; struct wsheader_type { unsigned header_size; @@ -96,13 +98,31 @@ namespace ix uint8_t masking_key[4]; }; + // Buffer for reading from our socket. That buffer is never resized. + std::vector _readbuf; + + // Contains all messages that were fetched in the last socket read. + // This could be a mix of control messages (Close, Ping, etc...) and + // data messages. That buffer std::vector _rxbuf; + + // Contains all messages that are waiting to be sent std::vector _txbuf; mutable std::mutex _txbufMutex; - std::vector _receivedData; + // Hold fragments for multi-fragments messages in a list. We support receiving very large + // messages (tested messages up to 700M) and we cannot put them in a single + // buffer that is resized, as this operation can be slow when a buffer has its + // size increased 2 fold, while appending to a list has a fixed cost. + std::list> _chunks; + + // Fragments are 32K long + static constexpr size_t kChunkSize = 1 << 15; + + // Underlying TCP socket std::shared_ptr _socket; + // Hold the state of the connection (OPEN, CLOSED, etc...) std::atomic _readyState; OnCloseCallback _onCloseCallback; @@ -111,13 +131,14 @@ namespace ix size_t _closeWireSize; mutable std::mutex _closeDataMutex; + // Data used for Per Message Deflate compression (with zlib) WebSocketPerMessageDeflate _perMessageDeflate; WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; std::atomic _enablePerMessageDeflate; // Used to cancel dns lookup + socket connect + http upgrade std::atomic _requestInitCancellation; - + // Optional Heartbeat int _heartBeatPeriod; static const int kDefaultHeartBeatPeriod; @@ -129,11 +150,18 @@ namespace ix bool heartBeatPeriodExceeded(); void sendOnSocket(); - WebSocketSendInfo sendData(wsheader_type::opcode_type type, + WebSocketSendInfo sendData(wsheader_type::opcode_type type, const std::string& message, - bool compress); + bool compress, + const OnProgressCallback& onProgressCallback = nullptr); - void emitMessage(MessageKind messageKind, + void sendFragment(wsheader_type::opcode_type type, + bool fin, + std::string::const_iterator begin, + std::string::const_iterator end, + bool compress); + + void emitMessage(MessageKind messageKind, const std::string& message, const wsheader_type& ws, const OnMessageCallback& onMessageCallback); @@ -148,5 +176,7 @@ namespace ix unsigned getRandomUnsigned(); void unmaskReceiveBuffer(const wsheader_type& ws); + + std::string getMergedChunks() const; }; } diff --git a/makefile b/makefile index 3f1df944..b08d82e7 100644 --- a/makefile +++ b/makefile @@ -10,6 +10,10 @@ docker: run: docker docker run --cap-add sys_ptrace -it ws_connect:latest bash +# this is helpful to remove trailing whitespaces +trail: + sh third_party/remote_trailing_whitespaces.sh + build: (cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make) (cd examples/chat ; mkdir -p build ; cd build ; cmake .. ; make) diff --git a/test/IXSocketTest.cpp b/test/IXSocketTest.cpp index 0856fa21..950de007 100644 --- a/test/IXSocketTest.cpp +++ b/test/IXSocketTest.cpp @@ -16,7 +16,7 @@ # endif #endif -#include "IXTest.h" +#include "IXTest.h" #include "catch.hpp" using namespace ix; diff --git a/test/IXTest.cpp b/test/IXTest.cpp index d2062524..0b62e916 100644 --- a/test/IXTest.cpp +++ b/test/IXTest.cpp @@ -57,7 +57,7 @@ namespace ix std::string generateSessionId() { auto now = std::chrono::system_clock::now(); - auto seconds = + auto seconds = std::chrono::duration_cast( now.time_since_epoch()).count(); @@ -73,7 +73,7 @@ namespace ix { int defaultPort = 8090; - int sockfd; + int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { log("Cannot compute a free port. socket error."); diff --git a/test/IXWebSocketHeartBeatTest.cpp b/test/IXWebSocketHeartBeatTest.cpp index 67301065..05314aac 100644 --- a/test/IXWebSocketHeartBeatTest.cpp +++ b/test/IXWebSocketHeartBeatTest.cpp @@ -34,7 +34,7 @@ namespace int _port; }; - WebSocketClient::WebSocketClient(int port) + WebSocketClient::WebSocketClient(int port) : _port(port) { ; @@ -56,7 +56,7 @@ namespace { std::stringstream ss; ss << "ws://localhost:" - << _port + << _port << "/"; url = ss.str(); @@ -64,7 +64,7 @@ namespace _webSocket.setUrl(url); - // The important bit for this test. + // The important bit for this test. // Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second // a ping message will be sent by the client. _webSocket.setHeartBeatPeriod(1); diff --git a/test/cmd_websocket_chat.cpp b/test/cmd_websocket_chat.cpp index 7b3e9d59..8a929524 100644 --- a/test/cmd_websocket_chat.cpp +++ b/test/cmd_websocket_chat.cpp @@ -79,7 +79,7 @@ namespace return _receivedMessages; } - void WebSocketChat::appendMessage(const std::string& message) + void WebSocketChat::appendMessage(const std::string& message) { std::lock_guard lock(_mutex); _receivedMessages.push_back(message); @@ -101,7 +101,7 @@ namespace { std::stringstream ss; ss << "ws://localhost:" - << _port + << _port << "/" << _user; @@ -150,10 +150,10 @@ namespace std::string payload = result.second; if (payload.size() > 2000) { - payload = ""; + payload = ""; } - ss << std::endl + ss << std::endl << result.first << " > " << payload << std::endl << _user << " > "; @@ -293,12 +293,13 @@ TEST_CASE("Websocket_chat", "[websocket_chat]") chatB.sendMessage("from B1"); chatB.sendMessage("from B2"); - // FIXME: cannot handle large message, we need to break them down - // into small one at the websocket layer (using CONTINUATION opcode) - size_t size = 512 * 1000; // 512K is OK, larger is not !! + // Test large messages that needs to be broken into small fragments + size_t size = 1 * 1024 * 1024; // ~1Mb std::string bigMessage(size, 'a'); chatB.sendMessage(bigMessage); + log("Sent all messages"); + // Wait until all messages are received. 10s timeout int attempts = 0; while (chatA.getReceivedMessagesCount() != 3 || diff --git a/test/run.py b/test/run.py index d4a3e214..664dee4c 100644 --- a/test/run.py +++ b/test/run.py @@ -20,7 +20,7 @@ if osName == 'Windows': else: generator = '' make = 'make -j6' - testBinary ='./ixwebsocket_unittest' + testBinary ='./ixwebsocket_unittest' sanitizersFlags = { 'asan': '-DSANITIZE_ADDRESS=On', diff --git a/test/test_runner.cpp b/test/test_runner.cpp index 03af61a6..2eb6ee1c 100644 --- a/test/test_runner.cpp +++ b/test/test_runner.cpp @@ -9,7 +9,7 @@ #include -int main(int argc, char* argv[]) +int main(int argc, char* argv[]) { ix::Socket::init(); // for Windows From 262de49c3c13af9c0511421b0413b3f7c93d1589 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 21 Feb 2019 14:08:27 -0800 Subject: [PATCH 05/16] Update README.md Add note about message fragmentation. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b671e052..e5b7b738 100644 --- a/README.md +++ b/README.md @@ -134,9 +134,12 @@ No manual polling to fetch data is required. Data is sent and received instantly If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. +### Large messages + +Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 500M were sent and received succesfully. + ## Limitations -* Sending large messages are not supported yet (see feature/send_large_message). This is a bug and will be fixed. * There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well. * Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that. * The server code is using select to detect incoming data, and creates one OS thread per connection. This isn't as scalable as strategies using epoll or kqueue. From 73d72807236c22e29adde1865a428aa172506155 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 21 Feb 2019 21:24:53 -0800 Subject: [PATCH 06/16] Feature/ws cli (#15) * New command line tool for transfering files / still very beta. * add readme * use cli11 for argument parsing * json -> msgpack * stop using base64 and use binary which can be stored in message pack --- .../ws_receive/.gitignore => .dockerignore | 0 docker/Dockerfile.debian | 7 +- .../broadcast_server/broadcast_server.cpp | 1 + examples/ws_receive/CMakeLists.txt | 30 - examples/ws_receive/README.md | 1 - examples/ws_receive/ixcrypto | 1 - examples/ws_receive/package-lock.json | 29 - examples/ws_receive/ws_receive.cpp | 153 - examples/ws_receive/ws_receive.js | 43 - examples/ws_send/README.md | 1 - examples/ws_send/ixcrypto | 1 - examples/ws_send/jsoncpp | 1 - makefile | 8 +- test/CMakeLists.txt | 3 +- third_party/cli11/CLI11.hpp | 4641 +++++++++++++++++ .../jsoncpp/json/json-forwards.h | 0 .../jsoncpp/json/json.h | 0 .../jsoncpp/jsoncpp.cpp | 0 {test => third_party/msgpack11}/msgpack11.cpp | 0 {test => third_party/msgpack11}/msgpack11.hpp | 0 third_party/remove_trailing_whitespaces.sh | 2 + {examples/ws_send => ws}/.gitignore | 0 {examples/ws_send => ws}/CMakeLists.txt | 23 +- ws/README.md | 10 + ws/docker_build.sh | 39 + .../ixcrypto/IXBase64.cpp | 0 .../ixcrypto/IXBase64.h | 0 .../ixcrypto/IXHMac.cpp | 0 .../cobra_publisher => ws}/ixcrypto/IXHMac.h | 0 .../ixcrypto/IXHash.cpp | 6 +- .../cobra_publisher => ws}/ixcrypto/IXHash.h | 4 +- .../ixcrypto/IXUuid.cpp | 0 .../cobra_publisher => ws}/ixcrypto/IXUuid.h | 0 ws/ws.cpp | 68 + ws/ws_receive.cpp | 251 + {examples/ws_send => ws}/ws_send.cpp | 88 +- ws/ws_transfer.cpp | 72 + 37 files changed, 5153 insertions(+), 330 deletions(-) rename examples/ws_receive/.gitignore => .dockerignore (100%) delete mode 100644 examples/ws_receive/CMakeLists.txt delete mode 100644 examples/ws_receive/README.md delete mode 120000 examples/ws_receive/ixcrypto delete mode 100644 examples/ws_receive/package-lock.json delete mode 100644 examples/ws_receive/ws_receive.cpp delete mode 100644 examples/ws_receive/ws_receive.js delete mode 100644 examples/ws_send/README.md delete mode 120000 examples/ws_send/ixcrypto delete mode 120000 examples/ws_send/jsoncpp create mode 100644 third_party/cli11/CLI11.hpp rename {examples/ws_receive => third_party}/jsoncpp/json/json-forwards.h (100%) rename {examples/ws_receive => third_party}/jsoncpp/json/json.h (100%) rename {examples/ws_receive => third_party}/jsoncpp/jsoncpp.cpp (100%) rename {test => third_party/msgpack11}/msgpack11.cpp (100%) rename {test => third_party/msgpack11}/msgpack11.hpp (100%) create mode 100644 third_party/remove_trailing_whitespaces.sh rename {examples/ws_send => ws}/.gitignore (100%) rename {examples/ws_send => ws}/CMakeLists.txt (53%) create mode 100644 ws/README.md create mode 100644 ws/docker_build.sh rename {examples/cobra_publisher => ws}/ixcrypto/IXBase64.cpp (100%) rename {examples/cobra_publisher => ws}/ixcrypto/IXBase64.h (100%) rename {examples/cobra_publisher => ws}/ixcrypto/IXHMac.cpp (100%) rename {examples/cobra_publisher => ws}/ixcrypto/IXHMac.h (100%) rename {examples/cobra_publisher => ws}/ixcrypto/IXHash.cpp (73%) rename {examples/cobra_publisher => ws}/ixcrypto/IXHash.h (65%) rename {examples/cobra_publisher => ws}/ixcrypto/IXUuid.cpp (100%) rename {examples/cobra_publisher => ws}/ixcrypto/IXUuid.h (100%) create mode 100644 ws/ws.cpp create mode 100644 ws/ws_receive.cpp rename {examples/ws_send => ws}/ws_send.cpp (83%) create mode 100644 ws/ws_transfer.cpp diff --git a/examples/ws_receive/.gitignore b/.dockerignore similarity index 100% rename from examples/ws_receive/.gitignore rename to .dockerignore diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index a2d28e55..821724cb 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -15,5 +15,8 @@ RUN apt-get -y install cmake COPY . . -WORKDIR test -RUN ["sh", "build_linux.sh"] +WORKDIR ws +RUN ["sh", "docker_build.sh"] + +EXPOSE 8765 +CMD ["/ws/ws", "transfer", "8765"] diff --git a/examples/broadcast_server/broadcast_server.cpp b/examples/broadcast_server/broadcast_server.cpp index 884697fa..f0ab7740 100644 --- a/examples/broadcast_server/broadcast_server.cpp +++ b/examples/broadcast_server/broadcast_server.cpp @@ -47,6 +47,7 @@ int main(int argc, char** argv) } else if (messageType == ix::WebSocket_MessageType_Message) { + std::cerr << "Received " << wireSize << " bytes" << std::endl; for (auto&& client : server.getClients()) { if (client != webSocket) diff --git a/examples/ws_receive/CMakeLists.txt b/examples/ws_receive/CMakeLists.txt deleted file mode 100644 index 797076dc..00000000 --- a/examples/ws_receive/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# Author: Benjamin Sergeant -# Copyright (c) 2019 Machine Zone, Inc. All rights reserved. -# - -cmake_minimum_required (VERSION 3.4.1) -project (ws_receive) - -# There's -Weverything too for clang -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32") - -set (CMAKE_CXX_STANDARD 14) - -option(USE_TLS "Add TLS support" ON) - -add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket) - -include_directories(ws_receive .) - -add_executable(ws_receive - jsoncpp/jsoncpp.cpp - ixcrypto/IXBase64.cpp - ixcrypto/IXHash.cpp - ws_receive.cpp) - -if (APPLE AND USE_TLS) - target_link_libraries(ws_receive "-framework foundation" "-framework security") -endif() - -target_link_libraries(ws_receive ixwebsocket) diff --git a/examples/ws_receive/README.md b/examples/ws_receive/README.md deleted file mode 100644 index 2b48e07c..00000000 --- a/examples/ws_receive/README.md +++ /dev/null @@ -1 +0,0 @@ -ws_receive is a simple server upload program. It needs to be used in conjonction with ws_send. diff --git a/examples/ws_receive/ixcrypto b/examples/ws_receive/ixcrypto deleted file mode 120000 index 1b13e4a5..00000000 --- a/examples/ws_receive/ixcrypto +++ /dev/null @@ -1 +0,0 @@ -../cobra_publisher/ixcrypto \ No newline at end of file diff --git a/examples/ws_receive/package-lock.json b/examples/ws_receive/package-lock.json deleted file mode 100644 index b0870d1b..00000000 --- a/examples/ws_receive/package-lock.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" - }, - "djb2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/djb2/-/djb2-0.0.2.tgz", - "integrity": "sha1-RAs4kao6uBQrVNRpsXe66p6W5O8=" - }, - "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "requires": { - "async-limiter": "1.0.0" - } - } - } -} diff --git a/examples/ws_receive/ws_receive.cpp b/examples/ws_receive/ws_receive.cpp deleted file mode 100644 index 323d550e..00000000 --- a/examples/ws_receive/ws_receive.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - * ws_receive.cpp - * Author: Benjamin Sergeant - * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. - */ - -#include -#include -#include -#include -#include -#include -#include - - -namespace -{ - // We should cleanup the file name and full path further to remove .. as well - std::string extractFilename(const std::string& path) - { - std::string filename("filename.conf"); - std::string::size_type idx; - - idx = path.rfind('/'); - if (idx != std::string::npos) - { - std::string filename = path.substr(idx+1); - return filename; - } - else - { - return std::string(); - } - } -} - -namespace ix -{ - void errorHandler(const std::string& errMsg, - const std::string& id, - std::shared_ptr webSocket) - { - Json::Value pdu; - pdu["kind"] = "error"; - pdu["id"] = id; - pdu["message"] = errMsg; - webSocket->send(pdu.toStyledString()); - } - - void messageHandler(const std::string& str, - std::shared_ptr webSocket) - { - std::cerr << "Received message: " << str.size() << std::endl; - - Json::Value data; - Json::Reader reader; - if (!reader.parse(str, data)) - { - errorHandler("Invalid JSON", std::string(), webSocket); - return; - } - - std::cout << "id: " << data["id"].asString() << std::endl; - - std::string content = ix::base64_decode(data["content"].asString()); - std::cout << "Content size: " << content.size() << std::endl; - - // Validate checksum - uint64_t cksum = ix::djb2Hash(data["content"].asString()); - uint64_t cksumRef = data["djb2_hash"].asUInt64(); - - std::cout << "Computed hash: " << cksum << std::endl; - std::cout << "Reference hash: " << cksumRef << std::endl; - - if (cksum != cksumRef) - { - errorHandler("Hash mismatch.", std::string(), webSocket); - return; - } - - std::string filename = data["filename"].asString(); - filename = extractFilename(filename); - - std::ofstream out(filename); - out << content; - out.close(); - - Json::Value pdu; - pdu["ack"] = true; - pdu["id"] = data["id"]; - pdu["filename"] = data["filename"]; - webSocket->send(pdu.toStyledString()); - } -} - -int main(int argc, char** argv) -{ - int port = 8080; - if (argc == 2) - { - std::stringstream ss; - ss << argv[1]; - ss >> port; - } - - ix::WebSocketServer server(port); - - server.setOnConnectionCallback( - [&server](std::shared_ptr webSocket) - { - webSocket->setOnMessageCallback( - [webSocket, &server](ix::WebSocketMessageType messageType, - const std::string& str, - size_t wireSize, - const ix::WebSocketErrorInfo& error, - const ix::WebSocketOpenInfo& openInfo, - const ix::WebSocketCloseInfo& closeInfo) - { - if (messageType == ix::WebSocket_MessageType_Open) - { - std::cerr << "New connection" << std::endl; - std::cerr << "Uri: " << openInfo.uri << std::endl; - std::cerr << "Headers:" << std::endl; - for (auto it : openInfo.headers) - { - std::cerr << it.first << ": " << it.second << std::endl; - } - } - else if (messageType == ix::WebSocket_MessageType_Close) - { - std::cerr << "Closed connection" << std::endl; - } - else if (messageType == ix::WebSocket_MessageType_Message) - { - messageHandler(str, webSocket); - } - } - ); - } - ); - - auto res = server.listen(); - if (!res.first) - { - std::cerr << res.second << std::endl; - return 1; - } - - server.start(); - server.wait(); - - return 0; -} diff --git a/examples/ws_receive/ws_receive.js b/examples/ws_receive/ws_receive.js deleted file mode 100644 index 465ff11e..00000000 --- a/examples/ws_receive/ws_receive.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * ws_receive.js - * Author: Benjamin Sergeant - * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. - */ -const WebSocket = require('ws') -const djb2 = require('djb2') -const fs = require('fs') - -const wss = new WebSocket.Server({ port: 8080, - perMessageDeflate: false, - maxPayload: 1024 * 1024 * 1024 * 1024}); - -wss.on('connection', function connection(ws) { - ws.on('message', function incoming(data) { - console.log('Received message') - - let str = data.toString() - let obj = JSON.parse(str) - - console.log(obj.id) - console.log(obj.djb2_hash) - console.log(djb2(obj.content)) - - var content = Buffer.from(obj.content, 'base64') - // let bytes = base64.decode(obj.content) - - let path = obj.filename - fs.writeFile(path, content, function(err) { - if (err) { - throw err - } else { - console.log('wrote data to disk') - } - }); - - let response = { - id: obj.id - } - - ws.send(JSON.stringify(response)) - }); -}); diff --git a/examples/ws_send/README.md b/examples/ws_send/README.md deleted file mode 100644 index 7bd97c52..00000000 --- a/examples/ws_send/README.md +++ /dev/null @@ -1 +0,0 @@ -ws_send is a simple upload program. It needs to be used in conjonction with ws_receive. diff --git a/examples/ws_send/ixcrypto b/examples/ws_send/ixcrypto deleted file mode 120000 index 1b13e4a5..00000000 --- a/examples/ws_send/ixcrypto +++ /dev/null @@ -1 +0,0 @@ -../cobra_publisher/ixcrypto \ No newline at end of file diff --git a/examples/ws_send/jsoncpp b/examples/ws_send/jsoncpp deleted file mode 120000 index 00ead268..00000000 --- a/examples/ws_send/jsoncpp +++ /dev/null @@ -1 +0,0 @@ -../cobra_publisher/jsoncpp \ No newline at end of file diff --git a/makefile b/makefile index b08d82e7..6d8cca42 100644 --- a/makefile +++ b/makefile @@ -5,14 +5,14 @@ all: run .PHONY: docker docker: - docker build -t ws_connect:latest . + docker build -t broadcast_server:latest . -run: docker - docker run --cap-add sys_ptrace -it ws_connect:latest bash +run: + docker run --cap-add sys_ptrace -it broadcast_server:latest bash # this is helpful to remove trailing whitespaces trail: - sh third_party/remote_trailing_whitespaces.sh + sh third_party/remove_trailing_whitespaces.sh build: (cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3a133b4c..78ed3348 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,13 +18,14 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket) include_directories( ${PROJECT_SOURCE_DIR}/Catch2/single_include + ../third_party/msgpack11 ) # Shared sources set (SOURCES test_runner.cpp IXTest.cpp - msgpack11.cpp + ../third_party/msgpack11/msgpack11.cpp IXDNSLookupTest.cpp IXSocketTest.cpp diff --git a/third_party/cli11/CLI11.hpp b/third_party/cli11/CLI11.hpp new file mode 100644 index 00000000..ae58c815 --- /dev/null +++ b/third_party/cli11/CLI11.hpp @@ -0,0 +1,4641 @@ +#pragma once + +// CLI11: Version 1.7.1 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v1.7.1 +// +// From LICENSE: +// +// CLI11 1.7 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Standard combined includes: + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Verbatim copy from CLI/Version.hpp: + + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 7 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.7.1" + + + + +// Verbatim copy from CLI/Macros.hpp: + + +// The following version macro is very similar to the one in PyBind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + + + + +// Verbatim copy from CLI/Optional.hpp: + +#ifdef __has_include + +// You can explicitly enable or disable support +// by defining these to 1 or 0. +#if defined(CLI11_CPP17) && __has_include() && \ + !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 1 +#elif !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 0 +#endif + +#if defined(CLI11_CPP14) && __has_include() && \ + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) +#define CLI11_EXPERIMENTAL_OPTIONAL 1 +#elif !defined(CLI11_EXPERIMENTAL_OPTIONAL) +#define CLI11_EXPERIMENTAL_OPTIONAL 0 +#endif + +#if __has_include() && !defined(CLI11_BOOST_OPTIONAL) +#include +#if BOOST_VERSION >= 105800 +#define CLI11_BOOST_OPTIONAL 1 +#endif +#elif !defined(CLI11_BOOST_OPTIONAL) +#define CLI11_BOOST_OPTIONAL 0 +#endif + +#endif + +#if CLI11_STD_OPTIONAL +#include +#endif +#if CLI11_EXPERIMENTAL_OPTIONAL +#include +#endif +#if CLI11_BOOST_OPTIONAL +#include +#endif + + +// From CLI/Version.hpp: + + + +// From CLI/Macros.hpp: + + + +// From CLI/Optional.hpp: + +namespace CLI { + +#if CLI11_STD_OPTIONAL +template std::istream &operator>>(std::istream &in, std::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_EXPERIMENTAL_OPTIONAL +template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_BOOST_OPTIONAL +template std::istream &operator>>(std::istream &in, boost::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if CLI11_STD_OPTIONAL +using std::optional; +#elif CLI11_EXPERIMENTAL_OPTIONAL +using std::experimental::optional; +#elif CLI11_BOOST_OPTIONAL +using boost::optional; +#endif + +// This is true if any optional is found +#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL +#define CLI11_OPTIONAL 1 +#endif + +} // namespace CLI + +// From CLI/StringTools.hpp: + +namespace CLI { +namespace detail { + +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(""); + else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << i; + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + out << description; + } + out << "\n"; + return out; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } + +/// Verify following characters of an option +template bool valid_later_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; +} + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str) { + + const std::string delims("\'\"`"); + auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + trim(str); + + std::vector output; + bool embeddedQuote = false; + char keyChar = ' '; + while(!str.empty()) { + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + trim(str); + } + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(std::string leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline size_t escape_detect(std::string &str, size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + +} // namespace detail +} // namespace CLI + +// From CLI/Error.hpp: + +namespace CLI { + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + int get_exit_code() const { return actual_exit_code; } + + std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, size_t recieved) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(recieved)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(recieved)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + +} // namespace CLI + +// From CLI/TypeTools.hpp: + +namespace CLI { + +// Type tools + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. + +template using enable_if_t = typename std::enable_if::type; + +/// Check to see if something is a vector (fail check by default) +template struct is_vector { static const bool value = false; }; + +/// Check to see if something is a vector (true if actually a vector) +template struct is_vector> { static bool const value = true; }; + +/// Check to see if something is bool (fail check by default) +template struct is_bool { static const bool value = false; }; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool { static bool const value = true; }; + +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value && std::is_signed::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// This one should not be used, since vector types print the internal type +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "VECTOR"; +} + +template ::value && !std::is_integral::value && !is_vector::value, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +// Lexical cast + +/// Signed integers / enums +template ::value && std::is_signed::value), detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + long long output_ll = std::stoll(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + unsigned long long output_ll = std::stoull(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Floats +template ::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar +template ::value && !std::is_integral::value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + output = input; + return true; +} + +/// Non-string parsable +template ::value && !std::is_integral::value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + std::istringstream is; + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Split.hpp: + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } else + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } else + return false; +} + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } else + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From CLI/ConfigFwd.hpp: + +namespace CLI { + +class App; + +namespace detail { + +/// Comma separated join, adds quotes if needed +inline std::string ini_join(std::vector args) { + std::ostringstream s; + size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) + s << " "; + + auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); + if(it == arg.end()) + s << arg; + else if(arg.find(R"(")") == std::string::npos) + s << R"(")" << arg << R"(")"; + else + s << R"(')" << arg << R"(')"; + } + + return s.str(); +} + +} // namespace detail + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents; + + /// This is the name + std::string name; + + /// Listing of inputs + std::vector inputs; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Convert a flag to a bool + virtual std::vector to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + std::string val = item.inputs.at(0); + val = detail::to_lower(val); + + if(val == "true" || val == "on" || val == "yes") { + return std::vector(1); + } else if(val == "false" || val == "off" || val == "no") { + return std::vector(); + } else { + try { + size_t ui = std::stoul(val); + return std::vector(ui); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); + } + } + } else { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override { + std::string line; + std::string section = "default"; + + std::vector output; + + while(getline(input, line)) { + std::vector items_buffer; + + detail::trim(line); + size_t len = line.length(); + if(len > 1 && line[0] == '[' && line[len - 1] == ']') { + section = line.substr(1, len - 2); + } else if(len > 0 && line[0] != ';') { + output.emplace_back(); + ConfigItem &out = output.back(); + + // Find = in string, split and recombine + auto pos = line.find('='); + if(pos != std::string::npos) { + out.name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + items_buffer = detail::split_up(item); + } else { + out.name = detail::trim_copy(line); + items_buffer = {"ON"}; + } + + if(detail::to_lower(section) != "default") { + out.parents = {section}; + } + + if(out.name.find('.') != std::string::npos) { + std::vector plist = detail::split(out.name, '.'); + out.name = plist.back(); + plist.pop_back(); + out.parents.insert(out.parents.end(), plist.begin(), plist.end()); + } + + out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); + } + } + return output; + } +}; + +} // namespace CLI + +// From CLI/Validators.hpp: + +namespace CLI { + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +struct Validator { + /// This is the type name, if empty the type name will not be changed + std::string tname; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func; + + /// This is the required operator for a validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { return func(str); }; + + /// Combining validators is a new validator + Validator operator&(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(!s1.empty() && !s2.empty()) + return s1 + " & " + s2; + else + return s1 + s2; + }; + return newval; + } + + /// Combining validators is a new validator + Validator operator|(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(s1.empty() || s2.empty()) + return std::string(); + else + return s1 + " & " + s2; + }; + return newval; + } +}; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Check for an existing file (returns error message if check fails) +struct ExistingFileValidator : public Validator { + ExistingFileValidator() { + tname = "FILE"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "File does not exist: " + filename; + } else if(is_dir) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +struct ExistingDirectoryValidator : public Validator { + ExistingDirectoryValidator() { + tname = "DIR"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "Directory does not exist: " + filename; + } else if(!is_dir) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +struct ExistingPathValidator : public Validator { + ExistingPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool const exist = stat(filename.c_str(), &buffer) == 0; + if(!exist) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +struct NonexistentPathValidator : public Validator { + NonexistentPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Produce a range (factory). Min and max are inclusive. +struct Range : public Validator { + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Range(T min, T max) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + + tname = out.str(); + func = [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(val < min || val > max) + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template explicit Range(T max) : Range(static_cast(0), max) {} +}; + +namespace detail { +/// split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second everything +/// else +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(!ExistingFile(commandline.substr(0, esp)).empty()) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + esp = commandline.find_first_of(' ', 1); + break; + } + } + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} +} // namespace detail +/// @} + +} // namespace CLI + +// From CLI/FormatterFwd.hpp: + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, //< The normal, detailed help + All, //< A fully expanded help + Sub, //< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + virtual ~FormatterBase() = default; + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI + +// From CLI/Option.hpp: + +namespace CLI { + +using results_t = std::vector; +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr