[WIP] Added wireguard, prepare to test and debug
This commit is contained in:
parent
e644575bc5
commit
7c7f77adc6
98 changed files with 4410 additions and 302 deletions
29
client/platforms/ios/WireGuard-Bridging-Header.h
Normal file
29
client/platforms/ios/WireGuard-Bridging-Header.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "wireguard-go-version.h"
|
||||
#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define WG_KEY_LEN (32)
|
||||
#define WG_KEY_LEN_BASE64 (45)
|
||||
#define WG_KEY_LEN_HEX (65)
|
||||
|
||||
void key_to_base64(char base64[WG_KEY_LEN_BASE64],
|
||||
const uint8_t key[WG_KEY_LEN]);
|
||||
bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64);
|
||||
|
||||
void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]);
|
||||
bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
|
||||
|
||||
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
|
||||
|
||||
void write_msg_to_log(const char* tag, const char* msg);
|
||||
|
||||
#import "TargetConditionals.h"
|
||||
#if TARGET_OS_OSX
|
||||
# include <libproc.h>
|
||||
#endif
|
||||
127
client/platforms/ios/bigint.h
Normal file
127
client/platforms/ios/bigint.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BIGINT_H
|
||||
#define BIGINT_H
|
||||
|
||||
#include <QVector>
|
||||
|
||||
// This BigInt implementation is meant to be used for IPv6 addresses. It
|
||||
// doesn't support dynamic resize: when the max size is reached, the value
|
||||
// overflows. If you need to change the size, use `resize()`.
|
||||
|
||||
class BigInt final {
|
||||
public:
|
||||
explicit BigInt(uint8_t bytes) {
|
||||
m_value.resize(bytes);
|
||||
memset(m_value.data(), 0, bytes);
|
||||
}
|
||||
|
||||
BigInt(const BigInt& other) { m_value = other.m_value; }
|
||||
|
||||
const uint8_t* value() const { return m_value.data(); }
|
||||
|
||||
uint8_t size() const { return m_value.size(); }
|
||||
|
||||
// Assign operator.
|
||||
|
||||
BigInt& operator=(const BigInt& other) {
|
||||
m_value = other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Comparison operators.
|
||||
|
||||
bool operator==(const BigInt& other) const {
|
||||
return m_value == other.m_value;
|
||||
}
|
||||
|
||||
bool operator!=(const BigInt& other) const { return !(*this == other); }
|
||||
|
||||
bool operator<(const BigInt& other) const { return cmp(other) < 0; }
|
||||
|
||||
bool operator>(const BigInt& other) const { return cmp(other) > 0; }
|
||||
|
||||
bool operator<=(const BigInt& other) const { return cmp(other) <= 0; }
|
||||
|
||||
bool operator>=(const BigInt& other) const { return cmp(other) >= 0; }
|
||||
|
||||
// math operators (only some of them are implemented)
|
||||
|
||||
BigInt& operator++() {
|
||||
for (int i = size() - 1; i >= 0; --i) {
|
||||
if (m_value[i] < UINT8_MAX) {
|
||||
++m_value[i];
|
||||
return *this;
|
||||
}
|
||||
m_value[i] = 0;
|
||||
}
|
||||
|
||||
// overflow
|
||||
memset(m_value.data(), 0, size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigInt& operator+=(const BigInt& other) {
|
||||
Q_ASSERT(other.size() == size());
|
||||
|
||||
uint8_t carry = 0;
|
||||
for (int i = m_value.size() - 1; i >= 0; --i) {
|
||||
uint16_t total = carry + m_value[i] + other.m_value[i];
|
||||
m_value[i] = (uint8_t)(total & UINT8_MAX);
|
||||
carry = (uint8_t)((total & 0xFF00) >> 8);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Shift operators
|
||||
|
||||
BigInt operator>>(int shift) {
|
||||
BigInt x(size());
|
||||
x = *this;
|
||||
|
||||
for (int i = 0; i < shift; i++) {
|
||||
BigInt a(size());
|
||||
a = x;
|
||||
|
||||
a.m_value[size() - 1] = x.m_value[size() - 1] >> 1;
|
||||
for (int j = size() - 2; j >= 0; j--) {
|
||||
a.m_value[j] = x.m_value[j] >> 1;
|
||||
if ((x.m_value[j] & 1) != 0) {
|
||||
a.m_value[j + 1] |= 128; // Set most significant bit or a uint8_t
|
||||
}
|
||||
}
|
||||
|
||||
x = a;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
void setValueAt(uint8_t value, uint8_t pos) {
|
||||
Q_ASSERT(pos < size());
|
||||
m_value[pos] = value;
|
||||
}
|
||||
|
||||
uint8_t valueAt(uint8_t pos) const {
|
||||
Q_ASSERT(size() > pos);
|
||||
return m_value[pos];
|
||||
}
|
||||
|
||||
private:
|
||||
int cmp(const BigInt& other) const {
|
||||
Q_ASSERT(size() == other.size());
|
||||
for (int i = 0; i < size(); i++) {
|
||||
int diff = (m_value[i] - other.m_value[i]);
|
||||
if (diff != 0) return diff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<uint8_t> m_value;
|
||||
};
|
||||
|
||||
#endif // BIGINT_H
|
||||
86
client/platforms/ios/bigintipv6addr.h
Normal file
86
client/platforms/ios/bigintipv6addr.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BIGINTIPV6ADDR_H
|
||||
#define BIGINTIPV6ADDR_H
|
||||
|
||||
#include "bigint.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
|
||||
class BigIntIPv6Addr final {
|
||||
public:
|
||||
BigIntIPv6Addr() : m_value(16) {}
|
||||
|
||||
explicit BigIntIPv6Addr(const Q_IPV6ADDR& a) : m_value(16) {
|
||||
for (int i = 0; i < 16; ++i) m_value.setValueAt(a[i], i);
|
||||
}
|
||||
|
||||
BigIntIPv6Addr(const BigIntIPv6Addr& other) : m_value(16) { *this = other; }
|
||||
|
||||
Q_IPV6ADDR value() const {
|
||||
Q_IPV6ADDR addr;
|
||||
for (int i = 0; i < 16; ++i) addr[i] = m_value.valueAt(i);
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Assign operator.
|
||||
|
||||
BigIntIPv6Addr& operator=(const BigIntIPv6Addr& other) {
|
||||
m_value = other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Comparison operators.
|
||||
|
||||
bool operator==(const BigIntIPv6Addr& other) const {
|
||||
return m_value == other.m_value;
|
||||
}
|
||||
|
||||
bool operator!=(const BigIntIPv6Addr& other) const {
|
||||
return m_value != other.m_value;
|
||||
}
|
||||
|
||||
bool operator<(const BigIntIPv6Addr& other) const {
|
||||
return m_value < other.m_value;
|
||||
}
|
||||
|
||||
bool operator>(const BigIntIPv6Addr& other) const {
|
||||
return m_value > other.m_value;
|
||||
}
|
||||
|
||||
bool operator<=(const BigIntIPv6Addr& other) const {
|
||||
return m_value <= other.m_value;
|
||||
}
|
||||
|
||||
bool operator>=(const BigIntIPv6Addr& other) const {
|
||||
return m_value >= other.m_value;
|
||||
}
|
||||
|
||||
// math operators (only some of them are implemented)
|
||||
|
||||
BigIntIPv6Addr& operator++() {
|
||||
++m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigIntIPv6Addr& operator+=(const BigIntIPv6Addr& b) {
|
||||
m_value += b.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Shift operators
|
||||
|
||||
BigIntIPv6Addr operator>>(int shift) {
|
||||
BigIntIPv6Addr x;
|
||||
|
||||
x.m_value = m_value >> shift;
|
||||
return x;
|
||||
}
|
||||
|
||||
private:
|
||||
BigInt m_value;
|
||||
};
|
||||
|
||||
#endif // BIGINTIPV6ADDR_H
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
#ifndef IOSCONTROLLER_H
|
||||
#define IOSCONTROLLER_H
|
||||
|
||||
#include "controllerimpl.h"
|
||||
#include "vpnprotocol.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class IOSController final : public ControllerImpl {
|
||||
Q_DISABLE_COPY_MOVE(IOSController)
|
||||
class IOSVPNProtocol final : public VpnProtocol {
|
||||
Q_DISABLE_COPY_MOVE(IOSVPNProtocol)
|
||||
|
||||
public:
|
||||
IOSController();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#ifndef NETWORK_EXTENSION
|
||||
# include "logger.h"
|
||||
//# include "logger.h"
|
||||
#else
|
||||
# import <Foundation/Foundation.h>
|
||||
# import <os/log.h>
|
||||
|
|
@ -183,13 +183,13 @@ EXPORT bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN
|
|||
|
||||
#ifndef NETWORK_EXTENSION
|
||||
namespace {
|
||||
Logger logger(LOG_IOS, "IOSSGlue");
|
||||
//Logger logger(LOG_IOS, "IOSSGlue");
|
||||
}
|
||||
#endif
|
||||
|
||||
EXPORT void write_msg_to_log(const char* tag, const char* msg) {
|
||||
#ifndef NETWORK_EXTENSION
|
||||
logger.debug() << "Swift log - tag:" << tag << "msg: " << msg;
|
||||
// logger.debug() << "Swift log - tag:" << tag << "msg: " << msg;
|
||||
#else
|
||||
os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "tag: %s - msg: %s", tag, msg);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import os
|
|||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
|
||||
private lazy var adapter: WireGuardAdapter = {
|
||||
private lazy var wgAdapter: WireGuardAdapter = {
|
||||
return WireGuardAdapter(with: self) { logLevel, message in
|
||||
wg_log(logLevel.osLogLevel, message: message)
|
||||
}
|
||||
|
|
@ -29,9 +29,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
}
|
||||
|
||||
// Start the tunnel
|
||||
adapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError = adapterError else {
|
||||
let interfaceName = self.adapter.interfaceName ?? "unknown"
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
adapter.stop { error in
|
||||
wgAdapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
if let error = error {
|
||||
|
|
@ -93,7 +93,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
guard let completionHandler = completionHandler else { return }
|
||||
|
||||
if messageData.count == 1 && messageData[0] == 0 {
|
||||
adapter.getRuntimeConfiguration { settings in
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings = settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
|
|
@ -111,14 +111,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
|
||||
do {
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||
adapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.adapter.getRuntimeConfiguration { settings in
|
||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings = settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
|
|
|
|||
292
client/platforms/ios/iosvpnprotocol.swift
Normal file
292
client/platforms/ios/iosvpnprotocol.swift
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
let vpnName = "AmneziaVPN"
|
||||
var vpnBundleID = "";
|
||||
|
||||
@objc class VPNIPAddressRange : NSObject {
|
||||
public var address: NSString = ""
|
||||
public var networkPrefixLength: UInt8 = 0
|
||||
public var isIpv6: Bool = false
|
||||
|
||||
@objc init(address: NSString, networkPrefixLength: UInt8, isIpv6: Bool) {
|
||||
super.init()
|
||||
|
||||
self.address = address
|
||||
self.networkPrefixLength = networkPrefixLength
|
||||
self.isIpv6 = isIpv6
|
||||
}
|
||||
}
|
||||
|
||||
public class IOSVpnProtocolImpl : NSObject {
|
||||
|
||||
private var tunnel: NETunnelProviderManager? = nil
|
||||
private var stateChangeCallback: ((Bool) -> Void?)? = nil
|
||||
private var privateKey : PrivateKey? = nil
|
||||
private var deviceIpv4Address: String? = nil
|
||||
private var deviceIpv6Address: String? = nil
|
||||
|
||||
@objc enum IOSConnectionState: Int { case Error, Connected, Disconnected }
|
||||
|
||||
@objc init(bundleID: String, privateKey: Data, deviceIpv4Address: String, deviceIpv6Address: String, closure: @escaping (IOSConnectionState, Date?) -> Void, callback: @escaping (Bool) -> Void) {
|
||||
super.init()
|
||||
|
||||
Logger.configureGlobal(tagged: "APP", withFilePath: "")
|
||||
|
||||
vpnBundleID = bundleID;
|
||||
precondition(!vpnBundleID.isEmpty)
|
||||
|
||||
stateChangeCallback = callback
|
||||
self.privateKey = PrivateKey(rawValue: privateKey)
|
||||
self.deviceIpv4Address = deviceIpv4Address
|
||||
self.deviceIpv6Address = deviceIpv6Address
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil)
|
||||
|
||||
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
|
||||
if let error = error {
|
||||
Logger.global?.log(message: "Loading from preference failed: \(error)")
|
||||
closure(IOSConnectionState.Error, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if self == nil {
|
||||
Logger.global?.log(message: "We are shutting down.")
|
||||
return
|
||||
}
|
||||
|
||||
let nsManagers = managers ?? []
|
||||
Logger.global?.log(message: "We have received \(nsManagers.count) managers.")
|
||||
|
||||
let tunnel = nsManagers.first(where: IOSVpnProtocolImpl.isOurManager(_:))
|
||||
if tunnel == nil {
|
||||
Logger.global?.log(message: "Creating the tunnel")
|
||||
self!.tunnel = NETunnelProviderManager()
|
||||
closure(IOSConnectionState.Disconnected, nil)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Tunnel already exists")
|
||||
|
||||
self!.tunnel = tunnel
|
||||
if tunnel?.connection.status == .connected {
|
||||
closure(IOSConnectionState.Connected, tunnel?.connection.connectedDate)
|
||||
} else {
|
||||
closure(IOSConnectionState.Disconnected, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func vpnStatusDidChange(notification: Notification) {
|
||||
guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return }
|
||||
|
||||
switch session.status {
|
||||
case .connected:
|
||||
Logger.global?.log(message: "STATE CHANGED: connected")
|
||||
case .connecting:
|
||||
Logger.global?.log(message: "STATE CHANGED: connecting")
|
||||
case .disconnected:
|
||||
Logger.global?.log(message: "STATE CHANGED: disconnected")
|
||||
case .disconnecting:
|
||||
Logger.global?.log(message: "STATE CHANGED: disconnecting")
|
||||
case .invalid:
|
||||
Logger.global?.log(message: "STATE CHANGED: invalid")
|
||||
case .reasserting:
|
||||
Logger.global?.log(message: "STATE CHANGED: reasserting")
|
||||
default:
|
||||
Logger.global?.log(message: "STATE CHANGED: unknown status")
|
||||
}
|
||||
|
||||
// We care about "unknown" state changes.
|
||||
if (session.status != .connected && session.status != .disconnected) {
|
||||
return
|
||||
}
|
||||
|
||||
stateChangeCallback?(session.status == .connected)
|
||||
}
|
||||
|
||||
private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool {
|
||||
guard
|
||||
let proto = manager.protocolConfiguration,
|
||||
let tunnelProto = proto as? NETunnelProviderProtocol
|
||||
else {
|
||||
Logger.global?.log(message: "Ignoring manager because the proto is invalid.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (tunnelProto.providerBundleIdentifier == nil) {
|
||||
Logger.global?.log(message: "Ignoring manager because the bundle identifier is null.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (tunnelProto.providerBundleIdentifier != vpnBundleID) {
|
||||
Logger.global?.log(message: "Ignoring manager because the bundle identifier doesn't match.")
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)")
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array<VPNIPAddressRange>, ipv6Enabled: Bool, reason: Int, failureCallback: @escaping () -> Void) {
|
||||
Logger.global?.log(message: "Connecting")
|
||||
assert(tunnel != nil)
|
||||
|
||||
// Let's remove the previous config if it exists.
|
||||
(tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
|
||||
let keyData = PublicKey(base64Key: serverPublicKey)!
|
||||
let dnsServerIP = IPv4Address(dnsServer)
|
||||
let ipv6GatewayIP = IPv6Address(serverIpv6Gateway)
|
||||
|
||||
var peerConfiguration = PeerConfiguration(publicKey: keyData)
|
||||
peerConfiguration.endpoint = Endpoint(from: serverIpv4AddrIn + ":\(serverPort )")
|
||||
peerConfiguration.allowedIPs = []
|
||||
|
||||
allowedIPAddressRanges.forEach {
|
||||
if (!$0.isIpv6) {
|
||||
peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv4Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength))
|
||||
} else if (ipv6Enabled) {
|
||||
peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv6Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength))
|
||||
}
|
||||
}
|
||||
|
||||
var peerConfigurations: [PeerConfiguration] = []
|
||||
peerConfigurations.append(peerConfiguration)
|
||||
|
||||
var interface = InterfaceConfiguration(privateKey: privateKey!)
|
||||
|
||||
if let ipv4Address = IPAddressRange(from: deviceIpv4Address!),
|
||||
let ipv6Address = IPAddressRange(from: deviceIpv6Address!) {
|
||||
interface.addresses = [ipv4Address]
|
||||
if (ipv6Enabled) {
|
||||
interface.addresses.append(ipv6Address)
|
||||
}
|
||||
}
|
||||
interface.dns = [ DNSServer(address: dnsServerIP!)]
|
||||
|
||||
if (ipv6Enabled) {
|
||||
interface.dns.append(DNSServer(address: ipv6GatewayIP!))
|
||||
}
|
||||
|
||||
let config = TunnelConfiguration(name: vpnName, interface: interface, peers: peerConfigurations)
|
||||
|
||||
self.configureTunnel(config: config, reason: reason, failureCallback: failureCallback)
|
||||
}
|
||||
|
||||
func configureTunnel(config: TunnelConfiguration, reason: Int, failureCallback: @escaping () -> Void) {
|
||||
guard let proto = NETunnelProviderProtocol(tunnelConfiguration: config) else {
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
proto.providerBundleIdentifier = vpnBundleID
|
||||
|
||||
tunnel!.protocolConfiguration = proto
|
||||
tunnel!.localizedDescription = vpnName
|
||||
tunnel!.isEnabled = true
|
||||
|
||||
tunnel!.saveToPreferences { [unowned self] saveError in
|
||||
if let error = saveError {
|
||||
Logger.global?.log(message: "Connect Tunnel Save Error: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Saving the tunnel succeeded")
|
||||
|
||||
self.tunnel!.loadFromPreferences { error in
|
||||
if let error = error {
|
||||
Logger.global?.log(message: "Connect Tunnel Load Error: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Loading the tunnel succeeded")
|
||||
|
||||
do {
|
||||
if (reason == 1 /* ReasonSwitching */) {
|
||||
let settings = config.asWgQuickConfig()
|
||||
let settingsData = settings.data(using: .utf8)!
|
||||
try (self.tunnel!.connection as? NETunnelProviderSession)?
|
||||
.sendProviderMessage(settingsData) { data in
|
||||
guard let data = data,
|
||||
let configString = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
Logger.global?.log(message: "Failed to convert response to string")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try (self.tunnel!.connection as? NETunnelProviderSession)?.startTunnel()
|
||||
}
|
||||
} catch let error {
|
||||
Logger.global?.log(message: "Something went wrong: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func disconnect() {
|
||||
Logger.global?.log(message: "Disconnecting")
|
||||
assert(tunnel != nil)
|
||||
(tunnel!.connection as? NETunnelProviderSession)?.stopTunnel()
|
||||
}
|
||||
|
||||
@objc func checkStatus(callback: @escaping (String, String, String) -> Void) {
|
||||
Logger.global?.log(message: "Check status")
|
||||
assert(tunnel != nil)
|
||||
|
||||
let proto = tunnel!.protocolConfiguration as? NETunnelProviderProtocol
|
||||
if proto == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
let tunnelConfiguration = proto?.asTunnelConfiguration()
|
||||
if tunnelConfiguration == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
let serverIpv4Gateway = tunnelConfiguration?.interface.dns[0].address
|
||||
if serverIpv4Gateway == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
let deviceIpv4Address = tunnelConfiguration?.interface.addresses[0].address
|
||||
if deviceIpv4Address == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
guard let session = tunnel?.connection as? NETunnelProviderSession
|
||||
else {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in
|
||||
guard let data = data,
|
||||
let configString = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
Logger.global?.log(message: "Failed to convert data to string")
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
callback("\(serverIpv4Gateway!)", "\(deviceIpv4Address!)", configString)
|
||||
}
|
||||
} catch {
|
||||
Logger.global?.log(message: "Failed to retrieve data from session")
|
||||
callback("", "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
313
client/platforms/ios/ipaddress.cpp
Normal file
313
client/platforms/ios/ipaddress.cpp
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ipaddress.h"
|
||||
#include "bigintipv6addr.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
namespace {
|
||||
|
||||
quint32 s_allIpV4Ones = static_cast<quint32>(qPow(2, 32) - 1);
|
||||
|
||||
BigIntIPv6Addr s_allIPv6Ones;
|
||||
bool s_ipv6Initialized = false;
|
||||
|
||||
void maybeInitialize() {
|
||||
if (s_ipv6Initialized) return;
|
||||
|
||||
s_ipv6Initialized = true;
|
||||
|
||||
Q_IPV6ADDR allOnes;
|
||||
memset((void*)&allOnes, static_cast<quint8>(qPow(2, 8) - 1), sizeof(allOnes));
|
||||
|
||||
s_allIPv6Ones = BigIntIPv6Addr(allOnes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
IPAddress IPAddress::create(const QString& ip) {
|
||||
if (ip.contains("/")) {
|
||||
QPair<QHostAddress, int> p = QHostAddress::parseSubnet(ip);
|
||||
|
||||
if (p.first.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
if (p.second < 32) {
|
||||
return IPAddress(p.first, p.second);
|
||||
}
|
||||
return IPAddress(p.first);
|
||||
}
|
||||
|
||||
if (p.first.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
if (p.second < 128) {
|
||||
return IPAddress(p.first, p.second);
|
||||
}
|
||||
return IPAddress(p.first);
|
||||
}
|
||||
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
return IPAddress(QHostAddress(ip));
|
||||
}
|
||||
|
||||
IPAddress::IPAddress() {
|
||||
maybeInitialize();
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(const IPAddress& other) {
|
||||
maybeInitialize();
|
||||
*this = other;
|
||||
}
|
||||
|
||||
IPAddress& IPAddress::operator=(const IPAddress& other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
m_address = other.m_address;
|
||||
m_prefixLength = other.m_prefixLength;
|
||||
m_netmask = other.m_netmask;
|
||||
m_hostmask = other.m_hostmask;
|
||||
m_broadcastAddress = other.m_broadcastAddress;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(const QHostAddress& address)
|
||||
: m_address(address), m_broadcastAddress(address) {
|
||||
maybeInitialize();
|
||||
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
m_prefixLength = 32;
|
||||
m_netmask = QHostAddress(s_allIpV4Ones);
|
||||
m_hostmask = QHostAddress((quint32)(0));
|
||||
} else {
|
||||
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
m_prefixLength = 128;
|
||||
|
||||
m_netmask = QHostAddress(s_allIPv6Ones.value());
|
||||
|
||||
{
|
||||
Q_IPV6ADDR ipv6;
|
||||
memset((void*)&ipv6, 0, sizeof(ipv6));
|
||||
m_hostmask = QHostAddress(ipv6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(const QHostAddress& address, int prefixLength)
|
||||
: m_address(address), m_prefixLength(prefixLength) {
|
||||
maybeInitialize();
|
||||
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
Q_ASSERT(prefixLength >= 0 && prefixLength <= 32);
|
||||
m_netmask = QHostAddress(s_allIpV4Ones ^ (s_allIpV4Ones >> prefixLength));
|
||||
m_hostmask = QHostAddress(m_netmask.toIPv4Address() ^ s_allIpV4Ones);
|
||||
m_broadcastAddress =
|
||||
QHostAddress(address.toIPv4Address() | m_hostmask.toIPv4Address());
|
||||
} else {
|
||||
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
Q_ASSERT(prefixLength >= 0 && prefixLength <= 128);
|
||||
|
||||
Q_IPV6ADDR netmask;
|
||||
{
|
||||
BigIntIPv6Addr tmp = (s_allIPv6Ones >> prefixLength);
|
||||
for (int i = 0; i < 16; ++i)
|
||||
netmask[i] = s_allIPv6Ones.value()[i] ^ tmp.value()[i];
|
||||
}
|
||||
m_netmask = QHostAddress(netmask);
|
||||
|
||||
{
|
||||
Q_IPV6ADDR tmp;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
tmp[i] = netmask[i] ^ s_allIPv6Ones.value()[i];
|
||||
m_hostmask = QHostAddress(tmp);
|
||||
}
|
||||
|
||||
{
|
||||
Q_IPV6ADDR ipv6Address = address.toIPv6Address();
|
||||
Q_IPV6ADDR ipv6Hostname = m_hostmask.toIPv6Address();
|
||||
for (int i = 0; i < 16; ++i) ipv6Address[i] |= ipv6Hostname[i];
|
||||
m_broadcastAddress = QHostAddress(ipv6Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress::~IPAddress() { }
|
||||
|
||||
QAbstractSocket::NetworkLayerProtocol IPAddress::type() const {
|
||||
return m_address.protocol();
|
||||
}
|
||||
|
||||
bool IPAddress::overlaps(const IPAddress& other) const {
|
||||
return other.contains(m_address) || other.contains(m_broadcastAddress) ||
|
||||
contains(other.m_address) || contains(other.m_broadcastAddress);
|
||||
}
|
||||
|
||||
bool IPAddress::contains(const QHostAddress& address) const {
|
||||
if (address.protocol() != m_address.protocol()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return (m_address.toIPv4Address() <= address.toIPv4Address()) &&
|
||||
(address.toIPv4Address() <= m_broadcastAddress.toIPv4Address());
|
||||
}
|
||||
|
||||
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
return (BigIntIPv6Addr(m_address.toIPv6Address()) <=
|
||||
BigIntIPv6Addr(address.toIPv6Address())) &&
|
||||
(BigIntIPv6Addr(address.toIPv6Address()) <=
|
||||
BigIntIPv6Addr(m_broadcastAddress.toIPv6Address()));
|
||||
}
|
||||
|
||||
bool IPAddress::operator==(const IPAddress& other) const {
|
||||
return m_address == other.m_address && m_netmask == other.m_netmask;
|
||||
}
|
||||
|
||||
bool IPAddress::subnetOf(const IPAddress& other) const {
|
||||
if (other.m_address.protocol() != m_address.protocol()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return other.m_address.toIPv4Address() <= m_address.toIPv4Address() &&
|
||||
other.m_broadcastAddress.toIPv4Address() >=
|
||||
m_broadcastAddress.toIPv4Address();
|
||||
}
|
||||
|
||||
Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
return BigIntIPv6Addr(other.m_address.toIPv6Address()) <=
|
||||
BigIntIPv6Addr(m_address.toIPv6Address()) &&
|
||||
BigIntIPv6Addr(other.m_broadcastAddress.toIPv6Address()) >=
|
||||
BigIntIPv6Addr(m_broadcastAddress.toIPv6Address());
|
||||
}
|
||||
|
||||
QList<IPAddress> IPAddress::subnets() const {
|
||||
QList<IPAddress> list;
|
||||
|
||||
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
if (m_prefixLength == 32) {
|
||||
list.append(*this);
|
||||
return list;
|
||||
}
|
||||
|
||||
quint64 start = m_address.toIPv4Address();
|
||||
quint64 end = quint64(m_broadcastAddress.toIPv4Address()) + 1;
|
||||
quint64 step = ((quint64)m_hostmask.toIPv4Address() + 1) >> 1;
|
||||
|
||||
while (start < end) {
|
||||
int newPrefixLength = m_prefixLength + 1;
|
||||
if (newPrefixLength == 32) {
|
||||
list.append(IPAddress(QHostAddress(static_cast<quint32>(start))));
|
||||
} else {
|
||||
list.append(IPAddress(QHostAddress(static_cast<quint32>(start)),
|
||||
m_prefixLength + 1));
|
||||
}
|
||||
start += step;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
|
||||
if (m_prefixLength == 128) {
|
||||
list.append(*this);
|
||||
return list;
|
||||
}
|
||||
|
||||
BigInt start(17);
|
||||
{
|
||||
Q_IPV6ADDR addr = m_address.toIPv6Address();
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
start.setValueAt(addr[i], i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
BigInt end(17);
|
||||
{
|
||||
Q_IPV6ADDR addr = m_broadcastAddress.toIPv6Address();
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
end.setValueAt(addr[i], i + 1);
|
||||
}
|
||||
++end;
|
||||
}
|
||||
|
||||
BigInt step(17);
|
||||
{
|
||||
Q_IPV6ADDR addr = m_hostmask.toIPv6Address();
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
step.setValueAt(addr[i], i + 1);
|
||||
}
|
||||
step = (++step) >> 1;
|
||||
}
|
||||
|
||||
while (start < end) {
|
||||
int newPrefixLength = m_prefixLength + 1;
|
||||
Q_IPV6ADDR startIPv6;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
startIPv6[i] = start.valueAt(i + 1);
|
||||
}
|
||||
|
||||
if (newPrefixLength == 128) {
|
||||
list.append(IPAddress(QHostAddress(startIPv6)));
|
||||
} else {
|
||||
list.append(IPAddress(QHostAddress(startIPv6), m_prefixLength + 1));
|
||||
}
|
||||
start += step;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// static
|
||||
QList<IPAddress> IPAddress::excludeAddresses(
|
||||
const QList<IPAddress>& sourceList, const QList<IPAddress>& excludeList) {
|
||||
QList<IPAddress> results = sourceList;
|
||||
|
||||
for (const IPAddress& exclude : excludeList) {
|
||||
QList<IPAddress> newResults;
|
||||
|
||||
for (const IPAddress& ip : results) {
|
||||
if (ip.overlaps(exclude)) {
|
||||
QList<IPAddress> range = ip.excludeAddresses(exclude);
|
||||
newResults.append(range);
|
||||
} else {
|
||||
newResults.append(ip);
|
||||
}
|
||||
}
|
||||
|
||||
results = newResults;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QList<IPAddress> IPAddress::excludeAddresses(const IPAddress& ip) const {
|
||||
QList<IPAddress> sn = subnets();
|
||||
Q_ASSERT(sn.length() >= 2);
|
||||
|
||||
QList<IPAddress> result;
|
||||
while (sn[0] != ip && sn[1] != ip) {
|
||||
if (ip.subnetOf(sn[0])) {
|
||||
result.append(sn[1]);
|
||||
sn = sn[0].subnets();
|
||||
} else if (ip.subnetOf(sn[1])) {
|
||||
result.append(sn[0]);
|
||||
sn = sn[1].subnets();
|
||||
} else {
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (sn[0] == ip) {
|
||||
result.append(sn[1]);
|
||||
} else if (sn[1] == ip) {
|
||||
result.append(sn[0]);
|
||||
} else {
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
59
client/platforms/ios/ipaddress.h
Normal file
59
client/platforms/ios/ipaddress.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IPADDRESS_H
|
||||
#define IPADDRESS_H
|
||||
|
||||
#include <QHostAddress>
|
||||
|
||||
class IPAddress final {
|
||||
public:
|
||||
static IPAddress create(const QString& ip);
|
||||
static QList<IPAddress> excludeAddresses(const QList<IPAddress>& sourceList,
|
||||
const QList<IPAddress>& excludeList);
|
||||
|
||||
IPAddress();
|
||||
IPAddress(const IPAddress& other);
|
||||
IPAddress& operator=(const IPAddress& other);
|
||||
~IPAddress();
|
||||
|
||||
QString toString() const {
|
||||
return QString("%1/%2").arg(m_address.toString()).arg(m_prefixLength);
|
||||
}
|
||||
|
||||
const QHostAddress& address() const { return m_address; }
|
||||
int prefixLength() const { return m_prefixLength; }
|
||||
const QHostAddress& netmask() const { return m_netmask; }
|
||||
const QHostAddress& hostmask() const { return m_hostmask; }
|
||||
const QHostAddress& broadcastAddress() const { return m_broadcastAddress; }
|
||||
|
||||
bool overlaps(const IPAddress& other) const;
|
||||
|
||||
bool contains(const QHostAddress& address) const;
|
||||
|
||||
bool operator==(const IPAddress& other) const;
|
||||
bool operator!=(const IPAddress& other) const { return !operator==(other); }
|
||||
|
||||
bool subnetOf(const IPAddress& other) const;
|
||||
|
||||
QList<IPAddress> subnets() const;
|
||||
|
||||
QList<IPAddress> excludeAddresses(const IPAddress& ip) const;
|
||||
|
||||
QAbstractSocket::NetworkLayerProtocol type() const;
|
||||
|
||||
private:
|
||||
IPAddress(const QHostAddress& address);
|
||||
IPAddress(const QHostAddress& address, int prefixLength);
|
||||
|
||||
private:
|
||||
QHostAddress m_address;
|
||||
int m_prefixLength;
|
||||
|
||||
QHostAddress m_netmask;
|
||||
QHostAddress m_hostmask;
|
||||
QHostAddress m_broadcastAddress;
|
||||
};
|
||||
|
||||
#endif // IPADDRESS_H
|
||||
60
client/platforms/ios/ipaddressrange.cpp
Normal file
60
client/platforms/ios/ipaddressrange.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ipaddressrange.h"
|
||||
#include "ipaddress.h"
|
||||
|
||||
IPAddressRange::IPAddressRange(const QString& ipAddress, uint32_t range,
|
||||
IPAddressType type)
|
||||
: m_ipAddress(ipAddress), m_range(range), m_type(type) {}
|
||||
|
||||
IPAddressRange::IPAddressRange(const QString& prefix) {
|
||||
QStringList split = prefix.split('/');
|
||||
m_ipAddress = split[0];
|
||||
if (m_ipAddress.contains(':')) {
|
||||
// Probably IPv6
|
||||
m_type = IPv6;
|
||||
m_range = 128;
|
||||
} else {
|
||||
// Assume IPv4
|
||||
m_type = IPv4;
|
||||
m_range = 32;
|
||||
}
|
||||
if (split.count() > 1) {
|
||||
m_range = split[1].toUInt();
|
||||
}
|
||||
}
|
||||
|
||||
IPAddressRange::IPAddressRange(const IPAddressRange& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
IPAddressRange& IPAddressRange::operator=(const IPAddressRange& other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
m_ipAddress = other.m_ipAddress;
|
||||
m_range = other.m_range;
|
||||
m_type = other.m_type;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool IPAddressRange::operator==(const IPAddressRange& other) const {
|
||||
if (this == &other) return true;
|
||||
|
||||
return m_ipAddress == other.m_ipAddress && m_range == other.m_range &&
|
||||
m_type == other.m_type;
|
||||
}
|
||||
|
||||
IPAddressRange::~IPAddressRange() { }
|
||||
|
||||
// static
|
||||
QList<IPAddressRange> IPAddressRange::fromIPAddressList(
|
||||
const QList<IPAddress>& list) {
|
||||
QList<IPAddressRange> result;
|
||||
for (const IPAddress& ip : list) {
|
||||
result.append(IPAddressRange(ip.toString()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
42
client/platforms/ios/ipaddressrange.h
Normal file
42
client/platforms/ios/ipaddressrange.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IPADDRESSRANGE_H
|
||||
#define IPADDRESSRANGE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class IPAddress;
|
||||
|
||||
class IPAddressRange final {
|
||||
public:
|
||||
enum IPAddressType {
|
||||
IPv4,
|
||||
IPv6,
|
||||
};
|
||||
|
||||
static QList<IPAddressRange> fromIPAddressList(const QList<IPAddress>& list);
|
||||
|
||||
IPAddressRange(const QString& prefix);
|
||||
IPAddressRange(const QString& ipAddress, uint32_t range, IPAddressType type);
|
||||
IPAddressRange(const IPAddressRange& other);
|
||||
IPAddressRange& operator=(const IPAddressRange& other);
|
||||
bool operator==(const IPAddressRange& other) const;
|
||||
~IPAddressRange();
|
||||
|
||||
const QString& ipAddress() const { return m_ipAddress; }
|
||||
uint32_t range() const { return m_range; }
|
||||
IPAddressType type() const { return m_type; }
|
||||
const QString toString() const {
|
||||
return QString("%1/%2").arg(m_ipAddress).arg(m_range);
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_ipAddress;
|
||||
uint32_t m_range;
|
||||
IPAddressType m_type;
|
||||
};
|
||||
|
||||
#endif // IPADDRESSRANGE_H
|
||||
732
client/platforms/ios/json.cpp
Normal file
732
client/platforms/ios/json.cpp
Normal file
|
|
@ -0,0 +1,732 @@
|
|||
/**
|
||||
* QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa.
|
||||
* Copyright (C) 2011 Eeli Reilin
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file json.cpp
|
||||
*/
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QStringList>
|
||||
#include "json.h"
|
||||
|
||||
namespace QtJson {
|
||||
static QString dateFormat, dateTimeFormat;
|
||||
static bool prettySerialize = false;
|
||||
|
||||
static QString sanitizeString(QString str);
|
||||
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep);
|
||||
static QVariant parseValue(const QString &json, int &index, bool &success);
|
||||
static QVariant parseObject(const QString &json, int &index, bool &success);
|
||||
static QVariant parseArray(const QString &json, int &index, bool &success);
|
||||
static QVariant parseString(const QString &json, int &index, bool &success);
|
||||
static QVariant parseNumber(const QString &json, int &index);
|
||||
static int lastIndexOfNumber(const QString &json, int index);
|
||||
static void eatWhitespace(const QString &json, int &index);
|
||||
static int lookAhead(const QString &json, int index);
|
||||
static int nextToken(const QString &json, int &index);
|
||||
|
||||
template<typename T>
|
||||
QByteArray serializeMap(const T &map, bool &success, int _level = 0) {
|
||||
QByteArray newline;
|
||||
QByteArray tabs;
|
||||
QByteArray tabsFields;
|
||||
if (prettySerialize && !map.isEmpty()) {
|
||||
newline = "\n";
|
||||
for (uint l=1; l<_level; l++) {
|
||||
tabs += " ";
|
||||
}
|
||||
tabsFields = tabs + " ";
|
||||
}
|
||||
|
||||
QByteArray str = "{" + newline;
|
||||
QList<QByteArray> pairs;
|
||||
for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) {
|
||||
bool otherSuccess = true;
|
||||
QByteArray serializedValue = serialize(it.value(), otherSuccess, _level);
|
||||
if (serializedValue.isNull()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
pairs << tabsFields + sanitizeString(it.key()).toUtf8() + ":" + (prettySerialize ? " " : "") + serializedValue;
|
||||
}
|
||||
|
||||
str += join(pairs, "," + newline) + newline;
|
||||
str += tabs + "}";
|
||||
return str;
|
||||
}
|
||||
|
||||
void insert(QVariant &v, const QString &key, const QVariant &value);
|
||||
void append(QVariant &v, const QVariant &value);
|
||||
|
||||
template<typename T>
|
||||
void cloneMap(QVariant &json, const T &map) {
|
||||
for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) {
|
||||
insert(json, it.key(), (*it));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void cloneList(QVariant &json, const T &list) {
|
||||
for (typename T::const_iterator it = list.begin(), itend = list.end(); it != itend; ++it) {
|
||||
append(json, (*it));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse
|
||||
*/
|
||||
QVariant parse(const QString &json) {
|
||||
bool success = true;
|
||||
return parse(json, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse
|
||||
*/
|
||||
QVariant parse(const QString &json, bool &success) {
|
||||
success = true;
|
||||
|
||||
// Return an empty QVariant if the JSON data is either null or empty
|
||||
if (!json.isNull() || !json.isEmpty()) {
|
||||
QString data = json;
|
||||
// We'll start from index 0
|
||||
int index = 0;
|
||||
|
||||
// Parse the first value
|
||||
QVariant value = parseValue(data, index, success);
|
||||
|
||||
// Return the parsed value
|
||||
return value;
|
||||
} else {
|
||||
// Return the empty QVariant
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clone
|
||||
*/
|
||||
QVariant clone(const QVariant &data) {
|
||||
QVariant v;
|
||||
|
||||
if (data.type() == QVariant::Map) {
|
||||
cloneMap(v, data.toMap());
|
||||
} else if (data.type() == QVariant::Hash) {
|
||||
cloneMap(v, data.toHash());
|
||||
} else if (data.type() == QVariant::List) {
|
||||
cloneList(v, data.toList());
|
||||
} else if (data.type() == QVariant::StringList) {
|
||||
cloneList(v, data.toStringList());
|
||||
} else {
|
||||
v = QVariant(data);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* insert value (map case)
|
||||
*/
|
||||
void insert(QVariant &v, const QString &key, const QVariant &value) {
|
||||
if (!v.canConvert<QVariantMap>()) v = QVariantMap();
|
||||
QVariantMap *p = (QVariantMap *)v.data();
|
||||
p->insert(key, clone(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* append value (list case)
|
||||
*/
|
||||
void append(QVariant &v, const QVariant &value) {
|
||||
if (!v.canConvert<QVariantList>()) v = QVariantList();
|
||||
QVariantList *p = (QVariantList *)v.data();
|
||||
p->append(value);
|
||||
}
|
||||
|
||||
QByteArray serialize(const QVariant &data) {
|
||||
bool success = true;
|
||||
return serialize(data, success);
|
||||
}
|
||||
|
||||
QByteArray serialize(const QVariant &data, bool &success, int _level /*= 0*/) {
|
||||
QByteArray newline;
|
||||
QByteArray tabs;
|
||||
QByteArray tabsFields;
|
||||
if (prettySerialize) {
|
||||
newline = "\n";
|
||||
for (uint l=0; l<_level; l++) {
|
||||
tabs += " ";
|
||||
}
|
||||
tabsFields = tabs + " ";
|
||||
}
|
||||
|
||||
QByteArray str;
|
||||
success = true;
|
||||
|
||||
if (!data.isValid()) { // invalid or null?
|
||||
str = "null";
|
||||
} else if ((data.type() == QVariant::List) ||
|
||||
(data.type() == QVariant::StringList)) { // variant is a list?
|
||||
QList<QByteArray> values;
|
||||
const QVariantList list = data.toList();
|
||||
Q_FOREACH(const QVariant& v, list) {
|
||||
bool otherSuccess = true;
|
||||
QByteArray serializedValue = serialize(v, otherSuccess, _level+1);
|
||||
if (serializedValue.isNull()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
values << tabsFields + serializedValue;
|
||||
}
|
||||
|
||||
if (!values.isEmpty()) {
|
||||
str = "[" + newline + join( values, "," + newline ) + newline + tabs + "]";
|
||||
} else {
|
||||
str = "[]";
|
||||
}
|
||||
} else if (data.type() == QVariant::Hash) { // variant is a hash?
|
||||
str = serializeMap<>(data.toHash(), success, _level+1);
|
||||
} else if (data.type() == QVariant::Map) { // variant is a map?
|
||||
str = serializeMap<>(data.toMap(), success, _level+1);
|
||||
} else if ((data.type() == QVariant::String) ||
|
||||
(data.type() == QVariant::ByteArray)) {// a string or a byte array?
|
||||
str = sanitizeString(data.toString()).toUtf8();
|
||||
} else if (data.type() == QVariant::Double) { // double?
|
||||
double value = data.toDouble(&success);
|
||||
if (success) {
|
||||
str = QByteArray::number(value, 'g');
|
||||
if (!str.contains(".") && ! str.contains("e")) {
|
||||
str += ".0";
|
||||
}
|
||||
}
|
||||
} else if (data.type() == QVariant::Bool) { // boolean value?
|
||||
str = data.toBool() ? "true" : "false";
|
||||
} else if (data.type() == QVariant::ULongLong) { // large unsigned number?
|
||||
str = QByteArray::number(data.value<qulonglong>());
|
||||
} else if (data.canConvert<qlonglong>()) { // any signed number?
|
||||
str = QByteArray::number(data.value<qlonglong>());
|
||||
} else if (data.canConvert<long>()) { //TODO: this code is never executed because all smaller types can be converted to qlonglong
|
||||
str = QString::number(data.value<long>()).toUtf8();
|
||||
} else if (data.type() == QVariant::DateTime) { // datetime value?
|
||||
str = sanitizeString(dateTimeFormat.isEmpty()
|
||||
? data.toDateTime().toString()
|
||||
: data.toDateTime().toString(dateTimeFormat)).toUtf8();
|
||||
} else if (data.type() == QVariant::Date) { // date value?
|
||||
str = sanitizeString(dateTimeFormat.isEmpty()
|
||||
? data.toDate().toString()
|
||||
: data.toDate().toString(dateFormat)).toUtf8();
|
||||
} else if (data.canConvert<QString>()) { // can value be converted to string?
|
||||
// this will catch QUrl, ... (all other types which can be converted to string)
|
||||
str = sanitizeString(data.toString()).toUtf8();
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return str;
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QString serializeStr(const QVariant &data) {
|
||||
return QString::fromUtf8(serialize(data));
|
||||
}
|
||||
|
||||
QString serializeStr(const QVariant &data, bool &success) {
|
||||
return QString::fromUtf8(serialize(data, success));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \enum JsonToken
|
||||
*/
|
||||
enum JsonToken {
|
||||
JsonTokenNone = 0,
|
||||
JsonTokenCurlyOpen = 1,
|
||||
JsonTokenCurlyClose = 2,
|
||||
JsonTokenSquaredOpen = 3,
|
||||
JsonTokenSquaredClose = 4,
|
||||
JsonTokenColon = 5,
|
||||
JsonTokenComma = 6,
|
||||
JsonTokenString = 7,
|
||||
JsonTokenNumber = 8,
|
||||
JsonTokenTrue = 9,
|
||||
JsonTokenFalse = 10,
|
||||
JsonTokenNull = 11
|
||||
};
|
||||
|
||||
static QString sanitizeString(QString str) {
|
||||
str.replace(QLatin1String("\\"), QLatin1String("\\\\"));
|
||||
str.replace(QLatin1String("\""), QLatin1String("\\\""));
|
||||
str.replace(QLatin1String("\b"), QLatin1String("\\b"));
|
||||
str.replace(QLatin1String("\f"), QLatin1String("\\f"));
|
||||
str.replace(QLatin1String("\n"), QLatin1String("\\n"));
|
||||
str.replace(QLatin1String("\r"), QLatin1String("\\r"));
|
||||
str.replace(QLatin1String("\t"), QLatin1String("\\t"));
|
||||
return QString(QLatin1String("\"%1\"")).arg(str);
|
||||
}
|
||||
|
||||
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep) {
|
||||
QByteArray res;
|
||||
Q_FOREACH(const QByteArray &i, list) {
|
||||
if (!res.isEmpty()) {
|
||||
res += sep;
|
||||
}
|
||||
res += i;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseValue
|
||||
*/
|
||||
static QVariant parseValue(const QString &json, int &index, bool &success) {
|
||||
// Determine what kind of data we should parse by
|
||||
// checking out the upcoming token
|
||||
switch(lookAhead(json, index)) {
|
||||
case JsonTokenString:
|
||||
return parseString(json, index, success);
|
||||
case JsonTokenNumber:
|
||||
return parseNumber(json, index);
|
||||
case JsonTokenCurlyOpen:
|
||||
return parseObject(json, index, success);
|
||||
case JsonTokenSquaredOpen:
|
||||
return parseArray(json, index, success);
|
||||
case JsonTokenTrue:
|
||||
nextToken(json, index);
|
||||
return QVariant(true);
|
||||
case JsonTokenFalse:
|
||||
nextToken(json, index);
|
||||
return QVariant(false);
|
||||
case JsonTokenNull:
|
||||
nextToken(json, index);
|
||||
return QVariant();
|
||||
case JsonTokenNone:
|
||||
break;
|
||||
}
|
||||
|
||||
// If there were no tokens, flag the failure and return an empty QVariant
|
||||
success = false;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
/**
|
||||
* parseObject
|
||||
*/
|
||||
static QVariant parseObject(const QString &json, int &index, bool &success) {
|
||||
QVariantMap map;
|
||||
int token;
|
||||
|
||||
// Get rid of the whitespace and increment index
|
||||
nextToken(json, index);
|
||||
|
||||
// Loop through all of the key/value pairs of the object
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
// Get the upcoming token
|
||||
token = lookAhead(json, index);
|
||||
|
||||
if (token == JsonTokenNone) {
|
||||
success = false;
|
||||
return QVariantMap();
|
||||
} else if (token == JsonTokenComma) {
|
||||
nextToken(json, index);
|
||||
} else if (token == JsonTokenCurlyClose) {
|
||||
nextToken(json, index);
|
||||
return map;
|
||||
} else {
|
||||
// Parse the key/value pair's name
|
||||
QString name = parseString(json, index, success).toString();
|
||||
|
||||
if (!success) {
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
// Get the next token
|
||||
token = nextToken(json, index);
|
||||
|
||||
// If the next token is not a colon, flag the failure
|
||||
// return an empty QVariant
|
||||
if (token != JsonTokenColon) {
|
||||
success = false;
|
||||
return QVariant(QVariantMap());
|
||||
}
|
||||
|
||||
// Parse the key/value pair's value
|
||||
QVariant value = parseValue(json, index, success);
|
||||
|
||||
if (!success) {
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
// Assign the value to the key in the map
|
||||
map[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the map successfully
|
||||
return QVariant(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseArray
|
||||
*/
|
||||
static QVariant parseArray(const QString &json, int &index, bool &success) {
|
||||
QVariantList list;
|
||||
|
||||
nextToken(json, index);
|
||||
|
||||
bool done = false;
|
||||
while(!done) {
|
||||
int token = lookAhead(json, index);
|
||||
|
||||
if (token == JsonTokenNone) {
|
||||
success = false;
|
||||
return QVariantList();
|
||||
} else if (token == JsonTokenComma) {
|
||||
nextToken(json, index);
|
||||
} else if (token == JsonTokenSquaredClose) {
|
||||
nextToken(json, index);
|
||||
break;
|
||||
} else {
|
||||
QVariant value = parseValue(json, index, success);
|
||||
if (!success) {
|
||||
return QVariantList();
|
||||
}
|
||||
list.push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseString
|
||||
*/
|
||||
static QVariant parseString(const QString &json, int &index, bool &success) {
|
||||
QString s;
|
||||
QChar c;
|
||||
|
||||
eatWhitespace(json, index);
|
||||
|
||||
c = json[index++];
|
||||
|
||||
bool complete = false;
|
||||
while(!complete) {
|
||||
if (index == json.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
c = json[index++];
|
||||
|
||||
if (c == '\"') {
|
||||
complete = true;
|
||||
break;
|
||||
} else if (c == '\\') {
|
||||
if (index == json.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
c = json[index++];
|
||||
|
||||
if (c == '\"') {
|
||||
s.append('\"');
|
||||
} else if (c == '\\') {
|
||||
s.append('\\');
|
||||
} else if (c == '/') {
|
||||
s.append('/');
|
||||
} else if (c == 'b') {
|
||||
s.append('\b');
|
||||
} else if (c == 'f') {
|
||||
s.append('\f');
|
||||
} else if (c == 'n') {
|
||||
s.append('\n');
|
||||
} else if (c == 'r') {
|
||||
s.append('\r');
|
||||
} else if (c == 't') {
|
||||
s.append('\t');
|
||||
} else if (c == 'u') {
|
||||
int remainingLength = json.size() - index;
|
||||
if (remainingLength >= 4) {
|
||||
QString unicodeStr = json.mid(index, 4);
|
||||
|
||||
int symbol = unicodeStr.toInt(0, 16);
|
||||
|
||||
s.append(QChar(symbol));
|
||||
|
||||
index += 4;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!complete) {
|
||||
success = false;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return QVariant(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseNumber
|
||||
*/
|
||||
static QVariant parseNumber(const QString &json, int &index) {
|
||||
eatWhitespace(json, index);
|
||||
|
||||
int lastIndex = lastIndexOfNumber(json, index);
|
||||
int charLength = (lastIndex - index) + 1;
|
||||
QString numberStr;
|
||||
|
||||
numberStr = json.mid(index, charLength);
|
||||
|
||||
index = lastIndex + 1;
|
||||
bool ok;
|
||||
|
||||
if (numberStr.contains('.')) {
|
||||
return QVariant(numberStr.toDouble(NULL));
|
||||
} else if (numberStr.startsWith('-')) {
|
||||
int i = numberStr.toInt(&ok);
|
||||
if (!ok) {
|
||||
qlonglong ll = numberStr.toLongLong(&ok);
|
||||
return ok ? ll : QVariant(numberStr);
|
||||
}
|
||||
return i;
|
||||
} else {
|
||||
uint u = numberStr.toUInt(&ok);
|
||||
if (!ok) {
|
||||
qulonglong ull = numberStr.toULongLong(&ok);
|
||||
return ok ? ull : QVariant(numberStr);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lastIndexOfNumber
|
||||
*/
|
||||
static int lastIndexOfNumber(const QString &json, int index) {
|
||||
int lastIndex;
|
||||
|
||||
for(lastIndex = index; lastIndex < json.size(); lastIndex++) {
|
||||
if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lastIndex -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* eatWhitespace
|
||||
*/
|
||||
static void eatWhitespace(const QString &json, int &index) {
|
||||
for(; index < json.size(); index++) {
|
||||
if (QString(" \t\n\r").indexOf(json[index]) == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lookAhead
|
||||
*/
|
||||
static int lookAhead(const QString &json, int index) {
|
||||
int saveIndex = index;
|
||||
return nextToken(json, saveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* nextToken
|
||||
*/
|
||||
static int nextToken(const QString &json, int &index) {
|
||||
eatWhitespace(json, index);
|
||||
|
||||
if (index == json.size()) {
|
||||
return JsonTokenNone;
|
||||
}
|
||||
|
||||
QChar c = json[index];
|
||||
index++;
|
||||
switch(c.toLatin1()) {
|
||||
case '{': return JsonTokenCurlyOpen;
|
||||
case '}': return JsonTokenCurlyClose;
|
||||
case '[': return JsonTokenSquaredOpen;
|
||||
case ']': return JsonTokenSquaredClose;
|
||||
case ',': return JsonTokenComma;
|
||||
case '"': return JsonTokenString;
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '-': return JsonTokenNumber;
|
||||
case ':': return JsonTokenColon;
|
||||
}
|
||||
index--; // ^ WTF?
|
||||
|
||||
int remainingLength = json.size() - index;
|
||||
|
||||
// True
|
||||
if (remainingLength >= 4) {
|
||||
if (json[index] == 't' && json[index + 1] == 'r' &&
|
||||
json[index + 2] == 'u' && json[index + 3] == 'e') {
|
||||
index += 4;
|
||||
return JsonTokenTrue;
|
||||
}
|
||||
}
|
||||
|
||||
// False
|
||||
if (remainingLength >= 5) {
|
||||
if (json[index] == 'f' && json[index + 1] == 'a' &&
|
||||
json[index + 2] == 'l' && json[index + 3] == 's' &&
|
||||
json[index + 4] == 'e') {
|
||||
index += 5;
|
||||
return JsonTokenFalse;
|
||||
}
|
||||
}
|
||||
|
||||
// Null
|
||||
if (remainingLength >= 4) {
|
||||
if (json[index] == 'n' && json[index + 1] == 'u' &&
|
||||
json[index + 2] == 'l' && json[index + 3] == 'l') {
|
||||
index += 4;
|
||||
return JsonTokenNull;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonTokenNone;
|
||||
}
|
||||
|
||||
void setDateTimeFormat(const QString &format) {
|
||||
dateTimeFormat = format;
|
||||
}
|
||||
|
||||
void setDateFormat(const QString &format) {
|
||||
dateFormat = format;
|
||||
}
|
||||
|
||||
QString getDateTimeFormat() {
|
||||
return dateTimeFormat;
|
||||
}
|
||||
|
||||
QString getDateFormat() {
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
void setPrettySerialize(bool enabled) {
|
||||
prettySerialize = enabled;
|
||||
}
|
||||
|
||||
bool isPrettySerialize() {
|
||||
return prettySerialize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
QQueue<BuilderJsonObject *> BuilderJsonObject::created_list;
|
||||
|
||||
BuilderJsonObject::BuilderJsonObject() {
|
||||
// clean objects previous "created"
|
||||
while (!BuilderJsonObject::created_list.isEmpty()) {
|
||||
delete BuilderJsonObject::created_list.dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
BuilderJsonObject::BuilderJsonObject(JsonObject &json) {
|
||||
BuilderJsonObject();
|
||||
|
||||
obj = json;
|
||||
}
|
||||
|
||||
BuilderJsonObject *BuilderJsonObject::set(const QString &key, const QVariant &value) {
|
||||
obj[key] = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
BuilderJsonObject *BuilderJsonObject::set(const QString &key, BuilderJsonObject *builder) {
|
||||
return set(key, builder->create());
|
||||
}
|
||||
|
||||
BuilderJsonObject *BuilderJsonObject::set(const QString &key, BuilderJsonArray *builder) {
|
||||
return set(key, builder->create());
|
||||
}
|
||||
|
||||
JsonObject BuilderJsonObject::create() {
|
||||
BuilderJsonObject::created_list.enqueue(this);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
QQueue<BuilderJsonArray *> BuilderJsonArray::created_list;
|
||||
|
||||
BuilderJsonArray::BuilderJsonArray() {
|
||||
// clean objects previous "created"
|
||||
while (!BuilderJsonArray::created_list.isEmpty()) {
|
||||
delete BuilderJsonArray::created_list.dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
BuilderJsonArray::BuilderJsonArray(JsonArray &json) {
|
||||
BuilderJsonArray();
|
||||
|
||||
array = json;
|
||||
}
|
||||
|
||||
BuilderJsonArray *BuilderJsonArray::add(const QVariant &element) {
|
||||
array.append(element);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
BuilderJsonArray *BuilderJsonArray::add(BuilderJsonObject *builder) {
|
||||
return add(builder->create());
|
||||
}
|
||||
|
||||
BuilderJsonArray *BuilderJsonArray::add(BuilderJsonArray *builder) {
|
||||
return add(builder->create());
|
||||
}
|
||||
|
||||
JsonArray BuilderJsonArray::create() {
|
||||
BuilderJsonArray::created_list.enqueue(this);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
BuilderJsonObject *objectBuilder() {
|
||||
return new BuilderJsonObject();
|
||||
}
|
||||
|
||||
BuilderJsonObject *objectBuilder(JsonObject &json) {
|
||||
return new BuilderJsonObject(json);
|
||||
}
|
||||
|
||||
BuilderJsonArray *arrayBuilder() {
|
||||
return new BuilderJsonArray();
|
||||
}
|
||||
|
||||
BuilderJsonArray *arrayBuilder(JsonArray &json) {
|
||||
return new BuilderJsonArray(json);
|
||||
}
|
||||
|
||||
} //end namespace
|
||||
265
client/platforms/ios/json.h
Normal file
265
client/platforms/ios/json.h
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/**
|
||||
* QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa.
|
||||
* Copyright (C) 2011 Eeli Reilin
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file json.h
|
||||
*/
|
||||
|
||||
#ifndef JSON_H
|
||||
#define JSON_H
|
||||
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QQueue>
|
||||
|
||||
|
||||
/**
|
||||
* \namespace QtJson
|
||||
* \brief A JSON data parser
|
||||
*
|
||||
* Json parses a JSON data into a QVariant hierarchy.
|
||||
*/
|
||||
namespace QtJson {
|
||||
typedef QVariantMap JsonObject;
|
||||
typedef QVariantList JsonArray;
|
||||
|
||||
/**
|
||||
* Clone a JSON object (makes a deep copy)
|
||||
*
|
||||
* \param data The JSON object
|
||||
*/
|
||||
QVariant clone(const QVariant &data);
|
||||
|
||||
/**
|
||||
* Insert value to JSON object (QVariantMap)
|
||||
*
|
||||
* \param v The JSON object
|
||||
* \param key The key
|
||||
* \param value The value
|
||||
*/
|
||||
void insert(QVariant &v, const QString &key, const QVariant &value);
|
||||
|
||||
/**
|
||||
* Append value to JSON array (QVariantList)
|
||||
*
|
||||
* \param v The JSON array
|
||||
* \param value The value
|
||||
*/
|
||||
void append(QVariant &v, const QVariant &value);
|
||||
|
||||
/**
|
||||
* Parse a JSON string
|
||||
*
|
||||
* \param json The JSON data
|
||||
*/
|
||||
QVariant parse(const QString &json);
|
||||
|
||||
/**
|
||||
* Parse a JSON string
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param success The success of the parsing
|
||||
*/
|
||||
QVariant parse(const QString &json, bool &success);
|
||||
|
||||
/**
|
||||
* This method generates a textual JSON representation
|
||||
*
|
||||
* \param data The JSON data generated by the parser.
|
||||
*
|
||||
* \return QByteArray Textual JSON representation in UTF-8
|
||||
*/
|
||||
QByteArray serialize(const QVariant &data);
|
||||
|
||||
/**
|
||||
* This method generates a textual JSON representation
|
||||
*
|
||||
* \param data The JSON data generated by the parser.
|
||||
* \param success The success of the serialization
|
||||
*
|
||||
* \return QByteArray Textual JSON representation in UTF-8
|
||||
*/
|
||||
QByteArray serialize(const QVariant &data, bool &success, int _level = 0);
|
||||
|
||||
/**
|
||||
* This method generates a textual JSON representation
|
||||
*
|
||||
* \param data The JSON data generated by the parser.
|
||||
*
|
||||
* \return QString Textual JSON representation
|
||||
*/
|
||||
QString serializeStr(const QVariant &data);
|
||||
|
||||
/**
|
||||
* This method generates a textual JSON representation
|
||||
*
|
||||
* \param data The JSON data generated by the parser.
|
||||
* \param success The success of the serialization
|
||||
*
|
||||
* \return QString Textual JSON representation
|
||||
*/
|
||||
QString serializeStr(const QVariant &data, bool &success, int _level = 0);
|
||||
|
||||
/**
|
||||
* This method sets date(time) format to be used for QDateTime::toString
|
||||
* If QString is empty, Qt::TextDate is used.
|
||||
*
|
||||
* \param format The JSON data generated by the parser.
|
||||
*/
|
||||
void setDateTimeFormat(const QString& format);
|
||||
void setDateFormat(const QString& format);
|
||||
|
||||
/**
|
||||
* This method gets date(time) format to be used for QDateTime::toString
|
||||
* If QString is empty, Qt::TextDate is used.
|
||||
*/
|
||||
QString getDateTimeFormat();
|
||||
QString getDateFormat();
|
||||
|
||||
/**
|
||||
* @brief setPrettySerialize enable/disabled pretty-print when serialize() a json
|
||||
* @param enabled
|
||||
*/
|
||||
void setPrettySerialize(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief isPrettySerialize check if is enabled pretty-print when serialize() a json
|
||||
* @return
|
||||
*/
|
||||
bool isPrettySerialize();
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* QVariant based Json object
|
||||
*/
|
||||
class Object : public QVariant {
|
||||
template<typename T>
|
||||
Object& insertKey(Object* ptr, const QString& key) {
|
||||
T* p = (T*)ptr->data();
|
||||
if (!p->contains(key)) p->insert(key, QVariant());
|
||||
return *reinterpret_cast<Object*>(&p->operator[](key));
|
||||
}
|
||||
template<typename T>
|
||||
void removeKey(Object *ptr, const QString& key) {
|
||||
T* p = (T*)ptr->data();
|
||||
p->remove(key);
|
||||
}
|
||||
public:
|
||||
Object() : QVariant() {}
|
||||
Object(const Object& ref) : QVariant(ref) {}
|
||||
|
||||
Object& operator=(const QVariant& rhs) {
|
||||
/** It maybe more robust when running under Qt versions below 4.7 */
|
||||
QObject * obj = qvariant_cast<QObject *>(rhs);
|
||||
// setValue(rhs);
|
||||
setValue(obj);
|
||||
return *this;
|
||||
}
|
||||
Object& operator[](const QString& key) {
|
||||
if (type() == QVariant::Map)
|
||||
return insertKey<QVariantMap>(this, key);
|
||||
else if (type() == QVariant::Hash)
|
||||
return insertKey<QVariantHash>(this, key);
|
||||
|
||||
setValue(QVariantMap());
|
||||
|
||||
return insertKey<QVariantMap>(this, key);
|
||||
}
|
||||
const Object& operator[](const QString& key) const {
|
||||
return const_cast<Object*>(this)->operator[](key);
|
||||
}
|
||||
void remove(const QString& key) {
|
||||
if (type() == QVariant::Map)
|
||||
removeKey<QVariantMap>(this, key);
|
||||
else if (type() == QVariant::Hash)
|
||||
removeKey<QVariantHash>(this, key);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class BuilderJsonArray;
|
||||
|
||||
/**
|
||||
* @brief The BuilderJsonObject class
|
||||
*/
|
||||
class BuilderJsonObject {
|
||||
|
||||
public:
|
||||
BuilderJsonObject();
|
||||
BuilderJsonObject(JsonObject &json);
|
||||
|
||||
BuilderJsonObject *set(const QString &key, const QVariant &value);
|
||||
BuilderJsonObject *set(const QString &key, BuilderJsonObject *builder);
|
||||
BuilderJsonObject *set(const QString &key, BuilderJsonArray *builder);
|
||||
JsonObject create();
|
||||
|
||||
private:
|
||||
static QQueue<BuilderJsonObject *> created_list;
|
||||
|
||||
JsonObject obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The BuilderJsonArray class
|
||||
*/
|
||||
class BuilderJsonArray {
|
||||
|
||||
public:
|
||||
BuilderJsonArray();
|
||||
BuilderJsonArray(JsonArray &json);
|
||||
|
||||
BuilderJsonArray *add(const QVariant &element);
|
||||
BuilderJsonArray *add(BuilderJsonObject *builder);
|
||||
BuilderJsonArray *add(BuilderJsonArray *builder);
|
||||
JsonArray create();
|
||||
|
||||
private:
|
||||
static QQueue<BuilderJsonArray *> created_list;
|
||||
|
||||
JsonArray array;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Create a BuilderJsonObject
|
||||
* @return
|
||||
*/
|
||||
BuilderJsonObject *objectBuilder();
|
||||
|
||||
/**
|
||||
* @brief Create a BuilderJsonObject starting from copy of another json
|
||||
* @return
|
||||
*/
|
||||
BuilderJsonObject *objectBuilder(JsonObject &json);
|
||||
|
||||
/**
|
||||
* @brief Create a BuilderJsonArray
|
||||
* @return
|
||||
*/
|
||||
BuilderJsonArray *arrayBuilder();
|
||||
|
||||
/**
|
||||
* @brief Create a BuilderJsonArray starting from copy of another json
|
||||
* @return
|
||||
*/
|
||||
BuilderJsonArray *arrayBuilder(JsonArray &json);
|
||||
}
|
||||
|
||||
#endif //JSON_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue