From 7e9fa25470196833f6ea55b15c5b1ad3fcdfd45c Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:44:16 +0800 Subject: [PATCH 01/13] feat: add Lua watchdog foundation --- docs/watchdog/README.md | 120 ++++ justfile | 3 + watchdog/abi.lua | 138 +++++ watchdog/alarm.lua | 43 ++ watchdog/checkpoint.lua | 153 ++++++ watchdog/compare.lua | 36 ++ watchdog/config.lua | 90 +++ watchdog/http.lua | 90 +++ watchdog/jsonrpc.lua | 107 ++++ watchdog/l1.lua | 137 +++++ watchdog/machine.lua | 43 ++ watchdog/machine_cli.lua | 129 +++++ watchdog/main.lua | 78 +++ watchdog/retry.lua | 30 + watchdog/runner.lua | 209 +++++++ watchdog/sequencer.lua | 48 ++ .../fixtures/input_added_evm_advance.lua | 17 + watchdog/tests/run.lua | 513 ++++++++++++++++++ 18 files changed, 1984 insertions(+) create mode 100644 docs/watchdog/README.md create mode 100644 watchdog/abi.lua create mode 100644 watchdog/alarm.lua create mode 100644 watchdog/checkpoint.lua create mode 100644 watchdog/compare.lua create mode 100644 watchdog/config.lua create mode 100644 watchdog/http.lua create mode 100644 watchdog/jsonrpc.lua create mode 100644 watchdog/l1.lua create mode 100644 watchdog/machine.lua create mode 100644 watchdog/machine_cli.lua create mode 100644 watchdog/main.lua create mode 100644 watchdog/retry.lua create mode 100644 watchdog/runner.lua create mode 100644 watchdog/sequencer.lua create mode 100644 watchdog/tests/fixtures/input_added_evm_advance.lua create mode 100644 watchdog/tests/run.lua diff --git a/docs/watchdog/README.md b/docs/watchdog/README.md new file mode 100644 index 0000000..843f508 --- /dev/null +++ b/docs/watchdog/README.md @@ -0,0 +1,120 @@ +# Watchdog + +The watchdog is an off-chain safety process that compares sequencer API state +against state produced by the canonical Cartesi Machine at an L1 safe block. + +## V1 Shape + +The implementation lives in `watchdog/` and is intentionally split into small +Lua modules: + +- `http.lua`: HTTP adapter, currently `lua-curl` oriented. +- `jsonrpc.lua`: JSON-RPC request/response validation. +- `l1.lua`: partitioned `eth_getLogs` scanning and strict L1 log ordering. +- `abi.lua`: decoding for the `InputAdded` / `EvmAdvance` envelope. +- `machine.lua`: narrow adapter boundary for Cartesi Machine bindings. +- `machine_cli.lua`: `cartesi-machine` CLI adapter for loading snapshot + directories, writing raw input files, advancing, and saving a new snapshot + directory. +- `compare.lua`: raw byte comparison. +- `checkpoint.lua`: manifest-backed checkpoint persistence. +- `alarm.lua`: webhook alarm delivery. +- `retry.lua`: bounded retry helper used by the runtime. +- `runner.lua`: one-shot orchestration across checkpoint load, sequencer poll, + L1 fetch, CM replay, raw compare, alarm, and checkpoint write. + +The L1 reader follows the Rust partition strategy from +`sequencer/src/partition.rs`: if an RPC provider rejects a large range, the +range is split recursively and retried. Lua decodes and validates input +envelopes, but it does not classify payload tags. Direct input vs batch +submission remains scheduler logic inside the canonical machine. + +`l1.lua` has the `InputAdded(address,uint256,bytes)` event topic baked in and +filters logs by `topic0 = InputAdded` and `topic1 = app address`, matching the +Rust reader's app-filtered InputBox scan. + +## Runtime Contract + +The future sequencer endpoint shape should be generic over the app state bytes, +even though the toy wallet app will likely use JSON: + +```json +{ + "safe_block": 123, + "state": "{\"balances\":{}}" +} +``` + +`state` must be the exact bytes produced by the bare-metal app serializer +for the app state anchored at `safe_block`. The watchdog compares those raw +bytes with the bytes returned by CM inspect. It must not canonicalize both +values before deciding pass/fail. + +The main design gate is safe-state semantics: if the sequencer has already +applied soft-confirmed transactions beyond L1 safety, `get_state` still needs a +safe-only state view through snapshotting, replay, or a separate projection. + +## Checkpoints + +V1 persists only the resulting Cartesi Machine checkpoint, not the fetched L1 +inputs. + +```text +checkpoint_dir/ + current.json + checkpoints/ + 00000000000001234567/ + snapshot/ + manifest.json +``` + +`manifest.json` records `safe_block`, timestamp, and optionally the CM image +hash. A new checkpoint directory is written first, then `current.json` is +atomically replaced to point at it. + +When bootstrapping without an existing checkpoint, the operator provides both: + +- `WATCHDOG_CM_SNAPSHOT_DIR` +- `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` + +## Modes + +The default `WATCHDOG_MODE` is `advance`. In this mode the watchdog does not +poll the sequencer. It: + +1. Loads the latest checkpoint, or the bootstrap snapshot directory. +2. Reads the L1 safe block from the RPC (or `WATCHDOG_TARGET_SAFE_BLOCK` when + provided for tests/manual runs). +3. Fetches and decodes `InputAdded` logs for the block range. +4. Feeds the raw InputBox input bytes into the CM adapter. +5. Saves a new snapshot directory and advances `current.json`. + +`WATCHDOG_MODE=compare` is reserved for the future state comparison flow once +CM inspect and the sequencer state endpoint are available. + +Useful runtime knobs: + +- `WATCHDOG_CM_EXECUTABLE`: Cartesi Machine executable, default + `cartesi-machine`. +- `WATCHDOG_CM_WORK_DIR`: temporary directory for staged input files, default + `/tmp`. +- `WATCHDOG_RETRY_ATTEMPTS`: bounded retry attempts per run, default `3`. +- `WATCHDOG_RETRY_DELAY_SEC`: delay between retry attempts, default `5`. +- `WATCHDOG_TARGET_SAFE_BLOCK`: manual/test override for the target safe block. + +## Local Tests + +Run the pure Lua tests with: + +```bash +just test-watchdog +``` + +These cover raw comparison, golden InputAdded ABI decoding, L1 ordering, +recursive range partitioning, JSON-RPC `eth_getLogs` filter construction, +config parsing, checkpoint writes, advance-mode runner behavior, the +fake-backed compare runner, the CLI adapter's input file staging, and retry +exhaustion/success behavior. + +End-to-end comparison tests will be added once CM inspect and the sequencer +`get_state` endpoint are available. diff --git a/justfile b/justfile index 61dfda5..bcf0e6d 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,9 @@ check-all-targets: test: cargo test --workspace +test-watchdog: + lua watchdog/tests/run.lua + # Run sequencer tests sequentially so partition static config (init) is not shared across parallel tests. test-sequencer: cargo test -p sequencer --lib -- --test-threads=1 diff --git a/watchdog/abi.lua b/watchdog/abi.lua new file mode 100644 index 0000000..0c423bd --- /dev/null +++ b/watchdog/abi.lua @@ -0,0 +1,138 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local abi = {} + +local WORD_HEX_LEN = 64 + +local function strip_0x(value) + assert(type(value) == "string", "hex value must be a string") + if value:sub(1, 2) == "0x" or value:sub(1, 2) == "0X" then + return value:sub(3) + end + return value +end + +local function assert_hex(value) + if value:match("^[0-9a-fA-F]*$") == nil then + error("invalid hex string") + end +end + +local function word_at(hex, index) + local start = (index * WORD_HEX_LEN) + 1 + local word = hex:sub(start, start + WORD_HEX_LEN - 1) + if #word ~= WORD_HEX_LEN then + error("ABI word out of bounds") + end + return word +end + +local function uint_word_to_number(word) + local value = 0 + for i = 1, #word do + local nibble = tonumber(word:sub(i, i), 16) + value = (value * 16) + nibble + if value > 9007199254740991 then + error("uint value too large for precise Lua number") + end + end + return value +end + +local function uint_word_to_hex(word) + local stripped = word:gsub("^0+", "") + if stripped == "" then + return "0x0" + end + return "0x" .. stripped:lower() +end + +local function address_from_word(word) + if word:sub(1, 24) ~= string.rep("0", 24) then + error("address word has non-zero high bytes") + end + return "0x" .. word:sub(25):lower() +end + +function abi.bytes_from_hex(hex) + hex = strip_0x(hex) + assert_hex(hex) + if (#hex % 2) ~= 0 then + error("hex string must have even length") + end + + return (hex:gsub("..", function(byte) + return string.char(tonumber(byte, 16)) + end)) +end + +function abi.hex_from_bytes(bytes) + return (bytes:gsub(".", function(char) + return string.format("%02x", char:byte()) + end)) +end + +function abi.decode_single_dynamic_bytes(encoded) + local hex = strip_0x(encoded) + assert_hex(hex) + local offset = uint_word_to_number(word_at(hex, 0)) + if (offset % 32) ~= 0 then + error("dynamic bytes offset is not word-aligned") + end + + local offset_words = offset // 32 + local len = uint_word_to_number(word_at(hex, offset_words)) + local data_hex_start = ((offset_words + 1) * WORD_HEX_LEN) + 1 + local data_hex = hex:sub(data_hex_start, data_hex_start + (len * 2) - 1) + if #data_hex ~= len * 2 then + error("dynamic bytes payload out of bounds") + end + return abi.bytes_from_hex(data_hex) +end + +function abi.decode_evm_advance_call(encoded) + local hex = strip_0x(encoded) + assert_hex(hex) + + -- EvmAdvanceCall is calldata, so accept and skip the 4-byte selector. + if (#hex % WORD_HEX_LEN) == 8 then + hex = hex:sub(9) + end + + local payload_offset = uint_word_to_number(word_at(hex, 7)) + if (payload_offset % 32) ~= 0 then + error("payload offset is not word-aligned") + end + + local payload_offset_words = payload_offset // 32 + local payload_len = uint_word_to_number(word_at(hex, payload_offset_words)) + local payload_hex_start = ((payload_offset_words + 1) * WORD_HEX_LEN) + 1 + local payload_hex = hex:sub(payload_hex_start, payload_hex_start + (payload_len * 2) - 1) + if #payload_hex ~= payload_len * 2 then + error("payload out of bounds") + end + + return { + chain_id_hex = uint_word_to_hex(word_at(hex, 0)), + app_contract = address_from_word(word_at(hex, 1)), + msg_sender = address_from_word(word_at(hex, 2)), + block_number = uint_word_to_number(word_at(hex, 3)), + block_timestamp_hex = uint_word_to_hex(word_at(hex, 4)), + prev_randao_hex = uint_word_to_hex(word_at(hex, 5)), + index_hex = uint_word_to_hex(word_at(hex, 6)), + payload = abi.bytes_from_hex(payload_hex), + } +end + +function abi.decode_input_added_log(log) + if type(log) ~= "table" or type(log.data) ~= "string" then + error("log.data is required") + end + local input = abi.decode_single_dynamic_bytes(log.data) + local decoded = abi.decode_evm_advance_call(abi.hex_from_bytes(input)) + decoded.raw_input = input + return decoded +end + +return abi diff --git a/watchdog/alarm.lua b/watchdog/alarm.lua new file mode 100644 index 0000000..085eff6 --- /dev/null +++ b/watchdog/alarm.lua @@ -0,0 +1,43 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local alarm = {} + +local function encode_json_object(fields) + local parts = {} + for key, value in pairs(fields) do + local encoded_value + if type(value) == "number" then + encoded_value = tostring(value) + elseif type(value) == "boolean" then + encoded_value = value and "true" or "false" + else + local string_value = tostring(value) + :gsub("\\", "\\\\") + :gsub('"', '\\"') + :gsub("\n", "\\n") + encoded_value = '"' .. string_value .. '"' + end + table.insert(parts, string.format('"%s":%s', key, encoded_value)) + end + return "{" .. table.concat(parts, ",") .. "}" +end + +function alarm.send_webhook(http, webhook_url, payload) + if not webhook_url or webhook_url == "" then + return nil, "WATCHDOG_WEBHOOK_URL is not configured" + end + local body = encode_json_object(payload) + local response, err = http:post(webhook_url, body, { + ["content-type"] = "application/json", + }) + if not response then + return nil, err + end + if response.status < 200 or response.status >= 300 then + return nil, "alarm webhook HTTP " .. tostring(response.status) + end + return true +end + +return alarm diff --git a/watchdog/checkpoint.lua b/watchdog/checkpoint.lua new file mode 100644 index 0000000..3c47ecd --- /dev/null +++ b/watchdog/checkpoint.lua @@ -0,0 +1,153 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local checkpoint = {} + +local function join(...) + local parts = { ... } + return table.concat(parts, "/"):gsub("//+", "/") +end + +local function read_all(path) + local file, err = io.open(path, "rb") + if not file then + return nil, err + end + local data = file:read("*a") + file:close() + return data +end + +local function write_all(path, data) + local file, err = io.open(path, "wb") + if not file then + return nil, err + end + file:write(data) + file:close() + return true +end + +local function shell_quote(value) + value = tostring(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function json_escape(value) + return value:gsub("\\", "\\\\"):gsub('"', '\\"'):gsub("\n", "\\n") +end + +local function manifest_json(manifest) + local fields = { + string.format('"safe_block":%d', manifest.safe_block), + string.format('"created_at":"%s"', json_escape(manifest.created_at or os.date("!%Y-%m-%dT%H:%M:%SZ"))), + } + if manifest.cm_image_hash then + table.insert(fields, string.format('"cm_image_hash":"%s"', json_escape(manifest.cm_image_hash))) + end + return "{" .. table.concat(fields, ",") .. "}\n" +end + +local function pointer_json(relative_path) + return string.format('{"checkpoint":"%s"}\n', json_escape(relative_path)) +end + +local function parse_pointer(data) + return data:match('"checkpoint"%s*:%s*"([^"]+)"') +end + +function checkpoint.safe_block_from_manifest(manifest_json) + local value = tostring(manifest_json or ""):match('"safe_block"%s*:%s*(%d+)') + if not value then + return nil, "manifest missing safe_block" + end + return tonumber(value) +end + +function checkpoint.load(dir) + local pointer_data, err = read_all(join(dir, "current.json")) + if not pointer_data then + return nil, err + end + local relative_path = parse_pointer(pointer_data) + if not relative_path then + return nil, "invalid checkpoint pointer" + end + local checkpoint_dir = join(dir, relative_path) + local manifest, manifest_err = read_all(join(checkpoint_dir, "manifest.json")) + if not manifest then + return nil, manifest_err + end + local safe_block, safe_block_err = checkpoint.safe_block_from_manifest(manifest) + if not safe_block then + return nil, safe_block_err + end + return { + path = checkpoint_dir, + manifest_json = manifest, + safe_block = safe_block, + snapshot_dir = join(checkpoint_dir, "snapshot"), + } +end + +function checkpoint.prepare(dir, safe_block) + assert(type(safe_block) == "number", "safe_block must be a number") + + local name = string.format("%020d", safe_block) + local relative_path = join("checkpoints", name) + local full_path = join(dir, relative_path) + local snapshot_dir = join(full_path, "snapshot") + + local ok = os.execute("mkdir -p " .. shell_quote(snapshot_dir)) + if ok ~= true and ok ~= 0 then + return nil, "mkdir failed: " .. snapshot_dir + end + + return { + path = full_path, + snapshot_dir = snapshot_dir, + relative_path = relative_path, + } +end + +function checkpoint.commit_prepared(dir, prepared, safe_block, manifest) + assert(type(prepared) == "table", "prepared checkpoint is required") + assert(type(safe_block) == "number", "safe_block must be a number") + manifest = manifest or {} + manifest.safe_block = safe_block + + local ok, err = write_all(join(prepared.path, "manifest.json"), manifest_json(manifest)) + if not ok then + return nil, err + end + + local tmp_pointer = join(dir, "current.json.tmp") + ok, err = write_all(tmp_pointer, pointer_json(prepared.relative_path)) + if not ok then + return nil, err + end + ok, err = os.rename(tmp_pointer, join(dir, "current.json")) + if not ok then + return nil, err + end + + return { + path = prepared.path, + snapshot_dir = prepared.snapshot_dir, + } +end + +function checkpoint.write(dir, safe_block, snapshot_writer, manifest) + assert(type(snapshot_writer) == "function", "snapshot_writer must be a function") + local prepared, prepare_err = checkpoint.prepare(dir, safe_block) + if not prepared then + return nil, prepare_err + end + local ok, err = snapshot_writer(prepared.snapshot_dir) + if not ok then + return nil, err + end + return checkpoint.commit_prepared(dir, prepared, safe_block, manifest) +end + +return checkpoint diff --git a/watchdog/compare.lua b/watchdog/compare.lua new file mode 100644 index 0000000..f1c0f52 --- /dev/null +++ b/watchdog/compare.lua @@ -0,0 +1,36 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local compare = {} + +function compare.first_mismatch_offset(a, b) + assert(type(a) == "string", "left value must be a string") + assert(type(b) == "string", "right value must be a string") + + local limit = math.min(#a, #b) + for i = 1, limit do + if a:byte(i) ~= b:byte(i) then + return i + end + end + + if #a ~= #b then + return limit + 1 + end + + return nil +end + +function compare.raw_equal(expected, actual) + local mismatch = compare.first_mismatch_offset(expected, actual) + return mismatch == nil, mismatch +end + +function compare.assert_state_response(state) + if type(state) ~= "string" then + return nil, "state must be a string" + end + return true +end + +return compare diff --git a/watchdog/config.lua b/watchdog/config.lua new file mode 100644 index 0000000..1f07dc1 --- /dev/null +++ b/watchdog/config.lua @@ -0,0 +1,90 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local config = {} + +local function required(name, env) + local value = env[name] + if value == nil or value == "" then + error(name .. " is required") + end + return value +end + +local function optional_number(name, default, env) + local value = env[name] + if value == nil or value == "" then + return default + end + local parsed = tonumber(value) + if not parsed then + error(name .. " must be a number") + end + return parsed +end + +local function optional_required_number(value_name, number_name, env) + if env[value_name] == nil or env[value_name] == "" then + return nil + end + local value = env[number_name] + if value == nil or value == "" then + error(number_name .. " is required when " .. value_name .. " is set") + end + return optional_number(number_name, nil, env) +end + +local function split_csv(value) + local out = {} + for part in tostring(value or ""):gmatch("[^,]+") do + table.insert(out, part) + end + return out +end + +function config.load(env) + env = env or os.getenv + if type(env) == "function" then + local getenv = env + env = setmetatable({}, { + __index = function(_, key) + return getenv(key) + end, + }) + end + + local mode = env.WATCHDOG_MODE or "advance" + if mode ~= "advance" and mode ~= "compare" then + error("WATCHDOG_MODE must be 'advance' or 'compare'") + end + + return { + mode = mode, + sequencer_url = mode == "compare" and required("WATCHDOG_SEQUENCER_URL", env) or env.WATCHDOG_SEQUENCER_URL, + l1_rpc_url = required("WATCHDOG_L1_RPC_URL", env), + input_box_address = required("WATCHDOG_INPUTBOX_ADDRESS", env), + app_address = required("WATCHDOG_APP_ADDRESS", env), + input_added_topic = env.WATCHDOG_INPUT_ADDED_TOPIC, + checkpoint_dir = required("WATCHDOG_CHECKPOINT_DIR", env), + cm_snapshot_dir = env.WATCHDOG_CM_SNAPSHOT_DIR, + cm_snapshot_safe_block = optional_required_number( + "WATCHDOG_CM_SNAPSHOT_DIR", + "WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK", + env + ), + cm_work_dir = env.WATCHDOG_CM_WORK_DIR or "/tmp", + cm_executable = env.WATCHDOG_CM_EXECUTABLE or "cartesi-machine", + target_safe_block = optional_number("WATCHDOG_TARGET_SAFE_BLOCK", nil, env), + poll_interval_sec = optional_number("WATCHDOG_POLL_INTERVAL_SEC", 30, env), + retry_attempts = optional_number("WATCHDOG_RETRY_ATTEMPTS", 3, env), + retry_delay_sec = optional_number("WATCHDOG_RETRY_DELAY_SEC", 5, env), + safe_confirmations = optional_number("WATCHDOG_SAFE_CONFIRMATIONS", 12, env), + webhook_url = env.WATCHDOG_WEBHOOK_URL, + once = env.WATCHDOG_ONCE == "1", + long_block_range_error_codes = split_csv( + env.WATCHDOG_LONG_BLOCK_RANGE_ERROR_CODES or "-32005,-32600,-32602,-32616" + ), + } +end + +return config diff --git a/watchdog/http.lua b/watchdog/http.lua new file mode 100644 index 0000000..5beed60 --- /dev/null +++ b/watchdog/http.lua @@ -0,0 +1,90 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local http = {} + +function http.new_curl() + local ok, curl = pcall(require, "cURL") + if not ok then + ok, curl = pcall(require, "lcurl") + end + if not ok then + error("lua-curl binding not found; install lua-curl/lcurl or inject an http adapter") + end + + local client = {} + + function client:post(url, body, headers) + local chunks = {} + local header_list = {} + for key, value in pairs(headers or {}) do + table.insert(header_list, key .. ": " .. value) + end + + local easy = curl.easy({ + url = url, + post = true, + postfields = body, + httpheader = header_list, + timeout = 30, + writefunction = function(chunk) + table.insert(chunks, chunk) + return #chunk + end, + }) + + local ok_perform, err = pcall(function() + easy:perform() + end) + if not ok_perform then + easy:close() + return nil, tostring(err) + end + + local status = easy:getinfo_response_code() + easy:close() + return { + status = status, + body = table.concat(chunks), + headers = {}, + } + end + + function client:get(url, headers) + local chunks = {} + local header_list = {} + for key, value in pairs(headers or {}) do + table.insert(header_list, key .. ": " .. value) + end + + local easy = curl.easy({ + url = url, + httpheader = header_list, + timeout = 30, + writefunction = function(chunk) + table.insert(chunks, chunk) + return #chunk + end, + }) + + local ok_perform, err = pcall(function() + easy:perform() + end) + if not ok_perform then + easy:close() + return nil, tostring(err) + end + + local status = easy:getinfo_response_code() + easy:close() + return { + status = status, + body = table.concat(chunks), + headers = {}, + } + end + + return client +end + +return http diff --git a/watchdog/jsonrpc.lua b/watchdog/jsonrpc.lua new file mode 100644 index 0000000..e2989de --- /dev/null +++ b/watchdog/jsonrpc.lua @@ -0,0 +1,107 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local jsonrpc = {} + +local function quantity(value) + assert(type(value) == "number" and value >= 0, "quantity must be a non-negative number") + return string.format("0x%x", value) +end + +local function strip_0x(value) + return tostring(value):gsub("^0[xX]", "") +end + +local function topic_address(address) + assert(type(address) == "string" and address ~= "", "topic address is required") + local raw = strip_0x(address):lower() + assert(#raw == 40 and raw:match("^[0-9a-f]+$") ~= nil, "topic address must be 20-byte hex") + return "0x" .. string.rep("0", 24) .. raw +end + +function jsonrpc.new(http, json, url) + assert(type(http) == "table" and type(http.post) == "function", "http.post is required") + assert(type(json) == "table" and type(json.encode) == "function", "json.encode is required") + assert(type(json.decode) == "function", "json.decode is required") + assert(type(url) == "string" and url ~= "", "url is required") + + local client = { + http = http, + json = json, + url = url, + next_id = 1, + } + + function client:call(method, params) + local id = self.next_id + self.next_id = self.next_id + 1 + local body = self.json.encode({ + jsonrpc = "2.0", + id = id, + method = method, + params = params or {}, + }) + + local response, http_err = self.http:post(self.url, body, { + ["content-type"] = "application/json", + }) + if not response then + return nil, http_err + end + if response.status < 200 or response.status >= 300 then + return nil, "HTTP " .. tostring(response.status) + end + + local decoded + local ok = pcall(function() + decoded = self.json.decode(response.body) + end) + if not ok then + return nil, "invalid JSON-RPC response JSON" + end + if type(decoded) ~= "table" then + return nil, "JSON-RPC response must be an object" + end + if decoded.id ~= id then + return nil, "JSON-RPC response id mismatch" + end + if decoded.error ~= nil then + local code = decoded.error.code or "unknown" + local message = decoded.error.message or "JSON-RPC error" + return nil, tostring(code) .. ": " .. tostring(message) + end + return decoded.result + end + + function client:get_logs(filter) + assert(type(filter.input_added_topic) == "string", "input_added_topic is required") + local topics = { filter.input_added_topic } + if filter.app_address then + topics[2] = topic_address(filter.app_address) + end + + return self:call("eth_getLogs", { + { + address = filter.address, + fromBlock = quantity(filter.from_block), + toBlock = quantity(filter.to_block), + topics = topics, + }, + }) + end + + function client:get_block_number_by_tag(tag) + local block, err = self:call("eth_getBlockByNumber", { tag, false }) + if not block then + return nil, err + end + if type(block.number) ~= "string" then + return nil, "block response missing number" + end + return tonumber(block.number:gsub("^0[xX]", ""), 16) + end + + return client +end + +return jsonrpc diff --git a/watchdog/l1.lua b/watchdog/l1.lua new file mode 100644 index 0000000..5db728a --- /dev/null +++ b/watchdog/l1.lua @@ -0,0 +1,137 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local abi = require("watchdog.abi") + +local l1 = {} + +l1.INPUT_ADDED_TOPIC = "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98" + +l1.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES = { + "-32005", + "-32600", + "-32602", + "-32616", +} + +local function contains_any(message, codes) + message = tostring(message or "") + for _, code in ipairs(codes or {}) do + if message:find(code, 1, true) then + return true + end + end + return false +end + +local function hex_quantity_to_number(value, field) + if type(value) == "number" then + return value + end + if type(value) ~= "string" or value:sub(1, 2) ~= "0x" then + error((field or "quantity") .. " must be an Ethereum hex quantity") + end + return tonumber(value:sub(3), 16) +end + +local function log_order_key(log) + return { + hex_quantity_to_number(log.blockNumber, "blockNumber"), + hex_quantity_to_number(log.transactionIndex or "0x0", "transactionIndex"), + hex_quantity_to_number(log.logIndex or "0x0", "logIndex"), + } +end + +function l1.sort_logs(logs) + table.sort(logs, function(a, b) + local ak = log_order_key(a) + local bk = log_order_key(b) + if ak[1] ~= bk[1] then + return ak[1] < bk[1] + end + if ak[2] ~= bk[2] then + return ak[2] < bk[2] + end + return ak[3] < bk[3] + end) + return logs +end + +function l1.fetch_logs_partitioned(rpc, params) + assert(type(rpc) == "table" and type(rpc.get_logs) == "function", "rpc.get_logs is required") + assert(type(params) == "table", "params are required") + + local start_block = assert(params.start_block, "start_block is required") + local end_block = assert(params.end_block, "end_block is required") + local codes = params.long_block_range_error_codes or l1.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES + local input_added_topic = params.input_added_topic or l1.INPUT_ADDED_TOPIC + + local function go(from_block, to_block) + local logs, err = rpc:get_logs({ + address = params.input_box_address, + app_address = params.app_address, + from_block = from_block, + to_block = to_block, + input_added_topic = input_added_topic, + }) + if logs then + return logs + end + + if from_block < to_block and contains_any(err, codes) then + local mid = from_block + ((to_block - from_block) // 2) + local left, left_err = go(from_block, mid) + if not left then + return nil, left_err + end + local right, right_err = go(mid + 1, to_block) + if not right then + return nil, right_err + end + for _, log in ipairs(right) do + table.insert(left, log) + end + return left + end + + return nil, err + end + + if end_block < start_block then + return {} + end + + local logs, err = go(start_block, end_block) + if not logs then + return nil, err + end + return l1.sort_logs(logs) +end + +function l1.decode_and_validate_log(log) + local decoded = abi.decode_input_added_log(log) + local block_number = hex_quantity_to_number(log.blockNumber, "blockNumber") + if decoded.block_number ~= block_number then + error(string.format( + "InputAdded block number mismatch: log=%d payload=%d", + block_number, + decoded.block_number + )) + end + return decoded +end + +function l1.fetch_inputs(rpc, params) + local logs, err = l1.fetch_logs_partitioned(rpc, params) + if not logs then + return nil, err + end + + local inputs = {} + for _, log in ipairs(logs) do + table.insert(inputs, l1.decode_and_validate_log(log)) + end + return inputs +end + +return l1 diff --git a/watchdog/machine.lua b/watchdog/machine.lua new file mode 100644 index 0000000..62bdc87 --- /dev/null +++ b/watchdog/machine.lua @@ -0,0 +1,43 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local machine = {} + +function machine.new(binding) + assert(type(binding) == "table", "Cartesi Machine binding is required") + assert(type(binding.load_snapshot) == "function", "binding.load_snapshot is required") + assert(type(binding.feed_input) == "function", "binding.feed_input is required") + assert(type(binding.inspect_state) == "function", "binding.inspect_state is required") + assert(type(binding.save_snapshot) == "function", "binding.save_snapshot is required") + + local driver = { binding = binding } + + function driver:load(path) + return self.binding.load_snapshot(path) + end + + function driver:feed_inputs(instance, inputs) + for _, input in ipairs(inputs) do + -- Do not classify payload tags here. The scheduler inside the CM owns + -- direct-input vs batch-submission semantics. + local ok, err = self.binding.feed_input(instance, input) + if not ok then + return nil, err + end + end + return true + end + + function driver:inspect_state(instance) + return self.binding.inspect_state(instance) + end + + function driver:save(instance, path) + assert(type(self.binding.save_snapshot) == "function", "binding.save_snapshot is required") + return self.binding.save_snapshot(instance, path) + end + + return driver +end + +return machine diff --git a/watchdog/machine_cli.lua b/watchdog/machine_cli.lua new file mode 100644 index 0000000..7b0fc4e --- /dev/null +++ b/watchdog/machine_cli.lua @@ -0,0 +1,129 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local machine = require("watchdog.machine") + +local machine_cli = {} + +local function shell_quote(value) + value = tostring(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function mkdir_p(path) + local ok = os.execute("mkdir -p " .. shell_quote(path)) + if ok ~= true and ok ~= 0 then + return nil, "mkdir failed: " .. path + end + return true +end + +local function write_all(path, data) + local file, err = io.open(path, "wb") + if not file then + return nil, err + end + file:write(data) + file:close() + return true +end + +local function run_command(command) + local ok = os.execute(command) + if ok == true or ok == 0 then + return true + end + return nil, "command failed: " .. command +end + +local function new_work_dir(base_dir) + base_dir = base_dir or "/tmp" + return string.format( + "%s/watchdog-cm-%d-%d", + base_dir:gsub("/+$", ""), + os.time(), + math.random(1000000) + ) +end + +function machine_cli.new(opts) + opts = opts or {} + local executable = opts.executable or "cartesi-machine" + local work_dir = opts.work_dir or "/tmp" + + local binding = {} + + function binding.load_snapshot(snapshot_dir) + assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") + local instance = { + source_snapshot_dir = snapshot_dir, + work_dir = new_work_dir(work_dir), + input_dir = nil, + input_count = 0, + } + local ok, err = mkdir_p(instance.work_dir) + if not ok then + return nil, err + end + instance.input_dir = instance.work_dir .. "/inputs" + ok, err = mkdir_p(instance.input_dir) + if not ok then + return nil, err + end + return instance + end + + function binding.feed_input(instance, input) + assert(type(instance) == "table", "machine instance is required") + assert(type(input) == "table", "input is required") + local raw_input = input.raw_input + if type(raw_input) ~= "string" then + return nil, "input.raw_input is required" + end + + local path = string.format("%s/input-%d.bin", instance.input_dir, instance.input_count) + local ok, err = write_all(path, raw_input) + if not ok then + return nil, err + end + instance.input_count = instance.input_count + 1 + return true + end + + function binding.inspect_state(_instance) + -- Inspect requires rollback semantics around the loaded snapshot. The + -- advance-only watchdog mode does not call this; compare mode remains + -- deferred until the canonical app inspect contract exists. + return nil, "CM inspect is not implemented for the CLI adapter yet" + end + + function binding.save_snapshot(instance, snapshot_dir) + assert(type(instance) == "table", "machine instance is required") + assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") + + local ok, err = mkdir_p(snapshot_dir) + if not ok then + return nil, err + end + + local command = table.concat({ + shell_quote(executable), + "--no-rollback", + "--load=" .. shell_quote(instance.source_snapshot_dir) .. ",sharing:none", + "--cmio-advance-state=input:" .. shell_quote(instance.input_dir .. "/input-%i.bin") + .. ",input_index_begin:0,input_index_end:" .. tostring(instance.input_count), + "--store=" .. shell_quote(snapshot_dir), + "--quiet", + }, " ") + + return run_command(command) + end + + return machine.new(binding) +end + +machine_cli._private = { + shell_quote = shell_quote, +} + +return machine_cli diff --git a/watchdog/main.lua b/watchdog/main.lua new file mode 100644 index 0000000..4096177 --- /dev/null +++ b/watchdog/main.lua @@ -0,0 +1,78 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local config = require("watchdog.config") +local http_mod = require("watchdog.http") +local jsonrpc = require("watchdog.jsonrpc") +local machine_cli = require("watchdog.machine_cli") +local retry = require("watchdog.retry") +local runner = require("watchdog.runner") +local sequencer_mod = require("watchdog.sequencer") + +local function load_json() + local ok, cjson = pcall(require, "cjson") + if ok then + return cjson + end + error("lua-cjson is required for watchdog runtime") +end + +local function load_machine(cfg) + return machine_cli.new({ + executable = cfg.cm_executable, + work_dir = cfg.cm_work_dir, + }) +end + +local function run_once(cfg, deps) + if cfg.mode == "compare" then + return runner.run_once(cfg, deps) + end + return runner.advance_checkpoint_once(cfg, deps) +end + +local function main() + local cfg = config.load() + local http = http_mod.new_curl() + local json = load_json() + local deps = { + http = http, + rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), + machine = load_machine(cfg), + } + if cfg.sequencer_url then + deps.sequencer = sequencer_mod.new(http, json, cfg.sequencer_url) + end + + repeat + local result, err = retry.with_retries(function() + local ok, value = pcall(run_once, cfg, deps) + if not ok then + return nil, value + end + return value + end, { + attempts = cfg.retry_attempts, + delay_sec = cfg.retry_delay_sec, + }) + if result == nil then + io.stderr:write("watchdog run failed: " .. tostring(err) .. "\n") + os.exit(1) + end + if cfg.once then + return + end + os.execute("sleep " .. tostring(cfg.poll_interval_sec)) + until false +end + +if ... == nil then + main() +end + +return { + main = main, + run_once = run_once, +} diff --git a/watchdog/retry.lua b/watchdog/retry.lua new file mode 100644 index 0000000..4316312 --- /dev/null +++ b/watchdog/retry.lua @@ -0,0 +1,30 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local retry = {} + +function retry.with_retries(fn, opts) + opts = opts or {} + local attempts = math.max(1, opts.attempts or 1) + local delay_sec = opts.delay_sec or 0 + local sleep = opts.sleep or function(seconds) + if seconds > 0 then + os.execute("sleep " .. tostring(seconds)) + end + end + + local last_err + for attempt = 1, attempts do + local result, err = fn(attempt) + if result then + return result + end + last_err = err + if attempt < attempts then + sleep(delay_sec) + end + end + return nil, last_err +end + +return retry diff --git a/watchdog/runner.lua b/watchdog/runner.lua new file mode 100644 index 0000000..87cd8d0 --- /dev/null +++ b/watchdog/runner.lua @@ -0,0 +1,209 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local alarm = require("watchdog.alarm") +local checkpoint = require("watchdog.checkpoint") +local compare = require("watchdog.compare") +local l1 = require("watchdog.l1") + +local runner = {} + +local function require_dep(deps, name) + local value = deps[name] + assert(value ~= nil, "missing dependency: " .. name) + return value +end + +local function load_checkpoint(cfg, checkpoint_mod) + local loaded = checkpoint_mod.load(cfg.checkpoint_dir) + if loaded then + return loaded + end + + if not cfg.cm_snapshot_dir or cfg.cm_snapshot_dir == "" then + error("no checkpoint found and WATCHDOG_CM_SNAPSHOT_DIR is not configured") + end + if type(cfg.cm_snapshot_safe_block) ~= "number" then + error("no checkpoint found and WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK is not configured") + end + + return { + snapshot_dir = cfg.cm_snapshot_dir, + safe_block = cfg.cm_snapshot_safe_block, + } +end + +local function fetch_inputs(cfg, deps, from_block, to_block) + if from_block > to_block then + return {} + end + + if deps.fetch_inputs then + return deps.fetch_inputs(from_block, to_block) + end + + local rpc = require_dep(deps, "rpc") + return l1.fetch_inputs(rpc, { + start_block = from_block, + end_block = to_block, + input_box_address = cfg.input_box_address, + app_address = cfg.app_address, + input_added_topic = cfg.input_added_topic, + long_block_range_error_codes = cfg.long_block_range_error_codes, + }) +end + +local function send_alarm(cfg, deps, payload) + if deps.alarm then + return deps.alarm(payload) + end + if deps.http and cfg.webhook_url then + return alarm.send_webhook(deps.http, cfg.webhook_url, payload) + end + return true +end + +local function target_safe_block(cfg, deps) + if type(cfg.target_safe_block) == "number" then + return cfg.target_safe_block + end + if deps.safe_block then + return deps.safe_block() + end + if deps.rpc and type(deps.rpc.get_block_number_by_tag) == "function" then + return deps.rpc:get_block_number_by_tag("safe") + end + return nil, "target safe block is not configured" +end + +function runner.run_once(cfg, deps) + deps = deps or {} + local checkpoint_mod = deps.checkpoint or checkpoint + local sequencer = require_dep(deps, "sequencer") + local machine = require_dep(deps, "machine") + + local loaded = load_checkpoint(cfg, checkpoint_mod) + local sequencer_state, state_err = sequencer:get_state() + if not sequencer_state then + return nil, state_err + end + + local safe_block_prev = loaded.safe_block or 0 + local safe_block_next = sequencer_state.safe_block + if safe_block_next < safe_block_prev then + local payload = { + kind = "safe_block_regressed", + previous_safe_block = safe_block_prev, + sequencer_safe_block = safe_block_next, + } + send_alarm(cfg, deps, payload) + return nil, payload + end + + local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) + if not inputs then + return nil, input_err + end + + local instance, load_err = machine:load(loaded.snapshot_dir) + if not instance then + return nil, load_err + end + + local fed, feed_err = machine:feed_inputs(instance, inputs) + if not fed then + return nil, feed_err + end + + local cm_state, inspect_err = machine:inspect_state(instance) + if not cm_state then + return nil, inspect_err + end + + local equal, mismatch_offset = compare.raw_equal(sequencer_state.state, cm_state) + if not equal then + local payload = { + kind = "state_mismatch", + previous_safe_block = safe_block_prev, + sequencer_safe_block = safe_block_next, + mismatch_offset = mismatch_offset, + } + send_alarm(cfg, deps, payload) + return nil, payload + end + + if safe_block_next > safe_block_prev then + local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) + return machine:save(instance, snapshot_dir) + end, { + created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), + cm_image_hash = cfg.cm_image_hash, + }) + if not written then + return nil, write_err + end + end + + return { + ok = true, + previous_safe_block = safe_block_prev, + safe_block = safe_block_next, + input_count = #inputs, + } +end + +function runner.advance_checkpoint_once(cfg, deps) + deps = deps or {} + local checkpoint_mod = deps.checkpoint or checkpoint + local machine = require_dep(deps, "machine") + + local loaded = load_checkpoint(cfg, checkpoint_mod) + local safe_block_prev = loaded.safe_block or 0 + local safe_block_next, safe_err = target_safe_block(cfg, deps) + if not safe_block_next then + return nil, safe_err + end + if safe_block_next < safe_block_prev then + return nil, { + kind = "safe_block_regressed", + previous_safe_block = safe_block_prev, + safe_block = safe_block_next, + } + end + + local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) + if not inputs then + return nil, input_err + end + + local instance, load_err = machine:load(loaded.snapshot_dir) + if not instance then + return nil, load_err + end + + local fed, feed_err = machine:feed_inputs(instance, inputs) + if not fed then + return nil, feed_err + end + + if safe_block_next > safe_block_prev then + local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) + return machine:save(instance, snapshot_dir) + end, { + created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), + cm_image_hash = cfg.cm_image_hash, + }) + if not written then + return nil, write_err + end + end + + return { + ok = true, + previous_safe_block = safe_block_prev, + safe_block = safe_block_next, + input_count = #inputs, + } +end + +return runner diff --git a/watchdog/sequencer.lua b/watchdog/sequencer.lua new file mode 100644 index 0000000..93b9ddb --- /dev/null +++ b/watchdog/sequencer.lua @@ -0,0 +1,48 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local compare = require("watchdog.compare") + +local sequencer = {} + +function sequencer.new(http, json, base_url) + assert(type(http) == "table" and type(http.get) == "function", "http.get is required") + assert(type(json) == "table" and type(json.decode) == "function", "json.decode is required") + assert(type(base_url) == "string" and base_url ~= "", "base_url is required") + + local client = { + http = http, + json = json, + base_url = base_url:gsub("/+$", ""), + } + + function client:get_state() + local response, err = self.http:get(self.base_url .. "/get_state") + if not response then + return nil, err + end + if response.status < 200 or response.status >= 300 then + return nil, "HTTP " .. tostring(response.status) + end + + local decoded + local ok_decode = pcall(function() + decoded = self.json.decode(response.body) + end) + if not ok_decode or type(decoded) ~= "table" then + return nil, "invalid get_state response JSON" + end + if type(decoded.safe_block) ~= "number" then + return nil, "safe_block must be a number" + end + local ok, validation_err = compare.assert_state_response(decoded.state) + if not ok then + return nil, validation_err + end + return decoded + end + + return client +end + +return sequencer diff --git a/watchdog/tests/fixtures/input_added_evm_advance.lua b/watchdog/tests/fixtures/input_added_evm_advance.lua new file mode 100644 index 0000000..5c1aabb --- /dev/null +++ b/watchdog/tests/fixtures/input_added_evm_advance.lua @@ -0,0 +1,17 @@ +-- Static fixture for an InputBox `InputAdded(address,uint256,bytes)` log whose +-- `bytes input` field contains an EvmAdvance calldata envelope. + +return { + log = { + blockNumber = "0x63", + transactionIndex = "0x0", + logIndex = "0x0", + data = "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144123456780000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000011111111111111111111111111111111111111110000000000000000000000002222222222222222222222222222222222222222000000000000000000000000000000000000000000000000000000000000006300000000000000000000000000000000000000000000000000000000000004d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000400aabbcc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + expected = { + app_contract = "0x1111111111111111111111111111111111111111", + msg_sender = "0x2222222222222222222222222222222222222222", + block_number = 99, + payload_hex = "00aabbcc", + }, +} diff --git a/watchdog/tests/run.lua b/watchdog/tests/run.lua new file mode 100644 index 0000000..36b413c --- /dev/null +++ b/watchdog/tests/run.lua @@ -0,0 +1,513 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local abi = require("watchdog.abi") +local alarm = require("watchdog.alarm") +local checkpoint = require("watchdog.checkpoint") +local compare = require("watchdog.compare") +local config = require("watchdog.config") +local jsonrpc = require("watchdog.jsonrpc") +local l1 = require("watchdog.l1") +local machine_cli = require("watchdog.machine_cli") +local retry = require("watchdog.retry") +local runner = require("watchdog.runner") +local sequencer = require("watchdog.sequencer") + +local tests = {} + +local function test(name, fn) + table.insert(tests, { name = name, fn = fn }) +end + +local function assert_eq(actual, expected) + if actual ~= expected then + error(string.format("expected %q, got %q", tostring(expected), tostring(actual)), 2) + end +end + +test("raw compare fails byte-different JSON", function() + local ok, offset = compare.raw_equal('{"a":1}', '{ "a": 1 }') + assert_eq(ok, false) + assert(offset ~= nil, "expected mismatch offset") +end) + +test("decodes InputAdded log EvmAdvance envelope", function() + local fixture = dofile("watchdog/tests/fixtures/input_added_evm_advance.lua") + local decoded = abi.decode_input_added_log(fixture.log) + assert_eq(decoded.app_contract, fixture.expected.app_contract) + assert_eq(decoded.msg_sender, fixture.expected.msg_sender) + assert_eq(decoded.block_number, fixture.expected.block_number) + assert_eq(abi.hex_from_bytes(decoded.payload), fixture.expected.payload_hex) + assert(decoded.raw_input ~= nil and #decoded.raw_input > 0, "fixture keeps raw input bytes") +end) + +test("sorts logs in L1 order", function() + local logs = { + { blockNumber = "0x2", transactionIndex = "0x0", logIndex = "0x5" }, + { blockNumber = "0x1", transactionIndex = "0x9", logIndex = "0x0" }, + { blockNumber = "0x2", transactionIndex = "0x0", logIndex = "0x1" }, + } + l1.sort_logs(logs) + assert_eq(logs[1].blockNumber, "0x1") + assert_eq(logs[2].logIndex, "0x1") + assert_eq(logs[3].logIndex, "0x5") +end) + +test("partitions long block range errors", function() + local calls = {} + local rpc = {} + function rpc:get_logs(filter) + table.insert(calls, { filter.from_block, filter.to_block, filter.input_added_topic }) + if filter.from_block == 1 and filter.to_block == 4 then + return nil, "RPC error -32005: query returned more than allowed" + end + return {} + end + + local logs, err = l1.fetch_logs_partitioned(rpc, { + start_block = 1, + end_block = 4, + input_box_address = "0xinputbox", + app_address = "0x1111111111111111111111111111111111111111", + }) + + assert(logs, err) + assert_eq(#calls, 3) + assert_eq(calls[2][1], 1) + assert_eq(calls[2][2], 2) + assert_eq(calls[3][1], 3) + assert_eq(calls[3][2], 4) + assert_eq(calls[3][3], l1.INPUT_ADDED_TOPIC) +end) + +test("jsonrpc get_logs builds InputAdded app filter", function() + local captured = nil + local json = {} + function json.encode(value) + captured = value + return "encoded" + end + function json.decode(_body) + return { jsonrpc = "2.0", id = 1, result = {} } + end + + local http = {} + function http:post(url, body, headers) + assert_eq(url, "http://rpc") + assert_eq(body, "encoded") + assert_eq(headers["content-type"], "application/json") + return { status = 200, body = "{}" } + end + + local client = jsonrpc.new(http, json, "http://rpc") + local logs, err = client:get_logs({ + address = "0x9999999999999999999999999999999999999999", + app_address = "0x1111111111111111111111111111111111111111", + from_block = 10, + to_block = 12, + input_added_topic = l1.INPUT_ADDED_TOPIC, + }) + + assert(logs, err) + assert_eq(captured.method, "eth_getLogs") + local filter = captured.params[1] + assert_eq(filter.fromBlock, "0xa") + assert_eq(filter.toBlock, "0xc") + assert_eq(filter.address, "0x9999999999999999999999999999999999999999") + assert_eq(filter.topics[1], l1.INPUT_ADDED_TOPIC) + assert_eq( + filter.topics[2], + "0x0000000000000000000000001111111111111111111111111111111111111111" + ) +end) + +test("config loads snapshot directory safe block and optional topic", function() + local env = { + WATCHDOG_L1_RPC_URL = "http://rpc", + WATCHDOG_INPUTBOX_ADDRESS = "0x9999999999999999999999999999999999999999", + WATCHDOG_APP_ADDRESS = "0x1111111111111111111111111111111111111111", + WATCHDOG_INPUT_ADDED_TOPIC = "0xtopic", + WATCHDOG_CHECKPOINT_DIR = "/tmp/checkpoints", + WATCHDOG_CM_SNAPSHOT_DIR = "/tmp/snapshot", + WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK = "42", + } + + local cfg = config.load(env) + + assert_eq(cfg.input_added_topic, "0xtopic") + assert_eq(cfg.cm_snapshot_dir, "/tmp/snapshot") + assert_eq(cfg.cm_snapshot_safe_block, 42) + assert_eq(cfg.mode, "advance") +end) + +test("config rejects unknown mode", function() + local ok, err = pcall(function() + config.load({ + WATCHDOG_MODE = "bad", + WATCHDOG_L1_RPC_URL = "http://rpc", + WATCHDOG_INPUTBOX_ADDRESS = "0x9999999999999999999999999999999999999999", + WATCHDOG_APP_ADDRESS = "0x1111111111111111111111111111111111111111", + WATCHDOG_CHECKPOINT_DIR = "/tmp/checkpoints", + }) + end) + assert_eq(ok, false) + assert(tostring(err):find("WATCHDOG_MODE", 1, true) ~= nil, "mode error is explicit") +end) + +test("checkpoint writes manifest-backed current pointer", function() + local dir = os.tmpname() + os.remove(dir) + os.execute(string.format('mkdir -p "%s"', dir)) + + local written, err = checkpoint.write(dir, 12, function(snapshot_dir) + local file = io.open(snapshot_dir .. "/marker", "wb") + file:write("snapshot") + file:close() + return true + end, { + created_at = "2026-04-28T00:00:00Z", + }) + assert(written, err) + + local loaded, load_err = checkpoint.load(dir) + assert(loaded, load_err) + assert_eq(loaded.snapshot_dir, dir .. "/checkpoints/00000000000000000012/snapshot") + assert(loaded.manifest_json:find('"safe_block":12', 1, true) ~= nil, "manifest has safe block") +end) + +test("checkpoint rejects manifest without safe block", function() + local safe_block, err = checkpoint.safe_block_from_manifest("{}") + assert_eq(safe_block, nil) + assert_eq(err, "manifest missing safe_block") +end) + +local function fake_cfg() + return { + checkpoint_dir = "/tmp/watchdog-test", + cm_snapshot_dir = "/tmp/genesis-snapshot", + cm_snapshot_safe_block = 0, + input_box_address = "0xinputbox", + app_address = "0x1111111111111111111111111111111111111111", + input_added_topic = "0xtopic", + long_block_range_error_codes = l1.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES, + } +end + +local function fake_machine(inspect_state) + local machine = { + loaded_path = nil, + fed_inputs = nil, + } + function machine:load(path) + self.loaded_path = path + return { path = path } + end + function machine:feed_inputs(_instance, inputs) + self.fed_inputs = inputs + return true + end + function machine:inspect_state(_instance) + return inspect_state + end + function machine:save(_instance, snapshot_dir) + self.saved_snapshot_dir = snapshot_dir + return true + end + return machine +end + +test("runner happy path replays inputs and writes checkpoint", function() + local checkpoint_writes = {} + local checkpoint_mod = { + load = function(_dir) + return { + snapshot_dir = "/tmp/checkpoints/0001/snapshot", + safe_block = 10, + } + end, + write = function(dir, safe_block, snapshot_writer, manifest) + local ok, err = snapshot_writer("/tmp/new-snapshot") + assert(ok, err) + table.insert(checkpoint_writes, { + dir = dir, + safe_block = safe_block, + manifest = manifest, + }) + return true + end, + } + local machine = fake_machine('{"ok":true}') + local result, err = runner.run_once(fake_cfg(), { + checkpoint = checkpoint_mod, + sequencer = { + get_state = function() + return { + safe_block = 12, + state = '{"ok":true}', + } + end, + }, + fetch_inputs = function(from_block, to_block) + assert_eq(from_block, 11) + assert_eq(to_block, 12) + return { { payload = "a" }, { payload = "b" } } + end, + machine = machine, + }) + + assert(result, err) + assert_eq(result.safe_block, 12) + assert_eq(result.input_count, 2) + assert_eq(machine.loaded_path, "/tmp/checkpoints/0001/snapshot") + assert_eq(machine.saved_snapshot_dir, "/tmp/new-snapshot") + assert_eq(#machine.fed_inputs, 2) + assert_eq(#checkpoint_writes, 1) + assert_eq(checkpoint_writes[1].safe_block, 12) +end) + +test("runner alarms on raw state mismatch", function() + local alarms = {} + local result, err = runner.run_once(fake_cfg(), { + checkpoint = { + load = function(_dir) + return { snapshot_dir = "/tmp/snapshot", safe_block = 1 } + end, + }, + sequencer = { + get_state = function() + return { + safe_block = 1, + state = '{"a":1}', + } + end, + }, + fetch_inputs = function() + return {} + end, + machine = fake_machine('{ "a": 1 }'), + alarm = function(payload) + table.insert(alarms, payload) + return true + end, + }) + + assert_eq(result, nil) + assert_eq(err.kind, "state_mismatch") + assert_eq(#alarms, 1) + assert_eq(alarms[1].kind, "state_mismatch") +end) + +test("runner alarms on sequencer safe block regression", function() + local alarms = {} + local result, err = runner.run_once(fake_cfg(), { + checkpoint = { + load = function(_dir) + return { snapshot_dir = "/tmp/snapshot", safe_block = 5 } + end, + }, + sequencer = { + get_state = function() + return { + safe_block = 4, + state = "{}", + } + end, + }, + machine = fake_machine("{}"), + alarm = function(payload) + table.insert(alarms, payload) + return true + end, + }) + + assert_eq(result, nil) + assert_eq(err.kind, "safe_block_regressed") + assert_eq(#alarms, 1) +end) + +test("sequencer client validates generic state response", function() + local http = {} + function http:get(url) + assert_eq(url, "http://sequencer/get_state") + return { + status = 200, + body = "body", + } + end + local json = {} + function json.decode(_body) + return { + safe_block = 7, + state = "raw-state", + } + end + + local client = sequencer.new(http, json, "http://sequencer/") + local state, err = client:get_state() + assert(state, err) + assert_eq(state.safe_block, 7) + assert_eq(state.state, "raw-state") +end) + +test("sequencer client rejects invalid JSON", function() + local http = {} + function http:get(_url) + return { + status = 200, + body = "not-json", + } + end + local json = {} + function json.decode(_body) + error("decode failed") + end + + local client = sequencer.new(http, json, "http://sequencer") + local state, err = client:get_state() + assert_eq(state, nil) + assert_eq(err, "invalid get_state response JSON") +end) + +test("advance runner fetches inputs and saves checkpoint without sequencer", function() + local checkpoint_writes = {} + local machine = fake_machine("unused") + local result, err = runner.advance_checkpoint_once(fake_cfg(), { + checkpoint = { + load = function(_dir) + return { snapshot_dir = "/tmp/snapshot", safe_block = 7 } + end, + write = function(dir, safe_block, snapshot_writer, manifest) + local ok, write_err = snapshot_writer("/tmp/advanced-snapshot") + assert(ok, write_err) + table.insert(checkpoint_writes, { dir = dir, safe_block = safe_block, manifest = manifest }) + return true + end, + }, + safe_block = function() + return 9 + end, + fetch_inputs = function(from_block, to_block) + assert_eq(from_block, 8) + assert_eq(to_block, 9) + return { { raw_input = "one" }, { raw_input = "two" } } + end, + machine = machine, + }) + + assert(result, err) + assert_eq(result.safe_block, 9) + assert_eq(result.input_count, 2) + assert_eq(machine.saved_snapshot_dir, "/tmp/advanced-snapshot") + assert_eq(#checkpoint_writes, 1) +end) + +test("machine cli adapter writes raw input files", function() + local base = os.tmpname() + os.remove(base) + os.execute(string.format('mkdir -p "%s"', base)) + local driver = machine_cli.new({ work_dir = base, executable = "cartesi-machine" }) + local instance = assert(driver:load("/tmp/source-snapshot")) + assert(driver:feed_inputs(instance, { + { raw_input = "abc" }, + { raw_input = "def" }, + })) + + local file = io.open(instance.input_dir .. "/input-0.bin", "rb") + assert(file ~= nil, "first input file exists") + assert_eq(file:read("*a"), "abc") + file:close() + file = io.open(instance.input_dir .. "/input-1.bin", "rb") + assert(file ~= nil, "second input file exists") + assert_eq(file:read("*a"), "def") + file:close() +end) + +test("retry succeeds after transient failures", function() + local attempts = 0 + local sleeps = 0 + local result, err = retry.with_retries(function() + attempts = attempts + 1 + if attempts < 3 then + return nil, "transient" + end + return "ok" + end, { + attempts = 3, + delay_sec = 1, + sleep = function(seconds) + assert_eq(seconds, 1) + sleeps = sleeps + 1 + end, + }) + + assert_eq(result, "ok") + assert_eq(err, nil) + assert_eq(attempts, 3) + assert_eq(sleeps, 2) +end) + +test("retry returns final error after exhaustion", function() + local attempts = 0 + local result, err = retry.with_retries(function() + attempts = attempts + 1 + return nil, "failed-" .. tostring(attempts) + end, { + attempts = 2, + delay_sec = 0, + sleep = function() end, + }) + + assert_eq(result, nil) + assert_eq(err, "failed-2") + assert_eq(attempts, 2) +end) + +test("alarm webhook posts JSON payload", function() + local sent = {} + local http = {} + function http:post(url, body, headers) + sent.url = url + sent.body = body + sent.headers = headers + return { status = 204, body = "" } + end + + local ok, err = alarm.send_webhook(http, "http://alarm", { + kind = "state_mismatch", + safe_block = 12, + }) + + assert(ok, err) + assert_eq(sent.url, "http://alarm") + assert_eq(sent.headers["content-type"], "application/json") + assert(sent.body:find('"kind":"state_mismatch"', 1, true) ~= nil, "body includes kind") + assert(sent.body:find('"safe_block":12', 1, true) ~= nil, "body includes safe block") +end) + +test("alarm webhook reports non-success status", function() + local http = {} + function http:post(_url, _body, _headers) + return { status = 500, body = "" } + end + + local ok, err = alarm.send_webhook(http, "http://alarm", { kind = "test" }) + assert_eq(ok, nil) + assert_eq(err, "alarm webhook HTTP 500") +end) + +local failures = 0 +for _, t in ipairs(tests) do + local ok, err = pcall(t.fn) + if ok then + io.write("ok - " .. t.name .. "\n") + else + failures = failures + 1 + io.write("not ok - " .. t.name .. ": " .. tostring(err) .. "\n") + end +end + +if failures > 0 then + os.exit(1) +end From 07e0b8fab1533e98eb218101be2410afc5dbfb56 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:37:01 +0800 Subject: [PATCH 02/13] ci: fix clippy version --- .github/workflows/ci.yml | 2 +- rust-toolchain.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65d9ecc..659ecea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: libslirp-dev - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@1.91.1 with: toolchain: ${{ env.RUST_TOOLCHAIN }} components: rustfmt, clippy diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..4f22047 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.91.1" +components = ["rustfmt", "clippy"] From f871284deb35cd01ec19da0ed62cf5624310465b Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 11 May 2026 22:08:09 +0800 Subject: [PATCH 03/13] feat: add Lua watchdog advance checkpointing --- .gitignore | 1 + examples/canonical-app/justfile | 4 +- watchdog/checkpoint.lua | 8 ++-- watchdog/http.lua | 4 +- watchdog/machine_cli.lua | 5 --- .../fixtures/input_added_evm_advance.lua | 15 +++++++- watchdog/tests/run.lua | 37 ++++++++++++++----- 7 files changed, 51 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 0359111..70d546f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ sequencer.db sequencer.db-shm sequencer.db-wal /out/ +examples/canonical-app/out/ /.DS_Store soljson-latest.js **/states/ diff --git a/examples/canonical-app/justfile b/examples/canonical-app/justfile index f3eb915..5c5ecb6 100644 --- a/examples/canonical-app/justfile +++ b/examples/canonical-app/justfile @@ -35,13 +35,13 @@ build-dapp: build-dapp-devnet build-dapp-devnet: mkdir -p {{out_dir}} - SOURCE_DATE_EPOCH={{source_date_epoch}} CARGO_PROFILE_RELEASE_STRIP=symbols CROSS_CONFIG=Cross.toml DOCKER_DEFAULT_PLATFORM=linux/amd64 cross build --package canonical-app --bin canonical-app-devnet --target riscv64gc-unknown-linux-musl --release + SOURCE_DATE_EPOCH={{source_date_epoch}} CARGO_PROFILE_RELEASE_STRIP=symbols CARGO_TARGET_DIR=../../target CROSS_CONFIG=Cross.toml DOCKER_DEFAULT_PLATFORM=linux/amd64 cross build --package canonical-app --bin canonical-app-devnet --target riscv64gc-unknown-linux-musl --release cp ../../target/riscv64gc-unknown-linux-musl/release/canonical-app-devnet {{dapp_binary_devnet}} cp {{dapp_binary_devnet}} {{dapp_binary}} build-dapp-sepolia: mkdir -p {{out_dir}} - SOURCE_DATE_EPOCH={{source_date_epoch}} CARGO_PROFILE_RELEASE_STRIP=symbols CROSS_CONFIG=Cross.toml DOCKER_DEFAULT_PLATFORM=linux/amd64 cross build --package canonical-app --bin canonical-app-sepolia --target riscv64gc-unknown-linux-musl --release + SOURCE_DATE_EPOCH={{source_date_epoch}} CARGO_PROFILE_RELEASE_STRIP=symbols CARGO_TARGET_DIR=../../target CROSS_CONFIG=Cross.toml DOCKER_DEFAULT_PLATFORM=linux/amd64 cross build --package canonical-app --bin canonical-app-sepolia --target riscv64gc-unknown-linux-musl --release cp ../../target/riscv64gc-unknown-linux-musl/release/canonical-app-sepolia {{dapp_binary_sepolia}} cp {{dapp_binary_sepolia}} {{dapp_binary}} diff --git a/watchdog/checkpoint.lua b/watchdog/checkpoint.lua index 3c47ecd..c43574d 100644 --- a/watchdog/checkpoint.lua +++ b/watchdog/checkpoint.lua @@ -56,8 +56,8 @@ local function parse_pointer(data) return data:match('"checkpoint"%s*:%s*"([^"]+)"') end -function checkpoint.safe_block_from_manifest(manifest_json) - local value = tostring(manifest_json or ""):match('"safe_block"%s*:%s*(%d+)') +function checkpoint.safe_block_from_manifest(manifest_data) + local value = tostring(manifest_data or ""):match('"safe_block"%s*:%s*(%d+)') if not value then return nil, "manifest missing safe_block" end @@ -98,9 +98,9 @@ function checkpoint.prepare(dir, safe_block) local full_path = join(dir, relative_path) local snapshot_dir = join(full_path, "snapshot") - local ok = os.execute("mkdir -p " .. shell_quote(snapshot_dir)) + local ok = os.execute("mkdir -p " .. shell_quote(full_path)) if ok ~= true and ok ~= 0 then - return nil, "mkdir failed: " .. snapshot_dir + return nil, "mkdir failed: " .. full_path end return { diff --git a/watchdog/http.lua b/watchdog/http.lua index 5beed60..ae8f1ae 100644 --- a/watchdog/http.lua +++ b/watchdog/http.lua @@ -14,7 +14,7 @@ function http.new_curl() local client = {} - function client:post(url, body, headers) + function client.post(_self, url, body, headers) local chunks = {} local header_list = {} for key, value in pairs(headers or {}) do @@ -50,7 +50,7 @@ function http.new_curl() } end - function client:get(url, headers) + function client.get(_self, url, headers) local chunks = {} local header_list = {} for key, value in pairs(headers or {}) do diff --git a/watchdog/machine_cli.lua b/watchdog/machine_cli.lua index 7b0fc4e..0ef8bd9 100644 --- a/watchdog/machine_cli.lua +++ b/watchdog/machine_cli.lua @@ -101,11 +101,6 @@ function machine_cli.new(opts) assert(type(instance) == "table", "machine instance is required") assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") - local ok, err = mkdir_p(snapshot_dir) - if not ok then - return nil, err - end - local command = table.concat({ shell_quote(executable), "--no-rollback", diff --git a/watchdog/tests/fixtures/input_added_evm_advance.lua b/watchdog/tests/fixtures/input_added_evm_advance.lua index 5c1aabb..da542eb 100644 --- a/watchdog/tests/fixtures/input_added_evm_advance.lua +++ b/watchdog/tests/fixtures/input_added_evm_advance.lua @@ -6,7 +6,20 @@ return { blockNumber = "0x63", transactionIndex = "0x0", logIndex = "0x0", - data = "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000144123456780000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000011111111111111111111111111111111111111110000000000000000000000002222222222222222222222222222222222222222000000000000000000000000000000000000000000000000000000000000006300000000000000000000000000000000000000000000000000000000000004d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000400aabbcc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + data = "0x" + .. "0000000000000000000000000000000000000000000000000000000000000020" + .. "0000000000000000000000000000000000000000000000000000000000000144" + .. "1234567800000000000000000000000000000000000000000000000000000000" + .. "00007a6900000000000000000000000011111111111111111111111111111111" + .. "1111111100000000000000000000000022222222222222222222222222222222" + .. "2222222200000000000000000000000000000000000000000000000000000000" + .. "0000006300000000000000000000000000000000000000000000000000000000" + .. "000004d200000000000000000000000000000000000000000000000000000000" + .. "0000000000000000000000000000000000000000000000000000000000000000" + .. "0000000300000000000000000000000000000000000000000000000000000000" + .. "0000010000000000000000000000000000000000000000000000000000000000" + .. "0000000400aabbcc000000000000000000000000000000000000000000000000" + .. "0000000000000000000000000000000000000000000000000000000000000000", }, expected = { app_contract = "0x1111111111111111111111111111111111111111", diff --git a/watchdog/tests/run.lua b/watchdog/tests/run.lua index 36b413c..3b392f7 100644 --- a/watchdog/tests/run.lua +++ b/watchdog/tests/run.lua @@ -58,7 +58,7 @@ end) test("partitions long block range errors", function() local calls = {} local rpc = {} - function rpc:get_logs(filter) + function rpc.get_logs(_self, filter) table.insert(calls, { filter.from_block, filter.to_block, filter.input_added_topic }) if filter.from_block == 1 and filter.to_block == 4 then return nil, "RPC error -32005: query returned more than allowed" @@ -94,7 +94,7 @@ test("jsonrpc get_logs builds InputAdded app filter", function() end local http = {} - function http:post(url, body, headers) + function http.post(_self, url, body, headers) assert_eq(url, "http://rpc") assert_eq(body, "encoded") assert_eq(headers["content-type"], "application/json") @@ -111,8 +111,10 @@ test("jsonrpc get_logs builds InputAdded app filter", function() }) assert(logs, err) - assert_eq(captured.method, "eth_getLogs") - local filter = captured.params[1] + assert(type(captured) == "table", "json request captured") + local request = captured + assert_eq(request.method, "eth_getLogs") + local filter = request.params[1] assert_eq(filter.fromBlock, "0xa") assert_eq(filter.toBlock, "0xc") assert_eq(filter.address, "0x9999999999999999999999999999999999999999") @@ -162,7 +164,9 @@ test("checkpoint writes manifest-backed current pointer", function() os.execute(string.format('mkdir -p "%s"', dir)) local written, err = checkpoint.write(dir, 12, function(snapshot_dir) + os.execute(string.format('mkdir -p "%s"', snapshot_dir)) local file = io.open(snapshot_dir .. "/marker", "wb") + assert(file ~= nil, "marker file opened") file:write("snapshot") file:close() return true @@ -208,7 +212,7 @@ local function fake_machine(inspect_state) self.fed_inputs = inputs return true end - function machine:inspect_state(_instance) + function machine.inspect_state(_self, _instance) return inspect_state end function machine:save(_instance, snapshot_dir) @@ -294,6 +298,7 @@ test("runner alarms on raw state mismatch", function() }) assert_eq(result, nil) + assert(type(err) == "table", "expected mismatch payload") assert_eq(err.kind, "state_mismatch") assert_eq(#alarms, 1) assert_eq(alarms[1].kind, "state_mismatch") @@ -323,13 +328,14 @@ test("runner alarms on sequencer safe block regression", function() }) assert_eq(result, nil) + assert(type(err) == "table", "expected regression payload") assert_eq(err.kind, "safe_block_regressed") assert_eq(#alarms, 1) end) test("sequencer client validates generic state response", function() local http = {} - function http:get(url) + function http.get(_self, url) assert_eq(url, "http://sequencer/get_state") return { status = 200, @@ -353,7 +359,7 @@ end) test("sequencer client rejects invalid JSON", function() local http = {} - function http:get(_url) + function http.get(_self, _url) return { status = 200, body = "not-json", @@ -424,6 +430,19 @@ test("machine cli adapter writes raw input files", function() file:close() end) +test("machine cli adapter leaves snapshot directory creation to cartesi-machine", function() + local base = os.tmpname() + os.remove(base) + os.execute(string.format('mkdir -p "%s"', base)) + local driver = machine_cli.new({ work_dir = base, executable = "true" }) + local instance = assert(driver:load("/tmp/source-snapshot")) + local snapshot_dir = base .. "/snapshot" + + assert(driver:save(instance, snapshot_dir)) + local exists = os.rename(snapshot_dir, snapshot_dir) + assert(not exists, "adapter must not pre-create --store target") +end) + test("retry succeeds after transient failures", function() local attempts = 0 local sleeps = 0 @@ -467,7 +486,7 @@ end) test("alarm webhook posts JSON payload", function() local sent = {} local http = {} - function http:post(url, body, headers) + function http.post(_self, url, body, headers) sent.url = url sent.body = body sent.headers = headers @@ -488,7 +507,7 @@ end) test("alarm webhook reports non-success status", function() local http = {} - function http:post(_url, _body, _headers) + function http.post(_self, _url, _body, _headers) return { status = 500, body = "" } end From c49364bf4b42c1fb1897b776c4ab4d242eb0f6fd Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 11 May 2026 22:08:57 +0800 Subject: [PATCH 04/13] chore: ignore ".vscode" --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 70d546f..009fc4c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ sequencer.db-wal /out/ examples/canonical-app/out/ /.DS_Store +.vscode/ soljson-latest.js **/states/ From de2add36d964259056c1371d0b72043921c100f5 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 11 May 2026 22:15:29 +0800 Subject: [PATCH 05/13] ci: add watchdog test to ci --- .github/workflows/ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 659ecea..6588308 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ env: CARTESI_MACHINE_SHA256_ARM64: 787d823756000cdecd72da8a3494b4c08613087379035959e561bbaef7a220ba jobs: - rust: + checks: runs-on: ubuntu-latest timeout-minutes: 30 @@ -55,13 +55,16 @@ jobs: - name: Clippy run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings + - name: Watchdog Lua tests + run: lua watchdog/tests/run.lua + - name: Test timeout-minutes: 15 run: cargo test --workspace --all-targets --all-features --locked canonical-guest: runs-on: ubuntu-latest - needs: rust + needs: checks timeout-minutes: 45 steps: @@ -87,7 +90,7 @@ jobs: rollups-e2e: runs-on: ubuntu-latest - needs: rust + needs: checks timeout-minutes: 60 steps: From 44cb84bbf0a078c3a18a5f04c75fc83b1233f6b1 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Thu, 21 May 2026 22:37:34 +0800 Subject: [PATCH 06/13] feat: add watchdog compare path with GET /get_state and devnet harness Wire sequencer safe-only state export, canonical CM inspect, and Lua compare-mode E2E against Anvil devnet. Adds staging drill docs and harness fixes (sequencer-devnet resolution, optional faketime). --- .gitignore | 2 + Cargo.lock | 2 + docs/watchdog/README.md | 101 +++++-- docs/watchdog/staging-drills.md | 95 ++++++ examples/app-core/src/application/wallet.rs | 65 +++- examples/canonical-app/src/scheduler/core.rs | 60 ++++ examples/canonical-app/src/scheduler/mod.rs | 47 ++- justfile | 17 ++ scripts/watchdog-lua-deps.sh | 86 ++++++ sequencer-core/src/application/mod.rs | 8 + sequencer/src/egress/api/get_state.rs | 111 +++++++ sequencer/src/egress/api/mod.rs | 9 +- sequencer/src/egress/api/state.rs | 20 +- sequencer/src/egress/app_state.rs | 292 ++++++++++++++++++ sequencer/src/egress/mod.rs | 1 + sequencer/src/http.rs | 9 + sequencer/src/runtime/mod.rs | 2 +- sequencer/src/runtime/workers.rs | 9 +- sequencer/src/storage/egress.rs | 55 ++++ sequencer/tests/e2e_sequencer.rs | 75 +++++ sequencer/tests/ws_broadcaster.rs | 9 +- tests/e2e/Cargo.toml | 4 +- tests/e2e/src/lib.rs | 1 + tests/e2e/src/main.rs | 13 +- tests/e2e/src/test_cases.rs | 5 + tests/e2e/src/watchdog_compare.rs | 194 ++++++++++++ tests/harness/src/lib.rs | 1 + tests/harness/src/paths.rs | 27 ++ tests/harness/src/sequencer.rs | 89 ++++-- watchdog/http.lua | 85 ++++++ watchdog/machine_cli.lua | 62 +++- watchdog/main.lua | 2 +- watchdog/runner.lua | 44 +++ watchdog/tests/drill_divergence.lua | 58 ++++ watchdog/tests/drill_webhook.lua | 64 ++++ watchdog/tests/e2e.lua | 302 +++++++++++++++++++ watchdog/tests/e2e_log.lua | 45 +++ watchdog/tests/run.lua | 25 ++ watchdog/tests/run_compare_once.lua | 68 +++++ 39 files changed, 2090 insertions(+), 74 deletions(-) create mode 100644 docs/watchdog/staging-drills.md create mode 100755 scripts/watchdog-lua-deps.sh create mode 100644 sequencer/src/egress/api/get_state.rs create mode 100644 sequencer/src/egress/app_state.rs create mode 100644 tests/e2e/src/watchdog_compare.rs create mode 100644 watchdog/tests/drill_divergence.lua create mode 100644 watchdog/tests/drill_webhook.lua create mode 100644 watchdog/tests/e2e.lua create mode 100644 watchdog/tests/e2e_log.lua create mode 100644 watchdog/tests/run_compare_once.lua diff --git a/.gitignore b/.gitignore index 009fc4c..e84d291 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target +.deps/ +watchdog-e2e-*/ .env .env.fish sequencer.db diff --git a/Cargo.lock b/Cargo.lock index a524514..04b118f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3568,6 +3568,8 @@ dependencies = [ "rollups-harness", "sequencer-core", "sequencer-rust-client", + "serde_json", + "tempfile", "tokio", ] diff --git a/docs/watchdog/README.md b/docs/watchdog/README.md index 843f508..8fd44d6 100644 --- a/docs/watchdog/README.md +++ b/docs/watchdog/README.md @@ -8,20 +8,20 @@ against state produced by the canonical Cartesi Machine at an L1 safe block. The implementation lives in `watchdog/` and is intentionally split into small Lua modules: -- `http.lua`: HTTP adapter, currently `lua-curl` oriented. +- `http.lua`: HTTP adapter (`lua-curl` / `lcurl` when installed, otherwise `curl` CLI via `new_auto()`). - `jsonrpc.lua`: JSON-RPC request/response validation. - `l1.lua`: partitioned `eth_getLogs` scanning and strict L1 log ordering. - `abi.lua`: decoding for the `InputAdded` / `EvmAdvance` envelope. - `machine.lua`: narrow adapter boundary for Cartesi Machine bindings. - `machine_cli.lua`: `cartesi-machine` CLI adapter for loading snapshot - directories, writing raw input files, advancing, and saving a new snapshot - directory. + directories, writing raw input files, advancing, inspecting, and saving snapshots. - `compare.lua`: raw byte comparison. - `checkpoint.lua`: manifest-backed checkpoint persistence. - `alarm.lua`: webhook alarm delivery. - `retry.lua`: bounded retry helper used by the runtime. - `runner.lua`: one-shot orchestration across checkpoint load, sequencer poll, L1 fetch, CM replay, raw compare, alarm, and checkpoint write. +- `main.lua`: compare or advance loop (daemon or `WATCHDOG_ONCE=1`). The L1 reader follows the Rust partition strategy from `sequencer/src/partition.rs`: if an RPC provider rejects a large range, the @@ -35,13 +35,14 @@ Rust reader's app-filtered InputBox scan. ## Runtime Contract -The future sequencer endpoint shape should be generic over the app state bytes, -even though the toy wallet app will likely use JSON: +The sequencer exposes `GET /get_state` for byte-exact state comparison. The +endpoint is generic over app state bytes, even though the toy wallet app +currently returns deterministic JSON: ```json { "safe_block": 123, - "state": "{\"balances\":{}}" + "state": "{\"balances\":{},\"nonces\":{}}" } ``` @@ -50,9 +51,13 @@ for the app state anchored at `safe_block`. The watchdog compares those raw bytes with the bytes returned by CM inspect. It must not canonicalize both values before deciding pass/fail. -The main design gate is safe-state semantics: if the sequencer has already -applied soft-confirmed transactions beyond L1 safety, `get_state` still needs a -safe-only state view through snapshotting, replay, or a separate projection. +`get_state` reconstructs a safe-only app state by replaying the persisted +scheduler-accepted safe batch prefix into a fresh app instance. It intentionally +excludes the current soft-confirmed Tip and any valid closed batches that have +not been accepted by the L1 scheduler view yet. + +The canonical scheduler answers `RollupRequest::Inspect` with query `state` by +calling `Application::export_state()` (see `examples/canonical-app`). ## Checkpoints @@ -89,32 +94,82 @@ poll the sequencer. It: 4. Feeds the raw InputBox input bytes into the CM adapter. 5. Saves a new snapshot directory and advances `current.json`. -`WATCHDOG_MODE=compare` is reserved for the future state comparison flow once -CM inspect and the sequencer state endpoint are available. +`WATCHDOG_MODE=compare` replays safe L1 inputs into the CM, calls +`--cmio-inspect-state` with the `state` query, and compares the returned report +bytes against `GET /get_state`. Useful runtime knobs: -- `WATCHDOG_CM_EXECUTABLE`: Cartesi Machine executable, default - `cartesi-machine`. -- `WATCHDOG_CM_WORK_DIR`: temporary directory for staged input files, default - `/tmp`. +- `WATCHDOG_CM_EXECUTABLE`: Cartesi Machine executable, default `cartesi-machine`. +- `WATCHDOG_CM_WORK_DIR`: temporary directory for staged input files, default `/tmp`. - `WATCHDOG_RETRY_ATTEMPTS`: bounded retry attempts per run, default `3`. - `WATCHDOG_RETRY_DELAY_SEC`: delay between retry attempts, default `5`. - `WATCHDOG_TARGET_SAFE_BLOCK`: manual/test override for the target safe block. ## Local Tests -Run the pure Lua tests with: +| Command | What it exercises | +|---------|-------------------| +| `just test-watchdog` | Lua unit tests (fake HTTP/RPC/CM; no live chain) | +| `just test-watchdog-e2e` | Real CM: advance, inspect; optional live compare if `WATCHDOG_E2E_SEQUENCER_URL` set | +| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `GET /get_state` + CM inspect + Lua compare | +| `just test-watchdog-webhook-drill` | Webhook delivery smoke (`WATCHDOG_WEBHOOK_URL` required) | + +Prerequisites for CM-backed tests: + +```bash +just canonical-build-machine-image # once, if out/ image is missing +just watchdog-lua-deps # lua-cjson into .deps/lua (system pkg or gcc) +``` + +`cartesi-machine`, `lua`, and `curl` on PATH. `lua-curl` is optional (CLI fallback). + +### Lua unit tests ```bash just test-watchdog ``` -These cover raw comparison, golden InputAdded ABI decoding, L1 ordering, -recursive range partitioning, JSON-RPC `eth_getLogs` filter construction, -config parsing, checkpoint writes, advance-mode runner behavior, the -fake-backed compare runner, the CLI adapter's input file staging, and retry -exhaustion/success behavior. +Covers raw comparison, golden InputAdded ABI decoding, L1 ordering, recursive +range partitioning, config, checkpoints, advance/compare runner (fakes), CM CLI +staging, retry, and alarm webhook encoding. + +### Lua CM end-to-end + +```bash +just test-watchdog-e2e +``` + +Scenarios (verbose `step NN/NN` logging): + +- `prerequisites` — `cartesi-machine` on PATH and machine image present. +- `advance-empty-range` — real CM advance + checkpoint write with zero new inputs. +- `cm-inspect-state-query` — real `--cmio-inspect-state` with query `state`. +- `compare-runner-with-sequencer` — skipped unless `WATCHDOG_E2E_SEQUENCER_URL` is set. + +Rebuild the machine image after changing the canonical scheduler/dapp. A stale +image makes `cm-inspect-state-query` skip with `inspect endpoint not implemented`. + +### Rust compare harness (most complete integration test) + +```bash +just test-watchdog-compare-harness +``` + +Spawns Anvil + rollups devnet + `sequencer-devnet`, proves CM inspect JSON at +genesis, then runs `watchdog/tests/run_compare_once.lua` in compare mode with +matching `WATCHDOG_*` addresses. Requires `RUN_WATCHDOG_E2E=1` (set by the recipe). + +### Staging / operator drills + +See [`staging-drills.md`](staging-drills.md) for webhook smoke, synthetic +divergence POST, and manual compare env vars. + +## Related sequencer tests + +```bash +cargo test -p sequencer get_state -- --test-threads=1 +``` -End-to-end comparison tests will be added once CM inspect and the sequencer -`get_state` endpoint are available. +HTTP integration for `GET /get_state` lives in `sequencer/tests/e2e_sequencer.rs`. +Storage/replay semantics are covered in `sequencer/src/egress/app_state.rs` unit tests. diff --git a/docs/watchdog/staging-drills.md b/docs/watchdog/staging-drills.md new file mode 100644 index 0000000..1ca7168 --- /dev/null +++ b/docs/watchdog/staging-drills.md @@ -0,0 +1,95 @@ +# Watchdog Staging Drills + +Operator drills for webhook delivery and divergence detection. Local harness +steps live in [`README.md`](README.md); this document covers staging and manual +verification. + +## Prerequisites + +- Built canonical machine image: `just canonical-build-machine-image` +- `cartesi-machine`, `lua`, and `curl` on PATH +- `lua-cjson` (system package, or `just watchdog-lua-deps` copies/builds `.deps/lua/cjson.so` via `gcc` — no `make`) +- `lua-curl` optional — drills and compare harness fall back to `curl` CLI when absent +- Staging or local sequencer reachable at `WATCHDOG_SEQUENCER_URL` +- L1 RPC + InputBox + app addresses matching that deployment +- Webhook receiver URL (Slack incoming webhook, PagerDuty, or `https://httpbin.org/post` for smoke tests) + +## Drill 1 — Webhook delivery (no sequencer) + +Verifies the alarm transport reaches your receiver. + +```bash +just watchdog-lua-deps +export WATCHDOG_WEBHOOK_URL="https://your-receiver.example/hook" +WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/drill_webhook.lua +# or: just test-watchdog-webhook-drill +``` + +Expected: HTTP 2xx for both `state_mismatch` and `safe_block_regressed` sample payloads. +Check the receiver shows JSON with `"kind"` and `"run_id"` fields. + +## Drill 2 — Divergence webhook (synthetic mismatch, no CM) + +Verifies the receiver gets a realistic `state_mismatch` payload without running compare mode: + +```bash +export WATCHDOG_WEBHOOK_URL="https://your-receiver.example/hook" +WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/drill_divergence.lua +``` + +Expected: HTTP 2xx, receiver shows `kind=state_mismatch` and a non-zero `mismatch_offset`. + +Unit coverage: `just test-watchdog` (`runner alarms on raw state mismatch`). + +## Drill 3 — Happy compare (local Anvil harness) + +Full stack: Anvil + devnet rollups + sequencer + CM inspect + `GET /get_state`. + +```bash +just test-watchdog-compare-harness +# equivalent: +# just setup && just watchdog-lua-deps && just ensure-machine-image +# cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e +# RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e -- watchdog_genesis_compare_test --exact +``` + +Or run the Lua compare pass manually after starting a devnet sequencer yourself: + +```bash +export WATCHDOG_MODE=compare +export WATCHDOG_SEQUENCER_URL=http://127.0.0.1: +export WATCHDOG_L1_RPC_URL=http://127.0.0.1:8545 +export WATCHDOG_INPUTBOX_ADDRESS= +export WATCHDOG_APP_ADDRESS= +export WATCHDOG_CHECKPOINT_DIR=/tmp/watchdog-checkpoints +export WATCHDOG_CM_SNAPSHOT_DIR=examples/canonical-app/out/canonical-machine-image +export WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK=0 +export WATCHDOG_LUA_DEPS=.deps/lua +lua watchdog/tests/run_compare_once.lua +``` + +Expected: exit 0, stdout `watchdog compare ok: safe_block=... input_count=...`, and genesis wallet state `{"balances":{},"nonces":{}}` on both sides. + +## Drill 4 — Production compare daemon + +Run the watchdog in compare mode against staging (daemon or cron): + +```bash +export WATCHDOG_MODE=compare +export WATCHDOG_ONCE=1 # or 0 for daemon +export WATCHDOG_WEBHOOK_URL=... +# ... all WATCHDOG_* vars from config.lua ... +lua watchdog/main.lua +``` + +On mismatch: non-zero exit, webhook fired, logs show `state_mismatch` and byte offset. + +## Triage checklist + +| Symptom | Likely cause | +|---------|----------------| +| `inspect endpoint not implemented` | Stale CM image — rebuild | +| `state_mismatch` at genesis | Checkpoint not aligned with sequencer history | +| Webhook 4xx | Wrong URL or auth on receiver | +| Compare skipped in Lua e2e | Set `WATCHDOG_E2E_SEQUENCER_URL` to a live sequencer | +| Compare harness skipped | Set `RUN_WATCHDOG_E2E=1` (see `just test-watchdog-compare-harness`) | diff --git a/examples/app-core/src/application/wallet.rs b/examples/app-core/src/application/wallet.rs index 845d9c7..ee8f7d3 100644 --- a/examples/app-core/src/application/wallet.rs +++ b/examples/app-core/src/application/wallet.rs @@ -51,7 +51,7 @@ impl Default for WalletConfig { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WalletApp { config: WalletConfig, balances: HashMap, @@ -224,6 +224,39 @@ impl WalletApp { executed_input_count: decoded.executed_input_count, }) } + + fn state_json(&self) -> String { + let mut balances: Vec<_> = self + .balances + .iter() + .filter(|(_, balance)| **balance != U256::ZERO) + .collect(); + balances.sort_by_key(|(address, _)| address.as_slice()); + + let mut nonces: Vec<_> = self + .nonces + .iter() + .filter(|(_, nonce)| **nonce != 0) + .collect(); + nonces.sort_by_key(|(address, _)| address.as_slice()); + + let balance_entries = balances + .into_iter() + .map(|(address, balance)| format!("\"{}\":\"{balance}\"", json_address(address))) + .collect::>() + .join(","); + let nonce_entries = nonces + .into_iter() + .map(|(address, nonce)| format!("\"{}\":{nonce}", json_address(address))) + .collect::>() + .join(","); + + format!("{{\"balances\":{{{balance_entries}}},\"nonces\":{{{nonce_entries}}}}}") + } +} + +fn json_address(address: &Address) -> String { + format!("0x{}", alloy_primitives::hex::encode(address.as_slice())) } impl Default for WalletApp { @@ -407,6 +440,10 @@ impl Application for WalletApp { fn state_file_in_dump(prefix: &Path) -> PathBuf { prefix.join("state") } + + fn export_state(&self) -> Result { + Ok(self.state_json()) + } } #[cfg(test)] @@ -994,4 +1031,30 @@ mod tests { path.push(format!("wallet-dump-{}-{nanos}-{n}", std::process::id())); path } + + #[test] + fn export_state_is_deterministic_and_omits_defaults() { + let mut app = WalletApp::new(WalletConfig::default()); + let high = address!("0xffffffffffffffffffffffffffffffffffffffff"); + let low = address!("0x1111111111111111111111111111111111111111"); + app.balances.insert(high, U256::from(20_u64)); + app.balances.insert(low, U256::from(10_u64)); + app.balances.insert(Address::ZERO, U256::ZERO); + app.nonces.insert(high, 2); + app.nonces.insert(low, 1); + app.nonces.insert(Address::ZERO, 0); + + assert_eq!( + app.export_state().expect("export state"), + concat!( + "{\"balances\":{", + "\"0x1111111111111111111111111111111111111111\":\"10\",", + "\"0xffffffffffffffffffffffffffffffffffffffff\":\"20\"", + "},\"nonces\":{", + "\"0x1111111111111111111111111111111111111111\":1,", + "\"0xffffffffffffffffffffffffffffffffffffffff\":2", + "}}" + ) + ); + } } diff --git a/examples/canonical-app/src/scheduler/core.rs b/examples/canonical-app/src/scheduler/core.rs index 7ea6e21..a10d78e 100644 --- a/examples/canonical-app/src/scheduler/core.rs +++ b/examples/canonical-app/src/scheduler/core.rs @@ -87,6 +87,15 @@ impl PartialEq for ProcessOutcome { } } +/// Inspect query accepted by the scheduler's state export endpoint. +pub const STATE_INSPECT_QUERY: &[u8] = b"state"; + +#[derive(Debug, PartialEq, Eq)] +pub(super) enum InspectError { + UnsupportedQuery, + Application(String), +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) enum BatchRejectReason { DecodeFailed, @@ -130,6 +139,17 @@ impl Scheduler { self.next_expected_batch_nonce } + pub(super) fn inspect_state(&self, query: &[u8]) -> Result, InspectError> { + if !query.is_empty() && query != STATE_INSPECT_QUERY { + return Err(InspectError::UnsupportedQuery); + } + + self.app + .export_state() + .map(|state| state.into_bytes()) + .map_err(|err| InspectError::Application(err.to_string())) + } + pub(super) fn process_input(&mut self, input: SchedulerInput) -> ProcessResult { // Execute overdue directs before any input to keep backstop semantics explicit. let mut outputs = Vec::new(); @@ -483,6 +503,10 @@ mod tests { fn state_file_in_dump(_prefix: &std::path::Path) -> std::path::PathBuf { unimplemented!("RecordingApp does not participate in snapshot lifecycle") } + + fn export_state(&self) -> Result { + Ok(format!("events:{}", self.executed.len())) + } } const SEQUENCER: Address = address!("0x1111111111111111111111111111111111111111"); @@ -1006,6 +1030,42 @@ mod tests { assert_eq!(scheduler.app.events(), [RecordedTx::UserOp(9)]); } + #[test] + fn inspect_exports_application_state_for_state_query() { + let mut scheduler = Scheduler::new( + RecordingApp::default(), + SchedulerConfig { + sequencer_address: SEQUENCER, + max_wait_blocks: 100, + }, + ); + assert_eq!( + scheduler.process_input(direct_input(1, 7)), + ProcessOutcome::DirectEnqueued + ); + + let state = scheduler + .inspect_state(STATE_INSPECT_QUERY) + .expect("inspect state"); + assert_eq!(state, b"events:1"); + } + + #[test] + fn inspect_rejects_unsupported_query() { + let scheduler = Scheduler::new( + RecordingApp::default(), + SchedulerConfig { + sequencer_address: SEQUENCER, + max_wait_blocks: 100, + }, + ); + + assert_eq!( + scheduler.inspect_state(b"balances"), + Err(InspectError::UnsupportedQuery) + ); + } + #[test] fn wrong_batch_nonce_is_rejected_without_consuming_nonce() { let mut scheduler = Scheduler::new( diff --git a/examples/canonical-app/src/scheduler/mod.rs b/examples/canonical-app/src/scheduler/mod.rs index 8cbc12d..dd2c59b 100644 --- a/examples/canonical-app/src/scheduler/mod.rs +++ b/examples/canonical-app/src/scheduler/mod.rs @@ -3,7 +3,9 @@ mod core; -pub use core::{DEVNET_SEQUENCER_ADDRESS, SEPOLIA_SEQUENCER_ADDRESS, SchedulerConfig}; +pub use core::{ + DEVNET_SEQUENCER_ADDRESS, SEPOLIA_SEQUENCER_ADDRESS, STATE_INSPECT_QUERY, SchedulerConfig, +}; use sequencer_core::application::AppOutput; use sequencer_core::application::Application; @@ -46,9 +48,12 @@ pub fn run_scheduler_forever( }); } } - Ok(RollupRequest::Inspect { .. }) => { + Ok(RollupRequest::Inspect { payload }) => { + let report = scheduler + .inspect_state(&payload) + .unwrap_or_else(|err| panic!("scheduler inspect failed: {err:?}")); rollup - .emit_report(b"scheduler inspect endpoint not implemented") + .emit_report(&report) .unwrap_or_else(|err| panic!("scheduler failed to emit inspect report: {err}")); } Err(err) => panic!("scheduler failed while reading next input: {err}"), @@ -157,6 +162,42 @@ mod tests { } } + #[test] + fn run_scheduler_emits_exported_state_for_state_inspect() { + let inspect = RollupRequest::Inspect { + payload: STATE_INSPECT_QUERY.to_vec(), + }; + let terminal_err = Err(RollupError::CmtCallFailed { + operation: "next_input", + code: -22, + }); + let (rollup, reports) = MockRollup::with_inputs(vec![Ok(inspect), terminal_err]); + let expected = WalletApp::new(WalletConfig::default()) + .export_state() + .expect("export state") + .into_bytes(); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + run_scheduler_forever( + rollup, + WalletApp::new(WalletConfig::default()), + SchedulerConfig::default(), + ) + })); + + assert!( + result.is_err(), + "scheduler loop should panic on rollup error" + ); + let reports = reports.lock().expect("poisoned reports mutex"); + assert!( + reports + .iter() + .any(|report| report.as_slice() == expected.as_slice()), + "missing state inspect report, got: {reports:?}" + ); + } + #[test] fn run_scheduler_emits_report_for_invalid_batch_before_rollup_error() { let sequencer = SchedulerConfig::default().sequencer_address; diff --git a/justfile b/justfile index bcf0e6d..13c2756 100644 --- a/justfile +++ b/justfile @@ -15,6 +15,23 @@ test: test-watchdog: lua watchdog/tests/run.lua +test-watchdog-e2e: + lua watchdog/tests/e2e.lua + +# POST sample alarms to WATCHDOG_WEBHOOK_URL (staging smoke). +test-watchdog-webhook-drill: watchdog-lua-deps + @test -n "${WATCHDOG_WEBHOOK_URL:-}" || { echo "set WATCHDOG_WEBHOOK_URL"; exit 1; } + WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_webhook.lua + WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_divergence.lua + +# Build lua-cjson into .deps/lua (for compare harness / drills without system packages). +watchdog-lua-deps: + @bash scripts/watchdog-lua-deps.sh + +test-watchdog-compare-harness: setup watchdog-lua-deps ensure-machine-image + cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e + RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e -- watchdog_genesis_compare_test --exact --nocapture + # Run sequencer tests sequentially so partition static config (init) is not shared across parallel tests. test-sequencer: cargo test -p sequencer --lib -- --test-threads=1 diff --git a/scripts/watchdog-lua-deps.sh b/scripts/watchdog-lua-deps.sh new file mode 100755 index 0000000..c7af0f5 --- /dev/null +++ b/scripts/watchdog-lua-deps.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Provide lua-cjson for watchdog tests: use system package or compile with gcc (no make). +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +out_dir="${root}/.deps/lua" +out_so="${out_dir}/cjson.so" + +mkdir -p "${out_dir}" + +cjson_loadable() { + # Prefer in-process cpath (works across Lua 5.x); LUA_CPATH_* env names vary by version. + lua -e "package.cpath='${out_dir}/?.so;'..package.cpath; require('cjson')" >/dev/null 2>&1 +} + +if [[ -f "${out_so}" ]] && cjson_loadable; then + exit 0 +fi + +# Prefer distro-packaged module (Debian/Ubuntu, etc.). +for candidate in \ + /usr/lib/x86_64-linux-gnu/lua/5.4/cjson.so \ + /usr/lib/x86_64-linux-gnu/lua/5.3/cjson.so \ + /usr/lib/lua/5.4/cjson.so \ + /usr/lib/lua/5.3/cjson.so; do + if [[ -f "${candidate}" ]]; then + cp "${candidate}" "${out_so}" + exit 0 + fi +done + +if cjson_loadable; then + exit 0 +fi + +lua_inc="" +for dir in /usr/include/lua5.4 /usr/include/lua5.3 /usr/include/lua; do + if [[ -f "${dir}/lua.h" ]]; then + lua_inc="${dir}" + break + fi +done + +if [[ -z "${lua_inc}" ]]; then + echo "watchdog-lua-deps: install lua-cjson (e.g. apt install lua-cjson) or Lua headers (lua5.4-dev)" >&2 + exit 1 +fi + +if ! command -v gcc >/dev/null 2>&1; then + echo "watchdog-lua-deps: install gcc or the lua-cjson system package" >&2 + exit 1 +fi + +tmp="$(mktemp -d)" +trap 'rm -rf "${tmp}"' EXIT + +src="${tmp}/lua-cjson" +if command -v curl >/dev/null 2>&1; then + curl -fsSL "https://github.com/openresty/lua-cjson/archive/refs/heads/master.tar.gz" \ + | tar -xz -C "${tmp}" +elif command -v wget >/dev/null 2>&1; then + wget -qO- "https://github.com/openresty/lua-cjson/archive/refs/heads/master.tar.gz" \ + | tar -xz -C "${tmp}" +else + echo "watchdog-lua-deps: need curl or wget to fetch lua-cjson sources" >&2 + exit 1 +fi + +shopt -s nullglob +dirs=("${tmp}"/lua-cjson-*) +if [[ ${#dirs[@]} -ne 1 ]]; then + echo "watchdog-lua-deps: unexpected lua-cjson extract layout" >&2 + exit 1 +fi +src="${dirs[0]}" + +cflags=(-O3 -Wall -pedantic -DNDEBUG -I"${lua_inc}" -fPIC) +gcc "${cflags[@]}" -c -o "${tmp}/lua_cjson.o" "${src}/lua_cjson.c" +gcc "${cflags[@]}" -c -o "${tmp}/strbuf.o" "${src}/strbuf.c" +gcc "${cflags[@]}" -c -o "${tmp}/fpconv.o" "${src}/fpconv.c" +gcc -shared -o "${out_so}" "${tmp}/lua_cjson.o" "${tmp}/strbuf.o" "${tmp}/fpconv.o" + +if ! cjson_loadable; then + echo "watchdog-lua-deps: built cjson.so but lua cannot load it (Lua version mismatch?)" >&2 + exit 1 +fi diff --git a/sequencer-core/src/application/mod.rs b/sequencer-core/src/application/mod.rs index 1844d94..7aa4763 100644 --- a/sequencer-core/src/application/mod.rs +++ b/sequencer-core/src/application/mod.rs @@ -185,4 +185,12 @@ pub trait Application: Send + Sized { /// is a pure function of `prefix`: callers may invoke it without /// loading the dump or instantiating the Application. fn state_file_in_dump(prefix: &Path) -> PathBuf; + + /// Human-readable state for `GET /get_state` and CM inspect (watchdog + /// dev path). Canonical dumps use SSZ via [`Application::create_dump`]. + fn export_state(&self) -> Result { + Err(AppError::Internal { + reason: "application state export is not implemented".to_string(), + }) + } } diff --git a/sequencer/src/egress/api/get_state.rs b/sequencer/src/egress/api/get_state.rs new file mode 100644 index 0000000..aa0b422 --- /dev/null +++ b/sequencer/src/egress/api/get_state.rs @@ -0,0 +1,111 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use axum::Json; +use axum::extract::State; +use serde::Serialize; + +use crate::egress::api::GetStateState; +use crate::egress::app_state::StateSnapshotError; +use crate::http::ApiError; + +#[derive(Debug, Serialize)] +pub(crate) struct GetStateResponse { + safe_block: u64, + state: String, +} + +pub(crate) async fn get_state( + State(state): State, +) -> Result, ApiError> { + state.reject_if_shutting_down()?; + let snapshotter = state.snapshotter.clone(); + let snapshot = tokio::task::spawn_blocking(move || snapshotter.snapshot()) + .await + .map_err(|err| ApiError::internal_error(format!("state snapshot task failed: {err}")))? + .map_err(map_snapshot_error)?; + + Ok(Json(GetStateResponse { + safe_block: snapshot.safe_block, + state: snapshot.state, + })) +} + +fn map_snapshot_error(err: StateSnapshotError) -> ApiError { + match err { + StateSnapshotError::SafeHeadUnavailable => ApiError::unavailable(err.to_string()), + StateSnapshotError::Storage { .. } + | StateSnapshotError::OpenStorage { .. } + | StateSnapshotError::Application { .. } => ApiError::internal_error(err.to_string()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::sync::Arc; + + use crate::egress::app_state::{StateSnapshot, StateSnapshotProvider}; + use crate::runtime::shutdown::ShutdownSignal; + + struct FixedSnapshotter; + + impl StateSnapshotProvider for FixedSnapshotter { + fn snapshot(&self) -> Result { + Ok(StateSnapshot { + safe_block: 42, + state: "{\"ok\":true}".to_string(), + }) + } + } + + struct UnavailableSnapshotter; + + impl StateSnapshotProvider for UnavailableSnapshotter { + fn snapshot(&self) -> Result { + Err(StateSnapshotError::SafeHeadUnavailable) + } + } + + #[tokio::test] + async fn get_state_returns_safe_block_and_raw_state() { + let state = GetStateState { + shutdown: ShutdownSignal::default(), + snapshotter: Arc::new(FixedSnapshotter), + }; + + let response = get_state(State(state)).await.expect("get state"); + + assert_eq!(response.safe_block, 42); + assert_eq!(response.state, "{\"ok\":true}"); + } + + #[tokio::test] + async fn get_state_rejects_shutdown() { + let shutdown = ShutdownSignal::default(); + shutdown.request_shutdown(); + let state = GetStateState { + shutdown, + snapshotter: Arc::new(FixedSnapshotter), + }; + + let err = get_state(State(state)).await.expect_err("shutdown"); + + assert_eq!(err.status(), axum::http::StatusCode::SERVICE_UNAVAILABLE); + } + + #[tokio::test] + async fn get_state_reports_unavailable_when_safe_head_missing() { + let state = GetStateState { + shutdown: ShutdownSignal::default(), + snapshotter: Arc::new(UnavailableSnapshotter), + }; + + let err = get_state(State(state)) + .await + .expect_err("missing safe head"); + + assert_eq!(err.status(), axum::http::StatusCode::SERVICE_UNAVAILABLE); + } +} diff --git a/sequencer/src/egress/api/mod.rs b/sequencer/src/egress/api/mod.rs index 223fe80..5031d68 100644 --- a/sequencer/src/egress/api/mod.rs +++ b/sequencer/src/egress/api/mod.rs @@ -4,6 +4,7 @@ //! Egress HTTP API routes: WebSocket subscribe + k8s-style health probes. //! Additional read endpoints will land here. +mod get_state; mod health; mod snapshot; mod state; @@ -16,13 +17,14 @@ use axum::routing::get; pub(crate) use health::HealthState; pub use snapshot::SnapshotState; -pub(crate) use state::SubscribeState; +pub(crate) use state::{GetStateState, SubscribeState}; /// Build the egress router. Each subrouter has its own state; the merge is /// transparent to axum's routing. Snapshot routes are always part of the /// egress (internal) side — the public/ingress side is a separate router. pub(crate) fn router( subscribe_state: Arc, + get_state_state: GetStateState, health_state: Arc, snapshot_state: Arc, ) -> Router { @@ -30,6 +32,10 @@ pub(crate) fn router( .route("/ws/subscribe", get(subscribe::subscribe_l2_txs)) .with_state(subscribe_state); + let state_router = Router::new() + .route("/get_state", get(get_state::get_state)) + .with_state(get_state_state); + let health_router = Router::new() .route("/livez", get(health::livez)) .route("/readyz", get(health::readyz)) @@ -37,6 +43,7 @@ pub(crate) fn router( .with_state(health_state); subscribe_router + .merge(state_router) .merge(health_router) .merge(snapshot::router(snapshot_state)) } diff --git a/sequencer/src/egress/api/state.rs b/sequencer/src/egress/api/state.rs index de15396..66ab498 100644 --- a/sequencer/src/egress/api/state.rs +++ b/sequencer/src/egress/api/state.rs @@ -1,13 +1,13 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -//! Egress-side axum state — feeds the WS subscribe handler today; will grow as -//! more egress routes are added. +//! Egress-side axum state for WS subscribe, `GET /get_state`, and health probes. use std::sync::Arc; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; +use crate::egress::app_state::StateSnapshotProvider; use crate::egress::l2_tx_feed::L2TxFeed; use crate::http::ApiError; use crate::runtime::shutdown::ShutdownSignal; @@ -20,6 +20,22 @@ pub(crate) struct SubscribeState { pub tx_feed: L2TxFeed, } +#[derive(Clone)] +pub(crate) struct GetStateState { + pub shutdown: ShutdownSignal, + pub snapshotter: Arc, +} + +impl GetStateState { + pub(crate) fn reject_if_shutting_down(&self) -> Result<(), ApiError> { + if self.shutdown.is_shutdown_requested() { + Err(ApiError::unavailable("sequencer shutting down")) + } else { + Ok(()) + } + } +} + impl SubscribeState { pub(crate) fn new( shutdown: ShutdownSignal, diff --git a/sequencer/src/egress/app_state.rs b/sequencer/src/egress/app_state.rs new file mode 100644 index 0000000..16719ca --- /dev/null +++ b/sequencer/src/egress/app_state.rs @@ -0,0 +1,292 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Safe app-state reconstruction for read-only egress endpoints. + +use alloy_primitives::Address; +use thiserror::Error; + +use crate::storage::Storage; +use sequencer_core::application::{AppError, Application}; +use sequencer_core::l2_tx::SequencedL2Tx; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StateSnapshot { + pub safe_block: u64, + pub state: String, +} + +#[derive(Debug, Error)] +pub enum StateSnapshotError { + #[error("safe L1 head has not been observed yet")] + SafeHeadUnavailable, + #[error("storage error: {source}")] + Storage { + #[from] + source: rusqlite::Error, + }, + #[error("storage open error: {source}")] + OpenStorage { + #[from] + source: crate::storage::StorageOpenError, + }, + #[error("application error: {source}")] + Application { + #[from] + source: AppError, + }, +} + +#[derive(Clone)] +pub struct StateSnapshotter { + db_path: String, + genesis_app: A, + batch_submitter_address: Address, +} + +pub trait StateSnapshotProvider: Send + Sync { + fn snapshot(&self) -> Result; +} + +impl StateSnapshotter +where + A: Application + Clone, +{ + pub fn new(db_path: String, genesis_app: A, batch_submitter_address: Address) -> Self { + Self { + db_path, + genesis_app, + batch_submitter_address, + } + } + + fn build_snapshot(&self) -> Result { + let mut storage = Storage::open_read_only(self.db_path.as_str())?; + let safe_block = storage + .current_safe_block()? + .ok_or(StateSnapshotError::SafeHeadUnavailable)?; + let txs = storage.safe_accepted_l2_txs(self.batch_submitter_address)?; + + let mut app = self.genesis_app.clone(); + replay_txs(&mut app, txs)?; + let state = app.export_state()?; + + Ok(StateSnapshot { safe_block, state }) + } +} + +impl StateSnapshotProvider for StateSnapshotter +where + A: Application + Clone + Sync, +{ + fn snapshot(&self) -> Result { + self.build_snapshot() + } +} + +fn replay_txs(app: &mut A, txs: Vec) -> Result<(), AppError> +where + A: Application, +{ + for tx in txs { + match tx { + SequencedL2Tx::UserOp(user_op) => { + app.execute_valid_user_op(&user_op)?; + } + SequencedL2Tx::Direct(input) => { + app.execute_direct_input(&input)?; + } + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::time::SystemTime; + + use crate::ingress::inclusion_lane::PendingUserOp; + use crate::storage::test_helpers::{default_protocol_timing, temp_db}; + use crate::storage::{SafeInputRange, Storage, StoredSafeInput}; + use alloy_primitives::Signature; + use sequencer_core::application::{AppOutputs, InvalidReason}; + use sequencer_core::batch::Batch; + use sequencer_core::l2_tx::{DirectInput, ValidUserOp}; + use sequencer_core::user_op::{SignedUserOp, UserOp}; + use tokio::sync::oneshot; + + #[derive(Clone, Default)] + struct RecordingApp { + events: Vec, + } + + impl Application for RecordingApp { + const MAX_METHOD_PAYLOAD_BYTES: usize = 1024; + + fn current_user_nonce(&self, _sender: Address) -> u32 { + 0 + } + + fn current_user_balance(&self, _sender: Address) -> alloy_primitives::U256 { + alloy_primitives::U256::ZERO + } + + fn validate_user_op( + &self, + _sender: Address, + _user_op: &UserOp, + _current_fee: u16, + ) -> Result<(), InvalidReason> { + Ok(()) + } + + fn execute_valid_user_op(&mut self, user_op: &ValidUserOp) -> Result { + self.events.push(format!( + "user:{}:{}:{}", + user_op.sender, + user_op.fee, + alloy_primitives::hex::encode(&user_op.data) + )); + Ok(Vec::new()) + } + + fn execute_direct_input(&mut self, input: &DirectInput) -> Result { + self.events.push(format!( + "direct:{}:{}:{}", + input.sender, + input.block_number, + alloy_primitives::hex::encode(&input.payload) + )); + Ok(Vec::new()) + } + + fn export_state(&self) -> Result { + Ok(self.events.join("|")) + } + } + + fn empty_batch_payload(nonce: u64) -> Vec { + ssz::Encode::as_ssz_bytes(&Batch { + nonce, + frames: Vec::new(), + }) + } + + #[test] + fn snapshot_replays_only_scheduler_accepted_batch_prefix() { + let db = temp_db("state-snapshot-accepted-prefix"); + let mut storage = Storage::open(db.path.as_str()).expect("open storage"); + let batch_submitter = Address::repeat_byte(0xfe); + let direct_sender = Address::repeat_byte(0x11); + + storage + .append_safe_inputs( + 10, + &[ + StoredSafeInput { + sender: direct_sender, + payload: vec![0xaa], + block_number: 10, + }, + StoredSafeInput { + sender: batch_submitter, + payload: empty_batch_payload(0), + block_number: 10, + }, + ], + batch_submitter, + &default_protocol_timing(), + ) + .expect("append safe inputs"); + let mut head = storage + .initialize_open_state(10, SafeInputRange::new(0, 2)) + .expect("initialize open state"); + storage + .close_frame_and_batch(&mut head, 10) + .expect("close accepted batch"); + + let snapshotter = + StateSnapshotter::new(db.path.clone(), RecordingApp::default(), batch_submitter); + let snapshot = snapshotter.snapshot().expect("snapshot"); + + assert_eq!(snapshot.safe_block, 10); + assert_eq!(snapshot.state, format!("direct:{direct_sender}:10:aa")); + } + + #[test] + fn snapshot_excludes_open_tip_user_op() { + let db = temp_db("state-snapshot-excludes-tip"); + let mut storage = Storage::open(db.path.as_str()).expect("open storage"); + let batch_submitter = Address::repeat_byte(0xfe); + let sender = Address::repeat_byte(0x22); + + storage + .append_safe_inputs(10, &[], batch_submitter, &default_protocol_timing()) + .expect("append safe head"); + let mut head = storage + .initialize_open_state(10, SafeInputRange::new(0, 0)) + .expect("initialize open state"); + let (respond_to, _recv) = oneshot::channel(); + storage + .append_user_ops_chunk( + &mut head, + &[PendingUserOp { + signed: SignedUserOp { + sender, + signature: Signature::test_signature(), + user_op: UserOp { + nonce: 0, + max_fee: u16::MAX, + data: vec![0x01].into(), + }, + }, + respond_to, + received_at: SystemTime::now(), + }], + ) + .expect("append tip user op"); + + let snapshotter = + StateSnapshotter::new(db.path.clone(), RecordingApp::default(), batch_submitter); + let snapshot = snapshotter.snapshot().expect("snapshot"); + + assert_eq!(snapshot.safe_block, 10); + assert_eq!(snapshot.state, ""); + } + + #[test] + fn snapshot_excludes_unaccepted_soft_batch() { + let db = temp_db("state-snapshot-excludes-soft"); + let mut storage = Storage::open(db.path.as_str()).expect("open storage"); + let batch_submitter = Address::repeat_byte(0xfe); + let direct_sender = Address::repeat_byte(0x11); + + storage + .append_safe_inputs( + 10, + &[StoredSafeInput { + sender: direct_sender, + payload: vec![0xaa], + block_number: 10, + }], + batch_submitter, + &default_protocol_timing(), + ) + .expect("append safe input"); + let mut head = storage + .initialize_open_state(10, SafeInputRange::new(0, 1)) + .expect("initialize open state"); + storage + .close_frame_and_batch(&mut head, 10) + .expect("close unaccepted batch"); + + let snapshotter = + StateSnapshotter::new(db.path.clone(), RecordingApp::default(), batch_submitter); + let snapshot = snapshotter.snapshot().expect("snapshot"); + + assert_eq!(snapshot.safe_block, 10); + assert_eq!(snapshot.state, ""); + } +} diff --git a/sequencer/src/egress/mod.rs b/sequencer/src/egress/mod.rs index ac7b75a..3bcfb8e 100644 --- a/sequencer/src/egress/mod.rs +++ b/sequencer/src/egress/mod.rs @@ -6,4 +6,5 @@ //! split puts these on a separate port from ingress. pub mod api; +pub mod app_state; pub mod l2_tx_feed; diff --git a/sequencer/src/http.rs b/sequencer/src/http.rs index 987f2b0..1dcd551 100644 --- a/sequencer/src/http.rs +++ b/sequencer/src/http.rs @@ -25,6 +25,7 @@ use tower_http::trace::TraceLayer; pub use crate::egress::api::SnapshotState; use crate::egress::api::SubscribeState; +use crate::egress::app_state::StateSnapshotProvider; use crate::egress::l2_tx_feed::L2TxFeed; use crate::ingress::api::SubmitState; use crate::ingress::inclusion_lane::{PendingUserOp, SequencerError}; @@ -175,6 +176,7 @@ pub async fn start( max_user_op_data_bytes: usize, shutdown: ShutdownSignal, tx_feed: L2TxFeed, + state_snapshotter: Arc, config: ApiConfig, snapshot_state: SnapshotState, ) -> io::Result { @@ -186,6 +188,7 @@ pub async fn start( max_user_op_data_bytes, shutdown, tx_feed, + state_snapshotter, config, snapshot_state, )) @@ -199,6 +202,7 @@ pub fn start_on_listener( max_user_op_data_bytes: usize, shutdown: ShutdownSignal, tx_feed: L2TxFeed, + state_snapshotter: Arc, config: ApiConfig, snapshot_state: SnapshotState, ) -> ApiServerTask { @@ -218,10 +222,15 @@ pub fn start_on_listener( config.ws_max_subscribers, config.ws_max_catchup_events, )); + let get_state_state = crate::egress::api::GetStateState { + shutdown: shutdown.clone(), + snapshotter: state_snapshotter, + }; let app: Router = crate::ingress::api::router(submit_state) .merge(crate::egress::api::router( subscribe_state, + get_state_state, health_state, Arc::new(snapshot_state), )) diff --git a/sequencer/src/runtime/mod.rs b/sequencer/src/runtime/mod.rs index 846c8a7..0c15f56 100644 --- a/sequencer/src/runtime/mod.rs +++ b/sequencer/src/runtime/mod.rs @@ -37,7 +37,7 @@ const INPUT_READER_POLL_INTERVAL: Duration = Duration::from_secs(2); pub async fn run(app: A, config: RunConfig) -> Result<(), RunError> where - A: Application + 'static, + A: Application + Clone + Sync + 'static, { // ── Bootstrap ──────────────────────────────────────────── std::fs::create_dir_all(&config.data_dir)?; diff --git a/sequencer/src/runtime/workers.rs b/sequencer/src/runtime/workers.rs index 630e963..4ff8d03 100644 --- a/sequencer/src/runtime/workers.rs +++ b/sequencer/src/runtime/workers.rs @@ -83,7 +83,7 @@ pub(crate) struct Workers { impl Workers { /// Build the worker configs, spawn each worker, return the owning struct. /// Logs `listening` once the HTTP server is bound. - pub(crate) async fn spawn( + pub(crate) async fn spawn( cfg: WorkersConfig, ) -> Result { let WorkersConfig { @@ -102,6 +102,12 @@ impl Workers { let shutdown = ShutdownSignal::default(); + let state_snapshotter = Arc::new(crate::egress::app_state::StateSnapshotter::new( + db_path.clone(), + app.clone(), + l1_config.batch_submitter_address, + )); + // Inclusion lane: takes the app, returns the tx-sender the HTTP // ingress route will publish to. let mut storage = crate::storage::Storage::open(&db_path)?; @@ -186,6 +192,7 @@ impl Workers { A::MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, + state_snapshotter, ApiConfig::default(), http::SnapshotState { db_path: db_path.clone(), diff --git a/sequencer/src/storage/egress.rs b/sequencer/src/storage/egress.rs index d04752e..1c6bbf6 100644 --- a/sequencer/src/storage/egress.rs +++ b/sequencer/src/storage/egress.rs @@ -16,6 +16,61 @@ use super::queries::decode_l2_tx_row; use sequencer_core::l2_tx::SequencedL2Tx; impl Storage { + /// Load L2 transactions for the scheduler-accepted safe batch prefix. + /// + /// This is the replay source for safe app-state export. Unlike + /// `valid_sequenced_l2_txs`, it excludes the soft-confirmed Tip and any + /// valid closed batches that have not yet been accepted by the L1 scheduler. + pub fn safe_accepted_l2_txs( + &mut self, + batch_submitter_address: Address, + ) -> Result> { + const SQL: &str = " + SELECT + CASE WHEN s.user_op_pos_in_frame IS NOT NULL THEN 0 ELSE 1 END AS kind, + CASE + WHEN s.user_op_pos_in_frame IS NOT NULL THEN u.sender + WHEN s.safe_input_index IS NOT NULL THEN d.sender + ELSE NULL + END AS sender, + CASE WHEN s.user_op_pos_in_frame IS NOT NULL THEN u.data ELSE NULL END AS data, + CASE WHEN s.user_op_pos_in_frame IS NOT NULL THEN f.fee ELSE NULL END AS fee, + CASE WHEN s.safe_input_index IS NOT NULL THEN d.payload ELSE NULL END AS payload, + CASE WHEN s.safe_input_index IS NOT NULL THEN d.block_number ELSE NULL END AS block_number + FROM safe_accepted_batches a + JOIN valid_closed_batches b + ON b.nonce = a.nonce + JOIN valid_sequenced_l2_txs s + ON s.batch_index = b.batch_index + AND NOT (s.safe_input_index IS NOT NULL + AND EXISTS (SELECT 1 FROM safe_inputs si + WHERE si.safe_input_index = s.safe_input_index + AND si.sender = ?1)) + LEFT JOIN user_ops u + ON u.batch_index = s.batch_index + AND u.frame_in_batch = s.frame_in_batch + AND u.pos_in_frame = s.user_op_pos_in_frame + LEFT JOIN frames f + ON f.batch_index = s.batch_index + AND f.frame_in_batch = s.frame_in_batch + LEFT JOIN safe_inputs d + ON d.safe_input_index = s.safe_input_index + ORDER BY a.nonce ASC, s.offset ASC + "; + let mut stmt = self.conn.prepare_cached(SQL)?; + let rows = stmt.query_map(params![batch_submitter_address.as_slice()], |row| { + Ok(decode_l2_tx_row( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + row.get(5)?, + )) + })?; + rows.collect::>>() + } + /// Load a page of ordered L2 transactions starting after the given offset. /// Returns `(db_offset, tx)` pairs. Callers should track `db_offset` of the /// last item as their cursor, not increment a counter. diff --git a/sequencer/tests/e2e_sequencer.rs b/sequencer/tests/e2e_sequencer.rs index b61795c..ff196e3 100644 --- a/sequencer/tests/e2e_sequencer.rs +++ b/sequencer/tests/e2e_sequencer.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) use std::io::ErrorKind; +use std::sync::Arc; use std::time::Duration; use alloy_primitives::{Address, Signature, U256}; @@ -12,6 +13,7 @@ use app_core::application::{ use futures_util::StreamExt; use k256::ecdsa::SigningKey; use k256::ecdsa::signature::hazmat::PrehashSigner; +use sequencer::egress::app_state::StateSnapshotter; use sequencer::egress::l2_tx_feed::{L2TxFeed, L2TxFeedConfig}; use sequencer::http::{self, ApiConfig}; use sequencer::ingress::inclusion_lane::{ @@ -193,6 +195,49 @@ fn v1_regression_domain_fields_all_affect_recovery() { ); } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_state_returns_safe_only_projection_over_http() { + let db = temp_db("get-state-http"); + let domain = test_domain(); + let sender = Address::repeat_byte(0x11); + bootstrap_open_frame_with_deposits(db.path.as_str(), &[(sender, U256::from(10_u64))]); + + let Some(runtime) = start_api_only_server(db.path.as_str(), domain, 4 * 1024, 8).await else { + return; + }; + + let (status, body) = get_raw(runtime.addr, "/get_state").await; + shutdown_runtime(runtime).await; + + assert_eq!(status, 200, "get_state response: {body}"); + let parsed: serde_json::Value = serde_json::from_str(&body).expect("parse get_state JSON"); + assert_eq!(parsed["safe_block"], 1); + assert_eq!( + parsed["state"], "{\"balances\":{},\"nonces\":{}}", + "soft-confirmed tip deposits must not appear in safe-only export" + ); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_state_is_unavailable_before_safe_head_is_observed() { + let db = temp_db("get-state-no-safe-head"); + let domain = test_domain(); + let _storage = Storage::open(db.path.as_str()).expect("open storage"); + + let Some(runtime) = start_api_only_server(db.path.as_str(), domain, 4 * 1024, 8).await else { + return; + }; + + let (status, body) = get_raw(runtime.addr, "/get_state").await; + shutdown_runtime(runtime).await; + + assert_eq!(status, 503, "get_state without safe head: {body}"); + assert!( + body.contains("safe L1 head has not been observed yet"), + "expected safe-head unavailable message, got: {body}" + ); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn e2e_submit_tx_ack_and_broadcast() { let db = temp_db("full-e2e"); @@ -1138,6 +1183,11 @@ async fn start_full_server_with_max_body( MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, + Arc::new(StateSnapshotter::new( + db_path.to_string(), + WalletApp::new(WalletConfig::default()), + Address::from([0xff; 20]), + )), ApiConfig { max_body_bytes, ..ApiConfig::default() @@ -1192,6 +1242,11 @@ async fn start_api_only_server( MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, + Arc::new(StateSnapshotter::new( + db_path.to_string(), + WalletApp::new(WalletConfig::default()), + Address::from([0xff; 20]), + )), ApiConfig { max_body_bytes, ..ApiConfig::default() @@ -1416,6 +1471,26 @@ async fn post_raw_body_no_content_type(addr: std::net::SocketAddr, body: &str) - parse_http_response(response.as_slice()) } +async fn get_raw(addr: std::net::SocketAddr, path: &str) -> (u16, String) { + let host_port = addr.to_string(); + let mut stream = tokio::net::TcpStream::connect(host_port.as_str()) + .await + .expect("connect test http socket"); + let request = format!("GET {path} HTTP/1.1\r\nHost: {host_port}\r\nConnection: close\r\n\r\n"); + stream + .write_all(request.as_bytes()) + .await + .expect("write raw request"); + stream.flush().await.expect("flush raw request"); + + let mut response = Vec::new(); + stream + .read_to_end(&mut response) + .await + .expect("read raw response"); + parse_http_response(response.as_slice()) +} + async fn post_raw_json(addr: std::net::SocketAddr, body: &str) -> (u16, String) { let host_port = addr.to_string(); let mut stream = tokio::net::TcpStream::connect(host_port.as_str()) diff --git a/sequencer/tests/ws_broadcaster.rs b/sequencer/tests/ws_broadcaster.rs index c6a6c6d..329d7fb 100644 --- a/sequencer/tests/ws_broadcaster.rs +++ b/sequencer/tests/ws_broadcaster.rs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) use std::io::ErrorKind; +use std::sync::Arc; use std::time::{Duration, SystemTime}; use alloy_primitives::{Address, Signature}; use alloy_sol_types::Eip712Domain; -use app_core::application::MAX_METHOD_PAYLOAD_BYTES; +use app_core::application::{MAX_METHOD_PAYLOAD_BYTES, WalletApp, WalletConfig}; use futures_util::{SinkExt, StreamExt}; +use sequencer::egress::app_state::StateSnapshotter; use sequencer::egress::l2_tx_feed::{L2TxFeed, L2TxFeedConfig}; use sequencer::http::{self, ApiConfig, WS_CATCHUP_WINDOW_EXCEEDED_REASON}; use sequencer::ingress::inclusion_lane::{PendingUserOp, SequencerError}; @@ -459,6 +461,11 @@ async fn start_test_server_with_limits( MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, + Arc::new(StateSnapshotter::new( + db_path.to_string(), + WalletApp::new(WalletConfig::default()), + Address::from([0xff; 20]), + )), ApiConfig { ws_max_subscribers, ws_max_catchup_events, diff --git a/tests/e2e/Cargo.toml b/tests/e2e/Cargo.toml index b227077..98a9308 100644 --- a/tests/e2e/Cargo.toml +++ b/tests/e2e/Cargo.toml @@ -19,4 +19,6 @@ alloy-sol-types = "1.4.1" futures = "0.3" libtest-mimic = "0.6.1" ssz = { package = "ethereum_ssz", version = "0.10" } -tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time", "net"] } +tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time", "net", "process", "io-util"] } +serde_json = "1.0" +tempfile = "3.10" diff --git a/tests/e2e/src/lib.rs b/tests/e2e/src/lib.rs index 879df69..5b72017 100644 --- a/tests/e2e/src/lib.rs +++ b/tests/e2e/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) pub mod test_cases; +mod watchdog_compare; use std::future::Future; use std::pin::Pin; diff --git a/tests/e2e/src/main.rs b/tests/e2e/src/main.rs index 7b3c903..6b16c9f 100644 --- a/tests/e2e/src/main.rs +++ b/tests/e2e/src/main.rs @@ -3,7 +3,9 @@ use libtest_mimic::{Arguments, Trial}; use rollups_e2e::run_trial; -use rollups_harness::{ManagedSequencer, default_devnet_sequencer_config}; +use rollups_harness::{ + ManagedSequencer, default_devnet_sequencer_config, devnet_sequencer_config_no_faketime, +}; fn main() { let mut args = Arguments::from_args(); @@ -14,10 +16,13 @@ fn main() { .map(|(name, scenario)| { Trial::test(name, move || { let log_prefix = format!("rollups-e2e-{name}"); + let spawn_config = if name == "watchdog_genesis_compare_test" { + devnet_sequencer_config_no_faketime(log_prefix) + } else { + default_devnet_sequencer_config(log_prefix) + }; run_trial(name, || async move { - let mut runtime = - ManagedSequencer::spawn(default_devnet_sequencer_config(log_prefix)) - .await?; + let mut runtime = ManagedSequencer::spawn(spawn_config).await?; let scenario_result = scenario(&mut runtime).await; // Post-test schema invariants: assert the DB's structural // invariants only if the scenario succeeded — otherwise diff --git a/tests/e2e/src/test_cases.rs b/tests/e2e/src/test_cases.rs index af3a41b..549a5bb 100644 --- a/tests/e2e/src/test_cases.rs +++ b/tests/e2e/src/test_cases.rs @@ -305,6 +305,11 @@ pub fn test_cases() -> Vec<(&'static str, ScenarioFn)> { ) }, ), + ("watchdog_genesis_compare_test", |runtime| { + Box::pin(crate::watchdog_compare::run_watchdog_genesis_compare_test( + runtime, + )) + }), ] } diff --git a/tests/e2e/src/watchdog_compare.rs b/tests/e2e/src/watchdog_compare.rs new file mode 100644 index 0000000..a7e63ef --- /dev/null +++ b/tests/e2e/src/watchdog_compare.rs @@ -0,0 +1,194 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Watchdog compare harness: Anvil + devnet sequencer + live CM inspect. + +use std::path::Path; +use std::process::Stdio; +use std::time::Duration; + +use rollups_harness::ManagedSequencer; +use rollups_harness::paths; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::process::Command; + +use crate::ScenarioResult; + +const MACHINE_IMAGE: &str = "examples/canonical-app/out/canonical-machine-image"; +const EMPTY_WALLET_STATE: &str = "{\"balances\":{},\"nonces\":{}}"; +const GENESIS_SAFE_BLOCK: &str = "0"; + +pub async fn run_watchdog_genesis_compare_test( + runtime: &mut ManagedSequencer, +) -> ScenarioResult<()> { + if std::env::var("RUN_WATCHDOG_E2E").ok().as_deref() != Some("1") { + eprintln!("skipping watchdog_genesis_compare_test: set RUN_WATCHDOG_E2E=1 to enable"); + return Ok(()); + } + + let workspace = paths::workspace_root(); + let machine_image = workspace.join(MACHINE_IMAGE); + if !machine_image.is_dir() { + return Err(format!( + "canonical machine image missing at {}; run: just canonical-build-machine-image", + machine_image.display() + ) + .into()); + } + + eprintln!("[watchdog-harness] step 1/5: wait for sequencer GET /get_state (safe head)"); + let get_state_url = format!("{}/get_state", runtime.endpoint()); + let (_status, body) = + wait_for_get_state(get_state_url.as_str(), Duration::from_secs(30)).await?; + let parsed: serde_json::Value = serde_json::from_str(&body) + .map_err(|err| format!("invalid /get_state JSON: {err}; body={body}"))?; + let sequencer_state = parsed["state"] + .as_str() + .ok_or("get_state response missing state field")?; + let safe_block = parsed["safe_block"] + .as_u64() + .ok_or("get_state response missing safe_block field")?; + eprintln!("[watchdog-harness] sequencer safe_block={safe_block} state={sequencer_state}"); + if sequencer_state != EMPTY_WALLET_STATE { + return Err(format!( + "expected genesis-empty wallet state {EMPTY_WALLET_STATE}, got {sequencer_state}" + ) + .into()); + } + + eprintln!("[watchdog-harness] step 2/5: prove CM inspect JSON on genesis image"); + let inspect_state = + prove_cm_inspect_genesis(workspace.as_path(), machine_image.as_path()).await?; + if inspect_state != EMPTY_WALLET_STATE { + return Err( + format!("CM inspect expected {EMPTY_WALLET_STATE}, got {inspect_state}").into(), + ); + } + + eprintln!("[watchdog-harness] step 3/5: prepare watchdog checkpoint dir"); + let checkpoint_dir = tempfile::tempdir() + .map_err(|err| format!("temp checkpoint dir: {err}"))? + .keep(); + + eprintln!("[watchdog-harness] step 4/5: run Lua compare-mode pass"); + let lua_deps = workspace.join(".deps/lua"); + let compare_status = Command::new("lua") + .current_dir(workspace.as_path()) + .arg("watchdog/tests/run_compare_once.lua") + .env("WATCHDOG_LUA_DEPS", lua_deps.as_os_str()) + .env("WATCHDOG_MODE", "compare") + .env("WATCHDOG_SEQUENCER_URL", runtime.endpoint()) + .env("WATCHDOG_L1_RPC_URL", runtime.l1_endpoint()) + .env( + "WATCHDOG_INPUTBOX_ADDRESS", + runtime.input_box_address().to_string(), + ) + .env("WATCHDOG_APP_ADDRESS", runtime.app_address().to_string()) + .env("WATCHDOG_CHECKPOINT_DIR", checkpoint_dir.as_os_str()) + .env("WATCHDOG_CM_SNAPSHOT_DIR", machine_image.as_os_str()) + .env("WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK", GENESIS_SAFE_BLOCK) + .env("WATCHDOG_CM_EXECUTABLE", "cartesi-machine") + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .await + .map_err(|err| format!("failed to run watchdog compare lua: {err}"))?; + if !compare_status.success() { + return Err(format!("watchdog compare lua exited with {}", compare_status).into()); + } + + eprintln!("[watchdog-harness] step 5/5: compare pass completed successfully"); + Ok(()) +} + +async fn prove_cm_inspect_genesis( + workspace: &Path, + machine_image: &Path, +) -> ScenarioResult { + let work_dir = tempfile::tempdir().map_err(|err| format!("temp cm work dir: {err}"))?; + let query_path = work_dir.path().join("inspect-query.bin"); + let report_path = work_dir.path().join("inspect-report-0.bin"); + std::fs::write(query_path.as_path(), b"state") + .map_err(|err| format!("write inspect query: {err}"))?; + + let status = Command::new("cartesi-machine") + .current_dir(workspace) + .arg("--no-rollback") + .arg(format!("--load={},sharing:none", machine_image.display())) + .arg(format!( + "--cmio-inspect-state=query:{},report:{}", + query_path.display(), + report_path.display() + )) + .arg("--quiet") + .status() + .await + .map_err(|err| format!("cartesi-machine inspect failed to start: {err}"))?; + if !status.success() { + return Err(format!("cartesi-machine inspect exited with {status}").into()); + } + + let report = std::fs::read_to_string(report_path.as_path()) + .map_err(|err| format!("read inspect report: {err}"))?; + if report.contains("inspect endpoint not implemented") { + return Err( + "CM dapp is stale (inspect not implemented); rebuild with just canonical-build-machine-image" + .into(), + ); + } + Ok(report) +} + +async fn http_get(url: &str) -> std::io::Result<(u16, String)> { + let remainder = url.strip_prefix("http://").ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "only http:// supported in harness", + ) + })?; + let (host_port, path) = match remainder.split_once('/') { + Some((host_port, path)) => (host_port.to_string(), format!("/{path}")), + None => (remainder.to_string(), "/".to_string()), + }; + + let mut stream = TcpStream::connect(host_port.as_str()).await?; + let request = format!("GET {path} HTTP/1.1\r\nHost: {host_port}\r\nConnection: close\r\n\r\n"); + stream.write_all(request.as_bytes()).await?; + stream.flush().await?; + + let mut raw = Vec::new(); + stream.read_to_end(&mut raw).await?; + let text = String::from_utf8(raw).map_err(std::io::Error::other)?; + let (headers, body) = text + .split_once("\r\n\r\n") + .ok_or_else(|| std::io::Error::other("missing HTTP body"))?; + let status = headers + .lines() + .next() + .and_then(|line| line.split_whitespace().nth(1)) + .and_then(|code| code.parse().ok()) + .unwrap_or(500); + Ok((status, body.to_string())) +} + +async fn wait_for_get_state(url: &str, deadline: Duration) -> ScenarioResult<(u16, String)> { + let started = std::time::Instant::now(); + let mut last = String::new(); + while started.elapsed() < deadline { + match http_get(url).await { + Ok((200, body)) => return Ok((200, body)), + Ok((503, body)) => { + last = body; + } + Ok((status, body)) => { + return Err(format!("GET /get_state returned HTTP {status}: {body}").into()); + } + Err(err) => { + last = err.to_string(); + } + } + tokio::time::sleep(Duration::from_millis(200)).await; + } + Err(format!("timed out waiting for GET /get_state 200; last response: {last}").into()) +} diff --git a/tests/harness/src/lib.rs b/tests/harness/src/lib.rs index a528461..d7dcbc0 100644 --- a/tests/harness/src/lib.rs +++ b/tests/harness/src/lib.rs @@ -18,6 +18,7 @@ pub use rollups::{DEVNET_CHAIN_ID, DevnetRollupsStack}; pub use sequencer::{ BatchCounts, DEFAULT_DEVNET_SEQUENCER_BIN, DEFAULT_TEST_LOGS_DIR, ManagedSequencer, ManagedSequencerConfig, RespawnAttemptOutcome, RespawnPolicy, default_devnet_sequencer_config, + devnet_sequencer_config_no_faketime, }; pub use wallet::{ TestSigner, WalletL1Client, WalletL2Client, address_from_signing_key, sign_user_op_hex, diff --git a/tests/harness/src/paths.rs b/tests/harness/src/paths.rs index 5098069..e0de961 100644 --- a/tests/harness/src/paths.rs +++ b/tests/harness/src/paths.rs @@ -38,3 +38,30 @@ pub fn mock_erc20_artifact_path() -> PathBuf { pub fn devnet_machine_image_path() -> PathBuf { workspace_root().join(DEFAULT_DEVNET_MACHINE_IMAGE_PATH) } + +const DEVNET_SEQUENCER_BIN: &str = "sequencer-devnet"; + +/// Resolve the `sequencer-devnet` binary built for the current Cargo invocation. +/// +/// Prefers `CARGO_TARGET_DIR` (set by `cargo run` / `cargo test` in sandboxes and +/// custom target dirs) over the workspace `target/debug/` tree, which may be stale +/// when builds only run through Cargo with a redirected target directory. +pub fn resolve_devnet_sequencer_bin() -> PathBuf { + if let Ok(path) = std::env::var("CARGO_BIN_EXE_SEQUENCER_DEVNET") { + let path = PathBuf::from(path); + if path.exists() { + return path; + } + } + if let Ok(target) = std::env::var("CARGO_TARGET_DIR") { + let path = PathBuf::from(target) + .join("debug") + .join(DEVNET_SEQUENCER_BIN); + if path.exists() { + return path; + } + } + workspace_root() + .join("target/debug") + .join(DEVNET_SEQUENCER_BIN) +} diff --git a/tests/harness/src/sequencer.rs b/tests/harness/src/sequencer.rs index f1e5f09..7f402df 100644 --- a/tests/harness/src/sequencer.rs +++ b/tests/harness/src/sequencer.rs @@ -33,6 +33,9 @@ pub struct ManagedSequencerConfig { pub sequencer_bin: PathBuf, pub log_prefix: String, pub logs_dir: PathBuf, + /// When false, the child runs without libfaketime (for tests that never + /// manipulate wall clock). Requires libfaketime in PATH when true. + pub faketime: bool, } /// Snapshot of the `batches` table. Returned by @@ -95,13 +98,12 @@ pub struct ManagedSequencer { /// When `None`, defaults to `DEVNET_CHAIN_ID` (matches Anvil). Set to /// a non-matching value to test chain-id-mismatch failure modes chain_id_override: Option, - /// Path to the file libfaketime re-reads for its offset, on every time - /// call (combined with `FAKETIME_NO_CACHE=1`). Writing to this file - /// shifts the sequencer's view of `SystemTime::now()` / `Instant::now()` - /// immediately — no respawn needed. - faketime_rc_path: PathBuf, - /// Cached libfaketime dylib/so path (computed once on spawn). - libfaketime_path: PathBuf, + /// Cached libfaketime dylib/so path (computed once on spawn). `None` when + /// [`ManagedSequencerConfig::faketime`] is false. + libfaketime_path: Option, + /// Path to the file libfaketime re-reads for its offset. `None` when + /// faketime is disabled. + faketime_rc_path: Option, /// Internal cumulative forward-offset tracker for /// [`Self::advance_wall_and_mine`]. Not touched by /// [`Self::set_faketime_offset`]. @@ -110,16 +112,31 @@ pub struct ManagedSequencer { pub fn default_devnet_sequencer_config(log_prefix: impl Into) -> ManagedSequencerConfig { ManagedSequencerConfig { - sequencer_bin: PathBuf::from(DEFAULT_DEVNET_SEQUENCER_BIN), + sequencer_bin: paths::resolve_devnet_sequencer_bin(), log_prefix: log_prefix.into(), logs_dir: PathBuf::from(DEFAULT_TEST_LOGS_DIR), + faketime: true, + } +} + +/// Devnet config without libfaketime (watchdog compare and other wall-clock-neutral tests). +pub fn devnet_sequencer_config_no_faketime( + log_prefix: impl Into, +) -> ManagedSequencerConfig { + ManagedSequencerConfig { + faketime: false, + ..default_devnet_sequencer_config(log_prefix) } } impl ManagedSequencer { pub async fn spawn(config: ManagedSequencerConfig) -> HarnessResult { let logs_dir = paths::resolve_from_workspace(&config.logs_dir); - let sequencer_bin = paths::resolve_from_workspace(&config.sequencer_bin); + let sequencer_bin = if config.sequencer_bin.is_absolute() { + config.sequencer_bin.clone() + } else { + paths::resolve_from_workspace(&config.sequencer_bin) + }; let log_prefix = config.log_prefix; let rollups = DevnetRollupsStack::spawn(log_prefix.as_str(), logs_dir.as_path()).await?; @@ -128,14 +145,15 @@ impl ManagedSequencer { .map_err(|err| io_other(format!("failed to create temp data dir: {err}")))?; let data_dir_path = data_dir.path().to_path_buf(); - // Set up faketime: locate libfaketime + create the rc file. Initial - // content `+0` means no offset; tests can overwrite with a new offset - // at any time and the running sequencer will see it on its next - // `SystemTime::now()` / `Instant::now()` call (FAKETIME_NO_CACHE=1). - let libfaketime_path = find_libfaketime()?; - let faketime_rc_path = data_dir_path.join("faketime.rc"); - fs::write(faketime_rc_path.as_path(), "+0\n") - .map_err(|err| io_other(format!("create faketime rc file: {err}")))?; + let (libfaketime_path, faketime_rc_path) = if config.faketime { + let libfaketime_path = find_libfaketime()?; + let faketime_rc_path = data_dir_path.join("faketime.rc"); + fs::write(faketime_rc_path.as_path(), "+0\n") + .map_err(|err| io_other(format!("create faketime rc file: {err}")))?; + (Some(libfaketime_path), Some(faketime_rc_path)) + } else { + (None, None) + }; let SpawnedSequencerProcess { child, @@ -149,8 +167,8 @@ impl ManagedSequencer { &rollups, None, None, - libfaketime_path.as_path(), - faketime_rc_path.as_path(), + libfaketime_path.as_deref(), + faketime_rc_path.as_deref(), ) .await?; @@ -210,8 +228,12 @@ impl ManagedSequencer { /// Replaces any cumulative advance tracked by /// [`Self::advance_wall_and_mine`], and resets its counter. pub fn set_faketime_offset(&mut self, offset: Option) -> HarnessResult<()> { + let rc = self + .faketime_rc_path + .as_ref() + .ok_or_else(|| io_other("faketime is disabled for this ManagedSequencer"))?; let s = offset.as_deref().unwrap_or("+0"); - fs::write(self.faketime_rc_path.as_path(), format!("{s}\n")) + fs::write(rc.as_path(), format!("{s}\n")) .map_err(|err| io_other(format!("write faketime rc file: {err}")))?; self.cumulative_offset_secs = 0; Ok(()) @@ -462,6 +484,10 @@ impl ManagedSequencer { self.rollups.app_address() } + pub fn input_box_address(&self) -> Address { + self.rollups.input_box_address() + } + pub fn erc20_portal_address(&self) -> Address { self.rollups.erc20_portal_address() } @@ -522,11 +548,12 @@ impl ManagedSequencer { let blocks = secs / SECONDS_PER_BLOCK; self.mine_l1_blocks(blocks).await?; self.cumulative_offset_secs = self.cumulative_offset_secs.saturating_add(secs); - fs::write( - self.faketime_rc_path.as_path(), - format!("+{}s\n", self.cumulative_offset_secs), - ) - .map_err(|err| io_other(format!("write faketime rc file: {err}")))?; + let rc = self + .faketime_rc_path + .as_ref() + .ok_or_else(|| io_other("faketime is disabled for this ManagedSequencer"))?; + fs::write(rc.as_path(), format!("+{}s\n", self.cumulative_offset_secs)) + .map_err(|err| io_other(format!("write faketime rc file: {err}")))?; Ok(()) } @@ -682,8 +709,8 @@ impl ManagedSequencer { &self.rollups, self.l1_endpoint_override.as_deref(), self.chain_id_override, - self.libfaketime_path.as_path(), - self.faketime_rc_path.as_path(), + self.libfaketime_path.as_deref(), + self.faketime_rc_path.as_deref(), ) .await?; self.child = child; @@ -772,8 +799,8 @@ async fn spawn_sequencer_process( rollups: &DevnetRollupsStack, l1_endpoint_override: Option<&str>, chain_id_override: Option, - libfaketime_path: &Path, - faketime_rc_path: &Path, + libfaketime_path: Option<&Path>, + faketime_rc_path: Option<&Path>, ) -> HarnessResult { let (endpoint, http_addr) = build_local_endpoint()?; let log_path = timestamped_log_path(logs_dir, log_prefix); @@ -796,7 +823,9 @@ async fn spawn_sequencer_process( // `Instant::now()` call thanks to FAKETIME_NO_CACHE=1, so tests can // shift the clock dynamically during a run. let mut cmd = Command::new(path_as_str(sequencer_bin)?); - apply_faketime_env(&mut cmd, libfaketime_path, faketime_rc_path)?; + if let (Some(lib), Some(rc)) = (libfaketime_path, faketime_rc_path) { + apply_faketime_env(&mut cmd, lib, rc)?; + } let chain_id = chain_id_override.unwrap_or(DEVNET_CHAIN_ID); let mut child = cmd diff --git a/watchdog/http.lua b/watchdog/http.lua index ae8f1ae..b821a03 100644 --- a/watchdog/http.lua +++ b/watchdog/http.lua @@ -3,6 +3,91 @@ local http = {} +local function shell_quote(value) + return "'" .. string.gsub(value, "'", "'\\''") .. "'" +end + +--- HTTP client backed by the `curl` binary (for dev shells without lua-curl). +function http.new_cli() + local client = {} + + local function run_curl(method, url, body, headers) + local cmd = { + "curl", + "-sS", + "-w", + "%{http_code}", + "-X", + method, + } + if body then + table.insert(cmd, "--data-binary") + table.insert(cmd, shell_quote(body)) + end + table.insert(cmd, shell_quote(url)) + for key, value in pairs(headers or {}) do + table.insert(cmd, "-H") + table.insert(cmd, shell_quote(key .. ": " .. value)) + end + + local shell_cmd = table.concat(cmd, " ") .. " 2>&1" + local handle = io.popen(shell_cmd, "r") + if not handle then + return nil, "failed to start curl" + end + local output = handle:read("*a") + local ok_exit, exit_type, exit_code = handle:close() + if not output or output == "" then + return nil, "empty curl response" + end + local status_line = output:match("(%d%d%d)$") + if not status_line then + return nil, string.format( + "curl response missing status trailer (exit=%s/%s): %s", + tostring(exit_type), + tostring(exit_code), + output + ) + end + local body_text = output:sub(1, #output - #status_line) + local status = tonumber(status_line) + if not ok_exit and status == 0 then + return nil, string.format("curl failed: %s", output) + end + return { + status = status, + body = body_text, + headers = {}, + } + end + + function client.post(_self, url, body, headers) + return run_curl("POST", url, body, headers) + end + + function client.get(_self, url, headers) + return run_curl("GET", url, nil, headers) + end + + return client +end + +function http.new_auto() + local ok = pcall(function() + require("cURL") + end) + if ok then + return http.new_curl() + end + ok = pcall(function() + require("lcurl") + end) + if ok then + return http.new_curl() + end + return http.new_cli() +end + function http.new_curl() local ok, curl = pcall(require, "cURL") if not ok then diff --git a/watchdog/machine_cli.lua b/watchdog/machine_cli.lua index 0ef8bd9..ac73a36 100644 --- a/watchdog/machine_cli.lua +++ b/watchdog/machine_cli.lua @@ -5,6 +5,8 @@ local machine = require("watchdog.machine") local machine_cli = {} +local STATE_INSPECT_QUERY = "state" + local function shell_quote(value) value = tostring(value) return "'" .. value:gsub("'", "'\\''") .. "'" @@ -28,6 +30,16 @@ local function write_all(path, data) return true end +local function read_all(path) + local file, err = io.open(path, "rb") + if not file then + return nil, err + end + local data = file:read("*a") + file:close() + return data +end + local function run_command(command) local ok = os.execute(command) if ok == true or ok == 0 then @@ -90,11 +102,50 @@ function machine_cli.new(opts) return true end - function binding.inspect_state(_instance) - -- Inspect requires rollback semantics around the loaded snapshot. The - -- advance-only watchdog mode does not call this; compare mode remains - -- deferred until the canonical app inspect contract exists. - return nil, "CM inspect is not implemented for the CLI adapter yet" + function binding.inspect_state(instance) + assert(type(instance) == "table", "machine instance is required") + + local query_path = instance.work_dir .. "/inspect-query.bin" + local report_path = instance.work_dir .. "/inspect-report-0.bin" + + local ok, err = write_all(query_path, STATE_INSPECT_QUERY) + if not ok then + return nil, err + end + + local command_parts = { + shell_quote(executable), + "--no-rollback", + "--load=" .. shell_quote(instance.source_snapshot_dir) .. ",sharing:none", + } + if instance.input_count > 0 then + table.insert( + command_parts, + "--cmio-advance-state=input:" + .. shell_quote(instance.input_dir .. "/input-%i.bin") + .. ",input_index_begin:0,input_index_end:" + .. tostring(instance.input_count) + ) + end + table.insert( + command_parts, + "--cmio-inspect-state=query:" + .. shell_quote(query_path) + .. ",report:" + .. shell_quote(instance.work_dir .. "/inspect-report-%o.bin") + ) + table.insert(command_parts, "--quiet") + + ok, err = run_command(table.concat(command_parts, " ")) + if not ok then + return nil, err + end + + local report, read_err = read_all(report_path) + if not report then + return nil, "failed to read inspect report: " .. tostring(read_err) + end + return report end function binding.save_snapshot(instance, snapshot_dir) @@ -119,6 +170,7 @@ end machine_cli._private = { shell_quote = shell_quote, + STATE_INSPECT_QUERY = STATE_INSPECT_QUERY, } return machine_cli diff --git a/watchdog/main.lua b/watchdog/main.lua index 4096177..2c93625 100644 --- a/watchdog/main.lua +++ b/watchdog/main.lua @@ -35,7 +35,7 @@ end local function main() local cfg = config.load() - local http = http_mod.new_curl() + local http = http_mod.new_auto() local json = load_json() local deps = { http = http, diff --git a/watchdog/runner.lua b/watchdog/runner.lua index 87cd8d0..6fc60bf 100644 --- a/watchdog/runner.lua +++ b/watchdog/runner.lua @@ -53,6 +53,12 @@ local function fetch_inputs(cfg, deps, from_block, to_block) }) end +local function step(deps, message) + if deps and type(deps.log_step) == "function" then + deps.log_step(message) + end +end + local function send_alarm(cfg, deps, payload) if deps.alarm then return deps.alarm(payload) @@ -82,7 +88,9 @@ function runner.run_once(cfg, deps) local sequencer = require_dep(deps, "sequencer") local machine = require_dep(deps, "machine") + step(deps, "load checkpoint or bootstrap CM snapshot") local loaded = load_checkpoint(cfg, checkpoint_mod) + step(deps, "fetch sequencer GET /get_state") local sequencer_state, state_err = sequencer:get_state() if not sequencer_state then return nil, state_err @@ -90,6 +98,11 @@ function runner.run_once(cfg, deps) local safe_block_prev = loaded.safe_block or 0 local safe_block_next = sequencer_state.safe_block + step(deps, string.format( + "check safe_block monotonicity (prev=%s next=%s)", + tostring(safe_block_prev), + tostring(safe_block_next) + )) if safe_block_next < safe_block_prev then local payload = { kind = "safe_block_regressed", @@ -100,26 +113,35 @@ function runner.run_once(cfg, deps) return nil, payload end + step(deps, string.format( + "fetch L1 InputAdded logs for blocks %s..%s", + tostring(safe_block_prev + 1), + tostring(safe_block_next) + )) local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) if not inputs then return nil, input_err end + step(deps, "load CM snapshot directory") local instance, load_err = machine:load(loaded.snapshot_dir) if not instance then return nil, load_err end + step(deps, string.format("feed %d decoded inputs into CM", #inputs)) local fed, feed_err = machine:feed_inputs(instance, inputs) if not fed then return nil, feed_err end + step(deps, "run CM inspect (state query)") local cm_state, inspect_err = machine:inspect_state(instance) if not cm_state then return nil, inspect_err end + step(deps, "compare sequencer state bytes against CM inspect report") local equal, mismatch_offset = compare.raw_equal(sequencer_state.state, cm_state) if not equal then local payload = { @@ -133,6 +155,7 @@ function runner.run_once(cfg, deps) end if safe_block_next > safe_block_prev then + step(deps, "persist new manifest-backed checkpoint") local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) return machine:save(instance, snapshot_dir) end, { @@ -142,8 +165,11 @@ function runner.run_once(cfg, deps) if not written then return nil, write_err end + else + step(deps, "safe_block unchanged; skip checkpoint write") end + step(deps, "compare pass complete") return { ok = true, previous_safe_block = safe_block_prev, @@ -157,12 +183,19 @@ function runner.advance_checkpoint_once(cfg, deps) local checkpoint_mod = deps.checkpoint or checkpoint local machine = require_dep(deps, "machine") + step(deps, "load checkpoint or bootstrap CM snapshot") local loaded = load_checkpoint(cfg, checkpoint_mod) local safe_block_prev = loaded.safe_block or 0 + step(deps, "resolve target safe block") local safe_block_next, safe_err = target_safe_block(cfg, deps) if not safe_block_next then return nil, safe_err end + step(deps, string.format( + "check safe_block monotonicity (prev=%s next=%s)", + tostring(safe_block_prev), + tostring(safe_block_next) + )) if safe_block_next < safe_block_prev then return nil, { kind = "safe_block_regressed", @@ -171,22 +204,30 @@ function runner.advance_checkpoint_once(cfg, deps) } end + step(deps, string.format( + "fetch L1 InputAdded logs for blocks %s..%s", + tostring(safe_block_prev + 1), + tostring(safe_block_next) + )) local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) if not inputs then return nil, input_err end + step(deps, "load CM snapshot directory") local instance, load_err = machine:load(loaded.snapshot_dir) if not instance then return nil, load_err end + step(deps, string.format("feed %d decoded inputs into CM", #inputs)) local fed, feed_err = machine:feed_inputs(instance, inputs) if not fed then return nil, feed_err end if safe_block_next > safe_block_prev then + step(deps, "persist new manifest-backed checkpoint") local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) return machine:save(instance, snapshot_dir) end, { @@ -196,8 +237,11 @@ function runner.advance_checkpoint_once(cfg, deps) if not written then return nil, write_err end + else + step(deps, "safe_block unchanged; skip checkpoint write") end + step(deps, "advance pass complete") return { ok = true, previous_safe_block = safe_block_prev, diff --git a/watchdog/tests/drill_divergence.lua b/watchdog/tests/drill_divergence.lua new file mode 100644 index 0000000..6e22e60 --- /dev/null +++ b/watchdog/tests/drill_divergence.lua @@ -0,0 +1,58 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) +-- +-- Staging drill: force a state_mismatch alarm through compare mode with a +-- deliberately wrong sequencer state (fake HTTP client). No CM required. +-- +-- Usage: +-- WATCHDOG_WEBHOOK_URL=https://example.com/hook lua watchdog/tests/drill_divergence.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local deps_lua = os.getenv("WATCHDOG_LUA_DEPS") +if deps_lua and deps_lua ~= "" then + package.cpath = deps_lua .. "/?.so;" .. package.cpath +end + +local alarm = require("watchdog.alarm") +local compare = require("watchdog.compare") +local http_mod = require("watchdog.http") +local log = dofile("watchdog/tests/e2e_log.lua") + +local webhook_url = os.getenv("WATCHDOG_WEBHOOK_URL") +if not webhook_url or webhook_url == "" then + io.stderr:write("WATCHDOG_WEBHOOK_URL is required\n") + os.exit(1) +end + +local sequencer_state = '{"balances":{"0x01":1},"nonces":{}}' +local cm_state = '{"balances":{},"nonces":{}}' +local equal, mismatch_offset = compare.raw_equal(sequencer_state, cm_state) +if equal then + log.fail("divergence-setup", "expected mismatch for drill") + os.exit(1) +end + +local payload = { + kind = "state_mismatch", + previous_safe_block = 0, + sequencer_safe_block = 0, + mismatch_offset = mismatch_offset, + run_id = "drill-forced-divergence", +} + +log.banner("divergence-webhook-drill") +log.step("divergence-webhook-drill", 1, 1, "POST state_mismatch payload (synthetic bytes)") + +local http = http_mod.new_auto() +local ok, err = alarm.send_webhook(http, webhook_url, payload) +if not ok then + log.fail("divergence-webhook-drill", err) + os.exit(1) +end + +log.pass( + "divergence-webhook-drill", + string.format("HTTP 2xx; mismatch_offset=%s", tostring(mismatch_offset)) +) +os.exit(0) diff --git a/watchdog/tests/drill_webhook.lua b/watchdog/tests/drill_webhook.lua new file mode 100644 index 0000000..b26a290 --- /dev/null +++ b/watchdog/tests/drill_webhook.lua @@ -0,0 +1,64 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) +-- +-- Staging/local drill: fire sample watchdog alarm payloads at WATCHDOG_WEBHOOK_URL. +-- Usage: +-- WATCHDOG_WEBHOOK_URL=https://example.com/hook lua watchdog/tests/drill_webhook.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local deps_lua = os.getenv("WATCHDOG_LUA_DEPS") +if deps_lua and deps_lua ~= "" then + package.cpath = deps_lua .. "/?.so;" .. package.cpath +end + +local alarm = require("watchdog.alarm") +local http_mod = require("watchdog.http") +local log = dofile("watchdog/tests/e2e_log.lua") + +local webhook_url = os.getenv("WATCHDOG_WEBHOOK_URL") +if not webhook_url or webhook_url == "" then + io.stderr:write("WATCHDOG_WEBHOOK_URL is required\n") + os.exit(1) +end + +local drills = { + { + name = "state_mismatch", + payload = { + kind = "state_mismatch", + previous_safe_block = 10, + sequencer_safe_block = 12, + mismatch_offset = 4, + run_id = "drill-state-mismatch", + }, + }, + { + name = "safe_block_regressed", + payload = { + kind = "safe_block_regressed", + previous_safe_block = 20, + sequencer_safe_block = 19, + run_id = "drill-safe-block-regressed", + }, + }, +} + +local http = http_mod.new_auto() +local failures = 0 + +log.banner("webhook-delivery-drills") +for index, drill in ipairs(drills) do + log.step("webhook-delivery-drills", index, #drills, "POST " .. drill.name .. " payload") + local ok, err = alarm.send_webhook(http, webhook_url, drill.payload) + if not ok then + failures = failures + 1 + log.fail(drill.name, err) + else + log.pass(drill.name, "HTTP 2xx from " .. webhook_url) + end +end + +if failures > 0 then + os.exit(1) +end diff --git a/watchdog/tests/e2e.lua b/watchdog/tests/e2e.lua new file mode 100644 index 0000000..7a799c1 --- /dev/null +++ b/watchdog/tests/e2e.lua @@ -0,0 +1,302 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) +-- +-- Real watchdog end-to-end checks against cartesi-machine (and optionally a +-- live sequencer). Run from repo root: +-- lua watchdog/tests/e2e.lua +-- or: +-- just test-watchdog-e2e + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local checkpoint = require("watchdog.checkpoint") +local machine_cli = require("watchdog.machine_cli") +local runner = require("watchdog.runner") +local log = dofile("watchdog/tests/e2e_log.lua") + +local MACHINE_IMAGE = "examples/canonical-app/out/canonical-machine-image" +local GENESIS_SAFE_BLOCK = 0 + +local scenarios = {} +local failures = 0 +local skips = 0 + +local function assert_true(value, message) + if not value then + error(message or "assertion failed", 2) + end +end + +local function command_exists(name) + local ok = os.execute("command -v " .. name .. " >/dev/null 2>&1") + return ok == true or ok == 0 +end + +local function path_is_dir(path) + local ok, err, code = os.rename(path, path) + if ok then + return true + end + if code == 13 then + return true + end + return false, err +end + +local function temp_dir(prefix) + local base = os.tmpname() + os.remove(base) + local dir = string.format("%s-%s", prefix, base:match("([^/]+)$")) + local ok = os.execute('mkdir -p "' .. dir .. '"') + if ok ~= true and ok ~= 0 then + error("mkdir failed for " .. dir) + end + return dir +end + +local function make_step_logger(scenario, total) + local index = 0 + return function(message) + index = index + 1 + log.step(scenario, index, total, message) + end +end + +local function run_scenario(name, fn) + log.banner(name) + local ok, result = pcall(fn) + if not ok then + failures = failures + 1 + log.fail(name, result) + return + end + if result == "skip" then + skips = skips + 1 + return + end + log.pass(name) +end + +local function skip(scenario, reason) + log.skip(scenario, reason) + return "skip" +end + +local function require_cartesi_machine(scenario) + if not command_exists("cartesi-machine") then + return skip(scenario, "cartesi-machine not on PATH (install via nix develop / Cartesi tools)") + end + return true +end + +local function require_machine_image(scenario) + if not path_is_dir(MACHINE_IMAGE) then + return skip( + scenario, + "canonical machine image missing at " + .. MACHINE_IMAGE + .. " (run: just canonical-build-machine-image)" + ) + end + return true +end + +local function advance_cfg(checkpoint_dir, target_safe_block) + return { + mode = "advance", + checkpoint_dir = checkpoint_dir, + cm_snapshot_dir = MACHINE_IMAGE, + cm_snapshot_safe_block = GENESIS_SAFE_BLOCK, + target_safe_block = target_safe_block, + cm_executable = "cartesi-machine", + cm_work_dir = temp_dir("watchdog-e2e-work"), + } +end + +table.insert(scenarios, { + name = "prerequisites", + fn = function() + local scenario = "prerequisites" + log.step(scenario, 1, 3, "check cartesi-machine is on PATH") + if not command_exists("cartesi-machine") then + error("cartesi-machine not on PATH") + end + log.step(scenario, 2, 3, "check canonical machine image directory exists") + if not path_is_dir(MACHINE_IMAGE) then + error("missing machine image at " .. MACHINE_IMAGE) + end + log.step(scenario, 3, 3, "record paths used by later scenarios") + log.info("machine image: " .. MACHINE_IMAGE) + log.info("genesis safe_block: " .. tostring(GENESIS_SAFE_BLOCK)) + end, +}) + +table.insert(scenarios, { + name = "advance-empty-range", + fn = function() + local scenario = "advance-empty-range" + if require_cartesi_machine(scenario) == "skip" then + return "skip" + end + if require_machine_image(scenario) == "skip" then + return "skip" + end + + local checkpoint_dir = temp_dir("watchdog-e2e-checkpoint") + local target_safe_block = 1 + log.step(scenario, 1, 5, "prepare temp checkpoint dir: " .. checkpoint_dir) + log.step(scenario, 2, 5, "bootstrap from genesis snapshot at safe_block=" .. GENESIS_SAFE_BLOCK) + log.step(scenario, 3, 5, "run advance mode with empty L1 input range 1.." .. target_safe_block) + local result, err = runner.advance_checkpoint_once(advance_cfg(checkpoint_dir, target_safe_block), { + machine = machine_cli.new({ + executable = "cartesi-machine", + work_dir = temp_dir("watchdog-e2e-advance-work"), + }), + log_step = make_step_logger(scenario .. "/runner", 8), + fetch_inputs = function(from_block, to_block) + assert_true(from_block == 1, "expected from_block=1") + assert_true(to_block == target_safe_block, "expected to_block=" .. target_safe_block) + return {} + end, + }) + assert_true(result, "advance failed: " .. tostring(err)) + assert_true(result.safe_block == target_safe_block, "unexpected safe_block") + assert_true(result.input_count == 0, "expected zero inputs") + + log.step(scenario, 4, 5, "verify manifest-backed checkpoint was written") + local current = checkpoint.load(checkpoint_dir) + assert_true(current, "checkpoint current.json missing after advance") + assert_true(current.safe_block == target_safe_block, "checkpoint safe_block mismatch") + + log.step(scenario, 5, 5, "verify snapshot directory exists under checkpoint") + assert_true(path_is_dir(current.snapshot_dir), "checkpoint snapshot dir missing") + log.info("wrote checkpoint safe_block=" .. tostring(current.safe_block)) + end, +}) + +table.insert(scenarios, { + name = "cm-inspect-state-query", + fn = function() + local scenario = "cm-inspect-state-query" + if require_cartesi_machine(scenario) == "skip" then + return "skip" + end + if require_machine_image(scenario) == "skip" then + return "skip" + end + + log.step(scenario, 1, 4, "create machine_cli adapter") + local work_dir = temp_dir("watchdog-e2e-inspect") + local machine = machine_cli.new({ + executable = "cartesi-machine", + work_dir = work_dir, + }) + + log.step(scenario, 2, 4, "load genesis snapshot from " .. MACHINE_IMAGE) + local instance = assert(machine:load(MACHINE_IMAGE), "load snapshot failed") + + log.step(scenario, 3, 4, "run --cmio-inspect-state with query=state (no new inputs)") + local report, inspect_err = machine:inspect_state(instance) + assert_true(report, "inspect failed: " .. tostring(inspect_err)) + + log.step(scenario, 4, 4, "validate inspect report looks like wallet JSON state") + if report:find("inspect endpoint not implemented", 1, true) then + return skip( + scenario, + "machine image dapp is stale; rebuild with: just canonical-build-machine-image" + ) + end + assert_true(report:sub(1, 1) == "{", "inspect report is not JSON object bytes: " .. report) + assert_true(report:find('"balances"', 1, true) ~= nil, "inspect report missing balances field") + log.info("inspect report bytes=" .. tostring(#report)) + end, +}) + +table.insert(scenarios, { + name = "compare-runner-with-sequencer", + fn = function() + local scenario = "compare-runner-with-sequencer" + local sequencer_url = os.getenv("WATCHDOG_E2E_SEQUENCER_URL") + if not sequencer_url or sequencer_url == "" then + return skip( + scenario, + "set WATCHDOG_E2E_SEQUENCER_URL to a live sequencer base URL to run this scenario" + ) + end + if require_cartesi_machine(scenario) == "skip" then + return "skip" + end + if require_machine_image(scenario) == "skip" then + return "skip" + end + + local http_mod = require("watchdog.http") + local jsonrpc = require("watchdog.jsonrpc") + local sequencer_mod = require("watchdog.sequencer") + local ok_json, cjson = pcall(require, "cjson") + if not ok_json then + return skip(scenario, "lua-cjson required for live sequencer compare") + end + + local checkpoint_dir = temp_dir("watchdog-e2e-compare") + log.step(scenario, 1, 2, "prepare compare-mode deps (sequencer=" .. sequencer_url .. ")") + log.step(scenario, 2, 2, "run compare runner against live sequencer + CM") + + local http = http_mod.new_auto() + local cfg = { + mode = "compare", + sequencer_url = sequencer_url, + checkpoint_dir = checkpoint_dir, + cm_snapshot_dir = MACHINE_IMAGE, + cm_snapshot_safe_block = GENESIS_SAFE_BLOCK, + cm_executable = "cartesi-machine", + cm_work_dir = temp_dir("watchdog-e2e-compare-work"), + l1_rpc_url = os.getenv("WATCHDOG_E2E_L1_RPC_URL") or "http://127.0.0.1:8545", + input_box_address = os.getenv("WATCHDOG_E2E_INPUTBOX_ADDRESS") + or "0x0000000000000000000000000000000000000000", + app_address = os.getenv("WATCHDOG_E2E_APP_ADDRESS") + or "0x1111111111111111111111111111111111111111", + long_block_range_error_codes = { "-32005" }, + } + + local step_no = 0 + local result, err = runner.run_once(cfg, { + http = http, + rpc = jsonrpc.new(http, cjson, cfg.l1_rpc_url), + sequencer = sequencer_mod.new(http, cjson, sequencer_url), + machine = machine_cli.new({ + executable = cfg.cm_executable, + work_dir = cfg.cm_work_dir, + }), + log_step = function(message) + step_no = step_no + 1 + log.step(scenario .. "/runner", step_no, 12, message) + end, + }) + + assert_true(result, "compare run failed: " .. tostring(err)) + log.info(string.format( + "compare ok: safe_block=%s input_count=%s", + tostring(result.safe_block), + tostring(result.input_count) + )) + end, +}) + +log.info("starting watchdog real end-to-end suite (" .. #scenarios .. " scenarios)") +for _, scenario in ipairs(scenarios) do + run_scenario(scenario.name, scenario.fn) +end + +io.write("\n[watchdog-e2e] ───────────────────────────────────────────────────────\n") +io.write(string.format( + "[watchdog-e2e] SUMMARY: %d passed, %d skipped, %d failed (of %d scenarios)\n", + #scenarios - failures - skips, + skips, + failures, + #scenarios +)) + +if failures > 0 then + os.exit(1) +end diff --git a/watchdog/tests/e2e_log.lua b/watchdog/tests/e2e_log.lua new file mode 100644 index 0000000..1e3525a --- /dev/null +++ b/watchdog/tests/e2e_log.lua @@ -0,0 +1,45 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local e2e_log = {} + +function e2e_log.banner(title) + io.write("\n[watchdog-e2e] ═══════════════════════════════════════════════════════\n") + io.write("[watchdog-e2e] SCENARIO: " .. tostring(title) .. "\n") + io.write("[watchdog-e2e] ═══════════════════════════════════════════════════════\n") + io.flush() +end + +function e2e_log.step(scenario, index, total, message) + io.write(string.format( + "[watchdog-e2e] [%s] step %02d/%02d: %s\n", + tostring(scenario), + index, + total, + tostring(message) + )) + io.flush() +end + +function e2e_log.info(message) + io.write("[watchdog-e2e] INFO: " .. tostring(message) .. "\n") + io.flush() +end + +function e2e_log.skip(scenario, reason) + io.write(string.format("[watchdog-e2e] [%s] SKIP: %s\n", tostring(scenario), tostring(reason))) + io.flush() +end + +function e2e_log.pass(scenario, detail) + local suffix = detail and (": " .. tostring(detail)) or "" + io.write(string.format("[watchdog-e2e] [%s] PASS%s\n", tostring(scenario), suffix)) + io.flush() +end + +function e2e_log.fail(scenario, err) + io.write(string.format("[watchdog-e2e] [%s] FAIL: %s\n", tostring(scenario), tostring(err))) + io.flush() +end + +return e2e_log diff --git a/watchdog/tests/run.lua b/watchdog/tests/run.lua index 3b392f7..3df26aa 100644 --- a/watchdog/tests/run.lua +++ b/watchdog/tests/run.lua @@ -430,6 +430,31 @@ test("machine cli adapter writes raw input files", function() file:close() end) +test("machine cli adapter inspect reads report file", function() + local base = os.tmpname() + os.remove(base) + os.execute(string.format('mkdir -p "%s"', base)) + local script_path = base .. "/fake-cartesi-machine.sh" + local driver = machine_cli.new({ work_dir = base, executable = script_path }) + local instance = assert(driver:load("/tmp/source-snapshot")) + + local script = io.open(script_path, "wb") + assert(script ~= nil, "fake cartesi-machine script opened") + script:write(string.format([[ +#!/bin/sh +mkdir -p "%s" +printf '%%s' '{"ok":true}' > "%s/inspect-report-0.bin" +exit 0 +]], instance.work_dir, instance.work_dir)) + script:close() + os.execute(string.format('chmod +x "%s"', script_path)) + + local state, err = driver:inspect_state(instance) + + assert_eq(err, nil) + assert_eq(state, '{"ok":true}') +end) + test("machine cli adapter leaves snapshot directory creation to cartesi-machine", function() local base = os.tmpname() os.remove(base) diff --git a/watchdog/tests/run_compare_once.lua b/watchdog/tests/run_compare_once.lua new file mode 100644 index 0000000..d3ee518 --- /dev/null +++ b/watchdog/tests/run_compare_once.lua @@ -0,0 +1,68 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) +-- +-- Single compare-mode pass for harness-driven E2E. Expects WATCHDOG_* env vars +-- (see docs/watchdog/staging-drills.md). Exits 0 on match, 1 on failure. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local deps_lua = os.getenv("WATCHDOG_LUA_DEPS") +if deps_lua and deps_lua ~= "" then + package.cpath = deps_lua .. "/?.so;" .. package.cpath +end + +local config = require("watchdog.config") +local http_mod = require("watchdog.http") +local jsonrpc = require("watchdog.jsonrpc") +local machine_cli = require("watchdog.machine_cli") +local runner = require("watchdog.runner") +local sequencer_mod = require("watchdog.sequencer") + +local ok_json, cjson = pcall(require, "cjson") +if not ok_json then + io.stderr:write("lua-cjson is required\n") + os.exit(1) +end + +local function getenv_table() + return setmetatable({}, { + __index = function(_, key) + return os.getenv(key) + end, + }) +end + +local cfg = config.load(getenv_table()) +if cfg.mode ~= "compare" then + io.stderr:write("WATCHDOG_MODE must be compare\n") + os.exit(1) +end + +local http = http_mod.new_auto() +local deps = { + http = http, + rpc = jsonrpc.new(http, cjson, cfg.l1_rpc_url), + sequencer = sequencer_mod.new(http, cjson, cfg.sequencer_url), + machine = machine_cli.new({ + executable = cfg.cm_executable, + work_dir = cfg.cm_work_dir, + }), +} + +local result, err = runner.run_once(cfg, deps) +if not result then + io.stderr:write("watchdog compare failed: " .. tostring(err) .. "\n") + if type(err) == "table" then + for key, value in pairs(err) do + io.stderr:write(string.format(" %s: %s\n", tostring(key), tostring(value))) + end + end + os.exit(1) +end + +print(string.format( + "watchdog compare ok: safe_block=%s input_count=%s", + tostring(result.safe_block), + tostring(result.input_count) +)) +os.exit(0) From 181709f733fe1b797421c3844d4274eb844b4933 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Thu, 21 May 2026 22:53:12 +0800 Subject: [PATCH 07/13] ci: fix ci rust check --- .github/workflows/ci.yml | 6 +++--- examples/canonical-app/src/scheduler/core.rs | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6588308..22c3897 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ env: CARTESI_MACHINE_SHA256_ARM64: 787d823756000cdecd72da8a3494b4c08613087379035959e561bbaef7a220ba jobs: - checks: + rust: runs-on: ubuntu-latest timeout-minutes: 30 @@ -64,7 +64,7 @@ jobs: canonical-guest: runs-on: ubuntu-latest - needs: checks + needs: rust timeout-minutes: 45 steps: @@ -90,7 +90,7 @@ jobs: rollups-e2e: runs-on: ubuntu-latest - needs: checks + needs: rust timeout-minutes: 60 steps: diff --git a/examples/canonical-app/src/scheduler/core.rs b/examples/canonical-app/src/scheduler/core.rs index a10d78e..79f502b 100644 --- a/examples/canonical-app/src/scheduler/core.rs +++ b/examples/canonical-app/src/scheduler/core.rs @@ -1043,10 +1043,28 @@ mod tests { scheduler.process_input(direct_input(1, 7)), ProcessOutcome::DirectEnqueued ); - + // Inspect reflects executed app state, not the direct-input queue. let state = scheduler .inspect_state(STATE_INSPECT_QUERY) .expect("inspect state"); + assert_eq!(state, b"events:0"); + + let batch = Batch { + nonce: 0, + frames: vec![Frame { + user_ops: vec![], + safe_block: 1, + fee_price: 0, + }], + }; + assert_eq!( + scheduler.process_input(batch_input(2, batch)), + ProcessOutcome::BatchExecuted + ); + + let state = scheduler + .inspect_state(STATE_INSPECT_QUERY) + .expect("inspect state after drain"); assert_eq!(state, b"events:1"); } From feb842a819d7cee5614f9224ff1ef3872d588220 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:18:21 +0800 Subject: [PATCH 08/13] feat(watchdog): compare via finalized_state, SSZ parity, operator docs Replace GET /get_state with finalized snapshot routes for operator compare. Unify wallet SSZ encoding across sequencer dumps, CM inspect, and watchdog; share L1 partition test vector with Rust. Restructure Lua modules (lcurl binding, machine_cartesi, sequencer_reader), add devnet-stack helper and production-like operator runbooks. --- .gitignore | 4 + AGENTS.md | 3 + CLAUDE.md | 2 + Cargo.lock | 1 + README.md | 4 + docs/watchdog/README.md | 196 +++++++--- docs/watchdog/getting-started.md | 220 +++++++++++ docs/watchdog/operator-deployment.md | 215 +++++++++++ docs/watchdog/sepolia.md | 5 + docs/watchdog/staging-drills.md | 24 +- examples/app-core/src/application/mod.rs | 4 + examples/app-core/src/application/wallet.rs | 206 ++++------ examples/app-core/src/lib.rs | 1 + examples/app-core/src/wallet_snapshot.rs | 160 ++++++++ examples/canonical-app/src/scheduler/core.rs | 9 +- examples/canonical-app/src/scheduler/mod.rs | 5 +- justfile | 16 +- scripts/watchdog-lua-deps.sh | 109 +++--- sequencer-core/src/application/mod.rs | 12 +- sequencer/src/egress/api/get_state.rs | 111 ------ sequencer/src/egress/api/mod.rs | 9 +- sequencer/src/egress/api/state.rs | 19 +- sequencer/src/egress/app_state.rs | 292 -------------- sequencer/src/egress/mod.rs | 1 - sequencer/src/http.rs | 10 - sequencer/src/l1/partition.rs | 215 ++++++++++- sequencer/src/runtime/workers.rs | 7 - sequencer/tests/e2e_sequencer.rs | 75 ---- sequencer/tests/ws_broadcaster.rs | 9 +- tests/e2e/Cargo.toml | 7 +- tests/e2e/src/bin/devnet_stack.rs | 73 ++++ tests/e2e/src/watchdog_compare.rs | 175 +++++++-- tests/fixtures/l1_partition_vector.json | 104 +++++ tests/fixtures/wallet_snapshot_v1_empty.bin | Bin 0 -> 76 bytes tests/fixtures/wallet_snapshot_v1_empty.hex | 1 + watchdog/README.md | 13 + watchdog/http.lua | 159 ++------ watchdog/json.lua | 12 + watchdog/{l1.lua => l1_reader.lua} | 26 +- watchdog/machine.lua | 43 --- watchdog/machine_cartesi.lua | 196 ++++++++++ watchdog/machine_cli.lua | 16 +- watchdog/machine_runner.lua | 87 +++++ watchdog/main.lua | 28 +- watchdog/runner.lua | 246 ++++++------ watchdog/sequencer.lua | 48 --- watchdog/sequencer_reader.lua | 100 +++++ watchdog/tests/drill_divergence.lua | 2 +- watchdog/tests/drill_webhook.lua | 12 +- watchdog/tests/e2e.lua | 24 +- watchdog/tests/run.lua | 268 ++++++++++--- watchdog/tests/run_compare_once.lua | 21 +- watchdog/third_party/json.lua | 379 +++++++++++++++++++ watchdog/third_party/lua-curl/UPSTREAM | 14 + 54 files changed, 2747 insertions(+), 1251 deletions(-) create mode 100644 docs/watchdog/getting-started.md create mode 100644 docs/watchdog/operator-deployment.md create mode 100644 docs/watchdog/sepolia.md create mode 100644 examples/app-core/src/wallet_snapshot.rs delete mode 100644 sequencer/src/egress/api/get_state.rs delete mode 100644 sequencer/src/egress/app_state.rs create mode 100644 tests/e2e/src/bin/devnet_stack.rs create mode 100644 tests/fixtures/l1_partition_vector.json create mode 100644 tests/fixtures/wallet_snapshot_v1_empty.bin create mode 100644 tests/fixtures/wallet_snapshot_v1_empty.hex create mode 100644 watchdog/README.md create mode 100644 watchdog/json.lua rename watchdog/{l1.lua => l1_reader.lua} (81%) delete mode 100644 watchdog/machine.lua create mode 100644 watchdog/machine_cartesi.lua create mode 100644 watchdog/machine_runner.lua delete mode 100644 watchdog/sequencer.lua create mode 100644 watchdog/sequencer_reader.lua create mode 100644 watchdog/third_party/json.lua create mode 100644 watchdog/third_party/lua-curl/UPSTREAM diff --git a/.gitignore b/.gitignore index e84d291..46d6cb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /target .deps/ +watchdog/third_party/lua-curl/* +!watchdog/third_party/lua-curl/UPSTREAM watchdog-e2e-*/ .env .env.fish @@ -12,3 +14,5 @@ examples/canonical-app/out/ .vscode/ soljson-latest.js **/states/ +__pycache__/ +/benchmarks/ diff --git a/AGENTS.md b/AGENTS.md index 41583d3..4be52e4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -343,4 +343,7 @@ Before finishing a change, ensure: - [`docs/threat-model/README.md`](docs/threat-model/README.md) — trust boundaries, in-scope and out-of-scope threats. - [`docs/recovery/README.md`](docs/recovery/README.md) — recovery design, TLA+ formal verification, design history. - [`docs/snapshots/`](docs/snapshots/) — app snapshots: [`format.md`](docs/snapshots/format.md) (dump trait + wire format) and [`lifecycle.md`](docs/snapshots/lifecycle.md) (take/promote/GC/lease design + crash-safety). +- [`docs/watchdog/operator-deployment.md`](docs/watchdog/operator-deployment.md) — production-like watchdog (Sepolia / mainnet; internal snapshot API). +- [`docs/watchdog/getting-started.md`](docs/watchdog/getting-started.md) — local dev: watchdog + `sequencer-devnet` on Anvil. +- [`docs/watchdog/README.md`](docs/watchdog/README.md) — watchdog architecture, compare vs advance modes, test commands. - [`sequencer-core/`](sequencer-core/) — shared domain types and protocol contracts. diff --git a/CLAUDE.md b/CLAUDE.md index 79065b2..aa9131e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,3 +56,5 @@ Rust edition 2024 / Axum API / SQLite (rusqlite, WAL) / EIP-712 signing / SSZ en - **[`docs/threat-model/README.md`](docs/threat-model/README.md)** — trust boundaries and in-scope threats. - **[`docs/recovery/README.md`](docs/recovery/README.md)** — preemptive recovery design + TLA+ proofs. - **[`docs/snapshots/lifecycle.md`](docs/snapshots/lifecycle.md)** — snapshot lifecycle design + invariants (take/promote/GC, crash-safety). Read before touching the inclusion lane's safe-frontier/snapshot path. +- **[`docs/watchdog/operator-deployment.md`](docs/watchdog/operator-deployment.md)** — watchdog on live L1 (Sepolia / mainnet, production-like). +- **[`docs/watchdog/getting-started.md`](docs/watchdog/getting-started.md)** — local dev: watchdog + `sequencer-devnet` on Anvil. diff --git a/Cargo.lock b/Cargo.lock index 04b118f..fad2b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3571,6 +3571,7 @@ dependencies = [ "serde_json", "tempfile", "tokio", + "tracing-subscriber", ] [[package]] diff --git a/README.md b/README.md index 78b1686..b45e736 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ released even on client disconnect. Related docs: - App snapshots (format + lifecycle): `docs/snapshots/` +- Watchdog — local dev: [`docs/watchdog/getting-started.md`](docs/watchdog/getting-started.md); Sepolia/mainnet: [`docs/watchdog/operator-deployment.md`](docs/watchdog/operator-deployment.md) ## Prototype Limits @@ -217,6 +218,9 @@ Some tests require [Foundry](https://getfoundry.sh) (`anvil` on PATH). They run - [`CLAUDE.md`](CLAUDE.md) — quick reference for shell setup and commands. - [`docs/threat-model/README.md`](docs/threat-model/README.md) — trust boundaries, in-scope and out-of-scope threats. - [`docs/recovery/README.md`](docs/recovery/README.md) — recovery design, TLA+ formal verification, design history. +- [`docs/watchdog/getting-started.md`](docs/watchdog/getting-started.md) — step-by-step: run the watchdog with a local sequencer. +- [`docs/watchdog/operator-deployment.md`](docs/watchdog/operator-deployment.md) — watchdog on live L1 (Sepolia staging, mainnet production). +- [`docs/watchdog/README.md`](docs/watchdog/README.md) — watchdog architecture, modules, and test commands. - [`sequencer-core/`](sequencer-core/) — shared domain types (`Application`, `SignedUserOp`, `Batch`, `Frame`). - [`examples/app-core/`](examples/app-core/) — placeholder wallet app implementing the `Application` trait. diff --git a/docs/watchdog/README.md b/docs/watchdog/README.md index 8fd44d6..27526f6 100644 --- a/docs/watchdog/README.md +++ b/docs/watchdog/README.md @@ -1,26 +1,101 @@ # Watchdog -The watchdog is an off-chain safety process that compares sequencer API state -against state produced by the canonical Cartesi Machine at an L1 safe block. +The watchdog is an off-chain safety process that compares the sequencer's +**finalized SSZ state dump** against state produced by the canonical Cartesi +Machine at the same L1 inclusion block. + +## Documentation + +| Doc | Audience | +|-----|----------| +| **[`operator-deployment.md`](operator-deployment.md)** | **Production-like** — Sepolia and mainnet: internal snapshot API, live L1, checkpoints (Sepolia = mainnet dress rehearsal) | +| **[`getting-started.md`](getting-started.md)** | **Local dev only** — Anvil + `sequencer-devnet`, harness smoke, two-terminal flow | +| This file | Architecture, modules, runtime contract, checkpoints, test commands | +| [`staging-drills.md`](staging-drills.md) | Webhook smoke, synthetic alarms, staging compare daemon | +| [`sepolia.md`](sepolia.md) | Redirect → [`operator-deployment.md`](operator-deployment.md) | + +### Quick start (pick your environment) + +**Sepolia / mainnet (operator):** [`operator-deployment.md`](operator-deployment.md) — shared checklist, internal URL, Sepolia CM image, mainnet notes. + +**Local devnet:** + +One-time setup, then either a single automated check or an interactive run: + +```bash +just setup && just canonical-build-machine-image && just watchdog-lua-deps + +# Path A — full smoke (Anvil + sequencer + CM + compare), one command: +just test-watchdog-compare-harness + +# Path B — two terminals: stack prints WATCHDOG_* exports, then run compare: +just devnet-for-watchdog # terminal 1 — leave running +# terminal 2: paste exports, then: +WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua +``` + +Details: **[`getting-started.md`](getting-started.md)**. + +## Host dependencies (`watchdog-lua-deps`) + +Compare mode and any test that hits HTTP need a native **`lcurl.so`** built into `.deps/lua/`. JSON is pure Lua (no compile step). + +```bash +just watchdog-lua-deps # idempotent; writes .deps/lua/lcurl.so +export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua" +``` + +You also need **`cartesi-machine`** on `PATH` (in-process `cartesi` Lua module) and **`lua`** (5.4 recommended). + +### System packages + +| OS | Packages | +|----|----------| +| Debian / Ubuntu / WSL | `libcurl4-openssl-dev` `liblua5.4-dev` `lua5.4` `build-essential` | +| Fedora | `libcurl-devel` `lua-devel` | +| Arch | `curl` `lua` | + +Verify before building: + +```bash +pkg-config --exists libcurl && echo "libcurl ok" +test -f /usr/include/lua5.4/lua.h && echo "lua headers ok" +``` + +On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/include/`. The repo script passes `LUA_INC` accordingly when invoking the vendored lua-cURL Makefile (`scripts/watchdog-lua-deps.sh`). + +### Troubleshooting `just watchdog-lua-deps` + +| Message / error | Fix | +|-----------------|-----| +| `install libcurl dev package` | `sudo apt-get install -y libcurl4-openssl-dev` (or distro equivalent), then rerun `just watchdog-lua-deps` | +| `install Lua headers` | `sudo apt-get install -y liblua5.4-dev` | +| `fatal error: lua.h: No such file or directory` | Install `liblua5.4-dev`. If headers are present but build still fails, ensure you are on a tree where `scripts/watchdog-lua-deps.sh` passes **`LUA_INC`** (not `LUA_INCLUDE_DIR`) to make — see script in repo | +| `built lcurl.so but lua cannot load it` | Lua version mismatch: build with the same `lua` you run (`lua -v` vs headers under `lua5.4`) | +| `need curl or wget` | Fetch tool to download pinned lua-cURL sources into `watchdog/third_party/lua-curl/` | + +CI runs **`just test-watchdog`** (mocked HTTP) only; it does **not** install libcurl or run `watchdog-lua-deps`. Full HTTP + compare smoke is local: `just test-watchdog-compare-harness` after deps above. ## V1 Shape The implementation lives in `watchdog/` and is intentionally split into small Lua modules: -- `http.lua`: HTTP adapter (`lua-curl` / `lcurl` when installed, otherwise `curl` CLI via `new_auto()`). +- `http.lua`: HTTP adapter via in-tree **lua-cURLv3** / `lcurl` (`just watchdog-lua-deps`). +- `json.lua` / `third_party/json.lua`: pure-Lua JSON (RPC + webhooks). - `jsonrpc.lua`: JSON-RPC request/response validation. -- `l1.lua`: partitioned `eth_getLogs` scanning and strict L1 log ordering. +- `l1_reader.lua`: partitioned `eth_getLogs` scanning and strict L1 log ordering. - `abi.lua`: decoding for the `InputAdded` / `EvmAdvance` envelope. -- `machine.lua`: narrow adapter boundary for Cartesi Machine bindings. -- `machine_cli.lua`: `cartesi-machine` CLI adapter for loading snapshot - directories, writing raw input files, advancing, inspecting, and saving snapshots. +- `machine_runner.lua`: CM driver (`load`, `advance`, `inspect`, `dump`). +- `machine_cartesi.lua`: in-process `cartesi` Lua module binding (production path). +- `machine_cli.lua`: legacy CLI adapter (tests only; prefer `machine_cartesi`). +- `sequencer_reader.lua`: sequencer HTTP client (`GET /finalized_state/inclusion_block`, `GET /finalized_state`). - `compare.lua`: raw byte comparison. - `checkpoint.lua`: manifest-backed checkpoint persistence. - `alarm.lua`: webhook alarm delivery. - `retry.lua`: bounded retry helper used by the runtime. -- `runner.lua`: one-shot orchestration across checkpoint load, sequencer poll, - L1 fetch, CM replay, raw compare, alarm, and checkpoint write. +- `runner.lua`: one-shot orchestration — cheap `/finalized_state/inclusion_block` + poll, optional full pass (L1 fetch, CM replay, SSZ compare, checkpoint write). - `main.lua`: compare or advance loop (daemon or `WATCHDOG_ONCE=1`). The L1 reader follows the Rust partition strategy from @@ -29,35 +104,27 @@ range is split recursively and retried. Lua decodes and validates input envelopes, but it does not classify payload tags. Direct input vs batch submission remains scheduler logic inside the canonical machine. -`l1.lua` has the `InputAdded(address,uint256,bytes)` event topic baked in and +`l1_reader.lua` has the `InputAdded(address,uint256,bytes)` event topic baked in and filters logs by `topic0 = InputAdded` and `topic1 = app address`, matching the Rust reader's app-filtered InputBox scan. ## Runtime Contract -The sequencer exposes `GET /get_state` for byte-exact state comparison. The -endpoint is generic over app state bytes, even though the toy wallet app -currently returns deterministic JSON: +The sequencer exposes operator-internal snapshot routes (see `sequencer/src/egress/api/snapshot.rs`): -```json -{ - "safe_block": 123, - "state": "{\"balances\":{},\"nonces\":{}}" -} -``` +- `GET /finalized_state/inclusion_block` — cheap JSON `{ inclusion_block, l2_tx_index }` polled every compare tick. +- `GET /finalized_state` — streams the finalized SSZ state file (`application/octet-stream`) with `X-Inclusion-Block` and `X-L2-Tx-Index` headers. -`state` must be the exact bytes produced by the bare-metal app serializer -for the app state anchored at `safe_block`. The watchdog compares those raw -bytes with the bytes returned by CM inspect. It must not canonicalize both -values before deciding pass/fail. +**Idle optimization (compare mode):** when `inclusion_block` has not advanced past the +checkpoint's `safe_block` (the last verified inclusion block), the runner returns +immediately — no `/finalized_state` download, no L1 `eth_getLogs`, no CM load/advance/inspect. -`get_state` reconstructs a safe-only app state by replaying the persisted -scheduler-accepted safe batch prefix into a fresh app instance. It intentionally -excludes the current soft-confirmed Tip and any valid closed batches that have -not been accepted by the L1 scheduler view yet. +The watchdog compares the finalized SSZ bytes with the bytes returned by CM +inspect. It must not canonicalize either side before deciding pass/fail. -The canonical scheduler answers `RollupRequest::Inspect` with query `state` by -calling `Application::export_state()` (see `examples/canonical-app`). +For the toy wallet app, SSZ encoding lives in `examples/app-core/src/wallet_snapshot.rs` +and is shared by `WalletApp::create_dump`, `Application::canonical_snapshot_bytes`, +and the canonical scheduler's `Inspect` handler (`examples/canonical-app`). ## Checkpoints @@ -73,9 +140,10 @@ checkpoint_dir/ manifest.json ``` -`manifest.json` records `safe_block`, timestamp, and optionally the CM image -hash. A new checkpoint directory is written first, then `current.json` is -atomically replaced to point at it. +`manifest.json` records `safe_block` (the L1 reference block the CM snapshot +covers — in compare mode this is the finalized `inclusion_block`), timestamp, +and optionally the CM image hash. A new checkpoint directory is written first, +then `current.json` is atomically replaced to point at it. When bootstrapping without an existing checkpoint, the operator provides both: @@ -94,14 +162,14 @@ poll the sequencer. It: 4. Feeds the raw InputBox input bytes into the CM adapter. 5. Saves a new snapshot directory and advances `current.json`. -`WATCHDOG_MODE=compare` replays safe L1 inputs into the CM, calls -`--cmio-inspect-state` with the `state` query, and compares the returned report -bytes against `GET /get_state`. +`WATCHDOG_MODE=compare` polls `/finalized_state/inclusion_block` first; when the +block advances, replays L1 inputs into the CM, inspects with query `state`, and +compares the SSZ report bytes against `GET /finalized_state`. Useful runtime knobs: -- `WATCHDOG_CM_EXECUTABLE`: Cartesi Machine executable, default `cartesi-machine`. -- `WATCHDOG_CM_WORK_DIR`: temporary directory for staged input files, default `/tmp`. +- `WATCHDOG_CM_EXECUTABLE` / `WATCHDOG_CM_WORK_DIR`: used only by `machine_cli.lua` + tests; production `main.lua` uses the in-process `cartesi` Lua module. - `WATCHDOG_RETRY_ATTEMPTS`: bounded retry attempts per run, default `3`. - `WATCHDOG_RETRY_DELAY_SEC`: delay between retry attempts, default `5`. - `WATCHDOG_TARGET_SAFE_BLOCK`: manual/test override for the target safe block. @@ -112,18 +180,17 @@ Useful runtime knobs: |---------|-------------------| | `just test-watchdog` | Lua unit tests (fake HTTP/RPC/CM; no live chain) | | `just test-watchdog-e2e` | Real CM: advance, inspect; optional live compare if `WATCHDOG_E2E_SEQUENCER_URL` set | -| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `GET /get_state` + CM inspect + Lua compare | +| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `/finalized_state` + CM inspect + Lua compare | | `just test-watchdog-webhook-drill` | Webhook delivery smoke (`WATCHDOG_WEBHOOK_URL` required) | -Prerequisites for CM-backed tests: +Prerequisites for CM-backed tests: see **[Host dependencies](#host-dependencies-watchdog-lua-deps)** above, then: ```bash just canonical-build-machine-image # once, if out/ image is missing -just watchdog-lua-deps # lua-cjson into .deps/lua (system pkg or gcc) +just watchdog-lua-deps +export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua" ``` -`cartesi-machine`, `lua`, and `curl` on PATH. `lua-curl` is optional (CLI fallback). - ### Lua unit tests ```bash @@ -156,9 +223,41 @@ image makes `cm-inspect-state-query` skip with `inspect endpoint not implemented just test-watchdog-compare-harness ``` -Spawns Anvil + rollups devnet + `sequencer-devnet`, proves CM inspect JSON at -genesis, then runs `watchdog/tests/run_compare_once.lua` in compare mode with -matching `WATCHDOG_*` addresses. Requires `RUN_WATCHDOG_E2E=1` (set by the recipe). +Spawns Anvil + rollups devnet + `sequencer-devnet`, proves CM inspect SSZ at +genesis matches `wallet_snapshot::encode(WalletConfig::devnet())` (same as +`tests/fixtures/wallet_snapshot_v1_empty.hex` only for Sepolia `default()`), then runs +`watchdog/tests/run_compare_once.lua` (in-process `cartesi` binding) in compare mode. +Requires `RUN_WATCHDOG_E2E=1` (set by the recipe). + +**Before first run (or after changing scheduler / SSZ / inspect code):** + +```bash +just watchdog-lua-deps +just canonical-build-machine-image # not only ensure-machine-image — rebuild when the guest changed +just test-watchdog-compare-harness +``` + +`ensure-machine-image` only checks that `examples/canonical-app/out/canonical-machine-image` +exists; it does **not** detect a stale guest. If you pulled SSZ/inspect changes, rebuild the image. + +### Troubleshooting `just test-watchdog-compare-harness` + +| Symptom | Likely cause | Fix | +|---------|----------------|-----| +| `install libcurl dev package` / `lua.h: No such file` | Missing host deps for `lcurl.so` | [Host dependencies](#host-dependencies-watchdog-lua-deps) | +| `could not determine which binary to run` | `rollups-e2e` crate has two bins | Use the just recipe, or `cargo run -p rollups-e2e --bin rollups-e2e -- …` | +| `invalid utf-8` / timeout on step 1 (older trees) | Harness treated SSZ body as UTF-8 | Update `tests/e2e/src/watchdog_compare.rs` (current tree decodes binary + chunked bodies) | +| `finalized_state bytes mismatch (len 87 vs expected 76)` | Wrong golden (Sepolia fixture vs devnet sequencer) and/or raw HTTP chunked framing | Harness expects **devnet** SSZ; `lcurl` decodes chunked responses automatically | +| `CM inspect bytes mismatch (len 27 vs expected 76)` | **Stale CM image** still returns JSON `{"balances":{},"nonces":{}}` from pre-SSZ inspect | `just canonical-build-machine-image` then rerun harness | +| `inspect endpoint not implemented` | Older guest without inspect handler | Same rebuild as above | +| Harness passes step 1–2 but Lua compare fails | `WATCHDOG_LUA_DEPS` or checkpoint/bootstrap | Set `export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"`; see [`getting-started.md`](getting-started.md) env table | + +Manual equivalent of the recipe: + +```bash +RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e --bin rollups-e2e -- \ + watchdog_genesis_compare_test --exact --nocapture +``` ### Staging / operator drills @@ -168,8 +267,9 @@ divergence POST, and manual compare env vars. ## Related sequencer tests ```bash -cargo test -p sequencer get_state -- --test-threads=1 +cargo test -p sequencer snapshot_endpoints -- --test-threads=1 +cargo test -p app-core wallet_snapshot -- --test-threads=1 ``` -HTTP integration for `GET /get_state` lives in `sequencer/tests/e2e_sequencer.rs`. -Storage/replay semantics are covered in `sequencer/src/egress/app_state.rs` unit tests. +HTTP integration for snapshot routes lives in `sequencer/tests/snapshot_endpoints.rs`. +SSZ golden bytes for the toy wallet live in `tests/fixtures/wallet_snapshot_v1_empty.{hex,bin}`. diff --git a/docs/watchdog/getting-started.md b/docs/watchdog/getting-started.md new file mode 100644 index 0000000..eff4ad2 --- /dev/null +++ b/docs/watchdog/getting-started.md @@ -0,0 +1,220 @@ +# Watchdog + sequencer: local development + +Step-by-step guide for running the watchdog alongside a **local** `sequencer-devnet` stack (Anvil + ephemeral ports). Use this for CI smoke tests and debugging the watchdog itself. + +**Running on Sepolia or mainnet?** That follows the same operator model as production — internal snapshot URL, live L1, persistent checkpoints, chain-specific CM image. See **[`operator-deployment.md`](operator-deployment.md)** (Sepolia is the usual dress rehearsal before mainnet). + +- Architecture and module map: [`README.md`](README.md) +- **Sepolia / mainnet (production-like):** [`operator-deployment.md`](operator-deployment.md) +- Webhook / staging drills: [`staging-drills.md`](staging-drills.md) +- Implementation: [`watchdog/`](../../watchdog/) (Lua) + +## Contents + +1. [What you are running](#what-you-are-running) +2. [Prerequisites](#prerequisites) +3. [Path A — Full automated smoke](#path-a--full-automated-smoke-recommended-first) +4. [Path B — Interactive (two terminals)](#path-b--interactive-sequencer--watchdog-two-terminals) +5. [Production-like deployments](#production-like-deployments-sepolia--mainnet) +6. [Environment reference](#environment-reference-compare-mode) +7. [Troubleshooting](#troubleshooting) +8. [Related commands](#related-commands) + +--- + +## What you are running + +| Process | Role | +|---------|------| +| **Anvil** | Local L1 with Cartesi rollups contracts pre-deployed (`just setup`) | +| **sequencer-devnet** | Off-chain sequencer (wallet app, batches, snapshot promotion) | +| **watchdog** (`WATCHDOG_MODE=compare`) | Polls `/finalized_state/inclusion_block`, replays L1 inputs in CM, compares SSZ to `/finalized_state` | + +The sequencer exposes (operator-internal, same HTTP listener today): + +- `GET /finalized_state/inclusion_block` — cheap cursor poll +- `GET /finalized_state` — SSZ state file when compare runs + +--- + +## Prerequisites + +From the repo root: + +1. **Rust** — `cargo` (edition 2024 workspace). + +2. **Nix / direnv (recommended)** — Foundry `anvil`, Cartesi tools, and consistent Lua headers: + + ```bash + eval "$(direnv export bash 2>/dev/null)" + ``` + + Without direnv you need on `PATH`: `anvil`, `lua`, `cartesi-machine`, and a C compiler for `lcurl`. + +3. **System packages for watchdog HTTP** — see [`README.md` — Host dependencies](README.md#host-dependencies-watchdog-lua-deps) (Debian/WSL: `libcurl4-openssl-dev`, `liblua5.4-dev`, `lua5.4`, then `just watchdog-lua-deps`). + +4. **Cartesi Machine** — `cartesi-machine` on `PATH` so the in-process `cartesi` Lua module loads (ships with Cartesi Machine install / nix shell). + +5. **One-time repo setup**: + + ```bash + just setup # Anvil state + contract artifacts + just canonical-build-machine-image # CM image (~minutes, needs cross toolchain) + just watchdog-lua-deps # builds .deps/lua/lcurl.so + ``` + +6. **Unit smoke (optional)**: + + ```bash + just test-watchdog + ``` + +--- + +## Path A — Full automated smoke (recommended first) + +Proves Anvil + devnet sequencer + CM inspect + Lua compare in one command: + +```bash +just test-watchdog-compare-harness +``` + +This builds `sequencer-devnet`, spawns the stack, waits for `GET /finalized_state`, compares genesis **devnet** SSZ to the CM inspect bytes, and runs one Lua compare pass. Expect exit code 0. + +**First time or after scheduler/SSZ changes:** run `just watchdog-lua-deps` and `just canonical-build-machine-image` before the harness (see [compare harness troubleshooting](README.md#troubleshooting-just-test-watchdog-compare-harness)). + +--- + +## Path B — Interactive: sequencer + watchdog (two terminals) + +### Terminal 1 — Devnet stack (Anvil + sequencer) + +```bash +just devnet-for-watchdog +``` + +This starts Anvil and `sequencer-devnet` on **ephemeral local ports** (not fixed 8545/3000) and prints a block of `export WATCHDOG_*=...` lines. **Copy those exports** into Terminal 2. + +Leave Terminal 1 running until you are done; Ctrl+C stops Anvil and the sequencer. + +### Wait for finalized snapshot + +Compare mode needs a **finalized** SSZ dump. Right after boot, the cheap endpoint may return **404** until the sequencer has promoted a snapshot. + +In another shell (use the printed `WATCHDOG_SEQUENCER_URL`): + +```bash +curl -s "$WATCHDOG_SEQUENCER_URL/finalized_state/inclusion_block" +``` + +When you see JSON like `{"inclusion_block":0,"l2_tx_index":0}` (numbers may differ), the watchdog can compare. If it stays 404 for a long time, check sequencer logs in `tests/logs/` and that L1 is mining (devnet Anvil auto-mines by default). + +Optional — inspect SSZ size: + +```bash +curl -s -D - "$WATCHDOG_SEQUENCER_URL/finalized_state" -o /tmp/finalized-state.bin +head -c 32 /tmp/finalized-state.bin | xxd +``` + +### Terminal 2 — Watchdog (compare mode) + +From repo root, after `just watchdog-lua-deps`: + +```bash +# Paste exports from Terminal 1, then: +export WATCHDOG_MODE=compare +export WATCHDOG_ONCE=1 # single pass; omit for daemon (poll every 30s) + +WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua +``` + +Success: exit **0**, stderr shows steps ending in `compare pass complete`. + +Daemon mode (production-like loop): + +```bash +export WATCHDOG_POLL_INTERVAL_SEC=30 +unset WATCHDOG_ONCE +WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua +``` + +When `inclusion_block` has not advanced since the last verified checkpoint, the runner **skips** L1/CM work (idle-cheap). + +### One-shot compare (same stack, no daemon) + +```bash +WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/run_compare_once.lua +``` + +Uses the same env vars as `main.lua` in compare mode. + +--- + +## Production-like deployments (Sepolia / mainnet) + +Local paths A–B do **not** apply to public L1. There is no `just devnet-for-watchdog` on Sepolia or mainnet. + +| Local devnet | Sepolia / mainnet | +|--------------|-------------------| +| You spawn Anvil + `sequencer-devnet` | Sequencer already run by ops | +| `canonical-machine-image` (devnet guest) | `canonical-machine-image-sepolia` (today); mainnet guest when released | +| Snapshot HTTP on localhost | **Internal** operator network only | +| Genesis bootstrap (`safe_block=0`) usual | Bootstrap must match **current** finalized `inclusion_block` | + +**Sepolia is the dress rehearsal for mainnet** — same checklist, alarms, checkpoint volume, and firewall rules; only chain IDs, RPC URLs, and contract addresses change. + +Full operator runbook: **[`operator-deployment.md`](operator-deployment.md)**. + +--- + +## Environment reference (compare mode) + +| Variable | Required | Description | +|----------|----------|-------------| +| `WATCHDOG_MODE` | yes | `compare` | +| `WATCHDOG_SEQUENCER_URL` | yes | e.g. `http://127.0.0.1:54321` | +| `WATCHDOG_L1_RPC_URL` | yes | L1 JSON-RPC | +| `WATCHDOG_INPUTBOX_ADDRESS` | yes | InputBox contract | +| `WATCHDOG_APP_ADDRESS` | yes | Rollup application contract | +| `WATCHDOG_CHECKPOINT_DIR` | yes | CM checkpoint storage | +| `WATCHDOG_CM_SNAPSHOT_DIR` | first run | Genesis CM image dir if no checkpoint yet | +| `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` | with above | Usually `0` on fresh devnet | +| `WATCHDOG_LUA_DEPS` | yes | `.deps/lua` after `just watchdog-lua-deps` | +| `WATCHDOG_ONCE` | no | `1` = one pass then exit | +| `WATCHDOG_POLL_INTERVAL_SEC` | no | Default `30` (daemon) | +| `WATCHDOG_WEBHOOK_URL` | no | Alarm POST on mismatch/regression | + +See `watchdog/config.lua` for the full list. + +--- + +## Troubleshooting + +| Symptom | What to check | +|---------|----------------| +| `install libcurl dev package` | Install `libcurl4-openssl-dev` (or distro equivalent); see [Host dependencies](README.md#host-dependencies-watchdog-lua-deps) | +| `lua.h: No such file or directory` when building lcurl | Install `liblua5.4-dev`; see [troubleshooting table](README.md#troubleshooting-just-watchdog-lua-deps) | +| `lcurl` / `cURL` not found at runtime | Run `just watchdog-lua-deps`, set `WATCHDOG_LUA_DEPS=.deps/lua` | +| `cartesi Lua module is required` | Install Cartesi Machine; use nix/direnv shell; ensure `cartesi-machine` on `PATH` | +| `inspect endpoint not implemented` | Rebuild CM image: `just canonical-build-machine-image` | +| CM inspect ~27 bytes / JSON in error | Stale image (old JSON inspect); rebuild: `just canonical-build-machine-image` | +| HTTP 404 on `/finalized_state/inclusion_block` | Sequencer not promoted yet; wait or drive L1 + batches | +| `state_mismatch` at genesis | Wrong `WATCHDOG_CM_SNAPSHOT_*` or stale CM image vs sequencer build | +| `inclusion_block_regressed` | Checkpoint ahead of sequencer (reset checkpoint dir or fix bootstrap block) | +| Compare harness skipped | Use `just test-watchdog-compare-harness` (sets `RUN_WATCHDOG_E2E=1` internally) | +| `could not determine which binary to run` | Use `just test-watchdog-compare-harness` (not bare `cargo run -p rollups-e2e`) | +| Harness `87 vs 76` or `27 vs 76` byte mismatch | Stale CM image and/or wrong fixture; see [harness troubleshooting](README.md#troubleshooting-just-test-watchdog-compare-harness) | + +Full harness failure table: **[`README.md` — Troubleshooting compare harness](README.md#troubleshooting-just-test-watchdog-compare-harness)**. + +--- + +## Related commands + +```bash +just test-watchdog # Lua unit tests (no live chain) +just test-watchdog-e2e # CM advance/inspect (optional live sequencer URL) +just test-watchdog-compare-harness # Full stack smoke +cargo test -p sequencer --test snapshot_endpoints +cargo test -p app-core wallet_snapshot +``` diff --git a/docs/watchdog/operator-deployment.md b/docs/watchdog/operator-deployment.md new file mode 100644 index 0000000..d3b2a42 --- /dev/null +++ b/docs/watchdog/operator-deployment.md @@ -0,0 +1,215 @@ +# Watchdog — operator deployment (Sepolia and mainnet) + +This is the **production-like** runbook for running the watchdog next to a **live** sequencer on a public L1 (Sepolia today, mainnet when deployed). + +**Sepolia is the dress rehearsal for mainnet.** The watchdog process, compare algorithm, internal snapshot API, checkpoint model, and network boundaries are the same. What changes per chain is: L1 RPC URL, deployed contract addresses, CM machine image build, wallet portal/token constants, and poll cadence. + +For **local development only** (Anvil + `sequencer-devnet`, CI smoke tests), use [`getting-started.md`](getting-started.md) instead. + +## Two deployment tiers + +```text + ┌─────────────────────────────────────┐ + Internet / users │ Public ingress (POST /tx, WS) │ ← benchmarks, wallets + └─────────────────┬───────────────────┘ + │ + ┌─────────────────▼───────────────────┐ + Operator network │ Sequencer process │ + │ + internal snapshot HTTP │ ← watchdog ONLY here + │ /finalized_state* │ + └─────────┬───────────────┬─────────┘ + │ │ + ┌─────────▼───┐ ┌───────▼────────┐ + │ L1 (Sepolia │ │ Watchdog host │ + │ or mainnet)│ │ (compare) │ + └─────────────┘ └────────────────┘ +``` + +The watchdog never substitutes for the sequencer. It reads **finalized SSZ** the sequencer already committed and independently replays L1 through the canonical CM. + +--- + +## Shared operator checklist (Sepolia and mainnet) + +Use this checklist for any live deployment. Chain-specific values are in the tables below. + +### 1. Network access + +- [ ] Watchdog host can reach **internal** `WATCHDOG_SEQUENCER_URL` (not only the public `/tx` URL). +- [ ] Watchdog host can reach **L1 JSON-RPC** with `eth_getLogs` (archive recommended if replaying long history). +- [ ] `/finalized_state` is **not** exposed on the public internet. + +Verify snapshot API before CM bootstrap: + +```bash +curl -sS -o /dev/null -w "%{http_code}\n" "$WATCHDOG_SEQUENCER_URL/finalized_state/inclusion_block" +# expect 200 when a finalized snapshot exists (404 = not promoted yet or wrong host) +``` + +### 2. Build watchdog runtime (once per host) + +```bash +eval "$(direnv export bash 2>/dev/null)" +just watchdog-lua-deps # .deps/lua/lcurl.so — needs libcurl + Lua dev headers; see README +``` + +Host packages and build errors: [`README.md` — Host dependencies](README.md#host-dependencies-watchdog-lua-deps). + +Requires: `lua`, `cartesi-machine` (in-process `cartesi` Lua module), libcurl + Lua headers. + +### 3. Build the CM image for **this chain** + +The RISC-V guest must use the same wallet/scheduler constants as the deployed app. + +| Chain | Command | Image directory | +|-------|---------|-----------------| +| **Sepolia** | `just canonical-build-machine-image-sepolia` | `examples/canonical-app/out/canonical-machine-image-sepolia` | +| **Local devnet** | `just canonical-build-machine-image` | `.../canonical-machine-image` (not for public L1) | +| **Mainnet** | *Ship a mainnet-targeted guest build when available* | Match production scheduler artifact | + +Today `WalletApp::default()` / `WalletConfig::sepolia()` align with Sepolia staging; mainnet production will need matching mainnet portal/token addresses in app-core before rebuilding the CM image. + +### 4. Collect deployment facts + +| Variable | Where it comes from | +|----------|---------------------| +| `WATCHDOG_MODE` | `compare` | +| `WATCHDOG_SEQUENCER_URL` | Ops: internal HTTP base (see network diagram) | +| `WATCHDOG_L1_RPC_URL` | Ops: chain RPC (archive for historical `getLogs`) | +| `WATCHDOG_APP_ADDRESS` | This rollup’s Cartesi **application** contract | +| `WATCHDOG_INPUTBOX_ADDRESS` | InputBox on that L1 ([Cartesi deployed contracts](https://docs.cartesi.io/cartesi-rollups/2.0/deployment/self-hosted.md)) | +| `WATCHDOG_CHECKPOINT_DIR` | Persistent volume on watchdog host | +| `WATCHDOG_CM_SNAPSHOT_DIR` | Bootstrap CM snapshot (first run only) | +| `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` | L1 block that bootstrap snapshot represents (= finalized `inclusion_block` at bootstrap) | +| `WATCHDOG_LUA_DEPS` | `.deps/lua` | +| `WATCHDOG_POLL_INTERVAL_SEC` | `120`–`300` on public L1 (finalized advances slowly) | +| `WATCHDOG_WEBHOOK_URL` | Optional alarm receiver | + +The sequencer discovers and pins `input_box_address` at startup; use the same values as `SEQ_ETH_RPC_URL` / `SEQ_APP_ADDRESS` configuration. + +### 5. Bootstrap checkpoint (first run on a live chain) + +On a long-lived deployment, **`WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK=0` is usually wrong** unless finalized state is still at genesis. + +Pick one: + +1. **Ops hands off** a CM snapshot directory + block number matching current finalized `inclusion_block`, or +2. **Watchdog reuses** `WATCHDOG_CHECKPOINT_DIR` from a prior successful compare on this deployment, or +3. **Replay from genesis** (only for new rollups / low block height — slow). + +After bootstrap, the watchdog advances its own checkpoint each successful compare. + +### 6. Run compare + +One shot (smoke): + +```bash +export WATCHDOG_ONCE=1 +lua watchdog/main.lua +``` + +Daemon (staging / production): + +```bash +unset WATCHDOG_ONCE +lua watchdog/main.lua +``` + +When `inclusion_block` ≤ last verified checkpoint, the runner only hits `/finalized_state/inclusion_block` and skips L1/CM work. + +--- + +## Sepolia (testnet staging) + +Use Sepolia to validate **the same procedure** you will run on mainnet: internal URLs, alarms, checkpoint persistence, RPC limits, CM image version pinning. + +### Sepolia-specific values + +| Item | Typical source | +|------|----------------| +| Chain ID | `11155111` | +| Public user ingress (tx demos only) | e.g. `https://eth-sepolia.rollups.cartesi.io/v2` — **may not** serve `/finalized_state` | +| Application instance | Per deployment (confirm with ops; demos have used `0x4CE633CA71071818cD73187765ee60F696dae083`) | +| InputBox (rollups v2.x on Sepolia) | Confirm on [deployed contracts](https://docs.cartesi.io/cartesi-rollups/2.0/deployment/self-hosted.md) (community examples use `0x58Df21fE097d4bE5dCf61e01d9ea3f6B81c2E1dB`) | +| CM image | `just canonical-build-machine-image-sepolia` | +| Tx / deposit demos | `tests/scripts/demo_sepolia.py` (copy `.env` locally; never commit secrets) | + +### Example env block (fill from ops) + +```bash +export WATCHDOG_MODE=compare +export WATCHDOG_SEQUENCER_URL="https://" +export WATCHDOG_L1_RPC_URL="https://" +export WATCHDOG_APP_ADDRESS="0x..." +export WATCHDOG_INPUTBOX_ADDRESS="0x..." +export WATCHDOG_CHECKPOINT_DIR="/var/lib/watchdog/checkpoints-sepolia" +export WATCHDOG_CM_SNAPSHOT_DIR="/path/to/canonical-machine-image-sepolia" +export WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK="" +export WATCHDOG_LUA_DEPS="/path/to/sequencer/.deps/lua" +export WATCHDOG_POLL_INTERVAL_SEC=120 +export WATCHDOG_WEBHOOK_URL="https://" +``` + +### Operating the Sepolia sequencer + +If your team runs the sequencer on Sepolia (not only the public endpoint): + +1. `sequencer` / release binary with Sepolia `SEQ_*` (chain id, app address, batch submitter key, L1 RPC). +2. Inclusion lane promotes finalized snapshots when L1 safe advances — required for `/finalized_state` 200. +3. Snapshot routes on an **internal** bind / port reachable by the watchdog host. +4. Sequencer binary built with **`WalletApp::new(WalletConfig::sepolia())`** (see `sequencer-devnet` vs production binary choice in your release pipeline). + +--- + +## Mainnet (production) + +When the rollup runs on Ethereum mainnet, **reuse the same operator checklist above**. Differences are operational scale, not watchdog logic: + +| Topic | Mainnet notes | +|-------|----------------| +| L1 RPC | Production-grade archive provider; rate limits matter for wide `getLogs` ranges | +| Contracts | Mainnet InputBox, application, portals from production deployment manifest | +| CM image | Build from production app/scheduler artifacts (mainnet wallet constants when defined in app-core) | +| `WATCHDOG_POLL_INTERVAL_SEC` | Often 300+; finalized promotion follows mainnet safe head | +| Security | Stricter firewall between public ingress and internal snapshot tier; secrets management for RPC and webhooks | +| Bootstrap | Almost always ops-provided CM snapshot or continued checkpoint dir — not genesis replay | + +There is no `just devnet-for-watchdog` or automated harness on mainnet; treat Sepolia compare success as the gate before mainnet go-live. + +--- + +## Compare mode behavior (all live chains) + +Same on Sepolia and mainnet: + +1. Load watchdog checkpoint (or bootstrap CM snapshot). +2. `GET /finalized_state/inclusion_block` — if unchanged, **stop** (cheap). +3. If advanced: `eth_getLogs` on InputBox for `(last_block+1)..inclusion_block`. +4. Advance CM incrementally; `inspect` → SSZ bytes. +5. `GET /finalized_state` → SSZ bytes. +6. Raw compare; alarm + non-zero exit on mismatch. +7. Write new CM checkpoint on success. + +Details: [`README.md`](README.md), [`docs/snapshots/lifecycle.md`](../snapshots/lifecycle.md). + +--- + +## Troubleshooting (live deployments) + +| Symptom | Likely cause | +|---------|----------------| +| `/finalized_state` missing on public URL | Wrong tier — use internal `WATCHDOG_SEQUENCER_URL` | +| `state_mismatch` | CM image / wallet constants ≠ sequencer build; or wrong bootstrap block | +| `inclusion_block_regressed` | Stale checkpoint dir vs sequencer finalized head | +| Slow or failing `getLogs` | RPC range limits — watchdog uses same partition strategy as sequencer | +| `inspect endpoint not implemented` | Rebuild CM image for the correct chain target | +| Works on Sepolia, fails on mainnet | Different deployment addresses or different guest build — do not reuse Sepolia env verbatim | + +--- + +## Related + +- **Local dev / CI:** [`getting-started.md`](getting-started.md) +- **Architecture:** [`README.md`](README.md) +- **Webhooks:** [`staging-drills.md`](staging-drills.md) +- **Public Sepolia latency demos** (not watchdog): [`docs/live-demo.md`](../live-demo.md) diff --git a/docs/watchdog/sepolia.md b/docs/watchdog/sepolia.md new file mode 100644 index 0000000..c3bf8fb --- /dev/null +++ b/docs/watchdog/sepolia.md @@ -0,0 +1,5 @@ +# Watchdog on Sepolia + +This page moved to **[`operator-deployment.md`](operator-deployment.md)**. + +Sepolia uses the same operator model as mainnet (internal snapshot API, live L1, per-deployment addresses, persistent checkpoints). That document is the production-like runbook; [`getting-started.md`](getting-started.md) remains for local Anvil + `sequencer-devnet` only. diff --git a/docs/watchdog/staging-drills.md b/docs/watchdog/staging-drills.md index 1ca7168..bd67765 100644 --- a/docs/watchdog/staging-drills.md +++ b/docs/watchdog/staging-drills.md @@ -1,15 +1,18 @@ # Watchdog Staging Drills -Operator drills for webhook delivery and divergence detection. Local harness -steps live in [`README.md`](README.md); this document covers staging and manual -verification. +Operator drills for webhook delivery and divergence detection. + +- **Sepolia / mainnet:** [`operator-deployment.md`](operator-deployment.md). **Local dev:** [`getting-started.md`](getting-started.md). +- Module map and local test recipes: [`README.md`](README.md). + +This document covers staging and manual verification beyond the devnet tutorial. ## Prerequisites - Built canonical machine image: `just canonical-build-machine-image` - `cartesi-machine`, `lua`, and `curl` on PATH -- `lua-cjson` (system package, or `just watchdog-lua-deps` copies/builds `.deps/lua/cjson.so` via `gcc` — no `make`) -- `lua-curl` optional — drills and compare harness fall back to `curl` CLI when absent +- `just watchdog-lua-deps` — builds `lcurl.so` into `.deps/lua` (libcurl + Lua headers on host) +- JSON is pure Lua (`watchdog/third_party/json.lua`); no cjson compile step - Staging or local sequencer reachable at `WATCHDOG_SEQUENCER_URL` - L1 RPC + InputBox + app addresses matching that deployment - Webhook receiver URL (Slack incoming webhook, PagerDuty, or `https://httpbin.org/post` for smoke tests) @@ -25,7 +28,7 @@ WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/drill_webhook.lua # or: just test-watchdog-webhook-drill ``` -Expected: HTTP 2xx for both `state_mismatch` and `safe_block_regressed` sample payloads. +Expected: HTTP 2xx for both `state_mismatch` and `inclusion_block_regressed` sample payloads. Check the receiver shows JSON with `"kind"` and `"run_id"` fields. ## Drill 2 — Divergence webhook (synthetic mismatch, no CM) @@ -43,14 +46,14 @@ Unit coverage: `just test-watchdog` (`runner alarms on raw state mismatch`). ## Drill 3 — Happy compare (local Anvil harness) -Full stack: Anvil + devnet rollups + sequencer + CM inspect + `GET /get_state`. +Full stack: Anvil + devnet rollups + sequencer + CM inspect + `GET /finalized_state`. ```bash just test-watchdog-compare-harness # equivalent: # just setup && just watchdog-lua-deps && just ensure-machine-image # cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e -# RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e -- watchdog_genesis_compare_test --exact +# RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e --bin rollups-e2e -- watchdog_genesis_compare_test --exact ``` Or run the Lua compare pass manually after starting a devnet sequencer yourself: @@ -68,7 +71,9 @@ export WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/run_compare_once.lua ``` -Expected: exit 0, stdout `watchdog compare ok: safe_block=... input_count=...`, and genesis wallet state `{"balances":{},"nonces":{}}` on both sides. +Expected: exit 0, stdout `watchdog compare ok: safe_block=... input_count=...`, and +byte-identical genesis SSZ state on sequencer `/finalized_state` and CM inspect +(`tests/fixtures/wallet_snapshot_v1_empty.hex`). ## Drill 4 — Production compare daemon @@ -93,3 +98,4 @@ On mismatch: non-zero exit, webhook fired, logs show `state_mismatch` and byte o | Webhook 4xx | Wrong URL or auth on receiver | | Compare skipped in Lua e2e | Set `WATCHDOG_E2E_SEQUENCER_URL` to a live sequencer | | Compare harness skipped | Set `RUN_WATCHDOG_E2E=1` (see `just test-watchdog-compare-harness`) | +| CM inspect 27 bytes / harness byte mismatch | Rebuild devnet image: `just canonical-build-machine-image` — see [`README.md`](README.md#troubleshooting-just-test-watchdog-compare-harness) | diff --git a/examples/app-core/src/application/mod.rs b/examples/app-core/src/application/mod.rs index 78d7cea..b1a9ee3 100644 --- a/examples/app-core/src/application/mod.rs +++ b/examples/app-core/src/application/mod.rs @@ -10,3 +10,7 @@ pub use anvil_accounts::default_private_keys; pub use method::{MAX_METHOD_PAYLOAD_BYTES, Method, Transfer, Withdrawal}; pub use notice::{DepositNotice, TransferNotice}; pub use wallet::{WalletApp, WalletConfig}; + +pub use crate::wallet_snapshot::{ + decode as decode_wallet_snapshot, encode as encode_wallet_snapshot, +}; diff --git a/examples/app-core/src/application/wallet.rs b/examples/app-core/src/application/wallet.rs index ee8f7d3..90e1af5 100644 --- a/examples/app-core/src/application/wallet.rs +++ b/examples/app-core/src/application/wallet.rs @@ -6,8 +6,7 @@ use std::io::Write; use std::path::{Path, PathBuf}; use alloy_primitives::{Address, U256, address}; -use ssz::{Decode, Encode}; -use ssz_derive::{Decode as SszDecode, Encode as SszEncode}; +use ssz::Decode; use tracing::{error, warn}; use types::alloy_sol_types::SolCall; use types::{Erc20Deposit, Erc20Transfer}; @@ -59,28 +58,6 @@ pub struct WalletApp { executed_input_count: u64, } -#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)] -struct SnapshotBalance { - address: [u8; 20], - balance_be: [u8; 32], -} - -#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)] -struct SnapshotNonce { - address: [u8; 20], - nonce: u32, -} - -#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)] -struct WalletSnapshotV1 { - erc20_portal_address: [u8; 20], - supported_erc20_token: [u8; 20], - sequencer_address: [u8; 20], - balances: Vec, - nonces: Vec, - executed_input_count: u64, -} - pub const SEPOLIA_ERC20_PORTAL_ADDRESS: Address = address!("0xACA6586A0Cf05bD831f2501E7B4aea550dA6562D"); pub const SEPOLIA_USDC_ADDRESS: Address = address!("0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"); @@ -92,14 +69,54 @@ pub const DEVNET_SEQUENCER_ADDRESS: Address = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); impl WalletApp { pub fn new(config: WalletConfig) -> Self { + Self::from_snapshot_parts(config, HashMap::new(), HashMap::new(), 0) + } + + pub(crate) fn from_snapshot_parts( + config: WalletConfig, + balances: HashMap, + nonces: HashMap, + executed_input_count: u64, + ) -> Self { Self { config, - balances: HashMap::new(), - nonces: HashMap::new(), - executed_input_count: 0, + balances, + nonces, + executed_input_count, } } + pub(crate) fn config(&self) -> &WalletConfig { + &self.config + } + + pub(crate) fn balances_iter(&self) -> impl Iterator { + self.balances.iter() + } + + pub(crate) fn nonces_iter(&self) -> impl Iterator { + self.nonces.iter() + } + + #[cfg(test)] + pub(crate) fn balances_mut(&mut self) -> &mut HashMap { + &mut self.balances + } + + #[cfg(test)] + pub(crate) fn nonces_mut(&mut self) -> &mut HashMap { + &mut self.nonces + } + + pub(crate) fn executed_input_count(&self) -> u64 { + self.executed_input_count + } + + #[cfg(test)] + pub(crate) fn set_executed_input_count(&mut self, count: u64) { + self.executed_input_count = count; + } + fn balance_of(&self, addr: &Address) -> U256 { *self.balances.get(addr).unwrap_or(&U256::ZERO) } @@ -138,93 +155,6 @@ impl WalletApp { Erc20Deposit::decode(&input.payload).map(Some) } - /// Serialize the wallet's logical state to deterministic SSZ bytes. - /// - /// Determinism is enforced by sorting `balances` and `nonces` by - /// address before encoding; `WalletApp` stores them in `HashMap`s - /// whose iteration order is non-deterministic. - fn snapshot_bytes(&self) -> Vec { - let mut balances: Vec<_> = self - .balances - .iter() - .map(|(address, balance)| SnapshotBalance { - address: address.into_array(), - balance_be: balance.to_be_bytes(), - }) - .collect(); - balances.sort_unstable_by_key(|entry| entry.address); - - let mut nonces: Vec<_> = self - .nonces - .iter() - .map(|(address, nonce)| SnapshotNonce { - address: address.into_array(), - nonce: *nonce, - }) - .collect(); - nonces.sort_unstable_by_key(|entry| entry.address); - - WalletSnapshotV1 { - erc20_portal_address: self.config.erc20_portal_address.into_array(), - supported_erc20_token: self.config.supported_erc20_token.into_array(), - sequencer_address: self.config.sequencer_address.into_array(), - balances, - nonces, - executed_input_count: self.executed_input_count, - } - .as_ssz_bytes() - } - - /// Decode SSZ-encoded snapshot bytes into a fresh `WalletApp`. - /// - /// Rejects snapshots containing duplicate addresses in `balances` or - /// `nonces`. Without this check, multiple distinct byte sequences - /// could decode to the same logical state (whichever entry came - /// last in the encoded list would win silently), breaking the - /// canonicality the watchdog relies on for byte-for-byte comparison. - fn from_snapshot_bytes(bytes: &[u8]) -> Result { - let decoded = WalletSnapshotV1::from_ssz_bytes(bytes).map_err(|e| AppError::Internal { - // ssz::DecodeError doesn't implement Display, so Debug is the - // only way to surface variant info as a string. If downstream - // needs to match on decode kinds programmatically, introduce a - // dedicated AppError variant that carries the typed error - // instead of fixing the format string. - reason: format!("snapshot decode failed: {e:?}"), - })?; - - let mut balances = HashMap::with_capacity(decoded.balances.len()); - for entry in decoded.balances { - let address = Address::from(entry.address); - let balance = U256::from_be_bytes(entry.balance_be); - if balances.insert(address, balance).is_some() { - return Err(AppError::Internal { - reason: format!("snapshot contains duplicate balance entry for {address}"), - }); - } - } - - let mut nonces = HashMap::with_capacity(decoded.nonces.len()); - for entry in decoded.nonces { - let address = Address::from(entry.address); - if nonces.insert(address, entry.nonce).is_some() { - return Err(AppError::Internal { - reason: format!("snapshot contains duplicate nonce entry for {address}"), - }); - } - } - - Ok(Self { - config: WalletConfig { - erc20_portal_address: Address::from(decoded.erc20_portal_address), - supported_erc20_token: Address::from(decoded.supported_erc20_token), - sequencer_address: Address::from(decoded.sequencer_address), - }, - balances, - nonces, - executed_input_count: decoded.executed_input_count, - }) - } - fn state_json(&self) -> String { let mut balances: Vec<_> = self .balances @@ -402,7 +332,11 @@ impl Application for WalletApp { fn from_dump(prefix: &Path) -> Result { let state_path = Self::state_file_in_dump(prefix); let bytes = std::fs::read(&state_path)?; - Self::from_snapshot_bytes(&bytes) + crate::wallet_snapshot::decode(&bytes) + } + + fn canonical_snapshot_bytes(&self) -> Result, AppError> { + Ok(crate::wallet_snapshot::encode(self)) } fn create_dump(&self, prefix: &Path) -> Result<(), AppError> { @@ -411,7 +345,7 @@ impl Application for WalletApp { // unique per call; a collision means a lane bug worth surfacing // loudly rather than silently overwriting prior state. std::fs::create_dir(prefix)?; - let bytes = self.snapshot_bytes(); + let bytes = crate::wallet_snapshot::encode(self); let state_path = Self::state_file_in_dump(prefix); let mut file = std::fs::File::create(&state_path)?; @@ -451,8 +385,10 @@ mod tests { use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; + use crate::wallet_snapshot::{SnapshotBalance, SnapshotNonce, WalletSnapshotV1}; use alloy_primitives::{Address, U256, address}; - use ssz_derive::{Decode, Encode}; + use ssz::Encode as SszEncodeTrait; + use ssz_derive::{Decode as SszDecode, Encode as SszEncode}; use types::ERC20_DEPOSIT_PREFIX_BYTES; use types::Erc20Transfer; use types::alloy_sol_types::SolCall; @@ -523,24 +459,24 @@ mod tests { assert_eq!(app.current_user_balance(recipient), U256::ZERO); } - #[derive(PartialEq, Debug, Encode, Decode, Clone)] + #[derive(PartialEq, Debug, SszEncode, SszDecode, Clone)] struct LegacyDeposit { amount: U256, to: Address, } - #[derive(PartialEq, Debug, Encode, Decode, Clone)] + #[derive(PartialEq, Debug, SszEncode, SszDecode, Clone)] struct LegacyWithdrawal { amount: U256, } - #[derive(PartialEq, Debug, Encode, Decode, Clone)] + #[derive(PartialEq, Debug, SszEncode, SszDecode, Clone)] struct LegacyTransfer { amount: U256, to: Address, } - #[derive(PartialEq, Debug, Encode, Decode, Clone)] + #[derive(PartialEq, Debug, SszEncode, SszDecode, Clone)] #[ssz(enum_behaviour = "union")] enum LegacyMethod { Withdrawal(LegacyWithdrawal), @@ -566,7 +502,7 @@ mod tests { let valid = ValidUserOp { sender, fee: 0, - data: ssz::Encode::as_ssz_bytes(&legacy), + data: SszEncodeTrait::as_ssz_bytes(&legacy), }; let outputs = app @@ -879,6 +815,16 @@ mod tests { assert_eq!(restored.executed_input_count, app.executed_input_count); } + #[test] + fn create_dump_state_file_matches_canonical_encode() { + let app = WalletApp::new(WalletConfig::default()); + let prefix = temp_dump_prefix(); + app.create_dump(&prefix).expect("create dump"); + let on_disk = std::fs::read(WalletApp::state_file_in_dump(&prefix)).expect("read state"); + WalletApp::delete_dump(&prefix).expect("cleanup"); + assert_eq!(on_disk, crate::wallet_snapshot::encode(&app)); + } + #[test] fn create_dump_produces_deterministic_bytes() { // Insert in scrambled order so the HashMap's non-deterministic @@ -946,16 +892,16 @@ mod tests { // The encoder never produces this, but the decoder must reject it // to keep snapshot bytes canonical: without this check, multiple // distinct byte sequences could decode to the same logical state. - let snapshot = super::WalletSnapshotV1 { + let snapshot = WalletSnapshotV1 { erc20_portal_address: [0; 20], supported_erc20_token: [0; 20], sequencer_address: [0; 20], balances: vec![ - super::SnapshotBalance { + SnapshotBalance { address: [1; 20], balance_be: [0; 32], }, - super::SnapshotBalance { + SnapshotBalance { address: [1; 20], balance_be: [0; 32], }, @@ -966,7 +912,7 @@ mod tests { let bytes = ssz::Encode::as_ssz_bytes(&snapshot); let err = - WalletApp::from_snapshot_bytes(&bytes).expect_err("duplicate balance should reject"); + crate::wallet_snapshot::decode(&bytes).expect_err("duplicate balance should reject"); match err { AppError::Internal { reason } => assert!( reason.contains("duplicate balance"), @@ -978,17 +924,17 @@ mod tests { #[test] fn from_snapshot_bytes_rejects_duplicate_nonce_addresses() { - let snapshot = super::WalletSnapshotV1 { + let snapshot = WalletSnapshotV1 { erc20_portal_address: [0; 20], supported_erc20_token: [0; 20], sequencer_address: [0; 20], balances: vec![], nonces: vec![ - super::SnapshotNonce { + SnapshotNonce { address: [1; 20], nonce: 0, }, - super::SnapshotNonce { + SnapshotNonce { address: [1; 20], nonce: 1, }, @@ -998,7 +944,7 @@ mod tests { let bytes = ssz::Encode::as_ssz_bytes(&snapshot); let err = - WalletApp::from_snapshot_bytes(&bytes).expect_err("duplicate nonce should reject"); + crate::wallet_snapshot::decode(&bytes).expect_err("duplicate nonce should reject"); match err { AppError::Internal { reason } => assert!( reason.contains("duplicate nonce"), diff --git a/examples/app-core/src/lib.rs b/examples/app-core/src/lib.rs index 90eb57f..7082663 100644 --- a/examples/app-core/src/lib.rs +++ b/examples/app-core/src/lib.rs @@ -2,3 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) pub mod application; +pub mod wallet_snapshot; diff --git a/examples/app-core/src/wallet_snapshot.rs b/examples/app-core/src/wallet_snapshot.rs new file mode 100644 index 0000000..4ec96f3 --- /dev/null +++ b/examples/app-core/src/wallet_snapshot.rs @@ -0,0 +1,160 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Canonical SSZ snapshot encoding for the toy wallet app. +//! +//! [`encode`] and [`decode`] are the single source of truth used by +//! [`WalletApp::create_dump`](crate::application::wallet::WalletApp::create_dump), +//! CM `inspect_state`, and the watchdog's `/finalized_state` byte compare. +//! +//! Golden bytes: `tests/fixtures/wallet_snapshot_v1_empty.{hex,bin}` (shared with +//! Rust and Lua parity tests). + +use std::collections::HashMap; + +use alloy_primitives::{Address, U256}; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode as SszDecode, Encode as SszEncode}; + +use crate::application::{WalletApp, WalletConfig}; +use sequencer_core::application::AppError; + +#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)] +pub struct SnapshotBalance { + pub address: [u8; 20], + pub balance_be: [u8; 32], +} + +#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)] +pub struct SnapshotNonce { + pub address: [u8; 20], + pub nonce: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)] +pub struct WalletSnapshotV1 { + pub erc20_portal_address: [u8; 20], + pub supported_erc20_token: [u8; 20], + pub sequencer_address: [u8; 20], + pub balances: Vec, + pub nonces: Vec, + pub executed_input_count: u64, +} + +/// Deterministic SSZ bytes for `app`'s logical state (sorted map entries). +pub fn encode(app: &WalletApp) -> Vec { + let mut balances: Vec<_> = app + .balances_iter() + .map(|(address, balance)| SnapshotBalance { + address: address.into_array(), + balance_be: balance.to_be_bytes(), + }) + .collect(); + balances.sort_unstable_by_key(|entry| entry.address); + + let mut nonces: Vec<_> = app + .nonces_iter() + .map(|(address, nonce)| SnapshotNonce { + address: address.into_array(), + nonce: *nonce, + }) + .collect(); + nonces.sort_unstable_by_key(|entry| entry.address); + + WalletSnapshotV1 { + erc20_portal_address: app.config().erc20_portal_address.into_array(), + supported_erc20_token: app.config().supported_erc20_token.into_array(), + sequencer_address: app.config().sequencer_address.into_array(), + balances, + nonces, + executed_input_count: app.executed_input_count(), + } + .as_ssz_bytes() +} + +/// Rehydrate a [`WalletApp`] from SSZ snapshot bytes. +pub fn decode(bytes: &[u8]) -> Result { + let decoded = WalletSnapshotV1::from_ssz_bytes(bytes).map_err(|e| AppError::Internal { + reason: format!("snapshot decode failed: {e:?}"), + })?; + + let mut balances = HashMap::with_capacity(decoded.balances.len()); + for entry in decoded.balances { + let address = Address::from(entry.address); + let balance = U256::from_be_bytes(entry.balance_be); + if balances.insert(address, balance).is_some() { + return Err(AppError::Internal { + reason: format!("snapshot contains duplicate balance entry for {address}"), + }); + } + } + + let mut nonces = HashMap::with_capacity(decoded.nonces.len()); + for entry in decoded.nonces { + let address = Address::from(entry.address); + if nonces.insert(address, entry.nonce).is_some() { + return Err(AppError::Internal { + reason: format!("snapshot contains duplicate nonce entry for {address}"), + }); + } + } + + Ok(WalletApp::from_snapshot_parts( + WalletConfig { + erc20_portal_address: Address::from(decoded.erc20_portal_address), + supported_erc20_token: Address::from(decoded.supported_erc20_token), + sequencer_address: Address::from(decoded.sequencer_address), + }, + balances, + nonces, + decoded.executed_input_count, + )) +} + +#[cfg(test)] +mod tests { + use alloy_primitives::address; + + use super::*; + + fn fixture_path(name: &str) -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../tests/fixtures") + .join(name) + } + + fn read_fixture_hex(name: &str) -> Vec { + let hex = std::fs::read_to_string(fixture_path(name)) + .unwrap_or_else(|e| panic!("read fixture {name}: {e}")); + let hex = hex.trim().replace(['\n', ' '], ""); + (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).expect("hex nibble")) + .collect() + } + + #[test] + fn encode_default_wallet_matches_golden_vector() { + let app = WalletApp::new(WalletConfig::default()); + assert_eq!( + encode(&app), + read_fixture_hex("wallet_snapshot_v1_empty.hex") + ); + } + + #[test] + fn encode_round_trips_through_decode() { + let mut app = WalletApp::new(WalletConfig::default()); + app.balances_mut().insert( + address!("0x1111111111111111111111111111111111111111"), + U256::from(10_u64), + ); + app.nonces_mut() + .insert(address!("0x1111111111111111111111111111111111111111"), 1); + app.set_executed_input_count(3); + + let bytes = encode(&app); + let restored = decode(&bytes).expect("decode snapshot"); + assert_eq!(encode(&restored), bytes); + } +} diff --git a/examples/canonical-app/src/scheduler/core.rs b/examples/canonical-app/src/scheduler/core.rs index 79f502b..07c641f 100644 --- a/examples/canonical-app/src/scheduler/core.rs +++ b/examples/canonical-app/src/scheduler/core.rs @@ -145,8 +145,7 @@ impl Scheduler { } self.app - .export_state() - .map(|state| state.into_bytes()) + .canonical_snapshot_bytes() .map_err(|err| InspectError::Application(err.to_string())) } @@ -504,8 +503,10 @@ mod tests { unimplemented!("RecordingApp does not participate in snapshot lifecycle") } - fn export_state(&self) -> Result { - Ok(format!("events:{}", self.executed.len())) + fn canonical_snapshot_bytes( + &self, + ) -> Result, sequencer_core::application::AppError> { + Ok(format!("events:{}", self.executed.len()).into_bytes()) } } diff --git a/examples/canonical-app/src/scheduler/mod.rs b/examples/canonical-app/src/scheduler/mod.rs index dd2c59b..385e948 100644 --- a/examples/canonical-app/src/scheduler/mod.rs +++ b/examples/canonical-app/src/scheduler/mod.rs @@ -172,10 +172,7 @@ mod tests { code: -22, }); let (rollup, reports) = MockRollup::with_inputs(vec![Ok(inspect), terminal_err]); - let expected = WalletApp::new(WalletConfig::default()) - .export_state() - .expect("export state") - .into_bytes(); + let expected = app_core::wallet_snapshot::encode(&WalletApp::new(WalletConfig::default())); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { run_scheduler_forever( diff --git a/justfile b/justfile index 13c2756..4870a46 100644 --- a/justfile +++ b/justfile @@ -24,13 +24,19 @@ test-watchdog-webhook-drill: watchdog-lua-deps WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_webhook.lua WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_divergence.lua -# Build lua-cjson into .deps/lua (for compare harness / drills without system packages). +# Build lcurl (lua-cURLv3) into .deps/lua; JSON is pure Lua under watchdog/third_party/. watchdog-lua-deps: @bash scripts/watchdog-lua-deps.sh +# Anvil + rollups + sequencer-devnet; prints WATCHDOG_* exports until Ctrl+C. +devnet-for-watchdog: setup ensure-machine-image + cargo build -p sequencer --bin sequencer-devnet + cargo build -p rollups-e2e --bin devnet-stack + cargo run -p rollups-e2e --bin devnet-stack + test-watchdog-compare-harness: setup watchdog-lua-deps ensure-machine-image - cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e - RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e -- watchdog_genesis_compare_test --exact --nocapture + cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e --bin rollups-e2e + RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e --bin rollups-e2e -- watchdog_genesis_compare_test --exact --nocapture # Run sequencer tests sequentially so partition static config (init) is not shared across parallel tests. test-sequencer: @@ -40,8 +46,8 @@ test-sequencer: cargo test -p sequencer --test batch_submitter_integration -- --test-threads=1 test-rollups-e2e: setup ensure-machine-image - cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e - cargo run -p rollups-e2e + cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e --bin rollups-e2e + cargo run -p rollups-e2e --bin rollups-e2e ensure-machine-image: @test -d examples/canonical-app/out/canonical-machine-image || just canonical-build-machine-image diff --git a/scripts/watchdog-lua-deps.sh b/scripts/watchdog-lua-deps.sh index c7af0f5..1f947ee 100755 --- a/scripts/watchdog-lua-deps.sh +++ b/scripts/watchdog-lua-deps.sh @@ -1,36 +1,46 @@ #!/usr/bin/env bash -# Provide lua-cjson for watchdog tests: use system package or compile with gcc (no make). +# Build watchdog Lua native deps: lcurl (lua-cURLv3) into .deps/lua. +# JSON is pure Lua under watchdog/third_party/json.lua (no compile step). set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" out_dir="${root}/.deps/lua" -out_so="${out_dir}/cjson.so" +out_so="${out_dir}/lcurl.so" +vendor_dir="${root}/watchdog/third_party/lua-curl" +upstream_sha="9f8b6dba8b5ef1b26309a571ae75cda4034279e5" +upstream_tar="https://github.com/Lua-cURL/Lua-cURLv3/archive/${upstream_sha}.tar.gz" mkdir -p "${out_dir}" -cjson_loadable() { - # Prefer in-process cpath (works across Lua 5.x); LUA_CPATH_* env names vary by version. - lua -e "package.cpath='${out_dir}/?.so;'..package.cpath; require('cjson')" >/dev/null 2>&1 +lcurl_loadable() { + lua -e "package.cpath='${out_dir}/?.so;'..package.cpath; require('lcurl')" >/dev/null 2>&1 } -if [[ -f "${out_so}" ]] && cjson_loadable; then +if [[ -f "${out_so}" ]] && lcurl_loadable; then exit 0 fi -# Prefer distro-packaged module (Debian/Ubuntu, etc.). -for candidate in \ - /usr/lib/x86_64-linux-gnu/lua/5.4/cjson.so \ - /usr/lib/x86_64-linux-gnu/lua/5.3/cjson.so \ - /usr/lib/lua/5.4/cjson.so \ - /usr/lib/lua/5.3/cjson.so; do - if [[ -f "${candidate}" ]]; then - cp "${candidate}" "${out_so}" - exit 0 +if [[ ! -f "${vendor_dir}/Makefile" ]]; then + echo "watchdog-lua-deps: populating ${vendor_dir} from pinned Lua-cURLv3 (${upstream_sha})" >&2 + tmp="$(mktemp -d)" + trap 'rm -rf "${tmp}"' EXIT + if command -v curl >/dev/null 2>&1; then + curl -fsSL "${upstream_tar}" | tar -xz -C "${tmp}" + elif command -v wget >/dev/null 2>&1; then + wget -qO- "${upstream_tar}" | tar -xz -C "${tmp}" + else + echo "watchdog-lua-deps: need curl or wget to fetch Lua-cURLv3" >&2 + exit 1 fi -done - -if cjson_loadable; then - exit 0 + shopt -s nullglob + dirs=("${tmp}"/Lua-cURLv3-*) + if [[ ${#dirs[@]} -ne 1 ]]; then + echo "watchdog-lua-deps: unexpected Lua-cURLv3 extract layout" >&2 + exit 1 + fi + rm -rf "${vendor_dir}" + mkdir -p "$(dirname "${vendor_dir}")" + cp -a "${dirs[0]}" "${vendor_dir}" fi lua_inc="" @@ -42,45 +52,50 @@ for dir in /usr/include/lua5.4 /usr/include/lua5.3 /usr/include/lua; do done if [[ -z "${lua_inc}" ]]; then - echo "watchdog-lua-deps: install lua-cjson (e.g. apt install lua-cjson) or Lua headers (lua5.4-dev)" >&2 + echo "watchdog-lua-deps: install Lua headers (e.g. lua5.4-dev)" >&2 exit 1 fi -if ! command -v gcc >/dev/null 2>&1; then - echo "watchdog-lua-deps: install gcc or the lua-cjson system package" >&2 +if ! command -v make >/dev/null 2>&1; then + echo "watchdog-lua-deps: install make" >&2 exit 1 fi -tmp="$(mktemp -d)" -trap 'rm -rf "${tmp}"' EXIT - -src="${tmp}/lua-cjson" -if command -v curl >/dev/null 2>&1; then - curl -fsSL "https://github.com/openresty/lua-cjson/archive/refs/heads/master.tar.gz" \ - | tar -xz -C "${tmp}" -elif command -v wget >/dev/null 2>&1; then - wget -qO- "https://github.com/openresty/lua-cjson/archive/refs/heads/master.tar.gz" \ - | tar -xz -C "${tmp}" -else - echo "watchdog-lua-deps: need curl or wget to fetch lua-cjson sources" >&2 +if ! pkg-config --exists libcurl 2>/dev/null; then + echo "watchdog-lua-deps: install libcurl dev package (libcurl4-openssl-dev or similar)" >&2 exit 1 fi -shopt -s nullglob -dirs=("${tmp}"/lua-cjson-*) -if [[ ${#dirs[@]} -ne 1 ]]; then - echo "watchdog-lua-deps: unexpected lua-cjson extract layout" >&2 - exit 1 +build_dir="$(mktemp -d)" +trap 'rm -rf "${build_dir}"' EXIT +cp -a "${vendor_dir}/." "${build_dir}/" +# Lua-cURL Makefile uses LUA_INC (not LUA_INCLUDE_DIR). On Debian/Ubuntu headers +# live under /usr/include/lua5.4/, not /usr/include/. +lua_impl="" +if pkg-config --exists lua5.4 2>/dev/null; then + lua_impl="lua5.4" +elif pkg-config --exists lua5.3 2>/dev/null; then + lua_impl="lua5.3" fi -src="${dirs[0]}" -cflags=(-O3 -Wall -pedantic -DNDEBUG -I"${lua_inc}" -fPIC) -gcc "${cflags[@]}" -c -o "${tmp}/lua_cjson.o" "${src}/lua_cjson.c" -gcc "${cflags[@]}" -c -o "${tmp}/strbuf.o" "${src}/strbuf.c" -gcc "${cflags[@]}" -c -o "${tmp}/fpconv.o" "${src}/fpconv.c" -gcc -shared -o "${out_so}" "${tmp}/lua_cjson.o" "${tmp}/strbuf.o" "${tmp}/fpconv.o" +make_args=( + "LUA_INC=${lua_inc}" + "CURL_LIBS=$(pkg-config --libs libcurl)" +) +if [[ -n "${lua_impl}" ]]; then + make_args+=("LUA_IMPL=${lua_impl}") +fi + +make -C "${build_dir}" "${make_args[@]}" >/dev/null + +built_so="$(find "${build_dir}" -name 'lcurl.so' -o -name 'cURL.so' | head -1)" +if [[ -z "${built_so}" ]]; then + echo "watchdog-lua-deps: make succeeded but lcurl.so not found" >&2 + exit 1 +fi +cp "${built_so}" "${out_so}" -if ! cjson_loadable; then - echo "watchdog-lua-deps: built cjson.so but lua cannot load it (Lua version mismatch?)" >&2 +if ! lcurl_loadable; then + echo "watchdog-lua-deps: built lcurl.so but lua cannot load it (Lua version mismatch?)" >&2 exit 1 fi diff --git a/sequencer-core/src/application/mod.rs b/sequencer-core/src/application/mod.rs index 7aa4763..d8bddec 100644 --- a/sequencer-core/src/application/mod.rs +++ b/sequencer-core/src/application/mod.rs @@ -186,8 +186,16 @@ pub trait Application: Send + Sized { /// loading the dump or instantiating the Application. fn state_file_in_dump(prefix: &Path) -> PathBuf; - /// Human-readable state for `GET /get_state` and CM inspect (watchdog - /// dev path). Canonical dumps use SSZ via [`Application::create_dump`]. + /// Deterministic canonical state bytes (SSZ for the toy wallet). Used by + /// CM `inspect_state` and the watchdog's `/finalized_state` compare. + /// Default: not implemented. + fn canonical_snapshot_bytes(&self) -> Result, AppError> { + Err(AppError::Internal { + reason: "canonical snapshot bytes are not implemented".to_string(), + }) + } + + /// Optional human-readable JSON for debugging only (not loaded on recovery). fn export_state(&self) -> Result { Err(AppError::Internal { reason: "application state export is not implemented".to_string(), diff --git a/sequencer/src/egress/api/get_state.rs b/sequencer/src/egress/api/get_state.rs deleted file mode 100644 index aa0b422..0000000 --- a/sequencer/src/egress/api/get_state.rs +++ /dev/null @@ -1,111 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -use axum::Json; -use axum::extract::State; -use serde::Serialize; - -use crate::egress::api::GetStateState; -use crate::egress::app_state::StateSnapshotError; -use crate::http::ApiError; - -#[derive(Debug, Serialize)] -pub(crate) struct GetStateResponse { - safe_block: u64, - state: String, -} - -pub(crate) async fn get_state( - State(state): State, -) -> Result, ApiError> { - state.reject_if_shutting_down()?; - let snapshotter = state.snapshotter.clone(); - let snapshot = tokio::task::spawn_blocking(move || snapshotter.snapshot()) - .await - .map_err(|err| ApiError::internal_error(format!("state snapshot task failed: {err}")))? - .map_err(map_snapshot_error)?; - - Ok(Json(GetStateResponse { - safe_block: snapshot.safe_block, - state: snapshot.state, - })) -} - -fn map_snapshot_error(err: StateSnapshotError) -> ApiError { - match err { - StateSnapshotError::SafeHeadUnavailable => ApiError::unavailable(err.to_string()), - StateSnapshotError::Storage { .. } - | StateSnapshotError::OpenStorage { .. } - | StateSnapshotError::Application { .. } => ApiError::internal_error(err.to_string()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::sync::Arc; - - use crate::egress::app_state::{StateSnapshot, StateSnapshotProvider}; - use crate::runtime::shutdown::ShutdownSignal; - - struct FixedSnapshotter; - - impl StateSnapshotProvider for FixedSnapshotter { - fn snapshot(&self) -> Result { - Ok(StateSnapshot { - safe_block: 42, - state: "{\"ok\":true}".to_string(), - }) - } - } - - struct UnavailableSnapshotter; - - impl StateSnapshotProvider for UnavailableSnapshotter { - fn snapshot(&self) -> Result { - Err(StateSnapshotError::SafeHeadUnavailable) - } - } - - #[tokio::test] - async fn get_state_returns_safe_block_and_raw_state() { - let state = GetStateState { - shutdown: ShutdownSignal::default(), - snapshotter: Arc::new(FixedSnapshotter), - }; - - let response = get_state(State(state)).await.expect("get state"); - - assert_eq!(response.safe_block, 42); - assert_eq!(response.state, "{\"ok\":true}"); - } - - #[tokio::test] - async fn get_state_rejects_shutdown() { - let shutdown = ShutdownSignal::default(); - shutdown.request_shutdown(); - let state = GetStateState { - shutdown, - snapshotter: Arc::new(FixedSnapshotter), - }; - - let err = get_state(State(state)).await.expect_err("shutdown"); - - assert_eq!(err.status(), axum::http::StatusCode::SERVICE_UNAVAILABLE); - } - - #[tokio::test] - async fn get_state_reports_unavailable_when_safe_head_missing() { - let state = GetStateState { - shutdown: ShutdownSignal::default(), - snapshotter: Arc::new(UnavailableSnapshotter), - }; - - let err = get_state(State(state)) - .await - .expect_err("missing safe head"); - - assert_eq!(err.status(), axum::http::StatusCode::SERVICE_UNAVAILABLE); - } -} diff --git a/sequencer/src/egress/api/mod.rs b/sequencer/src/egress/api/mod.rs index 5031d68..223fe80 100644 --- a/sequencer/src/egress/api/mod.rs +++ b/sequencer/src/egress/api/mod.rs @@ -4,7 +4,6 @@ //! Egress HTTP API routes: WebSocket subscribe + k8s-style health probes. //! Additional read endpoints will land here. -mod get_state; mod health; mod snapshot; mod state; @@ -17,14 +16,13 @@ use axum::routing::get; pub(crate) use health::HealthState; pub use snapshot::SnapshotState; -pub(crate) use state::{GetStateState, SubscribeState}; +pub(crate) use state::SubscribeState; /// Build the egress router. Each subrouter has its own state; the merge is /// transparent to axum's routing. Snapshot routes are always part of the /// egress (internal) side — the public/ingress side is a separate router. pub(crate) fn router( subscribe_state: Arc, - get_state_state: GetStateState, health_state: Arc, snapshot_state: Arc, ) -> Router { @@ -32,10 +30,6 @@ pub(crate) fn router( .route("/ws/subscribe", get(subscribe::subscribe_l2_txs)) .with_state(subscribe_state); - let state_router = Router::new() - .route("/get_state", get(get_state::get_state)) - .with_state(get_state_state); - let health_router = Router::new() .route("/livez", get(health::livez)) .route("/readyz", get(health::readyz)) @@ -43,7 +37,6 @@ pub(crate) fn router( .with_state(health_state); subscribe_router - .merge(state_router) .merge(health_router) .merge(snapshot::router(snapshot_state)) } diff --git a/sequencer/src/egress/api/state.rs b/sequencer/src/egress/api/state.rs index 66ab498..1edb5c1 100644 --- a/sequencer/src/egress/api/state.rs +++ b/sequencer/src/egress/api/state.rs @@ -1,13 +1,12 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -//! Egress-side axum state for WS subscribe, `GET /get_state`, and health probes. +//! Egress-side axum state for WS subscribe and health probes. use std::sync::Arc; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; -use crate::egress::app_state::StateSnapshotProvider; use crate::egress::l2_tx_feed::L2TxFeed; use crate::http::ApiError; use crate::runtime::shutdown::ShutdownSignal; @@ -20,22 +19,6 @@ pub(crate) struct SubscribeState { pub tx_feed: L2TxFeed, } -#[derive(Clone)] -pub(crate) struct GetStateState { - pub shutdown: ShutdownSignal, - pub snapshotter: Arc, -} - -impl GetStateState { - pub(crate) fn reject_if_shutting_down(&self) -> Result<(), ApiError> { - if self.shutdown.is_shutdown_requested() { - Err(ApiError::unavailable("sequencer shutting down")) - } else { - Ok(()) - } - } -} - impl SubscribeState { pub(crate) fn new( shutdown: ShutdownSignal, diff --git a/sequencer/src/egress/app_state.rs b/sequencer/src/egress/app_state.rs deleted file mode 100644 index 16719ca..0000000 --- a/sequencer/src/egress/app_state.rs +++ /dev/null @@ -1,292 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -//! Safe app-state reconstruction for read-only egress endpoints. - -use alloy_primitives::Address; -use thiserror::Error; - -use crate::storage::Storage; -use sequencer_core::application::{AppError, Application}; -use sequencer_core::l2_tx::SequencedL2Tx; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StateSnapshot { - pub safe_block: u64, - pub state: String, -} - -#[derive(Debug, Error)] -pub enum StateSnapshotError { - #[error("safe L1 head has not been observed yet")] - SafeHeadUnavailable, - #[error("storage error: {source}")] - Storage { - #[from] - source: rusqlite::Error, - }, - #[error("storage open error: {source}")] - OpenStorage { - #[from] - source: crate::storage::StorageOpenError, - }, - #[error("application error: {source}")] - Application { - #[from] - source: AppError, - }, -} - -#[derive(Clone)] -pub struct StateSnapshotter { - db_path: String, - genesis_app: A, - batch_submitter_address: Address, -} - -pub trait StateSnapshotProvider: Send + Sync { - fn snapshot(&self) -> Result; -} - -impl StateSnapshotter -where - A: Application + Clone, -{ - pub fn new(db_path: String, genesis_app: A, batch_submitter_address: Address) -> Self { - Self { - db_path, - genesis_app, - batch_submitter_address, - } - } - - fn build_snapshot(&self) -> Result { - let mut storage = Storage::open_read_only(self.db_path.as_str())?; - let safe_block = storage - .current_safe_block()? - .ok_or(StateSnapshotError::SafeHeadUnavailable)?; - let txs = storage.safe_accepted_l2_txs(self.batch_submitter_address)?; - - let mut app = self.genesis_app.clone(); - replay_txs(&mut app, txs)?; - let state = app.export_state()?; - - Ok(StateSnapshot { safe_block, state }) - } -} - -impl StateSnapshotProvider for StateSnapshotter -where - A: Application + Clone + Sync, -{ - fn snapshot(&self) -> Result { - self.build_snapshot() - } -} - -fn replay_txs(app: &mut A, txs: Vec) -> Result<(), AppError> -where - A: Application, -{ - for tx in txs { - match tx { - SequencedL2Tx::UserOp(user_op) => { - app.execute_valid_user_op(&user_op)?; - } - SequencedL2Tx::Direct(input) => { - app.execute_direct_input(&input)?; - } - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::time::SystemTime; - - use crate::ingress::inclusion_lane::PendingUserOp; - use crate::storage::test_helpers::{default_protocol_timing, temp_db}; - use crate::storage::{SafeInputRange, Storage, StoredSafeInput}; - use alloy_primitives::Signature; - use sequencer_core::application::{AppOutputs, InvalidReason}; - use sequencer_core::batch::Batch; - use sequencer_core::l2_tx::{DirectInput, ValidUserOp}; - use sequencer_core::user_op::{SignedUserOp, UserOp}; - use tokio::sync::oneshot; - - #[derive(Clone, Default)] - struct RecordingApp { - events: Vec, - } - - impl Application for RecordingApp { - const MAX_METHOD_PAYLOAD_BYTES: usize = 1024; - - fn current_user_nonce(&self, _sender: Address) -> u32 { - 0 - } - - fn current_user_balance(&self, _sender: Address) -> alloy_primitives::U256 { - alloy_primitives::U256::ZERO - } - - fn validate_user_op( - &self, - _sender: Address, - _user_op: &UserOp, - _current_fee: u16, - ) -> Result<(), InvalidReason> { - Ok(()) - } - - fn execute_valid_user_op(&mut self, user_op: &ValidUserOp) -> Result { - self.events.push(format!( - "user:{}:{}:{}", - user_op.sender, - user_op.fee, - alloy_primitives::hex::encode(&user_op.data) - )); - Ok(Vec::new()) - } - - fn execute_direct_input(&mut self, input: &DirectInput) -> Result { - self.events.push(format!( - "direct:{}:{}:{}", - input.sender, - input.block_number, - alloy_primitives::hex::encode(&input.payload) - )); - Ok(Vec::new()) - } - - fn export_state(&self) -> Result { - Ok(self.events.join("|")) - } - } - - fn empty_batch_payload(nonce: u64) -> Vec { - ssz::Encode::as_ssz_bytes(&Batch { - nonce, - frames: Vec::new(), - }) - } - - #[test] - fn snapshot_replays_only_scheduler_accepted_batch_prefix() { - let db = temp_db("state-snapshot-accepted-prefix"); - let mut storage = Storage::open(db.path.as_str()).expect("open storage"); - let batch_submitter = Address::repeat_byte(0xfe); - let direct_sender = Address::repeat_byte(0x11); - - storage - .append_safe_inputs( - 10, - &[ - StoredSafeInput { - sender: direct_sender, - payload: vec![0xaa], - block_number: 10, - }, - StoredSafeInput { - sender: batch_submitter, - payload: empty_batch_payload(0), - block_number: 10, - }, - ], - batch_submitter, - &default_protocol_timing(), - ) - .expect("append safe inputs"); - let mut head = storage - .initialize_open_state(10, SafeInputRange::new(0, 2)) - .expect("initialize open state"); - storage - .close_frame_and_batch(&mut head, 10) - .expect("close accepted batch"); - - let snapshotter = - StateSnapshotter::new(db.path.clone(), RecordingApp::default(), batch_submitter); - let snapshot = snapshotter.snapshot().expect("snapshot"); - - assert_eq!(snapshot.safe_block, 10); - assert_eq!(snapshot.state, format!("direct:{direct_sender}:10:aa")); - } - - #[test] - fn snapshot_excludes_open_tip_user_op() { - let db = temp_db("state-snapshot-excludes-tip"); - let mut storage = Storage::open(db.path.as_str()).expect("open storage"); - let batch_submitter = Address::repeat_byte(0xfe); - let sender = Address::repeat_byte(0x22); - - storage - .append_safe_inputs(10, &[], batch_submitter, &default_protocol_timing()) - .expect("append safe head"); - let mut head = storage - .initialize_open_state(10, SafeInputRange::new(0, 0)) - .expect("initialize open state"); - let (respond_to, _recv) = oneshot::channel(); - storage - .append_user_ops_chunk( - &mut head, - &[PendingUserOp { - signed: SignedUserOp { - sender, - signature: Signature::test_signature(), - user_op: UserOp { - nonce: 0, - max_fee: u16::MAX, - data: vec![0x01].into(), - }, - }, - respond_to, - received_at: SystemTime::now(), - }], - ) - .expect("append tip user op"); - - let snapshotter = - StateSnapshotter::new(db.path.clone(), RecordingApp::default(), batch_submitter); - let snapshot = snapshotter.snapshot().expect("snapshot"); - - assert_eq!(snapshot.safe_block, 10); - assert_eq!(snapshot.state, ""); - } - - #[test] - fn snapshot_excludes_unaccepted_soft_batch() { - let db = temp_db("state-snapshot-excludes-soft"); - let mut storage = Storage::open(db.path.as_str()).expect("open storage"); - let batch_submitter = Address::repeat_byte(0xfe); - let direct_sender = Address::repeat_byte(0x11); - - storage - .append_safe_inputs( - 10, - &[StoredSafeInput { - sender: direct_sender, - payload: vec![0xaa], - block_number: 10, - }], - batch_submitter, - &default_protocol_timing(), - ) - .expect("append safe input"); - let mut head = storage - .initialize_open_state(10, SafeInputRange::new(0, 1)) - .expect("initialize open state"); - storage - .close_frame_and_batch(&mut head, 10) - .expect("close unaccepted batch"); - - let snapshotter = - StateSnapshotter::new(db.path.clone(), RecordingApp::default(), batch_submitter); - let snapshot = snapshotter.snapshot().expect("snapshot"); - - assert_eq!(snapshot.safe_block, 10); - assert_eq!(snapshot.state, ""); - } -} diff --git a/sequencer/src/egress/mod.rs b/sequencer/src/egress/mod.rs index 3bcfb8e..ac7b75a 100644 --- a/sequencer/src/egress/mod.rs +++ b/sequencer/src/egress/mod.rs @@ -6,5 +6,4 @@ //! split puts these on a separate port from ingress. pub mod api; -pub mod app_state; pub mod l2_tx_feed; diff --git a/sequencer/src/http.rs b/sequencer/src/http.rs index 1dcd551..f9147f0 100644 --- a/sequencer/src/http.rs +++ b/sequencer/src/http.rs @@ -25,7 +25,6 @@ use tower_http::trace::TraceLayer; pub use crate::egress::api::SnapshotState; use crate::egress::api::SubscribeState; -use crate::egress::app_state::StateSnapshotProvider; use crate::egress::l2_tx_feed::L2TxFeed; use crate::ingress::api::SubmitState; use crate::ingress::inclusion_lane::{PendingUserOp, SequencerError}; @@ -176,7 +175,6 @@ pub async fn start( max_user_op_data_bytes: usize, shutdown: ShutdownSignal, tx_feed: L2TxFeed, - state_snapshotter: Arc, config: ApiConfig, snapshot_state: SnapshotState, ) -> io::Result { @@ -188,7 +186,6 @@ pub async fn start( max_user_op_data_bytes, shutdown, tx_feed, - state_snapshotter, config, snapshot_state, )) @@ -202,7 +199,6 @@ pub fn start_on_listener( max_user_op_data_bytes: usize, shutdown: ShutdownSignal, tx_feed: L2TxFeed, - state_snapshotter: Arc, config: ApiConfig, snapshot_state: SnapshotState, ) -> ApiServerTask { @@ -222,15 +218,9 @@ pub fn start_on_listener( config.ws_max_subscribers, config.ws_max_catchup_events, )); - let get_state_state = crate::egress::api::GetStateState { - shutdown: shutdown.clone(), - snapshotter: state_snapshotter, - }; - let app: Router = crate::ingress::api::router(submit_state) .merge(crate::egress::api::router( subscribe_state, - get_state_state, health_state, Arc::new(snapshot_state), )) diff --git a/sequencer/src/l1/partition.rs b/sequencer/src/l1/partition.rs index 0e39d7e..09383a1 100644 --- a/sequencer/src/l1/partition.rs +++ b/sequencer/src/l1/partition.rs @@ -89,17 +89,230 @@ pub fn error_message_matches_retry_codes(error_message: &str, codes: &[String]) codes.iter().any(|c| error_message.contains(c)) } +/// Simulates partitioned `eth_getLogs` bisect (same rules as `watchdog/l1_reader.lua`). +/// `get_logs` returns `Ok` for a successful leaf query or `Err(message)` on RPC failure. +/// Records every attempted `(from_block, to_block)` in call order. +pub fn simulate_partitioned_get_logs( + start_block: u64, + end_block: u64, + long_block_range_error_codes: &[String], + mut get_logs: F, +) -> (Vec<(u64, u64)>, Result<(), String>) +where + F: FnMut(u64, u64) -> Result<(), String>, +{ + if end_block < start_block { + return (Vec::new(), Ok(())); + } + + fn go( + start_block: u64, + end_block: u64, + long_block_range_error_codes: &[String], + get_logs: &mut F, + calls: &mut Vec<(u64, u64)>, + ) -> Result<(), String> + where + F: FnMut(u64, u64) -> Result<(), String>, + { + calls.push((start_block, end_block)); + match get_logs(start_block, end_block) { + Ok(()) => Ok(()), + Err(err) => { + if start_block < end_block + && error_message_matches_retry_codes(&err, long_block_range_error_codes) + { + let middle = start_block + (end_block - start_block) / 2; + go( + start_block, + middle, + long_block_range_error_codes, + get_logs, + calls, + )?; + go( + middle + 1, + end_block, + long_block_range_error_codes, + get_logs, + calls, + )?; + Ok(()) + } else { + Err(err) + } + } + } + } + + let mut calls = Vec::new(); + let result = go( + start_block, + end_block, + long_block_range_error_codes, + &mut get_logs, + &mut calls, + ); + (calls, result) +} + +/// Total order for merged logs (block, tx index, log index) — matches Lua `sort_logs`. +pub fn sort_logs_by_l1_order( + logs: &mut [T], + block: FBlock, + tx_index: FTx, + log_index: FLog, +) where + FBlock: Fn(&T) -> u64, + FTx: Fn(&T) -> u64, + FLog: Fn(&T) -> u64, +{ + logs.sort_by(|a, b| { + block(a) + .cmp(&block(b)) + .then_with(|| tx_index(a).cmp(&tx_index(b))) + .then_with(|| log_index(a).cmp(&log_index(b))) + }); +} + pub fn decode_evm_advance_input(input: &[u8]) -> Result { EvmAdvanceCall::abi_decode(input).map_err(|err| err.to_string()) } #[cfg(test)] mod tests { + use std::collections::HashMap; + use alloy_primitives::{U256, address}; use alloy_sol_types::SolCall; use cartesi_rollups_contracts::inputs::Inputs::EvmAdvanceCall; + use serde::Deserialize; + + use super::{ + DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES, decode_evm_advance_input, + error_message_matches_retry_codes, simulate_partitioned_get_logs, sort_logs_by_l1_order, + }; + + const PARTITION_VECTOR: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../tests/fixtures/l1_partition_vector.json" + )); + + #[derive(Debug, Deserialize)] + struct FailRange { + from: u64, + to: u64, + message: String, + } + + #[derive(Debug, Deserialize)] + struct PartitionScenario { + name: String, + start_block: u64, + end_block: u64, + fail_ranges: Vec, + expect_calls: Vec<[u64; 2]>, + expect_ok: bool, + } + + #[derive(Debug, Deserialize)] + struct LogSortFixture { + unsorted: Vec, + expect_block_order: Vec, + expect_log_index_order: Vec, + } - use super::{decode_evm_advance_input, error_message_matches_retry_codes}; + #[derive(Debug, Deserialize)] + struct LogSortEntry { + #[serde(rename = "blockNumber")] + block_number: String, + #[serde(rename = "transactionIndex", default)] + transaction_index: String, + #[serde(rename = "logIndex", default)] + log_index: String, + } + + #[derive(Debug, Deserialize)] + struct PartitionVector { + long_block_range_error_codes: Vec, + scenarios: Vec, + log_sort: LogSortFixture, + } + + fn load_partition_vector() -> PartitionVector { + serde_json::from_str(PARTITION_VECTOR).expect("parse l1_partition_vector.json") + } + + fn fail_map(fail_ranges: &[FailRange]) -> HashMap<(u64, u64), String> { + fail_ranges + .iter() + .map(|r| ((r.from, r.to), r.message.clone())) + .collect() + } + + #[test] + fn fixture_default_codes_match_rust_defaults() { + let vector = load_partition_vector(); + let expected: Vec = DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES + .iter() + .map(|c| (*c).to_string()) + .collect(); + assert_eq!(vector.long_block_range_error_codes, expected); + } + + #[test] + fn partition_vector_simulation_matches_watchdog() { + let vector = load_partition_vector(); + for scenario in vector.scenarios { + let fails = fail_map(&scenario.fail_ranges); + let codes = vector.long_block_range_error_codes.clone(); + let (calls, result) = simulate_partitioned_get_logs( + scenario.start_block, + scenario.end_block, + &codes, + |from, to| { + if let Some(message) = fails.get(&(from, to)) { + Err(message.clone()) + } else { + Ok(()) + } + }, + ); + + let expected_calls: Vec<(u64, u64)> = scenario + .expect_calls + .iter() + .map(|pair| (pair[0], pair[1])) + .collect(); + assert_eq!(calls, expected_calls, "scenario {}", scenario.name); + assert_eq!( + result.is_ok(), + scenario.expect_ok, + "scenario {}", + scenario.name + ); + } + } + + #[test] + fn log_sort_vector_matches_watchdog_order() { + let vector = load_partition_vector(); + let mut logs = vector.log_sort.unsorted; + sort_logs_by_l1_order( + &mut logs, + |e| parse_hex_quantity(&e.block_number), + |e| parse_hex_quantity(&e.transaction_index), + |e| parse_hex_quantity(&e.log_index), + ); + let blocks: Vec = logs.iter().map(|e| e.block_number.clone()).collect(); + let indices: Vec = logs.iter().map(|e| e.log_index.clone()).collect(); + assert_eq!(blocks, vector.log_sort.expect_block_order); + assert_eq!(indices, vector.log_sort.expect_log_index_order); + } + + fn parse_hex_quantity(value: &str) -> u64 { + u64::from_str_radix(value.strip_prefix("0x").unwrap_or(value), 16).expect("hex quantity") + } #[test] fn error_message_matches_retry_codes_returns_true_when_message_contains_code() { diff --git a/sequencer/src/runtime/workers.rs b/sequencer/src/runtime/workers.rs index 4ff8d03..e3bc6ce 100644 --- a/sequencer/src/runtime/workers.rs +++ b/sequencer/src/runtime/workers.rs @@ -102,12 +102,6 @@ impl Workers { let shutdown = ShutdownSignal::default(); - let state_snapshotter = Arc::new(crate::egress::app_state::StateSnapshotter::new( - db_path.clone(), - app.clone(), - l1_config.batch_submitter_address, - )); - // Inclusion lane: takes the app, returns the tx-sender the HTTP // ingress route will publish to. let mut storage = crate::storage::Storage::open(&db_path)?; @@ -192,7 +186,6 @@ impl Workers { A::MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, - state_snapshotter, ApiConfig::default(), http::SnapshotState { db_path: db_path.clone(), diff --git a/sequencer/tests/e2e_sequencer.rs b/sequencer/tests/e2e_sequencer.rs index ff196e3..b61795c 100644 --- a/sequencer/tests/e2e_sequencer.rs +++ b/sequencer/tests/e2e_sequencer.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) use std::io::ErrorKind; -use std::sync::Arc; use std::time::Duration; use alloy_primitives::{Address, Signature, U256}; @@ -13,7 +12,6 @@ use app_core::application::{ use futures_util::StreamExt; use k256::ecdsa::SigningKey; use k256::ecdsa::signature::hazmat::PrehashSigner; -use sequencer::egress::app_state::StateSnapshotter; use sequencer::egress::l2_tx_feed::{L2TxFeed, L2TxFeedConfig}; use sequencer::http::{self, ApiConfig}; use sequencer::ingress::inclusion_lane::{ @@ -195,49 +193,6 @@ fn v1_regression_domain_fields_all_affect_recovery() { ); } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_state_returns_safe_only_projection_over_http() { - let db = temp_db("get-state-http"); - let domain = test_domain(); - let sender = Address::repeat_byte(0x11); - bootstrap_open_frame_with_deposits(db.path.as_str(), &[(sender, U256::from(10_u64))]); - - let Some(runtime) = start_api_only_server(db.path.as_str(), domain, 4 * 1024, 8).await else { - return; - }; - - let (status, body) = get_raw(runtime.addr, "/get_state").await; - shutdown_runtime(runtime).await; - - assert_eq!(status, 200, "get_state response: {body}"); - let parsed: serde_json::Value = serde_json::from_str(&body).expect("parse get_state JSON"); - assert_eq!(parsed["safe_block"], 1); - assert_eq!( - parsed["state"], "{\"balances\":{},\"nonces\":{}}", - "soft-confirmed tip deposits must not appear in safe-only export" - ); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_state_is_unavailable_before_safe_head_is_observed() { - let db = temp_db("get-state-no-safe-head"); - let domain = test_domain(); - let _storage = Storage::open(db.path.as_str()).expect("open storage"); - - let Some(runtime) = start_api_only_server(db.path.as_str(), domain, 4 * 1024, 8).await else { - return; - }; - - let (status, body) = get_raw(runtime.addr, "/get_state").await; - shutdown_runtime(runtime).await; - - assert_eq!(status, 503, "get_state without safe head: {body}"); - assert!( - body.contains("safe L1 head has not been observed yet"), - "expected safe-head unavailable message, got: {body}" - ); -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn e2e_submit_tx_ack_and_broadcast() { let db = temp_db("full-e2e"); @@ -1183,11 +1138,6 @@ async fn start_full_server_with_max_body( MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, - Arc::new(StateSnapshotter::new( - db_path.to_string(), - WalletApp::new(WalletConfig::default()), - Address::from([0xff; 20]), - )), ApiConfig { max_body_bytes, ..ApiConfig::default() @@ -1242,11 +1192,6 @@ async fn start_api_only_server( MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, - Arc::new(StateSnapshotter::new( - db_path.to_string(), - WalletApp::new(WalletConfig::default()), - Address::from([0xff; 20]), - )), ApiConfig { max_body_bytes, ..ApiConfig::default() @@ -1471,26 +1416,6 @@ async fn post_raw_body_no_content_type(addr: std::net::SocketAddr, body: &str) - parse_http_response(response.as_slice()) } -async fn get_raw(addr: std::net::SocketAddr, path: &str) -> (u16, String) { - let host_port = addr.to_string(); - let mut stream = tokio::net::TcpStream::connect(host_port.as_str()) - .await - .expect("connect test http socket"); - let request = format!("GET {path} HTTP/1.1\r\nHost: {host_port}\r\nConnection: close\r\n\r\n"); - stream - .write_all(request.as_bytes()) - .await - .expect("write raw request"); - stream.flush().await.expect("flush raw request"); - - let mut response = Vec::new(); - stream - .read_to_end(&mut response) - .await - .expect("read raw response"); - parse_http_response(response.as_slice()) -} - async fn post_raw_json(addr: std::net::SocketAddr, body: &str) -> (u16, String) { let host_port = addr.to_string(); let mut stream = tokio::net::TcpStream::connect(host_port.as_str()) diff --git a/sequencer/tests/ws_broadcaster.rs b/sequencer/tests/ws_broadcaster.rs index 329d7fb..c6a6c6d 100644 --- a/sequencer/tests/ws_broadcaster.rs +++ b/sequencer/tests/ws_broadcaster.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) use std::io::ErrorKind; -use std::sync::Arc; use std::time::{Duration, SystemTime}; use alloy_primitives::{Address, Signature}; use alloy_sol_types::Eip712Domain; -use app_core::application::{MAX_METHOD_PAYLOAD_BYTES, WalletApp, WalletConfig}; +use app_core::application::MAX_METHOD_PAYLOAD_BYTES; use futures_util::{SinkExt, StreamExt}; -use sequencer::egress::app_state::StateSnapshotter; use sequencer::egress::l2_tx_feed::{L2TxFeed, L2TxFeedConfig}; use sequencer::http::{self, ApiConfig, WS_CATCHUP_WINDOW_EXCEEDED_REASON}; use sequencer::ingress::inclusion_lane::{PendingUserOp, SequencerError}; @@ -461,11 +459,6 @@ async fn start_test_server_with_limits( MAX_METHOD_PAYLOAD_BYTES, shutdown.clone(), tx_feed, - Arc::new(StateSnapshotter::new( - db_path.to_string(), - WalletApp::new(WalletConfig::default()), - Address::from([0xff; 20]), - )), ApiConfig { ws_max_subscribers, ws_max_catchup_events, diff --git a/tests/e2e/Cargo.toml b/tests/e2e/Cargo.toml index 98a9308..7f0bbee 100644 --- a/tests/e2e/Cargo.toml +++ b/tests/e2e/Cargo.toml @@ -9,8 +9,13 @@ repository.workspace = true readme.workspace = true authors.workspace = true +[[bin]] +name = "devnet-stack" +path = "src/bin/devnet_stack.rs" + [dependencies] rollups-harness = { path = "../harness" } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } app-core = { path = "../../examples/app-core" } sequencer-core = { path = "../../sequencer-core" } sequencer-rust-client = { path = "../../sdk/rust-client" } @@ -19,6 +24,6 @@ alloy-sol-types = "1.4.1" futures = "0.3" libtest-mimic = "0.6.1" ssz = { package = "ethereum_ssz", version = "0.10" } -tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time", "net", "process", "io-util"] } +tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time", "net", "process", "io-util", "signal"] } serde_json = "1.0" tempfile = "3.10" diff --git a/tests/e2e/src/bin/devnet_stack.rs b/tests/e2e/src/bin/devnet_stack.rs new file mode 100644 index 0000000..9369a97 --- /dev/null +++ b/tests/e2e/src/bin/devnet_stack.rs @@ -0,0 +1,73 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +//! Local Anvil + rollups devnet + `sequencer-devnet` for manual watchdog runs. +//! +//! Prints `WATCHDOG_*` exports, then blocks until Ctrl+C. + +use rollups_harness::{ + HarnessResult, ManagedSequencer, devnet_sequencer_config_no_faketime, paths, +}; + +#[tokio::main] +async fn main() -> HarnessResult<()> { + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); + + let runtime = + ManagedSequencer::spawn(devnet_sequencer_config_no_faketime("devnet-stack")).await?; + + let machine_image = paths::devnet_machine_image_path(); + let checkpoint_dir = std::env::temp_dir().join("watchdog-checkpoints-devnet"); + + eprintln!(); + eprintln!("=== Devnet stack is up ==="); + eprintln!("Sequencer HTTP: {}", runtime.endpoint()); + eprintln!("L1 RPC: {}", runtime.l1_endpoint()); + eprintln!("App address: {}", runtime.app_address()); + eprintln!("InputBox: {}", runtime.input_box_address()); + eprintln!(); + eprintln!("--- export these (watchdog compare mode) ---"); + eprintln!("export WATCHDOG_MODE=compare"); + eprintln!("export WATCHDOG_SEQUENCER_URL={}", runtime.endpoint()); + eprintln!("export WATCHDOG_L1_RPC_URL={}", runtime.l1_endpoint()); + eprintln!( + "export WATCHDOG_INPUTBOX_ADDRESS={}", + runtime.input_box_address() + ); + eprintln!("export WATCHDOG_APP_ADDRESS={}", runtime.app_address()); + eprintln!( + "export WATCHDOG_CHECKPOINT_DIR={}", + checkpoint_dir.display() + ); + eprintln!( + "export WATCHDOG_CM_SNAPSHOT_DIR={}", + machine_image.display() + ); + eprintln!("export WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK=0"); + eprintln!( + "export WATCHDOG_LUA_DEPS={}/.deps/lua", + paths::workspace_root().display() + ); + eprintln!("export WATCHDOG_ONCE=1 # or omit for daemon loop"); + eprintln!(); + eprintln!("Wait for finalized snapshot (404 until promotion):"); + eprintln!( + " curl -s {}/finalized_state/inclusion_block", + runtime.endpoint() + ); + eprintln!(); + eprintln!("Run watchdog (from repo root, after `just watchdog-lua-deps`):"); + eprintln!(" lua watchdog/main.lua"); + eprintln!(); + eprintln!("Press Ctrl+C here to stop Anvil + sequencer."); + + tokio::signal::ctrl_c() + .await + .map_err(|err| std::io::Error::other(err.to_string()))?; + runtime.shutdown().await +} diff --git a/tests/e2e/src/watchdog_compare.rs b/tests/e2e/src/watchdog_compare.rs index a7e63ef..0d67f21 100644 --- a/tests/e2e/src/watchdog_compare.rs +++ b/tests/e2e/src/watchdog_compare.rs @@ -7,6 +7,8 @@ use std::path::Path; use std::process::Stdio; use std::time::Duration; +use app_core::application::{WalletApp, WalletConfig}; +use app_core::wallet_snapshot; use rollups_harness::ManagedSequencer; use rollups_harness::paths; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -16,7 +18,6 @@ use tokio::process::Command; use crate::ScenarioResult; const MACHINE_IMAGE: &str = "examples/canonical-app/out/canonical-machine-image"; -const EMPTY_WALLET_STATE: &str = "{\"balances\":{},\"nonces\":{}}"; const GENESIS_SAFE_BLOCK: &str = "0"; pub async fn run_watchdog_genesis_compare_test( @@ -37,33 +38,38 @@ pub async fn run_watchdog_genesis_compare_test( .into()); } - eprintln!("[watchdog-harness] step 1/5: wait for sequencer GET /get_state (safe head)"); - let get_state_url = format!("{}/get_state", runtime.endpoint()); - let (_status, body) = - wait_for_get_state(get_state_url.as_str(), Duration::from_secs(30)).await?; - let parsed: serde_json::Value = serde_json::from_str(&body) - .map_err(|err| format!("invalid /get_state JSON: {err}; body={body}"))?; - let sequencer_state = parsed["state"] - .as_str() - .ok_or("get_state response missing state field")?; - let safe_block = parsed["safe_block"] - .as_u64() - .ok_or("get_state response missing safe_block field")?; - eprintln!("[watchdog-harness] sequencer safe_block={safe_block} state={sequencer_state}"); - if sequencer_state != EMPTY_WALLET_STATE { + // `sequencer-devnet` uses `WalletConfig::devnet()` (not `default()` / Sepolia). + let expected_snapshot = wallet_snapshot::encode(&WalletApp::new(WalletConfig::devnet())); + + eprintln!("[watchdog-harness] step 1/5: wait for sequencer GET /finalized_state"); + let finalized_url = format!("{}/finalized_state", runtime.endpoint()); + let (_status, body, headers) = + wait_for_finalized_state(finalized_url.as_str(), Duration::from_secs(30)).await?; + let inclusion_block = header_u64(&headers, "x-inclusion-block") + .ok_or("finalized_state response missing X-Inclusion-Block header")?; + eprintln!( + "[watchdog-harness] sequencer inclusion_block={inclusion_block} snapshot_bytes={}", + body.len() + ); + if body.as_slice() != expected_snapshot.as_slice() { return Err(format!( - "expected genesis-empty wallet state {EMPTY_WALLET_STATE}, got {sequencer_state}" + "finalized_state bytes mismatch (len {} vs expected {})", + body.len(), + expected_snapshot.len() ) .into()); } - eprintln!("[watchdog-harness] step 2/5: prove CM inspect JSON on genesis image"); + eprintln!("[watchdog-harness] step 2/5: prove CM inspect SSZ on genesis image"); let inspect_state = prove_cm_inspect_genesis(workspace.as_path(), machine_image.as_path()).await?; - if inspect_state != EMPTY_WALLET_STATE { - return Err( - format!("CM inspect expected {EMPTY_WALLET_STATE}, got {inspect_state}").into(), - ); + if inspect_state.as_slice() != expected_snapshot.as_slice() { + return Err(format!( + "CM inspect bytes mismatch (len {} vs expected {})", + inspect_state.len(), + expected_snapshot.len() + ) + .into()); } eprintln!("[watchdog-harness] step 3/5: prepare watchdog checkpoint dir"); @@ -105,7 +111,7 @@ pub async fn run_watchdog_genesis_compare_test( async fn prove_cm_inspect_genesis( workspace: &Path, machine_image: &Path, -) -> ScenarioResult { +) -> ScenarioResult> { let work_dir = tempfile::tempdir().map_err(|err| format!("temp cm work dir: {err}"))?; let query_path = work_dir.path().join("inspect-query.bin"); let report_path = work_dir.path().join("inspect-report-0.bin"); @@ -129,18 +135,30 @@ async fn prove_cm_inspect_genesis( return Err(format!("cartesi-machine inspect exited with {status}").into()); } - let report = std::fs::read_to_string(report_path.as_path()) + let report = std::fs::read(report_path.as_path()) .map_err(|err| format!("read inspect report: {err}"))?; - if report.contains("inspect endpoint not implemented") { + if report.starts_with(b"inspect endpoint not implemented".as_slice()) + || report.starts_with("inspect endpoint not implemented".as_bytes()) + { return Err( - "CM dapp is stale (inspect not implemented); rebuild with just canonical-build-machine-image" + "CM dapp is stale (inspect not implemented); rebuild with: just canonical-build-machine-image" .into(), ); } + // Pre-SSZ images returned JSON from export_state (~27 bytes for empty wallet). + if report.first() == Some(&b'{') { + return Err(format!( + "CM inspect returned JSON ({} bytes), expected SSZ; rebuild devnet image: \ + just canonical-build-machine-image (report starts with {:?})", + report.len(), + String::from_utf8_lossy(&report[..report.len().min(40)]) + ) + .into()); + } Ok(report) } -async fn http_get(url: &str) -> std::io::Result<(u16, String)> { +async fn http_get(url: &str) -> std::io::Result<(u16, Vec, Vec<(String, String)>)> { let remainder = url.strip_prefix("http://").ok_or_else(|| { std::io::Error::new( std::io::ErrorKind::InvalidInput, @@ -159,30 +177,111 @@ async fn http_get(url: &str) -> std::io::Result<(u16, String)> { let mut raw = Vec::new(); stream.read_to_end(&mut raw).await?; - let text = String::from_utf8(raw).map_err(std::io::Error::other)?; - let (headers, body) = text - .split_once("\r\n\r\n") + let header_end = raw + .windows(4) + .position(|w| w == b"\r\n\r\n") .ok_or_else(|| std::io::Error::other("missing HTTP body"))?; - let status = headers + let header_bytes = &raw[..header_end]; + let body_raw = raw[header_end + 4..].to_vec(); + let header_text = std::str::from_utf8(header_bytes).map_err(std::io::Error::other)?; + let status = header_text .lines() .next() .and_then(|line| line.split_whitespace().nth(1)) .and_then(|code| code.parse().ok()) .unwrap_or(500); - Ok((status, body.to_string())) + let header_pairs: Vec<(String, String)> = header_text + .lines() + .skip(1) + .filter_map(|line| line.split_once(": ")) + .map(|(name, value)| (name.to_ascii_lowercase(), value.to_string())) + .collect(); + let body = decode_response_body(&header_pairs, body_raw)?; + Ok((status, body, header_pairs)) +} + +/// Axum streams snapshot files without `Content-Length`, so bodies are often +/// `Transfer-Encoding: chunked`. Raw TCP clients must decode (lcurl does this +/// automatically; this harness must too). +fn decode_response_body(headers: &[(String, String)], body: Vec) -> std::io::Result> { + let chunked = headers.iter().any(|(name, value)| { + name == "transfer-encoding" && value.to_ascii_lowercase().contains("chunked") + }); + if chunked { + return decode_chunked_body(body.as_slice()); + } + if let Some(len) = headers + .iter() + .find(|(name, _)| name == "content-length") + .and_then(|(_, value)| value.parse::().ok()) + { + let mut out = body; + out.truncate(len.min(out.len())); + return Ok(out); + } + Ok(body) +} + +fn decode_chunked_body(mut input: &[u8]) -> std::io::Result> { + let mut out = Vec::new(); + loop { + let line_end = input + .iter() + .position(|&b| b == b'\n') + .ok_or_else(|| std::io::Error::other("chunked body: missing size line"))?; + let size_line = std::str::from_utf8(&input[..line_end]) + .map_err(std::io::Error::other)? + .trim_end_matches('\r'); + let chunk_size = usize::from_str_radix(size_line, 16).map_err(std::io::Error::other)?; + input = &input[line_end + 1..]; + if chunk_size == 0 { + break; + } + if input.len() < chunk_size + 2 { + return Err(std::io::Error::other("chunked body: truncated chunk")); + } + out.extend_from_slice(&input[..chunk_size]); + input = &input[chunk_size + 2..]; + } + Ok(out) +} + +fn body_snippet_for_error(body: &[u8]) -> String { + if body.is_empty() { + return "(empty body)".to_string(); + } + match std::str::from_utf8(body) { + Ok(text) if text.len() <= 512 => text.to_string(), + Ok(text) => format!("{}…", &text[..512.min(text.len())]), + Err(_) => format!("{} binary octets", body.len()), + } +} + +fn header_u64(headers: &[(String, String)], name: &str) -> Option { + headers + .iter() + .find(|(key, _)| key == name) + .and_then(|(_, value)| value.parse().ok()) } -async fn wait_for_get_state(url: &str, deadline: Duration) -> ScenarioResult<(u16, String)> { +async fn wait_for_finalized_state( + url: &str, + deadline: Duration, +) -> ScenarioResult<(u16, Vec, Vec<(String, String)>)> { let started = std::time::Instant::now(); let mut last = String::new(); while started.elapsed() < deadline { match http_get(url).await { - Ok((200, body)) => return Ok((200, body)), - Ok((503, body)) => { - last = body; + Ok((200, body, headers)) => return Ok((200, body, headers)), + Ok((404, body, _)) => { + last = body_snippet_for_error(body.as_slice()); } - Ok((status, body)) => { - return Err(format!("GET /get_state returned HTTP {status}: {body}").into()); + Ok((status, body, _)) => { + return Err(format!( + "GET /finalized_state returned HTTP {status}: {}", + body_snippet_for_error(body.as_slice()) + ) + .into()); } Err(err) => { last = err.to_string(); @@ -190,5 +289,5 @@ async fn wait_for_get_state(url: &str, deadline: Duration) -> ScenarioResult<(u1 } tokio::time::sleep(Duration::from_millis(200)).await; } - Err(format!("timed out waiting for GET /get_state 200; last response: {last}").into()) + Err(format!("timed out waiting for GET /finalized_state 200; last response: {last}").into()) } diff --git a/tests/fixtures/l1_partition_vector.json b/tests/fixtures/l1_partition_vector.json new file mode 100644 index 0000000..18bdf3f --- /dev/null +++ b/tests/fixtures/l1_partition_vector.json @@ -0,0 +1,104 @@ +{ + "long_block_range_error_codes": ["-32005", "-32600", "-32602", "-32616"], + "scenarios": [ + { + "name": "bisect_on_default_infura_code", + "start_block": 1, + "end_block": 4, + "fail_ranges": [ + { + "from": 1, + "to": 4, + "message": "RPC error -32005: query returned more than allowed" + } + ], + "expect_calls": [[1, 4], [1, 2], [3, 4]], + "expect_ok": true + }, + { + "name": "single_range_success", + "start_block": 10, + "end_block": 12, + "fail_ranges": [], + "expect_calls": [[10, 12]], + "expect_ok": true + }, + { + "name": "empty_range_no_calls", + "start_block": 5, + "end_block": 4, + "fail_ranges": [], + "expect_calls": [], + "expect_ok": true + }, + { + "name": "non_retryable_error_aborts", + "start_block": 1, + "end_block": 4, + "fail_ranges": [ + { + "from": 1, + "to": 4, + "message": "connection refused" + } + ], + "expect_calls": [[1, 4]], + "expect_ok": false + }, + { + "name": "nested_bisect_alchemy_code", + "start_block": 1, + "end_block": 8, + "fail_ranges": [ + { "from": 1, "to": 8, "message": "error -32600: block range too large" }, + { "from": 1, "to": 4, "message": "error -32600: block range too large" }, + { "from": 1, "to": 2, "message": "error -32600: block range too large" } + ], + "expect_calls": [ + [1, 8], + [1, 4], + [1, 2], + [1, 1], + [2, 2], + [3, 4], + [5, 8] + ], + "expect_ok": true + }, + { + "name": "single_block_fail_no_split", + "start_block": 7, + "end_block": 7, + "fail_ranges": [ + { + "from": 7, + "to": 7, + "message": "error -32616: range limit" + } + ], + "expect_calls": [[7, 7]], + "expect_ok": false + } + ], + "log_sort": { + "unsorted": [ + { + "blockNumber": "0x2", + "transactionIndex": "0x0", + "logIndex": "0x5" + }, + { + "blockNumber": "0x1", + "transactionIndex": "0x9", + "logIndex": "0x0" + }, + { + "blockNumber": "0x2", + "transactionIndex": "0x0", + "logIndex": "0x1" + } + ], + "expect_block_order": ["0x1", "0x2", "0x2"], + "expect_log_index_order": ["0x0", "0x1", "0x5"] + } +} diff --git a/tests/fixtures/wallet_snapshot_v1_empty.bin b/tests/fixtures/wallet_snapshot_v1_empty.bin new file mode 100644 index 0000000000000000000000000000000000000000..047814d5b6facdc206bc3fd93dee5039f4ed4058 GIT binary patch literal 76 zcmZ3pEFz2NL-Y;9PXTh(UavxVmxbxd)Ot(iY&gC_w#0r2ujOHxFy~fA(QuU;tql000Q`9WMX? literal 0 HcmV?d00001 diff --git a/tests/fixtures/wallet_snapshot_v1_empty.hex b/tests/fixtures/wallet_snapshot_v1_empty.hex new file mode 100644 index 0000000..624ddd1 --- /dev/null +++ b/tests/fixtures/wallet_snapshot_v1_empty.hex @@ -0,0 +1 @@ +aca6586a0cf05bd831f2501e7b4aea550da6562d1c7d4b196cb0c7b01d743fbc6116a902379c723816d5ff3fdd14e2a86fba77cbce6b3cd9c32b8ff34c0000004c0000000000000000000000 diff --git a/watchdog/README.md b/watchdog/README.md new file mode 100644 index 0000000..3fedeb3 --- /dev/null +++ b/watchdog/README.md @@ -0,0 +1,13 @@ +# Watchdog (Lua) + +Off-chain sidecar that compares the sequencer's finalized SSZ snapshot to state from the canonical Cartesi Machine. + +**Documentation:** [`docs/watchdog/operator-deployment.md`](../docs/watchdog/operator-deployment.md) (Sepolia / mainnet) · [`docs/watchdog/getting-started.md`](../docs/watchdog/getting-started.md) (local devnet) · [`docs/watchdog/README.md`](../docs/watchdog/README.md) (architecture) + +```bash +just watchdog-lua-deps # .deps/lua/lcurl.so (needs libcurl + liblua5.4-dev) +just test-watchdog # unit tests (mocked HTTP; no lcurl required) +just devnet-for-watchdog # local Anvil + sequencer-devnet (prints WATCHDOG_* env) +``` + +Host packages and build errors: [`docs/watchdog/README.md`](../docs/watchdog/README.md#host-dependencies-watchdog-lua-deps). diff --git a/watchdog/http.lua b/watchdog/http.lua index b821a03..9eb53c6 100644 --- a/watchdog/http.lua +++ b/watchdog/http.lua @@ -1,91 +1,19 @@ -- (c) Cartesi and individual authors (see AUTHORS) -- SPDX-License-Identifier: Apache-2.0 (see LICENSE) -local http = {} - -local function shell_quote(value) - return "'" .. string.gsub(value, "'", "'\\''") .. "'" -end - ---- HTTP client backed by the `curl` binary (for dev shells without lua-curl). -function http.new_cli() - local client = {} +--- HTTP client via in-tree lua-curl / lcurl (libcurl on the host). - local function run_curl(method, url, body, headers) - local cmd = { - "curl", - "-sS", - "-w", - "%{http_code}", - "-X", - method, - } - if body then - table.insert(cmd, "--data-binary") - table.insert(cmd, shell_quote(body)) - end - table.insert(cmd, shell_quote(url)) - for key, value in pairs(headers or {}) do - table.insert(cmd, "-H") - table.insert(cmd, shell_quote(key .. ": " .. value)) - end +local http = {} - local shell_cmd = table.concat(cmd, " ") .. " 2>&1" - local handle = io.popen(shell_cmd, "r") - if not handle then - return nil, "failed to start curl" +local function parse_response_headers(header_lines) + local headers = {} + for _, line in ipairs(header_lines) do + local name, value = line:match("^([^:]+):%s*(.+)$") + if name and value then + headers[name:lower()] = value end - local output = handle:read("*a") - local ok_exit, exit_type, exit_code = handle:close() - if not output or output == "" then - return nil, "empty curl response" - end - local status_line = output:match("(%d%d%d)$") - if not status_line then - return nil, string.format( - "curl response missing status trailer (exit=%s/%s): %s", - tostring(exit_type), - tostring(exit_code), - output - ) - end - local body_text = output:sub(1, #output - #status_line) - local status = tonumber(status_line) - if not ok_exit and status == 0 then - return nil, string.format("curl failed: %s", output) - end - return { - status = status, - body = body_text, - headers = {}, - } - end - - function client.post(_self, url, body, headers) - return run_curl("POST", url, body, headers) end - - function client.get(_self, url, headers) - return run_curl("GET", url, nil, headers) - end - - return client -end - -function http.new_auto() - local ok = pcall(function() - require("cURL") - end) - if ok then - return http.new_curl() - end - ok = pcall(function() - require("lcurl") - end) - if ok then - return http.new_curl() - end - return http.new_cli() + return headers end function http.new_curl() @@ -94,28 +22,39 @@ function http.new_curl() ok, curl = pcall(require, "lcurl") end if not ok then - error("lua-curl binding not found; install lua-curl/lcurl or inject an http adapter") + error( + "lua-curl binding not found; run: just watchdog-lua-deps " + .. "(requires libcurl dev headers on the host)" + ) end local client = {} - function client.post(_self, url, body, headers) + local function perform_request(opts) local chunks = {} + local header_lines = {} local header_list = {} - for key, value in pairs(headers or {}) do + for key, value in pairs(opts.headers or {}) do table.insert(header_list, key .. ": " .. value) end local easy = curl.easy({ - url = url, - post = true, - postfields = body, + url = opts.url, + post = opts.post, + postfields = opts.postfields, httpheader = header_list, timeout = 30, writefunction = function(chunk) table.insert(chunks, chunk) return #chunk end, + headerfunction = function(line) + local trimmed = line:gsub("\r?\n$", "") + if trimmed ~= "" then + table.insert(header_lines, trimmed) + end + return #line + end, }) local ok_perform, err = pcall(function() @@ -131,45 +70,31 @@ function http.new_curl() return { status = status, body = table.concat(chunks), - headers = {}, + headers = parse_response_headers(header_lines), } end - function client.get(_self, url, headers) - local chunks = {} - local header_list = {} - for key, value in pairs(headers or {}) do - table.insert(header_list, key .. ": " .. value) - end - - local easy = curl.easy({ + function client.post(_self, url, body, headers) + return perform_request({ url = url, - httpheader = header_list, - timeout = 30, - writefunction = function(chunk) - table.insert(chunks, chunk) - return #chunk - end, + post = true, + postfields = body, + headers = headers, }) + end - local ok_perform, err = pcall(function() - easy:perform() - end) - if not ok_perform then - easy:close() - return nil, tostring(err) - end - - local status = easy:getinfo_response_code() - easy:close() - return { - status = status, - body = table.concat(chunks), - headers = {}, - } + function client.get(_self, url, headers) + return perform_request({ + url = url, + headers = headers, + }) end return client end +function http.new() + return http.new_curl() +end + return http diff --git a/watchdog/json.lua b/watchdog/json.lua new file mode 100644 index 0000000..005f423 --- /dev/null +++ b/watchdog/json.lua @@ -0,0 +1,12 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +--- Watchdog JSON facade (pure Lua; see third_party/json.lua). + +local json = {} + +function json.new() + return require("watchdog.third_party.json") +end + +return json diff --git a/watchdog/l1.lua b/watchdog/l1_reader.lua similarity index 81% rename from watchdog/l1.lua rename to watchdog/l1_reader.lua index 5db728a..0eb59d0 100644 --- a/watchdog/l1.lua +++ b/watchdog/l1_reader.lua @@ -3,11 +3,11 @@ local abi = require("watchdog.abi") -local l1 = {} +local l1_reader = {} -l1.INPUT_ADDED_TOPIC = "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98" +l1_reader.INPUT_ADDED_TOPIC = "0xc05d337121a6e8605c6ec0b72aa29c4210ffe6e5b9cefdd6a7058188a8f66f98" -l1.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES = { +l1_reader.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES = { "-32005", "-32600", "-32602", @@ -42,7 +42,7 @@ local function log_order_key(log) } end -function l1.sort_logs(logs) +function l1_reader.sort_logs(logs) table.sort(logs, function(a, b) local ak = log_order_key(a) local bk = log_order_key(b) @@ -57,14 +57,14 @@ function l1.sort_logs(logs) return logs end -function l1.fetch_logs_partitioned(rpc, params) +function l1_reader.fetch_logs_partitioned(rpc, params) assert(type(rpc) == "table" and type(rpc.get_logs) == "function", "rpc.get_logs is required") assert(type(params) == "table", "params are required") local start_block = assert(params.start_block, "start_block is required") local end_block = assert(params.end_block, "end_block is required") - local codes = params.long_block_range_error_codes or l1.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES - local input_added_topic = params.input_added_topic or l1.INPUT_ADDED_TOPIC + local codes = params.long_block_range_error_codes or l1_reader.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES + local input_added_topic = params.input_added_topic or l1_reader.INPUT_ADDED_TOPIC local function go(from_block, to_block) local logs, err = rpc:get_logs({ @@ -105,10 +105,10 @@ function l1.fetch_logs_partitioned(rpc, params) if not logs then return nil, err end - return l1.sort_logs(logs) + return l1_reader.sort_logs(logs) end -function l1.decode_and_validate_log(log) +function l1_reader.decode_and_validate_log(log) local decoded = abi.decode_input_added_log(log) local block_number = hex_quantity_to_number(log.blockNumber, "blockNumber") if decoded.block_number ~= block_number then @@ -121,17 +121,17 @@ function l1.decode_and_validate_log(log) return decoded end -function l1.fetch_inputs(rpc, params) - local logs, err = l1.fetch_logs_partitioned(rpc, params) +function l1_reader.fetch_inputs(rpc, params) + local logs, err = l1_reader.fetch_logs_partitioned(rpc, params) if not logs then return nil, err end local inputs = {} for _, log in ipairs(logs) do - table.insert(inputs, l1.decode_and_validate_log(log)) + table.insert(inputs, l1_reader.decode_and_validate_log(log)) end return inputs end -return l1 +return l1_reader diff --git a/watchdog/machine.lua b/watchdog/machine.lua deleted file mode 100644 index 62bdc87..0000000 --- a/watchdog/machine.lua +++ /dev/null @@ -1,43 +0,0 @@ --- (c) Cartesi and individual authors (see AUTHORS) --- SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -local machine = {} - -function machine.new(binding) - assert(type(binding) == "table", "Cartesi Machine binding is required") - assert(type(binding.load_snapshot) == "function", "binding.load_snapshot is required") - assert(type(binding.feed_input) == "function", "binding.feed_input is required") - assert(type(binding.inspect_state) == "function", "binding.inspect_state is required") - assert(type(binding.save_snapshot) == "function", "binding.save_snapshot is required") - - local driver = { binding = binding } - - function driver:load(path) - return self.binding.load_snapshot(path) - end - - function driver:feed_inputs(instance, inputs) - for _, input in ipairs(inputs) do - -- Do not classify payload tags here. The scheduler inside the CM owns - -- direct-input vs batch-submission semantics. - local ok, err = self.binding.feed_input(instance, input) - if not ok then - return nil, err - end - end - return true - end - - function driver:inspect_state(instance) - return self.binding.inspect_state(instance) - end - - function driver:save(instance, path) - assert(type(self.binding.save_snapshot) == "function", "binding.save_snapshot is required") - return self.binding.save_snapshot(instance, path) - end - - return driver -end - -return machine diff --git a/watchdog/machine_cartesi.lua b/watchdog/machine_cartesi.lua new file mode 100644 index 0000000..d55e929 --- /dev/null +++ b/watchdog/machine_cartesi.lua @@ -0,0 +1,196 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +--- In-process Cartesi Machine binding via the `cartesi` Lua module (ships with cartesi-machine). + +local machine_runner = require("watchdog.machine_runner") + +local machine_cartesi = {} + +local STATE_INSPECT_QUERY = "state" +local MAX_MCYCLE = (1 << 53) - 1 + +local function require_cartesi() + local ok, cartesi = pcall(require, "cartesi") + if not ok then + error( + "cartesi Lua module is required; ensure cartesi-machine is installed and LUA_PATH includes its scripts" + ) + end + return cartesi +end + +local function run_loop(machine, cartesi, handlers) + while true do + machine:run(MAX_MCYCLE) + if machine:read_reg("iflags_H") ~= 0 then + local exit_code = machine:read_reg("htif_tohost_data") >> 1 + return nil, "machine halted with exit code " .. tostring(exit_code) + end + if machine:read_reg("iflags_Y") ~= 0 then + local cmd, reason, data = machine:receive_cmio_request() + local err = handlers.on_manual(cmd, reason, data) + if err then + return nil, err + end + if machine:read_reg("iflags_Y") ~= 0 then + return true + end + elseif machine:read_reg("iflags_X") ~= 0 then + local cmd, reason, data = machine:receive_cmio_request() + handlers.on_automatic(cmd, reason, data) + end + end +end + +local function advance_inputs_on_machine(machine, cartesi, inputs) + local queue = {} + for _, input in ipairs(inputs) do + table.insert(queue, input) + end + local next_index = 0 + local outputs_done = true + + local handlers = { + on_manual = function(cmd, reason, data) + if reason == cartesi.CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION then + return "CMIO exception: " .. tostring(data) + end + if next_index < #queue then + if reason ~= cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED + and reason ~= cartesi.CMIO_YIELD_MANUAL_REASON_RX_REJECTED + then + return "unexpected manual yield before feeding input" + end + next_index = next_index + 1 + local raw = queue[next_index].raw_input + if type(raw) ~= "string" then + return "input.raw_input is required" + end + machine:send_cmio_response(cartesi.CMIO_YIELD_REASON_ADVANCE_STATE, raw) + outputs_done = false + return nil + end + if not outputs_done then + if reason == cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED + or reason == cartesi.CMIO_YIELD_MANUAL_REASON_RX_REJECTED + then + outputs_done = true + end + return nil + end + return nil + end, + on_automatic = function(_cmd, reason, data) + if not outputs_done and reason == cartesi.CMIO_YIELD_AUTOMATIC_REASON_TX_OUTPUT then + return + end + if not outputs_done and reason == cartesi.CMIO_YIELD_AUTOMATIC_REASON_TX_REPORT then + return + end + end, + } + + local ok, err = run_loop(machine, cartesi, handlers) + if not ok then + return nil, err + end + if next_index < #queue then + return nil, "machine stopped before all inputs were fed" + end + return true +end + +local function inspect_on_machine(machine, cartesi) + local reports = {} + local query_sent = false + local query_done = false + + local handlers = { + on_manual = function(_cmd, reason, _data) + if not query_sent then + machine:send_cmio_response(cartesi.CMIO_YIELD_REASON_INSPECT_STATE, STATE_INSPECT_QUERY) + query_sent = true + return nil + end + if reason == cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED + or reason == cartesi.CMIO_YIELD_MANUAL_REASON_RX_REJECTED + then + query_done = true + end + return nil + end, + on_automatic = function(_cmd, reason, data) + if query_sent and not query_done and reason == cartesi.CMIO_YIELD_AUTOMATIC_REASON_TX_REPORT then + table.insert(reports, data) + end + end, + } + + local ok, err = run_loop(machine, cartesi, handlers) + if not ok then + return nil, err + end + if #reports == 0 then + return nil, "inspect produced no report" + end + return reports[1] +end + +function machine_cartesi.new(_opts) + local cartesi = require_cartesi() + local runtime_config = { + skip_root_hash_check = true, + skip_root_hash_store = true, + } + + local binding = {} + + function binding.load_snapshot(snapshot_dir) + assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") + local ok, machine = pcall(function() + return cartesi.machine():load(snapshot_dir, runtime_config) + end) + if not ok then + return nil, tostring(machine) + end + return { + cartesi = cartesi, + machine = machine, + reference_block = 0, + } + end + + function binding.advance_inputs(instance, inputs, range) + assert(type(instance) == "table" and instance.machine, "machine instance is required") + if range.from_block > range.to_block then + return true + end + return advance_inputs_on_machine(instance.machine, instance.cartesi, inputs) + end + + function binding.inspect_state(instance) + assert(type(instance) == "table" and instance.machine, "machine instance is required") + return inspect_on_machine(instance.machine, instance.cartesi) + end + + function binding.store_snapshot(instance, snapshot_dir) + assert(type(instance) == "table" and instance.machine, "machine instance is required") + assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") + local ok, err = pcall(function() + instance.machine:store(snapshot_dir) + end) + if not ok then + return nil, tostring(err) + end + return true + end + + return machine_runner.new(binding) +end + +machine_cartesi._private = { + STATE_INSPECT_QUERY = STATE_INSPECT_QUERY, +} + +return machine_cartesi diff --git a/watchdog/machine_cli.lua b/watchdog/machine_cli.lua index ac73a36..0b64583 100644 --- a/watchdog/machine_cli.lua +++ b/watchdog/machine_cli.lua @@ -1,7 +1,7 @@ -- (c) Cartesi and individual authors (see AUTHORS) -- SPDX-License-Identifier: Apache-2.0 (see LICENSE) -local machine = require("watchdog.machine") +local machine_runner = require("watchdog.machine_runner") local machine_cli = {} @@ -148,7 +148,17 @@ function machine_cli.new(opts) return report end - function binding.save_snapshot(instance, snapshot_dir) + function binding.advance_inputs(instance, inputs, _range) + for _, input in ipairs(inputs or {}) do + local ok, err = binding.feed_input(instance, input) + if not ok then + return nil, err + end + end + return true + end + + function binding.store_snapshot(instance, snapshot_dir) assert(type(instance) == "table", "machine instance is required") assert(type(snapshot_dir) == "string" and snapshot_dir ~= "", "snapshot_dir is required") @@ -165,7 +175,7 @@ function machine_cli.new(opts) return run_command(command) end - return machine.new(binding) + return machine_runner.new(binding) end machine_cli._private = { diff --git a/watchdog/machine_runner.lua b/watchdog/machine_runner.lua new file mode 100644 index 0000000..04a72a4 --- /dev/null +++ b/watchdog/machine_runner.lua @@ -0,0 +1,87 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +--- Cartesi Machine driver: load, incremental advance, inspect, dump. + +local machine_runner = {} + +local function assert_contiguous_advance(instance, range) + assert(type(range) == "table", "range is required") + assert(type(range.from_block) == "number", "range.from_block is required") + assert(type(range.to_block) == "number", "range.to_block is required") + if range.from_block > range.to_block then + error("advance range is empty or inverted") + end + local expected_from = (instance.reference_block or 0) + 1 + if range.from_block ~= expected_from then + error(string.format( + "non-contiguous advance: expected from_block %d, got %d", + expected_from, + range.from_block + )) + end +end + +function machine_runner.new(binding) + assert(type(binding) == "table", "Cartesi Machine binding is required") + assert(type(binding.load_snapshot) == "function", "binding.load_snapshot is required") + assert(type(binding.advance_inputs) == "function", "binding.advance_inputs is required") + assert(type(binding.inspect_state) == "function", "binding.inspect_state is required") + assert(type(binding.store_snapshot) == "function", "binding.store_snapshot is required") + + local driver = { binding = binding } + + function driver:load(path, reference_block) + local instance, err = self.binding.load_snapshot(path) + if not instance then + return nil, err + end + instance.reference_block = reference_block or instance.reference_block or 0 + return instance + end + + function driver:advance(instance, inputs, range) + assert_contiguous_advance(instance, range) + local ok, err = self.binding.advance_inputs(instance, inputs or {}, range) + if not ok then + return nil, err + end + instance.reference_block = range.to_block + return true + end + + function driver:inspect(instance) + return self.binding.inspect_state(instance) + end + + function driver:dump(instance, path, reference_block) + assert(type(reference_block) == "number", "reference_block is required for dump") + if instance.reference_block ~= reference_block then + error(string.format( + "dump reference_block %d does not match instance reference_block %d", + reference_block, + instance.reference_block or -1 + )) + end + return self.binding.store_snapshot(instance, path) + end + + -- Legacy helpers used by machine_cli tests and older unit tests. + function driver:feed_inputs(instance, inputs) + local from = (instance.reference_block or 0) + 1 + return self:advance(instance, inputs, { from_block = from, to_block = from }) + end + + function driver:inspect_state(instance) + return self:inspect(instance) + end + + function driver:save(instance, path) + assert(instance.reference_block ~= nil, "cannot dump before advance establishes reference_block") + return self:dump(instance, path, instance.reference_block) + end + + return driver +end + +return machine_runner diff --git a/watchdog/main.lua b/watchdog/main.lua index 2c93625..1084fe5 100644 --- a/watchdog/main.lua +++ b/watchdog/main.lua @@ -5,25 +5,22 @@ package.path = "./?.lua;./?/init.lua;" .. package.path local config = require("watchdog.config") local http_mod = require("watchdog.http") +local json_mod = require("watchdog.json") local jsonrpc = require("watchdog.jsonrpc") -local machine_cli = require("watchdog.machine_cli") +local machine_cartesi = require("watchdog.machine_cartesi") local retry = require("watchdog.retry") local runner = require("watchdog.runner") -local sequencer_mod = require("watchdog.sequencer") +local sequencer_reader = require("watchdog.sequencer_reader") -local function load_json() - local ok, cjson = pcall(require, "cjson") - if ok then - return cjson +local function prepend_deps_cpath() + local deps = os.getenv("WATCHDOG_LUA_DEPS") + if deps and deps ~= "" then + package.cpath = deps .. "/?.so;" .. package.cpath end - error("lua-cjson is required for watchdog runtime") end -local function load_machine(cfg) - return machine_cli.new({ - executable = cfg.cm_executable, - work_dir = cfg.cm_work_dir, - }) +local function load_machine(_cfg) + return machine_cartesi.new() end local function run_once(cfg, deps) @@ -34,16 +31,17 @@ local function run_once(cfg, deps) end local function main() + prepend_deps_cpath() local cfg = config.load() - local http = http_mod.new_auto() - local json = load_json() + local http = http_mod.new() + local json = json_mod.new() local deps = { http = http, rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), machine = load_machine(cfg), } if cfg.sequencer_url then - deps.sequencer = sequencer_mod.new(http, json, cfg.sequencer_url) + deps.sequencer = sequencer_reader.new(http, json, cfg.sequencer_url) end repeat diff --git a/watchdog/runner.lua b/watchdog/runner.lua index 6fc60bf..2d23fc3 100644 --- a/watchdog/runner.lua +++ b/watchdog/runner.lua @@ -4,7 +4,7 @@ local alarm = require("watchdog.alarm") local checkpoint = require("watchdog.checkpoint") local compare = require("watchdog.compare") -local l1 = require("watchdog.l1") +local l1_reader = require("watchdog.l1_reader") local runner = {} @@ -43,7 +43,7 @@ local function fetch_inputs(cfg, deps, from_block, to_block) end local rpc = require_dep(deps, "rpc") - return l1.fetch_inputs(rpc, { + return l1_reader.fetch_inputs(rpc, { start_block = from_block, end_block = to_block, input_box_address = cfg.input_box_address, @@ -82,82 +82,72 @@ local function target_safe_block(cfg, deps) return nil, "target safe block is not configured" end -function runner.run_once(cfg, deps) - deps = deps or {} +local function advance_compare_and_checkpoint( + cfg, + deps, + loaded, + inputs, + safe_block_prev, + safe_block_next, + with_compare +) local checkpoint_mod = deps.checkpoint or checkpoint - local sequencer = require_dep(deps, "sequencer") local machine = require_dep(deps, "machine") - step(deps, "load checkpoint or bootstrap CM snapshot") - local loaded = load_checkpoint(cfg, checkpoint_mod) - step(deps, "fetch sequencer GET /get_state") - local sequencer_state, state_err = sequencer:get_state() - if not sequencer_state then - return nil, state_err - end - - local safe_block_prev = loaded.safe_block or 0 - local safe_block_next = sequencer_state.safe_block - step(deps, string.format( - "check safe_block monotonicity (prev=%s next=%s)", - tostring(safe_block_prev), - tostring(safe_block_next) - )) - if safe_block_next < safe_block_prev then - local payload = { - kind = "safe_block_regressed", - previous_safe_block = safe_block_prev, - sequencer_safe_block = safe_block_next, - } - send_alarm(cfg, deps, payload) - return nil, payload - end - - step(deps, string.format( - "fetch L1 InputAdded logs for blocks %s..%s", - tostring(safe_block_prev + 1), - tostring(safe_block_next) - )) - local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) - if not inputs then - return nil, input_err - end - step(deps, "load CM snapshot directory") - local instance, load_err = machine:load(loaded.snapshot_dir) + local instance, load_err = machine:load(loaded.snapshot_dir, safe_block_prev) if not instance then return nil, load_err end - step(deps, string.format("feed %d decoded inputs into CM", #inputs)) - local fed, feed_err = machine:feed_inputs(instance, inputs) - if not fed then - return nil, feed_err + if safe_block_next > safe_block_prev then + step(deps, string.format("advance CM for blocks %d..%d", safe_block_prev + 1, safe_block_next)) + local advanced, advance_err = machine:advance(instance, inputs, { + from_block = safe_block_prev + 1, + to_block = safe_block_next, + }) + if not advanced then + return nil, advance_err + end + else + instance.reference_block = safe_block_prev end - step(deps, "run CM inspect (state query)") - local cm_state, inspect_err = machine:inspect_state(instance) - if not cm_state then - return nil, inspect_err - end + if with_compare then + step(deps, "run CM inspect (state query)") + local cm_state, inspect_err = machine:inspect(instance) + if not cm_state then + return nil, inspect_err + end - step(deps, "compare sequencer state bytes against CM inspect report") - local equal, mismatch_offset = compare.raw_equal(sequencer_state.state, cm_state) - if not equal then - local payload = { - kind = "state_mismatch", - previous_safe_block = safe_block_prev, - sequencer_safe_block = safe_block_next, - mismatch_offset = mismatch_offset, - } - send_alarm(cfg, deps, payload) - return nil, payload + local sequencer = require_dep(deps, "sequencer") + step(deps, "fetch sequencer GET /finalized_state") + local finalized, state_err = sequencer:get_finalized_state() + if not finalized then + return nil, state_err + end + if finalized.not_modified then + return nil, "finalized state unexpectedly returned 304 during compare" + end + + step(deps, "compare finalized SSZ bytes against CM inspect report") + local equal, mismatch_offset = compare.raw_equal(finalized.state, cm_state) + if not equal then + local payload = { + kind = "state_mismatch", + previous_safe_block = safe_block_prev, + sequencer_inclusion_block = finalized.inclusion_block, + mismatch_offset = mismatch_offset, + } + send_alarm(cfg, deps, payload) + return nil, payload + end end if safe_block_next > safe_block_prev then step(deps, "persist new manifest-backed checkpoint") local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) - return machine:save(instance, snapshot_dir) + return machine:dump(instance, snapshot_dir, safe_block_next) end, { created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), cm_image_hash = cfg.cm_image_hash, @@ -166,10 +156,9 @@ function runner.run_once(cfg, deps) return nil, write_err end else - step(deps, "safe_block unchanged; skip checkpoint write") + step(deps, "reference block unchanged; skip checkpoint write") end - step(deps, "compare pass complete") return { ok = true, previous_safe_block = safe_block_prev, @@ -178,19 +167,98 @@ function runner.run_once(cfg, deps) } end +--- Shared path: L1 fetch → CM advance/inspect/compare → optional checkpoint write. +local function run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next, with_compare) + step(deps, string.format( + "fetch L1 InputAdded logs for blocks %s..%s", + tostring(safe_block_prev + 1), + tostring(safe_block_next) + )) + local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) + if not inputs then + return nil, input_err + end + + step(deps, string.format("feed %d decoded inputs into CM", #inputs)) + return advance_compare_and_checkpoint( + cfg, + deps, + loaded, + inputs, + safe_block_prev, + safe_block_next, + with_compare + ) +end + +local function skip_result(safe_block_prev, safe_block_next, reason) + return { + ok = true, + skipped = true, + skip_reason = reason, + previous_safe_block = safe_block_prev, + safe_block = safe_block_next, + input_count = 0, + } +end + +function runner.run_once(cfg, deps) + deps = deps or {} + local checkpoint_mod = deps.checkpoint or checkpoint + + step(deps, "load checkpoint or bootstrap CM snapshot") + local loaded = load_checkpoint(cfg, checkpoint_mod) + + local safe_block_prev = loaded.safe_block or 0 + local sequencer = require_dep(deps, "sequencer") + step(deps, "fetch sequencer GET /finalized_state/inclusion_block") + local head, head_err = sequencer:get_finalized_inclusion_block() + if not head then + return nil, head_err + end + + local safe_block_next = head.inclusion_block + step(deps, string.format( + "check inclusion_block monotonicity (prev=%s next=%s)", + tostring(safe_block_prev), + tostring(safe_block_next) + )) + if safe_block_next < safe_block_prev then + local payload = { + kind = "inclusion_block_regressed", + previous_safe_block = safe_block_prev, + sequencer_inclusion_block = safe_block_next, + } + send_alarm(cfg, deps, payload) + return nil, payload + end + + if safe_block_next <= safe_block_prev then + step(deps, "finalized inclusion_block unchanged; skip L1/CM/compare cycle") + return skip_result(safe_block_prev, safe_block_next, "finalized_unchanged") + end + + local result, err = run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next, true) + if result then + step(deps, "compare pass complete") + end + return result, err +end + function runner.advance_checkpoint_once(cfg, deps) deps = deps or {} local checkpoint_mod = deps.checkpoint or checkpoint - local machine = require_dep(deps, "machine") step(deps, "load checkpoint or bootstrap CM snapshot") local loaded = load_checkpoint(cfg, checkpoint_mod) local safe_block_prev = loaded.safe_block or 0 + step(deps, "resolve target safe block") local safe_block_next, safe_err = target_safe_block(cfg, deps) if not safe_block_next then return nil, safe_err end + step(deps, string.format( "check safe_block monotonicity (prev=%s next=%s)", tostring(safe_block_prev), @@ -204,50 +272,16 @@ function runner.advance_checkpoint_once(cfg, deps) } end - step(deps, string.format( - "fetch L1 InputAdded logs for blocks %s..%s", - tostring(safe_block_prev + 1), - tostring(safe_block_next) - )) - local inputs, input_err = fetch_inputs(cfg, deps, safe_block_prev + 1, safe_block_next) - if not inputs then - return nil, input_err - end - - step(deps, "load CM snapshot directory") - local instance, load_err = machine:load(loaded.snapshot_dir) - if not instance then - return nil, load_err + if safe_block_next <= safe_block_prev then + step(deps, "L1 safe block unchanged; skip advance cycle") + return skip_result(safe_block_prev, safe_block_next, "safe_unchanged") end - step(deps, string.format("feed %d decoded inputs into CM", #inputs)) - local fed, feed_err = machine:feed_inputs(instance, inputs) - if not fed then - return nil, feed_err + local result, err = run_pass(cfg, deps, loaded, safe_block_prev, safe_block_next, false) + if result then + step(deps, "advance pass complete") end - - if safe_block_next > safe_block_prev then - step(deps, "persist new manifest-backed checkpoint") - local written, write_err = checkpoint_mod.write(cfg.checkpoint_dir, safe_block_next, function(snapshot_dir) - return machine:save(instance, snapshot_dir) - end, { - created_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), - cm_image_hash = cfg.cm_image_hash, - }) - if not written then - return nil, write_err - end - else - step(deps, "safe_block unchanged; skip checkpoint write") - end - - step(deps, "advance pass complete") - return { - ok = true, - previous_safe_block = safe_block_prev, - safe_block = safe_block_next, - input_count = #inputs, - } + return result, err end return runner diff --git a/watchdog/sequencer.lua b/watchdog/sequencer.lua deleted file mode 100644 index 93b9ddb..0000000 --- a/watchdog/sequencer.lua +++ /dev/null @@ -1,48 +0,0 @@ --- (c) Cartesi and individual authors (see AUTHORS) --- SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -local compare = require("watchdog.compare") - -local sequencer = {} - -function sequencer.new(http, json, base_url) - assert(type(http) == "table" and type(http.get) == "function", "http.get is required") - assert(type(json) == "table" and type(json.decode) == "function", "json.decode is required") - assert(type(base_url) == "string" and base_url ~= "", "base_url is required") - - local client = { - http = http, - json = json, - base_url = base_url:gsub("/+$", ""), - } - - function client:get_state() - local response, err = self.http:get(self.base_url .. "/get_state") - if not response then - return nil, err - end - if response.status < 200 or response.status >= 300 then - return nil, "HTTP " .. tostring(response.status) - end - - local decoded - local ok_decode = pcall(function() - decoded = self.json.decode(response.body) - end) - if not ok_decode or type(decoded) ~= "table" then - return nil, "invalid get_state response JSON" - end - if type(decoded.safe_block) ~= "number" then - return nil, "safe_block must be a number" - end - local ok, validation_err = compare.assert_state_response(decoded.state) - if not ok then - return nil, validation_err - end - return decoded - end - - return client -end - -return sequencer diff --git a/watchdog/sequencer_reader.lua b/watchdog/sequencer_reader.lua new file mode 100644 index 0000000..b03f96c --- /dev/null +++ b/watchdog/sequencer_reader.lua @@ -0,0 +1,100 @@ +-- (c) Cartesi and individual authors (see AUTHORS) +-- SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +local compare = require("watchdog.compare") + +local sequencer_reader = {} + +local function parse_header_number(headers, name) + if type(headers) ~= "table" then + return nil, name .. " header missing" + end + local value = headers[name:lower()] + if value == nil then + return nil, name .. " header missing" + end + local number = tonumber(value) + if number == nil then + return nil, name .. " header must be numeric" + end + return number +end + +function sequencer_reader.new(http, json, base_url) + assert(type(http) == "table" and type(http.get) == "function", "http.get is required") + assert(type(json) == "table" and type(json.decode) == "function", "json.decode is required") + assert(type(base_url) == "string" and base_url ~= "", "base_url is required") + + local client = { + http = http, + json = json, + base_url = base_url:gsub("/+$", ""), + } + + function client:get_finalized_inclusion_block() + local response, err = self.http:get(self.base_url .. "/finalized_state/inclusion_block") + if not response then + return nil, err + end + if response.status < 200 or response.status >= 300 then + return nil, "HTTP " .. tostring(response.status) + end + + local decoded + local ok_decode = pcall(function() + decoded = self.json.decode(response.body) + end) + if not ok_decode or type(decoded) ~= "table" then + return nil, "invalid finalized inclusion_block response JSON" + end + if type(decoded.inclusion_block) ~= "number" then + return nil, "inclusion_block must be a number" + end + if type(decoded.l2_tx_index) ~= "number" then + return nil, "l2_tx_index must be a number" + end + return decoded + end + + function client:get_finalized_state(request_headers) + local response, err = self.http:get( + self.base_url .. "/finalized_state", + request_headers or {} + ) + if not response then + return nil, err + end + if response.status == 304 then + return { not_modified = true } + end + if response.status < 200 or response.status >= 300 then + return nil, "HTTP " .. tostring(response.status) + end + + local inclusion_block, inclusion_err = + parse_header_number(response.headers, "X-Inclusion-Block") + if not inclusion_block then + return nil, inclusion_err + end + local l2_tx_index, index_err = parse_header_number(response.headers, "X-L2-Tx-Index") + if not l2_tx_index then + return nil, index_err + end + + local ok, validation_err = compare.assert_state_response(response.body) + if not ok then + return nil, validation_err + end + + return { + inclusion_block = inclusion_block, + l2_tx_index = l2_tx_index, + state = response.body, + etag = response.headers and response.headers["etag"], + } + end + + return client +end + +return sequencer_reader diff --git a/watchdog/tests/drill_divergence.lua b/watchdog/tests/drill_divergence.lua index 6e22e60..cb86854 100644 --- a/watchdog/tests/drill_divergence.lua +++ b/watchdog/tests/drill_divergence.lua @@ -44,7 +44,7 @@ local payload = { log.banner("divergence-webhook-drill") log.step("divergence-webhook-drill", 1, 1, "POST state_mismatch payload (synthetic bytes)") -local http = http_mod.new_auto() +local http = http_mod.new() local ok, err = alarm.send_webhook(http, webhook_url, payload) if not ok then log.fail("divergence-webhook-drill", err) diff --git a/watchdog/tests/drill_webhook.lua b/watchdog/tests/drill_webhook.lua index b26a290..b52630d 100644 --- a/watchdog/tests/drill_webhook.lua +++ b/watchdog/tests/drill_webhook.lua @@ -28,23 +28,23 @@ local drills = { payload = { kind = "state_mismatch", previous_safe_block = 10, - sequencer_safe_block = 12, + sequencer_inclusion_block = 12, mismatch_offset = 4, run_id = "drill-state-mismatch", }, }, { - name = "safe_block_regressed", + name = "inclusion_block_regressed", payload = { - kind = "safe_block_regressed", + kind = "inclusion_block_regressed", previous_safe_block = 20, - sequencer_safe_block = 19, - run_id = "drill-safe-block-regressed", + sequencer_inclusion_block = 19, + run_id = "drill-inclusion-block-regressed", }, }, } -local http = http_mod.new_auto() +local http = http_mod.new() local failures = 0 log.banner("webhook-delivery-drills") diff --git a/watchdog/tests/e2e.lua b/watchdog/tests/e2e.lua index 7a799c1..1a65a9d 100644 --- a/watchdog/tests/e2e.lua +++ b/watchdog/tests/e2e.lua @@ -199,15 +199,20 @@ table.insert(scenarios, { local report, inspect_err = machine:inspect_state(instance) assert_true(report, "inspect failed: " .. tostring(inspect_err)) - log.step(scenario, 4, 4, "validate inspect report looks like wallet JSON state") + log.step(scenario, 4, 4, "validate inspect report is SSZ (not legacy JSON)") if report:find("inspect endpoint not implemented", 1, true) then return skip( scenario, "machine image dapp is stale; rebuild with: just canonical-build-machine-image" ) end - assert_true(report:sub(1, 1) == "{", "inspect report is not JSON object bytes: " .. report) - assert_true(report:find('"balances"', 1, true) ~= nil, "inspect report missing balances field") + if report:sub(1, 1) == "{" then + return skip( + scenario, + "machine image still returns JSON export_state; rebuild with: just canonical-build-machine-image" + ) + end + assert_true(#report >= 76, "inspect SSZ report too short: " .. tostring(#report)) log.info("inspect report bytes=" .. tostring(#report)) end, }) @@ -232,17 +237,14 @@ table.insert(scenarios, { local http_mod = require("watchdog.http") local jsonrpc = require("watchdog.jsonrpc") - local sequencer_mod = require("watchdog.sequencer") - local ok_json, cjson = pcall(require, "cjson") - if not ok_json then - return skip(scenario, "lua-cjson required for live sequencer compare") - end + local sequencer_reader = require("watchdog.sequencer_reader") + local json = require("watchdog.json").new() local checkpoint_dir = temp_dir("watchdog-e2e-compare") log.step(scenario, 1, 2, "prepare compare-mode deps (sequencer=" .. sequencer_url .. ")") log.step(scenario, 2, 2, "run compare runner against live sequencer + CM") - local http = http_mod.new_auto() + local http = http_mod.new() local cfg = { mode = "compare", sequencer_url = sequencer_url, @@ -262,8 +264,8 @@ table.insert(scenarios, { local step_no = 0 local result, err = runner.run_once(cfg, { http = http, - rpc = jsonrpc.new(http, cjson, cfg.l1_rpc_url), - sequencer = sequencer_mod.new(http, cjson, sequencer_url), + rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), + sequencer = sequencer_reader.new(http, json, sequencer_url), machine = machine_cli.new({ executable = cfg.cm_executable, work_dir = cfg.cm_work_dir, diff --git a/watchdog/tests/run.lua b/watchdog/tests/run.lua index 3df26aa..637f708 100644 --- a/watchdog/tests/run.lua +++ b/watchdog/tests/run.lua @@ -9,11 +9,11 @@ local checkpoint = require("watchdog.checkpoint") local compare = require("watchdog.compare") local config = require("watchdog.config") local jsonrpc = require("watchdog.jsonrpc") -local l1 = require("watchdog.l1") +local l1_reader = require("watchdog.l1_reader") local machine_cli = require("watchdog.machine_cli") local retry = require("watchdog.retry") local runner = require("watchdog.runner") -local sequencer = require("watchdog.sequencer") +local sequencer_reader = require("watchdog.sequencer_reader") local tests = {} @@ -49,37 +49,112 @@ test("sorts logs in L1 order", function() { blockNumber = "0x1", transactionIndex = "0x9", logIndex = "0x0" }, { blockNumber = "0x2", transactionIndex = "0x0", logIndex = "0x1" }, } - l1.sort_logs(logs) + l1_reader.sort_logs(logs) assert_eq(logs[1].blockNumber, "0x1") assert_eq(logs[2].logIndex, "0x1") assert_eq(logs[3].logIndex, "0x5") end) -test("partitions long block range errors", function() - local calls = {} - local rpc = {} - function rpc.get_logs(_self, filter) - table.insert(calls, { filter.from_block, filter.to_block, filter.input_added_topic }) - if filter.from_block == 1 and filter.to_block == 4 then - return nil, "RPC error -32005: query returned more than allowed" +local function load_partition_vector() + local json = require("watchdog.json").new() + local path = "tests/fixtures/l1_partition_vector.json" + local file, err = io.open(path, "rb") + if not file then + error("open " .. path .. ": " .. tostring(err)) + end + local body = file:read("*a") + file:close() + return json.decode(body) +end + +local function fail_lookup(fail_ranges) + local map = {} + for _, entry in ipairs(fail_ranges) do + map[entry.from .. ":" .. entry.to] = entry.message + end + return map +end + +local function load_wallet_snapshot_hex_fixture() + local path = "tests/fixtures/wallet_snapshot_v1_empty.hex" + local file, err = io.open(path, "rb") + if not file then + error("open " .. path .. ": " .. tostring(err)) + end + local hex = file:read("*a"):gsub("%s+", "") + file:close() + local bytes = {} + for i = 1, #hex, 2 do + table.insert(bytes, string.char(tonumber(hex:sub(i, i + 1), 16))) + end + return table.concat(bytes) +end + +test("wallet SSZ golden fixture loads for cross-stack parity", function() + local bytes = load_wallet_snapshot_hex_fixture() + assert(#bytes > 0, "golden fixture must not be empty") + -- Fixed prefix from WalletSnapshotV1 default config (see wallet_snapshot.rs tests). + assert_eq(bytes:byte(1), 0xac) + assert_eq(bytes:byte(2), 0xa6) +end) + +test("shared partition vector matches l1_reader bisect plan", function() + local vector = load_partition_vector() + local codes = vector.long_block_range_error_codes + + local defaults = l1_reader.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES + assert_eq(#codes, #defaults) + for i, code in ipairs(codes) do + assert_eq(code, defaults[i]) + end + + for _, scenario in ipairs(vector.scenarios) do + local calls = {} + local fails = fail_lookup(scenario.fail_ranges) + local rpc = {} + function rpc.get_logs(_self, filter) + table.insert(calls, { filter.from_block, filter.to_block }) + local message = fails[filter.from_block .. ":" .. filter.to_block] + if message then + return nil, message + end + return {} + end + + local logs, err = l1_reader.fetch_logs_partitioned(rpc, { + start_block = scenario.start_block, + end_block = scenario.end_block, + input_box_address = "0xinputbox", + app_address = "0x1111111111111111111111111111111111111111", + long_block_range_error_codes = codes, + }) + + if scenario.expect_ok then + assert(logs, scenario.name .. ": " .. tostring(err)) + else + assert_eq(logs, nil, scenario.name) + assert(type(err) == "string", scenario.name) + end + + assert_eq(#calls, #scenario.expect_calls, scenario.name .. " call count") + for i, expected in ipairs(scenario.expect_calls) do + assert_eq(calls[i][1], expected[1], scenario.name .. " call " .. i .. " from") + assert_eq(calls[i][2], expected[2], scenario.name .. " call " .. i .. " to") end - return {} end +end) - local logs, err = l1.fetch_logs_partitioned(rpc, { - start_block = 1, - end_block = 4, - input_box_address = "0xinputbox", - app_address = "0x1111111111111111111111111111111111111111", - }) +test("shared log sort vector matches l1_reader.sort_logs", function() + local vector = load_partition_vector() + local logs = vector.log_sort.unsorted + l1_reader.sort_logs(logs) - assert(logs, err) - assert_eq(#calls, 3) - assert_eq(calls[2][1], 1) - assert_eq(calls[2][2], 2) - assert_eq(calls[3][1], 3) - assert_eq(calls[3][2], 4) - assert_eq(calls[3][3], l1.INPUT_ADDED_TOPIC) + for i, expected in ipairs(vector.log_sort.expect_block_order) do + assert_eq(logs[i].blockNumber, expected, "block order at " .. i) + end + for i, expected in ipairs(vector.log_sort.expect_log_index_order) do + assert_eq(logs[i].logIndex, expected, "log index order at " .. i) + end end) test("jsonrpc get_logs builds InputAdded app filter", function() @@ -107,7 +182,7 @@ test("jsonrpc get_logs builds InputAdded app filter", function() app_address = "0x1111111111111111111111111111111111111111", from_block = 10, to_block = 12, - input_added_topic = l1.INPUT_ADDED_TOPIC, + input_added_topic = l1_reader.INPUT_ADDED_TOPIC, }) assert(logs, err) @@ -118,7 +193,7 @@ test("jsonrpc get_logs builds InputAdded app filter", function() assert_eq(filter.fromBlock, "0xa") assert_eq(filter.toBlock, "0xc") assert_eq(filter.address, "0x9999999999999999999999999999999999999999") - assert_eq(filter.topics[1], l1.INPUT_ADDED_TOPIC) + assert_eq(filter.topics[1], l1_reader.INPUT_ADDED_TOPIC) assert_eq( filter.topics[2], "0x0000000000000000000000001111111111111111111111111111111111111111" @@ -195,7 +270,7 @@ local function fake_cfg() input_box_address = "0xinputbox", app_address = "0x1111111111111111111111111111111111111111", input_added_topic = "0xtopic", - long_block_range_error_codes = l1.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES, + long_block_range_error_codes = l1_reader.DEFAULT_LONG_BLOCK_RANGE_ERROR_CODES, } end @@ -204,21 +279,33 @@ local function fake_machine(inspect_state) loaded_path = nil, fed_inputs = nil, } - function machine:load(path) + function machine:load(path, reference_block) self.loaded_path = path - return { path = path } + return { path = path, reference_block = reference_block or 0 } end - function machine:feed_inputs(_instance, inputs) + function machine:advance(_instance, inputs, range) self.fed_inputs = inputs + _instance.reference_block = range.to_block return true end - function machine.inspect_state(_self, _instance) + function machine:inspect(_self, _instance) return inspect_state end - function machine:save(_instance, snapshot_dir) + function machine:dump(_instance, snapshot_dir, reference_block) self.saved_snapshot_dir = snapshot_dir + _instance.reference_block = reference_block return true end + function machine:feed_inputs(instance, inputs) + local from = (instance.reference_block or 0) + 1 + return self:advance(instance, inputs, { from_block = from, to_block = from }) + end + function machine:inspect_state(_self, instance) + return self:inspect(_self, instance) + end + function machine:save(instance, snapshot_dir) + return self:dump(instance, snapshot_dir, instance.reference_block) + end return machine end @@ -246,9 +333,13 @@ test("runner happy path replays inputs and writes checkpoint", function() local result, err = runner.run_once(fake_cfg(), { checkpoint = checkpoint_mod, sequencer = { - get_state = function() + get_finalized_inclusion_block = function() + return { inclusion_block = 12, l2_tx_index = 0 } + end, + get_finalized_state = function() return { - safe_block = 12, + inclusion_block = 12, + l2_tx_index = 0, state = '{"ok":true}', } end, @@ -280,14 +371,20 @@ test("runner alarms on raw state mismatch", function() end, }, sequencer = { - get_state = function() + get_finalized_inclusion_block = function() + return { inclusion_block = 2, l2_tx_index = 0 } + end, + get_finalized_state = function() return { - safe_block = 1, + inclusion_block = 2, + l2_tx_index = 0, state = '{"a":1}', } end, }, - fetch_inputs = function() + fetch_inputs = function(from_block, to_block) + assert_eq(from_block, 2) + assert_eq(to_block, 2) return {} end, machine = fake_machine('{ "a": 1 }'), @@ -304,7 +401,36 @@ test("runner alarms on raw state mismatch", function() assert_eq(alarms[1].kind, "state_mismatch") end) -test("runner alarms on sequencer safe block regression", function() +test("runner skips compare cycle when finalized inclusion_block is unchanged", function() + local machine = fake_machine('{"ok":true}') + local result, err = runner.run_once(fake_cfg(), { + checkpoint = { + load = function(_dir) + return { snapshot_dir = "/tmp/snapshot", safe_block = 5 } + end, + }, + sequencer = { + get_finalized_inclusion_block = function() + return { inclusion_block = 5, l2_tx_index = 0 } + end, + get_finalized_state = function() + error("get_finalized_state must not run when inclusion_block is unchanged") + end, + }, + fetch_inputs = function() + error("fetch_inputs must not run when inclusion_block is unchanged") + end, + machine = machine, + }) + + assert(result, err) + assert_eq(result.skipped, true) + assert_eq(result.skip_reason, "finalized_unchanged") + assert_eq(result.safe_block, 5) + assert_eq(machine.fed_inputs, nil) +end) + +test("runner alarms on sequencer inclusion_block regression", function() local alarms = {} local result, err = runner.run_once(fake_cfg(), { checkpoint = { @@ -313,9 +439,13 @@ test("runner alarms on sequencer safe block regression", function() end, }, sequencer = { - get_state = function() + get_finalized_inclusion_block = function() + return { inclusion_block = 4, l2_tx_index = 0 } + end, + get_finalized_state = function() return { - safe_block = 4, + inclusion_block = 4, + l2_tx_index = 0, state = "{}", } end, @@ -329,40 +459,68 @@ test("runner alarms on sequencer safe block regression", function() assert_eq(result, nil) assert(type(err) == "table", "expected regression payload") - assert_eq(err.kind, "safe_block_regressed") + assert_eq(err.kind, "inclusion_block_regressed") assert_eq(#alarms, 1) end) -test("sequencer client validates generic state response", function() +test("sequencer client reads finalized inclusion_block", function() local http = {} function http.get(_self, url) - assert_eq(url, "http://sequencer/get_state") + assert_eq(url, "http://sequencer/finalized_state/inclusion_block") return { status = 200, - body = "body", + body = '{"inclusion_block":7,"l2_tx_index":3}', + headers = {}, } end local json = {} - function json.decode(_body) + function json.decode(body) return { - safe_block = 7, - state = "raw-state", + inclusion_block = 7, + l2_tx_index = 3, } end - local client = sequencer.new(http, json, "http://sequencer/") - local state, err = client:get_state() + local client = sequencer_reader.new(http, json, "http://sequencer/") + local head, err = client:get_finalized_inclusion_block() + assert(head, err) + assert_eq(head.inclusion_block, 7) + assert_eq(head.l2_tx_index, 3) +end) + +test("sequencer client reads finalized SSZ body and headers", function() + local http = {} + function http.get(_self, url, _headers) + assert_eq(url, "http://sequencer/finalized_state") + return { + status = 200, + body = "raw-state", + headers = { + ["x-inclusion-block"] = "9", + ["x-l2-tx-index"] = "1", + }, + } + end + local json = {} + function json.decode(_body) + error("unexpected JSON decode for finalized_state body") + end + + local client = sequencer_reader.new(http, json, "http://sequencer") + local state, err = client:get_finalized_state() assert(state, err) - assert_eq(state.safe_block, 7) + assert_eq(state.inclusion_block, 9) + assert_eq(state.l2_tx_index, 1) assert_eq(state.state, "raw-state") end) -test("sequencer client rejects invalid JSON", function() +test("sequencer client rejects invalid inclusion_block JSON", function() local http = {} function http.get(_self, _url) return { status = 200, body = "not-json", + headers = {}, } end local json = {} @@ -370,10 +528,10 @@ test("sequencer client rejects invalid JSON", function() error("decode failed") end - local client = sequencer.new(http, json, "http://sequencer") - local state, err = client:get_state() - assert_eq(state, nil) - assert_eq(err, "invalid get_state response JSON") + local client = sequencer_reader.new(http, json, "http://sequencer") + local head, err = client:get_finalized_inclusion_block() + assert_eq(head, nil) + assert_eq(err, "invalid finalized inclusion_block response JSON") end) test("advance runner fetches inputs and saves checkpoint without sequencer", function() diff --git a/watchdog/tests/run_compare_once.lua b/watchdog/tests/run_compare_once.lua index d3ee518..838aa42 100644 --- a/watchdog/tests/run_compare_once.lua +++ b/watchdog/tests/run_compare_once.lua @@ -14,15 +14,11 @@ end local config = require("watchdog.config") local http_mod = require("watchdog.http") local jsonrpc = require("watchdog.jsonrpc") -local machine_cli = require("watchdog.machine_cli") +local machine_cartesi = require("watchdog.machine_cartesi") local runner = require("watchdog.runner") -local sequencer_mod = require("watchdog.sequencer") +local sequencer_reader = require("watchdog.sequencer_reader") -local ok_json, cjson = pcall(require, "cjson") -if not ok_json then - io.stderr:write("lua-cjson is required\n") - os.exit(1) -end +local json = require("watchdog.json").new() local function getenv_table() return setmetatable({}, { @@ -38,15 +34,12 @@ if cfg.mode ~= "compare" then os.exit(1) end -local http = http_mod.new_auto() +local http = http_mod.new() local deps = { http = http, - rpc = jsonrpc.new(http, cjson, cfg.l1_rpc_url), - sequencer = sequencer_mod.new(http, cjson, cfg.sequencer_url), - machine = machine_cli.new({ - executable = cfg.cm_executable, - work_dir = cfg.cm_work_dir, - }), + rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), + sequencer = sequencer_reader.new(http, json, cfg.sequencer_url), + machine = machine_cartesi.new(), } local result, err = runner.run_once(cfg, deps) diff --git a/watchdog/third_party/json.lua b/watchdog/third_party/json.lua new file mode 100644 index 0000000..dc06f90 --- /dev/null +++ b/watchdog/third_party/json.lua @@ -0,0 +1,379 @@ +-- +-- json.lua — vendored pure-Lua JSON (RPC + webhook payloads only). +-- UPSTREAM: https://github.com/rxi/json.lua @ 0.1.2 (commit on master, MIT) +-- See LICENSE note in file header below. + +-- +-- Copyright (c) 2020 rxi +-- +-- 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. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + if str:sub(i, i) == "]" then + i = i + 1 + break + end + x, i = parse(str, i) + res[n] = x + n = n + 1 + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + if str:sub(i, i) == "}" then + i = i + 1 + break + end + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + val, i = parse(str, i) + res[key] = val + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/watchdog/third_party/lua-curl/UPSTREAM b/watchdog/third_party/lua-curl/UPSTREAM new file mode 100644 index 0000000..ee9ee5a --- /dev/null +++ b/watchdog/third_party/lua-curl/UPSTREAM @@ -0,0 +1,14 @@ +Lua-cURLv3 (lcurl native binding; libcurl must be installed on the host). + +URL: https://github.com/Lua-cURL/Lua-cURLv3 +Commit: 9f8b6dba8b5ef1b26309a571ae75cda4034279e5 +License: MIT (see upstream LICENSE) + +Sources are extracted here by `scripts/watchdog-lua-deps.sh` (pinned tarball). +We build only the lcurl C module into `.deps/lua/lcurl.so` — not the optional +`cURL/*.lua` helper layer. + +To populate manually: + + curl -fsSL https://github.com/Lua-cURL/Lua-cURLv3/archive/9f8b6dba8b5ef1b26309a571ae75cda4034279e5.tar.gz \ + | tar -xz -C watchdog/third_party/lua-curl --strip-components=1 From b9fce51dd48eacad5fcc579bf15c519ed9a43d1a Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:47:02 +0800 Subject: [PATCH 09/13] feat(watchdog): structured divergence signal, CI compare, non-genesis e2e Replace webhook alarms with watchdog_event JSON and exit codes 0/1/2. Run genesis and non-genesis compare in rollups-e2e; build lcurl.so in CI before the harness. --- .github/workflows/ci.yml | 10 +++ docs/watchdog/README.md | 27 +++--- docs/watchdog/getting-started.md | 6 +- docs/watchdog/operator-deployment.md | 16 ++-- docs/watchdog/staging-drills.md | 46 ++++------ justfile | 10 +-- tests/e2e/src/main.rs | 4 +- tests/e2e/src/test_cases.rs | 48 ++++++++++- tests/e2e/src/watchdog_compare.rs | 124 +++++++++++++++++++++++++-- watchdog/alarm.lua | 43 ---------- watchdog/compare.lua | 4 +- watchdog/config.lua | 1 - watchdog/main.lua | 39 ++++++++- watchdog/retry.lua | 6 ++ watchdog/runner.lua | 19 +--- watchdog/tests/drill_divergence.lua | 36 +++----- watchdog/tests/drill_webhook.lua | 64 -------------- watchdog/tests/e2e.lua | 4 +- watchdog/tests/run.lua | 67 +++++---------- watchdog/tests/run_compare_once.lua | 9 +- watchdog/third_party/json.lua | 2 +- 21 files changed, 313 insertions(+), 272 deletions(-) delete mode 100644 watchdog/alarm.lua delete mode 100644 watchdog/tests/drill_webhook.lua diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22c3897..4bf6e6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,5 +114,15 @@ jobs: sudo apt-get update sudo apt-get install -y faketime libfaketime + - name: Build watchdog Lua deps + run: | + sudo apt-get install -y libcurl4-openssl-dev build-essential pkg-config + just watchdog-lua-deps + - name: Run rollups E2E tests run: just test-rollups-e2e + + - name: Run watchdog genesis compare harness + run: | + cargo run -p rollups-e2e --bin rollups-e2e -- \ + watchdog_genesis_compare_test --exact --nocapture diff --git a/docs/watchdog/README.md b/docs/watchdog/README.md index 27526f6..12cfb1d 100644 --- a/docs/watchdog/README.md +++ b/docs/watchdog/README.md @@ -74,7 +74,7 @@ On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/i | `built lcurl.so but lua cannot load it` | Lua version mismatch: build with the same `lua` you run (`lua -v` vs headers under `lua5.4`) | | `need curl or wget` | Fetch tool to download pinned lua-cURL sources into `watchdog/third_party/lua-curl/` | -CI runs **`just test-watchdog`** (mocked HTTP) only; it does **not** install libcurl or run `watchdog-lua-deps`. Full HTTP + compare smoke is local: `just test-watchdog-compare-harness` after deps above. +CI runs **`just test-watchdog`** (mocked HTTP) and the Rust watchdog compare harness (`watchdog_genesis_compare_test`) in the rollups-e2e job. Full local smoke remains available via `just test-watchdog-compare-harness`. ## V1 Shape @@ -82,17 +82,18 @@ The implementation lives in `watchdog/` and is intentionally split into small Lua modules: - `http.lua`: HTTP adapter via in-tree **lua-cURLv3** / `lcurl` (`just watchdog-lua-deps`). -- `json.lua` / `third_party/json.lua`: pure-Lua JSON (RPC + webhooks). +- `json.lua` / `third_party/json.lua`: pure-Lua JSON (RPC + structured watchdog events). - `jsonrpc.lua`: JSON-RPC request/response validation. - `l1_reader.lua`: partitioned `eth_getLogs` scanning and strict L1 log ordering. - `abi.lua`: decoding for the `InputAdded` / `EvmAdvance` envelope. - `machine_runner.lua`: CM driver (`load`, `advance`, `inspect`, `dump`). - `machine_cartesi.lua`: in-process `cartesi` Lua module binding (production path). -- `machine_cli.lua`: legacy CLI adapter (tests only; prefer `machine_cartesi`). +- `machine_cli.lua`: CLI adapter (`cartesi-machine` subprocess). Used by the Rust + compare harness (`run_compare_once.lua`) and Lua CM e2e; `main.lua` uses + `machine_cartesi` in production. - `sequencer_reader.lua`: sequencer HTTP client (`GET /finalized_state/inclusion_block`, `GET /finalized_state`). - `compare.lua`: raw byte comparison. - `checkpoint.lua`: manifest-backed checkpoint persistence. -- `alarm.lua`: webhook alarm delivery. - `retry.lua`: bounded retry helper used by the runtime. - `runner.lua`: one-shot orchestration — cheap `/finalized_state/inclusion_block` poll, optional full pass (L1 fetch, CM replay, SSZ compare, checkpoint write). @@ -180,8 +181,9 @@ Useful runtime knobs: |---------|-------------------| | `just test-watchdog` | Lua unit tests (fake HTTP/RPC/CM; no live chain) | | `just test-watchdog-e2e` | Real CM: advance, inspect; optional live compare if `WATCHDOG_E2E_SEQUENCER_URL` set | -| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `/finalized_state` + CM inspect + Lua compare | -| `just test-watchdog-webhook-drill` | Webhook delivery smoke (`WATCHDOG_WEBHOOK_URL` required) | +| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `/finalized_state` + CM inspect + Lua compare (genesis) | +| `just test-rollups-e2e` | All rollups e2e scenarios; includes `deposit_transfer_withdrawal_test` (wallet workload + **non-genesis** watchdog compare) and `watchdog_genesis_compare_test` | +| `just test-watchdog-divergence-drill` | Synthetic divergence signal drill (`watchdog_event` + exit `2`) | Prerequisites for CM-backed tests: see **[Host dependencies](#host-dependencies-watchdog-lua-deps)** above, then: @@ -199,7 +201,7 @@ just test-watchdog Covers raw comparison, golden InputAdded ABI decoding, L1 ordering, recursive range partitioning, config, checkpoints, advance/compare runner (fakes), CM CLI -staging, retry, and alarm webhook encoding. +staging, and retry behavior. ### Lua CM end-to-end @@ -226,9 +228,9 @@ just test-watchdog-compare-harness Spawns Anvil + rollups devnet + `sequencer-devnet`, proves CM inspect SSZ at genesis matches `wallet_snapshot::encode(WalletConfig::devnet())` (same as `tests/fixtures/wallet_snapshot_v1_empty.hex` only for Sepolia `default()`), then runs -`watchdog/tests/run_compare_once.lua` (in-process `cartesi` binding) in compare mode. -Requires `RUN_WATCHDOG_E2E=1` (set by the recipe). - +`watchdog/tests/run_compare_once.lua` (CLI `machine_cli` binding) in compare mode. +When `inclusion_block` is unchanged at genesis, the runner skips L1/CM work (idle-cheap); +`deposit_transfer_withdrawal_test` drives a gold batch first so compare replays real L1 inputs. **Before first run (or after changing scheduler / SSZ / inspect code):** ```bash @@ -255,14 +257,13 @@ exists; it does **not** detect a stale guest. If you pulled SSZ/inspect changes, Manual equivalent of the recipe: ```bash -RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e --bin rollups-e2e -- \ +cargo run -p rollups-e2e --bin rollups-e2e -- \ watchdog_genesis_compare_test --exact --nocapture ``` ### Staging / operator drills -See [`staging-drills.md`](staging-drills.md) for webhook smoke, synthetic -divergence POST, and manual compare env vars. +See [`staging-drills.md`](staging-drills.md) for divergence signal and compare-mode drills. ## Related sequencer tests diff --git a/docs/watchdog/getting-started.md b/docs/watchdog/getting-started.md index eff4ad2..b21d9ec 100644 --- a/docs/watchdog/getting-started.md +++ b/docs/watchdog/getting-started.md @@ -128,7 +128,9 @@ export WATCHDOG_ONCE=1 # single pass; omit for daemon (poll every 30s) WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/main.lua ``` -Success: exit **0**, stderr shows steps ending in `compare pass complete`. +Success: exit **0**, stderr shows `watchdog_step` lines ending in `compare pass complete`. + +Exit codes from `watchdog/main.lua`: **0** clean, **1** transient failure (RPC/CM/network after retries), **2** deterministic divergence (`watchdog_event` emitted on stderr before exit). Daemon mode (production-like loop): @@ -182,7 +184,6 @@ Full operator runbook: **[`operator-deployment.md`](operator-deployment.md)**. | `WATCHDOG_LUA_DEPS` | yes | `.deps/lua` after `just watchdog-lua-deps` | | `WATCHDOG_ONCE` | no | `1` = one pass then exit | | `WATCHDOG_POLL_INTERVAL_SEC` | no | Default `30` (daemon) | -| `WATCHDOG_WEBHOOK_URL` | no | Alarm POST on mismatch/regression | See `watchdog/config.lua` for the full list. @@ -201,7 +202,6 @@ See `watchdog/config.lua` for the full list. | HTTP 404 on `/finalized_state/inclusion_block` | Sequencer not promoted yet; wait or drive L1 + batches | | `state_mismatch` at genesis | Wrong `WATCHDOG_CM_SNAPSHOT_*` or stale CM image vs sequencer build | | `inclusion_block_regressed` | Checkpoint ahead of sequencer (reset checkpoint dir or fix bootstrap block) | -| Compare harness skipped | Use `just test-watchdog-compare-harness` (sets `RUN_WATCHDOG_E2E=1` internally) | | `could not determine which binary to run` | Use `just test-watchdog-compare-harness` (not bare `cargo run -p rollups-e2e`) | | Harness `87 vs 76` or `27 vs 76` byte mismatch | Stale CM image and/or wrong fixture; see [harness troubleshooting](README.md#troubleshooting-just-test-watchdog-compare-harness) | diff --git a/docs/watchdog/operator-deployment.md b/docs/watchdog/operator-deployment.md index d3b2a42..05bd237 100644 --- a/docs/watchdog/operator-deployment.md +++ b/docs/watchdog/operator-deployment.md @@ -46,7 +46,13 @@ curl -sS -o /dev/null -w "%{http_code}\n" "$WATCHDOG_SEQUENCER_URL/finalized_sta # expect 200 when a finalized snapshot exists (404 = not promoted yet or wrong host) ``` -### 2. Build watchdog runtime (once per host) +### 2. Watchdog runtime (release bundle or local build) + +**Production (recommended):** use the **release bundle** for tag `vX` — same git tag as the sequencer binary and `canonical-machine-image-*-vX.tar.gz`. Load `sequencer-watchdog-vX-linux-.tar.gz` (`docker load`), verify alignment via `release-manifest-vX.json` and `/opt/watchdog/RELEASE.json` inside the image. See [`release/README.md`](../../release/README.md). + +`cartesi-machine` in the watchdog image **must** match `CARTESI_MACHINE_VERSION` in `release/versions.env` (the emulator that built the CM image tarball). Mismatch causes load failures or false `state_mismatch`. + +**Local / dev build:** ```bash eval "$(direnv export bash 2>/dev/null)" @@ -55,7 +61,7 @@ just watchdog-lua-deps # .deps/lua/lcurl.so — needs libcurl + Lua dev heade Host packages and build errors: [`README.md` — Host dependencies](README.md#host-dependencies-watchdog-lua-deps). -Requires: `lua`, `cartesi-machine` (in-process `cartesi` Lua module), libcurl + Lua headers. +Requires: `lua`, `cartesi-machine` (in-process `cartesi` Lua module), libcurl + Lua headers. Pin `cartesi-machine` to the same version as your CM bootstrap tarball. ### 3. Build the CM image for **this chain** @@ -83,7 +89,6 @@ Today `WalletApp::default()` / `WalletConfig::sepolia()` align with Sepolia stag | `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK` | L1 block that bootstrap snapshot represents (= finalized `inclusion_block` at bootstrap) | | `WATCHDOG_LUA_DEPS` | `.deps/lua` | | `WATCHDOG_POLL_INTERVAL_SEC` | `120`–`300` on public L1 (finalized advances slowly) | -| `WATCHDOG_WEBHOOK_URL` | Optional alarm receiver | The sequencer discovers and pins `input_box_address` at startup; use the same values as `SEQ_ETH_RPC_URL` / `SEQ_APP_ADDRESS` configuration. @@ -147,7 +152,6 @@ export WATCHDOG_CM_SNAPSHOT_DIR="/path/to/canonical-machine-image-sepolia" export WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK="" export WATCHDOG_LUA_DEPS="/path/to/sequencer/.deps/lua" export WATCHDOG_POLL_INTERVAL_SEC=120 -export WATCHDOG_WEBHOOK_URL="https://" ``` ### Operating the Sepolia sequencer @@ -171,7 +175,7 @@ When the rollup runs on Ethereum mainnet, **reuse the same operator checklist ab | Contracts | Mainnet InputBox, application, portals from production deployment manifest | | CM image | Build from production app/scheduler artifacts (mainnet wallet constants when defined in app-core) | | `WATCHDOG_POLL_INTERVAL_SEC` | Often 300+; finalized promotion follows mainnet safe head | -| Security | Stricter firewall between public ingress and internal snapshot tier; secrets management for RPC and webhooks | +| Security | Stricter firewall between public ingress and internal snapshot tier; secrets management for RPC credentials | | Bootstrap | Almost always ops-provided CM snapshot or continued checkpoint dir — not genesis replay | There is no `just devnet-for-watchdog` or automated harness on mainnet; treat Sepolia compare success as the gate before mainnet go-live. @@ -187,7 +191,7 @@ Same on Sepolia and mainnet: 3. If advanced: `eth_getLogs` on InputBox for `(last_block+1)..inclusion_block`. 4. Advance CM incrementally; `inspect` → SSZ bytes. 5. `GET /finalized_state` → SSZ bytes. -6. Raw compare; alarm + non-zero exit on mismatch. +6. Raw compare; emit `watchdog_event` + non-zero exit on mismatch. 7. Write new CM checkpoint on success. Details: [`README.md`](README.md), [`docs/snapshots/lifecycle.md`](../snapshots/lifecycle.md). diff --git a/docs/watchdog/staging-drills.md b/docs/watchdog/staging-drills.md index bd67765..3a78471 100644 --- a/docs/watchdog/staging-drills.md +++ b/docs/watchdog/staging-drills.md @@ -1,6 +1,6 @@ # Watchdog Staging Drills -Operator drills for webhook delivery and divergence detection. +Operator drills for divergence detection and compare-mode verification. - **Sepolia / mainnet:** [`operator-deployment.md`](operator-deployment.md). **Local dev:** [`getting-started.md`](getting-started.md). - Module map and local test recipes: [`README.md`](README.md). @@ -9,42 +9,31 @@ This document covers staging and manual verification beyond the devnet tutorial. ## Prerequisites +- **Release bundle (staging/production):** deploy `sequencer-watchdog-vX` and `canonical-machine-image-*-vX` from the same git tag; see [`release/README.md`](../../release/README.md). - Built canonical machine image: `just canonical-build-machine-image` - `cartesi-machine`, `lua`, and `curl` on PATH - `just watchdog-lua-deps` — builds `lcurl.so` into `.deps/lua` (libcurl + Lua headers on host) - JSON is pure Lua (`watchdog/third_party/json.lua`); no cjson compile step - Staging or local sequencer reachable at `WATCHDOG_SEQUENCER_URL` - L1 RPC + InputBox + app addresses matching that deployment -- Webhook receiver URL (Slack incoming webhook, PagerDuty, or `https://httpbin.org/post` for smoke tests) +- Log collection for `watchdog_event` lines and process exit codes -## Drill 1 — Webhook delivery (no sequencer) +## Drill 1 — Divergence signal (synthetic mismatch, no CM) -Verifies the alarm transport reaches your receiver. +Verifies the watchdog's signal contract (`watchdog_event` + exit code `2`) without a sequencer. ```bash just watchdog-lua-deps -export WATCHDOG_WEBHOOK_URL="https://your-receiver.example/hook" -WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/drill_webhook.lua -# or: just test-watchdog-webhook-drill -``` - -Expected: HTTP 2xx for both `state_mismatch` and `inclusion_block_regressed` sample payloads. -Check the receiver shows JSON with `"kind"` and `"run_id"` fields. - -## Drill 2 — Divergence webhook (synthetic mismatch, no CM) - -Verifies the receiver gets a realistic `state_mismatch` payload without running compare mode: - -```bash -export WATCHDOG_WEBHOOK_URL="https://your-receiver.example/hook" WATCHDOG_LUA_DEPS=.deps/lua lua watchdog/tests/drill_divergence.lua +# or: just test-watchdog-divergence-drill ``` -Expected: HTTP 2xx, receiver shows `kind=state_mismatch` and a non-zero `mismatch_offset`. +Expected: a structured `watchdog_event` line containing `kind=state_mismatch` and non-zero +`mismatch_offset`, then process exits with code `2`. -Unit coverage: `just test-watchdog` (`runner alarms on raw state mismatch`). +Unit coverage: `just test-watchdog` (`runner returns state mismatch payload`). -## Drill 3 — Happy compare (local Anvil harness) +## Drill 2 — Happy compare (local Anvil harness) Full stack: Anvil + devnet rollups + sequencer + CM inspect + `GET /finalized_state`. @@ -53,7 +42,7 @@ just test-watchdog-compare-harness # equivalent: # just setup && just watchdog-lua-deps && just ensure-machine-image # cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e -# RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e --bin rollups-e2e -- watchdog_genesis_compare_test --exact +# cargo run -p rollups-e2e --bin rollups-e2e -- watchdog_genesis_compare_test --exact ``` Or run the Lua compare pass manually after starting a devnet sequencer yourself: @@ -75,19 +64,24 @@ Expected: exit 0, stdout `watchdog compare ok: safe_block=... input_count=...`, byte-identical genesis SSZ state on sequencer `/finalized_state` and CM inspect (`tests/fixtures/wallet_snapshot_v1_empty.hex`). -## Drill 4 — Production compare daemon +## Drill 3 — Production compare daemon Run the watchdog in compare mode against staging (daemon or cron): ```bash export WATCHDOG_MODE=compare export WATCHDOG_ONCE=1 # or 0 for daemon -export WATCHDOG_WEBHOOK_URL=... # ... all WATCHDOG_* vars from config.lua ... lua watchdog/main.lua ``` -On mismatch: non-zero exit, webhook fired, logs show `state_mismatch` and byte offset. +Exit codes from `watchdog/main.lua`: + +| Code | Meaning | +|------|---------| +| `0` | Compare/advance pass completed (or `WATCHDOG_ONCE=1` clean exit) | +| `1` | Transient error after retries (RPC, CM, network) | +| `2` | Deterministic divergence — `watchdog_event` on stderr with `{kind, previous_safe_block, sequencer_inclusion_block, mismatch_offset?}` | ## Triage checklist @@ -95,7 +89,5 @@ On mismatch: non-zero exit, webhook fired, logs show `state_mismatch` and byte o |---------|----------------| | `inspect endpoint not implemented` | Stale CM image — rebuild | | `state_mismatch` at genesis | Checkpoint not aligned with sequencer history | -| Webhook 4xx | Wrong URL or auth on receiver | | Compare skipped in Lua e2e | Set `WATCHDOG_E2E_SEQUENCER_URL` to a live sequencer | -| Compare harness skipped | Set `RUN_WATCHDOG_E2E=1` (see `just test-watchdog-compare-harness`) | | CM inspect 27 bytes / harness byte mismatch | Rebuild devnet image: `just canonical-build-machine-image` — see [`README.md`](README.md#troubleshooting-just-test-watchdog-compare-harness) | diff --git a/justfile b/justfile index 4870a46..7a08ad9 100644 --- a/justfile +++ b/justfile @@ -18,10 +18,8 @@ test-watchdog: test-watchdog-e2e: lua watchdog/tests/e2e.lua -# POST sample alarms to WATCHDOG_WEBHOOK_URL (staging smoke). -test-watchdog-webhook-drill: watchdog-lua-deps - @test -n "${WATCHDOG_WEBHOOK_URL:-}" || { echo "set WATCHDOG_WEBHOOK_URL"; exit 1; } - WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_webhook.lua +# Verify divergence signal event+exit-code contract. +test-watchdog-divergence-drill: watchdog-lua-deps WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_divergence.lua # Build lcurl (lua-cURLv3) into .deps/lua; JSON is pure Lua under watchdog/third_party/. @@ -36,7 +34,7 @@ devnet-for-watchdog: setup ensure-machine-image test-watchdog-compare-harness: setup watchdog-lua-deps ensure-machine-image cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e --bin rollups-e2e - RUN_WATCHDOG_E2E=1 cargo run -p rollups-e2e --bin rollups-e2e -- watchdog_genesis_compare_test --exact --nocapture + cargo run -p rollups-e2e --bin rollups-e2e -- watchdog_genesis_compare_test --exact --nocapture # Run sequencer tests sequentially so partition static config (init) is not shared across parallel tests. test-sequencer: @@ -45,7 +43,7 @@ test-sequencer: cargo test -p sequencer --test ws_broadcaster -- --test-threads=1 cargo test -p sequencer --test batch_submitter_integration -- --test-threads=1 -test-rollups-e2e: setup ensure-machine-image +test-rollups-e2e: setup watchdog-lua-deps ensure-machine-image cargo build -p sequencer --bin sequencer-devnet -p rollups-e2e --bin rollups-e2e cargo run -p rollups-e2e --bin rollups-e2e diff --git a/tests/e2e/src/main.rs b/tests/e2e/src/main.rs index 6b16c9f..4b0d401 100644 --- a/tests/e2e/src/main.rs +++ b/tests/e2e/src/main.rs @@ -16,7 +16,9 @@ fn main() { .map(|(name, scenario)| { Trial::test(name, move || { let log_prefix = format!("rollups-e2e-{name}"); - let spawn_config = if name == "watchdog_genesis_compare_test" { + let spawn_config = if name == "watchdog_genesis_compare_test" + || name == "deposit_transfer_withdrawal_test" + { devnet_sequencer_config_no_faketime(log_prefix) } else { default_devnet_sequencer_config(log_prefix) diff --git a/tests/e2e/src/test_cases.rs b/tests/e2e/src/test_cases.rs index 549a5bb..3e0060b 100644 --- a/tests/e2e/src/test_cases.rs +++ b/tests/e2e/src/test_cases.rs @@ -7,7 +7,7 @@ use crate::{ScenarioFn, ScenarioResult}; use alloy_primitives::{Address, U256}; use rollups_harness::{ ManagedSequencer, ReplayWalletApp, RespawnAttemptOutcome, RespawnPolicy, TcpProxy, TestSigner, - WalletL1Client, WsClient, sign_user_op_hex, + WalletL1Client, WalletL2Client, WsClient, sign_user_op_hex, }; use sequencer_core::api::{TxRequest, WsTxMessage}; use sequencer_core::fee::fee_to_linear; @@ -327,7 +327,9 @@ async fn run_deposit_transfer_withdrawal_test( let mut bob_l2 = runtime.wallet_l2(bob)?; let mut replay = ReplayWalletApp::devnet(); - let deposit_amount = U256::from(600_000_u64); + // Large deposit leaves headroom for the post-scenario batch-close pump + // (~150 user ops) that drives a gold finalized snapshot for watchdog compare. + let deposit_amount = U256::from(10_000_000_u64); let transfer_amount = U256::from(400_000_u64); let withdrawal_amount = U256::from(150_000_u64); let gas = fee_to_linear(DEFAULT_FRAME_FEE); @@ -340,7 +342,6 @@ async fn run_deposit_transfer_withdrawal_test( bob_l2.withdraw(withdrawal_amount).await?; replay.apply(ws.expect_user_op_from(bob_address).await?)?; - // Alice: 600_000 - 400_000 - gas. Bob: 400_000 - 150_000 - gas. assert_wallet_state( &replay, ExpectedWalletState { @@ -355,6 +356,47 @@ async fn run_deposit_transfer_withdrawal_test( }, 3, ); + + drive_finalized_gold_batch_for_watchdog( + runtime, + &mut ws, + &mut replay, + &mut alice_l2, + alice_address, + ) + .await?; + + crate::watchdog_compare::run_watchdog_non_genesis_compare_test(runtime).await?; + Ok(()) +} + +/// Close the open batch, land it on L1, and wait for snapshot promotion so +/// `/finalized_state` reports `inclusion_block > 0`. +async fn drive_finalized_gold_batch_for_watchdog( + runtime: &ManagedSequencer, + ws: &mut WsClient, + replay: &mut ReplayWalletApp, + alice_l2: &mut WalletL2Client, + alice_address: Address, +) -> ScenarioResult<()> { + const TRANSFERS_TO_FORCE_BATCH_CLOSE: usize = 150; + const SUBMITTER_TICK_WAIT: Duration = Duration::from_secs(7); + + let batches_before = runtime.count_batches()?; + for _ in 0..TRANSFERS_TO_FORCE_BATCH_CLOSE { + alice_l2.transfer(alice_address, U256::from(1_u64)).await?; + replay.apply(ws.expect_user_op_from(alice_address).await?)?; + } + let batches_after = runtime.count_batches()?; + if batches_after.sealed <= batches_before.sealed { + return Err(format!( + "expected batch close before watchdog compare: before={batches_before:?} after={batches_after:?}" + ) + .into()); + } + + tokio::time::sleep(SUBMITTER_TICK_WAIT).await; + runtime.mine_l1_blocks(3).await?; Ok(()) } diff --git a/tests/e2e/src/watchdog_compare.rs b/tests/e2e/src/watchdog_compare.rs index 0d67f21..7e2dc90 100644 --- a/tests/e2e/src/watchdog_compare.rs +++ b/tests/e2e/src/watchdog_compare.rs @@ -20,13 +20,22 @@ use crate::ScenarioResult; const MACHINE_IMAGE: &str = "examples/canonical-app/out/canonical-machine-image"; const GENESIS_SAFE_BLOCK: &str = "0"; +fn require_cartesi_machine() { + assert!( + std::process::Command::new("cartesi-machine") + .arg("--version") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .is_ok(), + "cartesi-machine not found on PATH — install Cartesi tools (or run `nix develop`)" + ); +} + pub async fn run_watchdog_genesis_compare_test( runtime: &mut ManagedSequencer, ) -> ScenarioResult<()> { - if std::env::var("RUN_WATCHDOG_E2E").ok().as_deref() != Some("1") { - eprintln!("skipping watchdog_genesis_compare_test: set RUN_WATCHDOG_E2E=1 to enable"); - return Ok(()); - } + require_cartesi_machine(); let workspace = paths::workspace_root(); let machine_image = workspace.join(MACHINE_IMAGE); @@ -78,9 +87,69 @@ pub async fn run_watchdog_genesis_compare_test( .keep(); eprintln!("[watchdog-harness] step 4/5: run Lua compare-mode pass"); + run_lua_compare(runtime, workspace.as_path(), checkpoint_dir.as_path()).await?; + + eprintln!("[watchdog-harness] step 5/5: compare pass completed successfully"); + Ok(()) +} + +pub async fn run_watchdog_non_genesis_compare_test( + runtime: &mut ManagedSequencer, +) -> ScenarioResult<()> { + require_cartesi_machine(); + + let workspace = paths::workspace_root(); + let machine_image = workspace.join(MACHINE_IMAGE); + if !machine_image.is_dir() { + return Err(format!( + "canonical machine image missing at {}; run: just canonical-build-machine-image", + machine_image.display() + ) + .into()); + } + + eprintln!("[watchdog-harness] step 1/3: wait for non-genesis GET /finalized_state"); + let finalized_url = format!("{}/finalized_state", runtime.endpoint()); + let (_status, body, headers) = wait_for_non_genesis_finalized_state( + finalized_url.as_str(), + runtime, + Duration::from_secs(60), + ) + .await?; + let inclusion_block = header_u64(&headers, "x-inclusion-block") + .ok_or("finalized_state response missing X-Inclusion-Block header")?; + eprintln!( + "[watchdog-harness] finalized non-genesis snapshot inclusion_block={inclusion_block} snapshot_bytes={}", + body.len() + ); + + eprintln!("[watchdog-harness] step 2/3: prepare watchdog checkpoint dir"); + let checkpoint_dir = tempfile::tempdir() + .map_err(|err| format!("temp checkpoint dir: {err}"))? + .keep(); + + eprintln!("[watchdog-harness] step 3/3: run Lua compare-mode pass"); + run_lua_compare(runtime, workspace.as_path(), checkpoint_dir.as_path()).await?; + Ok(()) +} + +async fn run_lua_compare( + runtime: &mut ManagedSequencer, + workspace: &Path, + checkpoint_dir: &Path, +) -> ScenarioResult<()> { + let machine_image = workspace.join(MACHINE_IMAGE); let lua_deps = workspace.join(".deps/lua"); + let lcurl_so = lua_deps.join("lcurl.so"); + if !lcurl_so.is_file() { + return Err(format!( + "lcurl.so missing at {}; run: just watchdog-lua-deps (needs libcurl + Lua dev headers)", + lcurl_so.display() + ) + .into()); + } let compare_status = Command::new("lua") - .current_dir(workspace.as_path()) + .current_dir(workspace) .arg("watchdog/tests/run_compare_once.lua") .env("WATCHDOG_LUA_DEPS", lua_deps.as_os_str()) .env("WATCHDOG_MODE", "compare") @@ -103,8 +172,6 @@ pub async fn run_watchdog_genesis_compare_test( if !compare_status.success() { return Err(format!("watchdog compare lua exited with {}", compare_status).into()); } - - eprintln!("[watchdog-harness] step 5/5: compare pass completed successfully"); Ok(()) } @@ -264,6 +331,49 @@ fn header_u64(headers: &[(String, String)], name: &str) -> Option { .and_then(|(_, value)| value.parse().ok()) } +async fn wait_for_non_genesis_finalized_state( + url: &str, + runtime: &ManagedSequencer, + deadline: Duration, +) -> ScenarioResult<(u16, Vec, Vec<(String, String)>)> { + let started = std::time::Instant::now(); + let mut last = String::new(); + while started.elapsed() < deadline { + match http_get(url).await { + Ok((200, body, headers)) => { + let inclusion_block = header_u64(&headers, "x-inclusion-block") + .ok_or("finalized_state response missing X-Inclusion-Block header")?; + if inclusion_block > 0 { + return Ok((200, body, headers)); + } + last = format!("inclusion_block still at 0 (snapshot_bytes={})", body.len()); + } + Ok((404, body, _)) => { + last = body_snippet_for_error(body.as_slice()); + } + Ok((status, body, _)) => { + return Err(format!( + "GET /finalized_state returned HTTP {status}: {}", + body_snippet_for_error(body.as_slice()) + ) + .into()); + } + Err(err) => { + last = err.to_string(); + } + } + runtime + .mine_l1_blocks(1) + .await + .map_err(|err| format!("mine while waiting for non-genesis finalized state: {err}"))?; + tokio::time::sleep(Duration::from_secs(6)).await; + } + Err(format!( + "timed out waiting for GET /finalized_state with inclusion_block > 0; last: {last}" + ) + .into()) +} + async fn wait_for_finalized_state( url: &str, deadline: Duration, diff --git a/watchdog/alarm.lua b/watchdog/alarm.lua deleted file mode 100644 index 085eff6..0000000 --- a/watchdog/alarm.lua +++ /dev/null @@ -1,43 +0,0 @@ --- (c) Cartesi and individual authors (see AUTHORS) --- SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -local alarm = {} - -local function encode_json_object(fields) - local parts = {} - for key, value in pairs(fields) do - local encoded_value - if type(value) == "number" then - encoded_value = tostring(value) - elseif type(value) == "boolean" then - encoded_value = value and "true" or "false" - else - local string_value = tostring(value) - :gsub("\\", "\\\\") - :gsub('"', '\\"') - :gsub("\n", "\\n") - encoded_value = '"' .. string_value .. '"' - end - table.insert(parts, string.format('"%s":%s', key, encoded_value)) - end - return "{" .. table.concat(parts, ",") .. "}" -end - -function alarm.send_webhook(http, webhook_url, payload) - if not webhook_url or webhook_url == "" then - return nil, "WATCHDOG_WEBHOOK_URL is not configured" - end - local body = encode_json_object(payload) - local response, err = http:post(webhook_url, body, { - ["content-type"] = "application/json", - }) - if not response then - return nil, err - end - if response.status < 200 or response.status >= 300 then - return nil, "alarm webhook HTTP " .. tostring(response.status) - end - return true -end - -return alarm diff --git a/watchdog/compare.lua b/watchdog/compare.lua index f1c0f52..927da56 100644 --- a/watchdog/compare.lua +++ b/watchdog/compare.lua @@ -27,8 +27,8 @@ function compare.raw_equal(expected, actual) end function compare.assert_state_response(state) - if type(state) ~= "string" then - return nil, "state must be a string" + if type(state) ~= "string" or #state == 0 then + return nil, "state must be a non-empty string" end return true end diff --git a/watchdog/config.lua b/watchdog/config.lua index 1f07dc1..02b7fbc 100644 --- a/watchdog/config.lua +++ b/watchdog/config.lua @@ -79,7 +79,6 @@ function config.load(env) retry_attempts = optional_number("WATCHDOG_RETRY_ATTEMPTS", 3, env), retry_delay_sec = optional_number("WATCHDOG_RETRY_DELAY_SEC", 5, env), safe_confirmations = optional_number("WATCHDOG_SAFE_CONFIRMATIONS", 12, env), - webhook_url = env.WATCHDOG_WEBHOOK_URL, once = env.WATCHDOG_ONCE == "1", long_block_range_error_codes = split_csv( env.WATCHDOG_LONG_BLOCK_RANGE_ERROR_CODES or "-32005,-32600,-32602,-32616" diff --git a/watchdog/main.lua b/watchdog/main.lua index 1084fe5..8778b16 100644 --- a/watchdog/main.lua +++ b/watchdog/main.lua @@ -12,6 +12,10 @@ local retry = require("watchdog.retry") local runner = require("watchdog.runner") local sequencer_reader = require("watchdog.sequencer_reader") +local EXIT_OK = 0 +local EXIT_TRANSIENT = 1 +local EXIT_DIVERGENCE = 2 + local function prepend_deps_cpath() local deps = os.getenv("WATCHDOG_LUA_DEPS") if deps and deps ~= "" then @@ -30,6 +34,23 @@ local function run_once(cfg, deps) return runner.advance_checkpoint_once(cfg, deps) end +local function is_table_with_kind(value) + return type(value) == "table" and type(value.kind) == "string" +end + +local function is_terminal_error(value) + if not is_table_with_kind(value) then + return false + end + return value.kind == "state_mismatch" + or value.kind == "inclusion_block_regressed" + or value.kind == "safe_block_regressed" +end + +local function emit_watchdog_event(json, payload) + io.stderr:write("watchdog_event " .. json.encode(payload) .. "\n") +end + local function main() prepend_deps_cpath() local cfg = config.load() @@ -39,6 +60,9 @@ local function main() http = http, rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), machine = load_machine(cfg), + log_step = function(message) + io.stderr:write("watchdog_step " .. tostring(message) .. "\n") + end, } if cfg.sequencer_url then deps.sequencer = sequencer_reader.new(http, json, cfg.sequencer_url) @@ -46,21 +70,28 @@ local function main() repeat local result, err = retry.with_retries(function() - local ok, value = pcall(run_once, cfg, deps) + local ok, value, run_err = pcall(run_once, cfg, deps) if not ok then return nil, value end - return value + return value, run_err end, { attempts = cfg.retry_attempts, delay_sec = cfg.retry_delay_sec, + should_retry = function(retry_err) + return not is_terminal_error(retry_err) + end, }) if result == nil then + if is_terminal_error(err) then + emit_watchdog_event(json, err) + os.exit(EXIT_DIVERGENCE) + end io.stderr:write("watchdog run failed: " .. tostring(err) .. "\n") - os.exit(1) + os.exit(EXIT_TRANSIENT) end if cfg.once then - return + os.exit(EXIT_OK) end os.execute("sleep " .. tostring(cfg.poll_interval_sec)) until false diff --git a/watchdog/retry.lua b/watchdog/retry.lua index 4316312..e167ca0 100644 --- a/watchdog/retry.lua +++ b/watchdog/retry.lua @@ -7,6 +7,9 @@ function retry.with_retries(fn, opts) opts = opts or {} local attempts = math.max(1, opts.attempts or 1) local delay_sec = opts.delay_sec or 0 + local should_retry = opts.should_retry or function(_err) + return true + end local sleep = opts.sleep or function(seconds) if seconds > 0 then os.execute("sleep " .. tostring(seconds)) @@ -20,6 +23,9 @@ function retry.with_retries(fn, opts) return result end last_err = err + if not should_retry(err) then + break + end if attempt < attempts then sleep(delay_sec) end diff --git a/watchdog/runner.lua b/watchdog/runner.lua index 2d23fc3..f29e362 100644 --- a/watchdog/runner.lua +++ b/watchdog/runner.lua @@ -1,7 +1,6 @@ -- (c) Cartesi and individual authors (see AUTHORS) -- SPDX-License-Identifier: Apache-2.0 (see LICENSE) -local alarm = require("watchdog.alarm") local checkpoint = require("watchdog.checkpoint") local compare = require("watchdog.compare") local l1_reader = require("watchdog.l1_reader") @@ -59,16 +58,6 @@ local function step(deps, message) end end -local function send_alarm(cfg, deps, payload) - if deps.alarm then - return deps.alarm(payload) - end - if deps.http and cfg.webhook_url then - return alarm.send_webhook(deps.http, cfg.webhook_url, payload) - end - return true -end - local function target_safe_block(cfg, deps) if type(cfg.target_safe_block) == "number" then return cfg.target_safe_block @@ -133,14 +122,12 @@ local function advance_compare_and_checkpoint( step(deps, "compare finalized SSZ bytes against CM inspect report") local equal, mismatch_offset = compare.raw_equal(finalized.state, cm_state) if not equal then - local payload = { + return nil, { kind = "state_mismatch", previous_safe_block = safe_block_prev, sequencer_inclusion_block = finalized.inclusion_block, mismatch_offset = mismatch_offset, } - send_alarm(cfg, deps, payload) - return nil, payload end end @@ -224,13 +211,11 @@ function runner.run_once(cfg, deps) tostring(safe_block_next) )) if safe_block_next < safe_block_prev then - local payload = { + return nil, { kind = "inclusion_block_regressed", previous_safe_block = safe_block_prev, sequencer_inclusion_block = safe_block_next, } - send_alarm(cfg, deps, payload) - return nil, payload end if safe_block_next <= safe_block_prev then diff --git a/watchdog/tests/drill_divergence.lua b/watchdog/tests/drill_divergence.lua index cb86854..3be4d8f 100644 --- a/watchdog/tests/drill_divergence.lua +++ b/watchdog/tests/drill_divergence.lua @@ -1,11 +1,11 @@ -- (c) Cartesi and individual authors (see AUTHORS) -- SPDX-License-Identifier: Apache-2.0 (see LICENSE) -- --- Staging drill: force a state_mismatch alarm through compare mode with a --- deliberately wrong sequencer state (fake HTTP client). No CM required. +-- Staging drill: force a state_mismatch signal and verify the structured +-- event line + divergence exit code contract. -- -- Usage: --- WATCHDOG_WEBHOOK_URL=https://example.com/hook lua watchdog/tests/drill_divergence.lua +-- lua watchdog/tests/drill_divergence.lua package.path = "./?.lua;./?/init.lua;" .. package.path @@ -14,17 +14,9 @@ if deps_lua and deps_lua ~= "" then package.cpath = deps_lua .. "/?.so;" .. package.cpath end -local alarm = require("watchdog.alarm") local compare = require("watchdog.compare") -local http_mod = require("watchdog.http") local log = dofile("watchdog/tests/e2e_log.lua") -local webhook_url = os.getenv("WATCHDOG_WEBHOOK_URL") -if not webhook_url or webhook_url == "" then - io.stderr:write("WATCHDOG_WEBHOOK_URL is required\n") - os.exit(1) -end - local sequencer_state = '{"balances":{"0x01":1},"nonces":{}}' local cm_state = '{"balances":{},"nonces":{}}' local equal, mismatch_offset = compare.raw_equal(sequencer_state, cm_state) @@ -36,23 +28,17 @@ end local payload = { kind = "state_mismatch", previous_safe_block = 0, - sequencer_safe_block = 0, + sequencer_inclusion_block = 0, mismatch_offset = mismatch_offset, - run_id = "drill-forced-divergence", } -log.banner("divergence-webhook-drill") -log.step("divergence-webhook-drill", 1, 1, "POST state_mismatch payload (synthetic bytes)") - -local http = http_mod.new() -local ok, err = alarm.send_webhook(http, webhook_url, payload) -if not ok then - log.fail("divergence-webhook-drill", err) - os.exit(1) -end +log.banner("divergence-signal-drill") +log.step("divergence-signal-drill", 1, 1, "emit watchdog_event payload and exit with divergence code") +io.stderr:write('watchdog_event {"kind":"state_mismatch","previous_safe_block":0,"sequencer_inclusion_block":0,"mismatch_offset":' + .. tostring(payload.mismatch_offset) .. "}\n") log.pass( - "divergence-webhook-drill", - string.format("HTTP 2xx; mismatch_offset=%s", tostring(mismatch_offset)) + "divergence-signal-drill", + string.format("event emitted; mismatch_offset=%s", tostring(mismatch_offset)) ) -os.exit(0) +os.exit(2) diff --git a/watchdog/tests/drill_webhook.lua b/watchdog/tests/drill_webhook.lua deleted file mode 100644 index b52630d..0000000 --- a/watchdog/tests/drill_webhook.lua +++ /dev/null @@ -1,64 +0,0 @@ --- (c) Cartesi and individual authors (see AUTHORS) --- SPDX-License-Identifier: Apache-2.0 (see LICENSE) --- --- Staging/local drill: fire sample watchdog alarm payloads at WATCHDOG_WEBHOOK_URL. --- Usage: --- WATCHDOG_WEBHOOK_URL=https://example.com/hook lua watchdog/tests/drill_webhook.lua - -package.path = "./?.lua;./?/init.lua;" .. package.path - -local deps_lua = os.getenv("WATCHDOG_LUA_DEPS") -if deps_lua and deps_lua ~= "" then - package.cpath = deps_lua .. "/?.so;" .. package.cpath -end - -local alarm = require("watchdog.alarm") -local http_mod = require("watchdog.http") -local log = dofile("watchdog/tests/e2e_log.lua") - -local webhook_url = os.getenv("WATCHDOG_WEBHOOK_URL") -if not webhook_url or webhook_url == "" then - io.stderr:write("WATCHDOG_WEBHOOK_URL is required\n") - os.exit(1) -end - -local drills = { - { - name = "state_mismatch", - payload = { - kind = "state_mismatch", - previous_safe_block = 10, - sequencer_inclusion_block = 12, - mismatch_offset = 4, - run_id = "drill-state-mismatch", - }, - }, - { - name = "inclusion_block_regressed", - payload = { - kind = "inclusion_block_regressed", - previous_safe_block = 20, - sequencer_inclusion_block = 19, - run_id = "drill-inclusion-block-regressed", - }, - }, -} - -local http = http_mod.new() -local failures = 0 - -log.banner("webhook-delivery-drills") -for index, drill in ipairs(drills) do - log.step("webhook-delivery-drills", index, #drills, "POST " .. drill.name .. " payload") - local ok, err = alarm.send_webhook(http, webhook_url, drill.payload) - if not ok then - failures = failures + 1 - log.fail(drill.name, err) - else - log.pass(drill.name, "HTTP 2xx from " .. webhook_url) - end -end - -if failures > 0 then - os.exit(1) -end diff --git a/watchdog/tests/e2e.lua b/watchdog/tests/e2e.lua index 1a65a9d..444c47a 100644 --- a/watchdog/tests/e2e.lua +++ b/watchdog/tests/e2e.lua @@ -267,8 +267,8 @@ table.insert(scenarios, { rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), sequencer = sequencer_reader.new(http, json, sequencer_url), machine = machine_cli.new({ - executable = cfg.cm_executable, - work_dir = cfg.cm_work_dir, + executable = "cartesi-machine", + work_dir = temp_dir("watchdog-e2e-advance-work"), }), log_step = function(message) step_no = step_no + 1 diff --git a/watchdog/tests/run.lua b/watchdog/tests/run.lua index 637f708..620b256 100644 --- a/watchdog/tests/run.lua +++ b/watchdog/tests/run.lua @@ -4,7 +4,6 @@ package.path = "./?.lua;./?/init.lua;" .. package.path local abi = require("watchdog.abi") -local alarm = require("watchdog.alarm") local checkpoint = require("watchdog.checkpoint") local compare = require("watchdog.compare") local config = require("watchdog.config") @@ -362,8 +361,7 @@ test("runner happy path replays inputs and writes checkpoint", function() assert_eq(checkpoint_writes[1].safe_block, 12) end) -test("runner alarms on raw state mismatch", function() - local alarms = {} +test("runner returns state mismatch payload", function() local result, err = runner.run_once(fake_cfg(), { checkpoint = { load = function(_dir) @@ -388,17 +386,11 @@ test("runner alarms on raw state mismatch", function() return {} end, machine = fake_machine('{ "a": 1 }'), - alarm = function(payload) - table.insert(alarms, payload) - return true - end, }) assert_eq(result, nil) assert(type(err) == "table", "expected mismatch payload") assert_eq(err.kind, "state_mismatch") - assert_eq(#alarms, 1) - assert_eq(alarms[1].kind, "state_mismatch") end) test("runner skips compare cycle when finalized inclusion_block is unchanged", function() @@ -430,8 +422,7 @@ test("runner skips compare cycle when finalized inclusion_block is unchanged", f assert_eq(machine.fed_inputs, nil) end) -test("runner alarms on sequencer inclusion_block regression", function() - local alarms = {} +test("runner returns sequencer inclusion_block regression payload", function() local result, err = runner.run_once(fake_cfg(), { checkpoint = { load = function(_dir) @@ -451,16 +442,11 @@ test("runner alarms on sequencer inclusion_block regression", function() end, }, machine = fake_machine("{}"), - alarm = function(payload) - table.insert(alarms, payload) - return true - end, }) assert_eq(result, nil) assert(type(err) == "table", "expected regression payload") assert_eq(err.kind, "inclusion_block_regressed") - assert_eq(#alarms, 1) end) test("sequencer client reads finalized inclusion_block", function() @@ -666,37 +652,28 @@ test("retry returns final error after exhaustion", function() assert_eq(attempts, 2) end) -test("alarm webhook posts JSON payload", function() - local sent = {} - local http = {} - function http.post(_self, url, body, headers) - sent.url = url - sent.body = body - sent.headers = headers - return { status = 204, body = "" } - end - - local ok, err = alarm.send_webhook(http, "http://alarm", { - kind = "state_mismatch", - safe_block = 12, +test("retry stops immediately on terminal errors", function() + local attempts = 0 + local sleeps = 0 + local result, err = retry.with_retries(function() + attempts = attempts + 1 + return nil, { kind = "state_mismatch" } + end, { + attempts = 3, + delay_sec = 1, + should_retry = function(retry_err) + return not (type(retry_err) == "table" and retry_err.kind == "state_mismatch") + end, + sleep = function(_seconds) + sleeps = sleeps + 1 + end, }) - assert(ok, err) - assert_eq(sent.url, "http://alarm") - assert_eq(sent.headers["content-type"], "application/json") - assert(sent.body:find('"kind":"state_mismatch"', 1, true) ~= nil, "body includes kind") - assert(sent.body:find('"safe_block":12', 1, true) ~= nil, "body includes safe block") -end) - -test("alarm webhook reports non-success status", function() - local http = {} - function http.post(_self, _url, _body, _headers) - return { status = 500, body = "" } - end - - local ok, err = alarm.send_webhook(http, "http://alarm", { kind = "test" }) - assert_eq(ok, nil) - assert_eq(err, "alarm webhook HTTP 500") + assert_eq(result, nil) + assert(type(err) == "table", "terminal payload returned") + assert_eq(err.kind, "state_mismatch") + assert_eq(attempts, 1) + assert_eq(sleeps, 0) end) local failures = 0 diff --git a/watchdog/tests/run_compare_once.lua b/watchdog/tests/run_compare_once.lua index 838aa42..37b449d 100644 --- a/watchdog/tests/run_compare_once.lua +++ b/watchdog/tests/run_compare_once.lua @@ -14,7 +14,7 @@ end local config = require("watchdog.config") local http_mod = require("watchdog.http") local jsonrpc = require("watchdog.jsonrpc") -local machine_cartesi = require("watchdog.machine_cartesi") +local machine_cli = require("watchdog.machine_cli") local runner = require("watchdog.runner") local sequencer_reader = require("watchdog.sequencer_reader") @@ -39,7 +39,12 @@ local deps = { http = http, rpc = jsonrpc.new(http, json, cfg.l1_rpc_url), sequencer = sequencer_reader.new(http, json, cfg.sequencer_url), - machine = machine_cartesi.new(), + -- Harness uses the CLI binding: it tracks `cartesi-machine` archive support + -- for stored snapshots (the in-process `cartesi` module can lag the CLI). + machine = machine_cli.new({ + executable = cfg.cm_executable, + work_dir = cfg.cm_work_dir, + }), } local result, err = runner.run_once(cfg, deps) diff --git a/watchdog/third_party/json.lua b/watchdog/third_party/json.lua index dc06f90..aba62da 100644 --- a/watchdog/third_party/json.lua +++ b/watchdog/third_party/json.lua @@ -1,5 +1,5 @@ -- --- json.lua — vendored pure-Lua JSON (RPC + webhook payloads only). +-- json.lua — vendored pure-Lua JSON (RPC + structured watchdog events). -- UPSTREAM: https://github.com/rxi/json.lua @ 0.1.2 (commit on master, MIT) -- See LICENSE note in file header below. From a710f271d4ae19fececdb85e685881c16a0e305c Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:47:12 +0800 Subject: [PATCH 10/13] chore(watchdog): vendor lua-cURLv3 sources in-tree Track pinned Lua-cURLv3 under watchdog/third_party/lua-curl so CI and watchdog-lua-deps can build lcurl.so without a network fetch. --- .gitignore | 3 +- scripts/watchdog-lua-deps.sh | 2 +- .../lua-curl/.appveyor/install.bat | 216 ++ .../lua-curl/.appveyor/install_curl.bat | 49 + .../lua-curl/.appveyor/pack_artifact.bat | 12 + watchdog/third_party/lua-curl/.config | 2 + watchdog/third_party/lua-curl/.gitignore | 32 + watchdog/third_party/lua-curl/.travis.yml | 84 + .../third_party/lua-curl/.travis/platform.sh | 15 + .../third_party/lua-curl/.travis/setup_uv.sh | 25 + watchdog/third_party/lua-curl/CMakeLists.txt | 8 + watchdog/third_party/lua-curl/LICENSE | 21 + watchdog/third_party/lua-curl/Makefile | 82 + watchdog/third_party/lua-curl/README.md | 147 + watchdog/third_party/lua-curl/appveyor.yml | 90 + watchdog/third_party/lua-curl/doc/config.ld | 41 + watchdog/third_party/lua-curl/doc/curl.ldoc | 175 ++ watchdog/third_party/lua-curl/doc/lcurl.ldoc | 657 +++++ .../lua-curl/examples/cURLv2/browser.lua | 266 ++ .../lua-curl/examples/cURLv2/file.lua | 16 + .../lua-curl/examples/cURLv2/multi.lua | 22 + .../lua-curl/examples/cURLv2/post_stream.lua | 49 + .../lua-curl/examples/cURLv2/rss.lua | 54 + .../lua-curl/examples/cURLv3/file.lua | 16 + .../lua-curl/examples/cURLv3/multi.lua | 17 + .../lua-curl/examples/cURLv3/multi2.lua | 35 + .../lua-curl/examples/cURLv3/multi3.lua | 28 + .../lua-curl/examples/cURLv3/pop3.lua | 346 +++ .../lua-curl/examples/cURLv3/post_form.lua | 87 + .../lua-curl/examples/cURLv3/post_json.lua | 12 + .../lua-curl/examples/cURLv3/post_mime.lua | 47 + .../lua-curl/examples/cURLv3/rss.lua | 47 + .../lua-curl/examples/cURLv3/uvwget.lua | 278 ++ .../lua-curl/examples/lcurl/crul_info.lua | 36 + .../lua-curl/examples/lcurl/curl_debug.lua | 42 + .../lua-curl/examples/lcurl/curl_info.lua | 36 + .../lua-curl/examples/lcurl/easy.obj.lua | 147 + .../lua-curl/examples/lcurl/file.lua | 18 + .../lua-curl/examples/lcurl/fnmatch.lua | 47 + .../lua-curl/examples/lcurl/ftpupload.lua | 17 + .../lua-curl/examples/lcurl/multi.lua | 24 + .../examples/lcurl/multi_iterator.lua | 78 + .../lua-curl/examples/lcurl/pause.lua | 78 + .../lua-curl/examples/lcurl/post.lua | 69 + .../lua-curl/examples/lcurl/post_stream.lua | 26 + .../lua-curl/examples/lcurl/share.lua | 23 + .../lua-curl/examples/lcurl/smtp-mime.lua | 85 + watchdog/third_party/lua-curl/lakeconfig.lua | 260 ++ watchdog/third_party/lua-curl/lakefile | 44 + .../rockspecs/lua-curl-0.3.0-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.1-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.10-1.rockspec | 75 + .../rockspecs/lua-curl-0.3.11-1.rockspec | 75 + .../rockspecs/lua-curl-0.3.12-1.rockspec | 75 + .../rockspecs/lua-curl-0.3.13-1.rockspec | 75 + .../rockspecs/lua-curl-0.3.2-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.3-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.4-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.5-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.6-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.7-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.8-1.rockspec | 74 + .../rockspecs/lua-curl-0.3.8-2.rockspec | 74 + .../rockspecs/lua-curl-0.3.9-1.rockspec | 75 + .../rockspecs/lua-curl-scm-0.rockspec | 75 + watchdog/third_party/lua-curl/src/l52util.c | 178 ++ watchdog/third_party/lua-curl/src/l52util.h | 97 + watchdog/third_party/lua-curl/src/lceasy.c | 2469 +++++++++++++++++ watchdog/third_party/lua-curl/src/lceasy.h | 127 + .../third_party/lua-curl/src/lcerr_easy.h | 146 + .../third_party/lua-curl/src/lcerr_form.h | 8 + .../third_party/lua-curl/src/lcerr_multi.h | 14 + .../third_party/lua-curl/src/lcerr_share.h | 8 + watchdog/third_party/lua-curl/src/lcerr_url.h | 20 + watchdog/third_party/lua-curl/src/lcerror.c | 342 +++ watchdog/third_party/lua-curl/src/lcerror.h | 34 + watchdog/third_party/lua-curl/src/lcflags.h | 283 ++ .../third_party/lua-curl/src/lchttppost.c | 595 ++++ .../third_party/lua-curl/src/lchttppost.h | 47 + .../third_party/lua-curl/src/lcinfoeasy.h | 94 + watchdog/third_party/lua-curl/src/lcmime.c | 686 +++++ watchdog/third_party/lua-curl/src/lcmime.h | 66 + watchdog/third_party/lua-curl/src/lcmulti.c | 670 +++++ watchdog/third_party/lua-curl/src/lcmulti.h | 50 + watchdog/third_party/lua-curl/src/lcopteasy.h | 557 ++++ .../third_party/lua-curl/src/lcoptmulti.h | 17 + .../third_party/lua-curl/src/lcoptshare.h | 27 + watchdog/third_party/lua-curl/src/lcopturl.h | 29 + watchdog/third_party/lua-curl/src/lcshare.c | 152 + watchdog/third_party/lua-curl/src/lcshare.h | 30 + watchdog/third_party/lua-curl/src/lcurl.c | 487 ++++ watchdog/third_party/lua-curl/src/lcurl.h | 31 + watchdog/third_party/lua-curl/src/lcurlapi.c | 218 ++ watchdog/third_party/lua-curl/src/lcurlapi.h | 34 + watchdog/third_party/lua-curl/src/lcutils.c | 408 +++ watchdog/third_party/lua-curl/src/lcutils.h | 108 + .../third_party/lua-curl/src/lua/cURL.lua | 14 + .../lua-curl/src/lua/cURL/impl/cURL.lua | 739 +++++ .../lua-curl/src/lua/cURL/safe.lua | 14 + .../lua-curl/src/lua/cURL/utils.lua | 79 + watchdog/third_party/lua-curl/test/.luacov | 38 + .../test/deps/lua-http-parser-2.7-1.rockspec | 28 + .../lua-curl/test/lunit/console.lua | 132 + watchdog/third_party/lua-curl/test/run.lua | 34 + watchdog/third_party/lua-curl/test/server.lua | 121 + .../third_party/lua-curl/test/test_curl.lua | 451 +++ .../third_party/lua-curl/test/test_easy.lua | 1309 +++++++++ .../third_party/lua-curl/test/test_form.lua | 490 ++++ .../third_party/lua-curl/test/test_mime.lua | 631 +++++ .../lua-curl/test/test_multi_callback.lua | 116 + .../test/test_multi_nested_callback.lua | 47 + .../lua-curl/test/test_pause02.c.lua | 89 + .../third_party/lua-curl/test/test_safe.lua | 287 ++ .../third_party/lua-curl/test/test_urlapi.lua | 315 +++ watchdog/third_party/lua-curl/test/utils.lua | 151 + 115 files changed, 18315 insertions(+), 3 deletions(-) create mode 100644 watchdog/third_party/lua-curl/.appveyor/install.bat create mode 100644 watchdog/third_party/lua-curl/.appveyor/install_curl.bat create mode 100644 watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat create mode 100644 watchdog/third_party/lua-curl/.config create mode 100644 watchdog/third_party/lua-curl/.gitignore create mode 100644 watchdog/third_party/lua-curl/.travis.yml create mode 100644 watchdog/third_party/lua-curl/.travis/platform.sh create mode 100644 watchdog/third_party/lua-curl/.travis/setup_uv.sh create mode 100644 watchdog/third_party/lua-curl/CMakeLists.txt create mode 100644 watchdog/third_party/lua-curl/LICENSE create mode 100644 watchdog/third_party/lua-curl/Makefile create mode 100644 watchdog/third_party/lua-curl/README.md create mode 100644 watchdog/third_party/lua-curl/appveyor.yml create mode 100644 watchdog/third_party/lua-curl/doc/config.ld create mode 100644 watchdog/third_party/lua-curl/doc/curl.ldoc create mode 100644 watchdog/third_party/lua-curl/doc/lcurl.ldoc create mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/browser.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/file.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/multi.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/rss.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/file.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/multi.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/rss.lua create mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/file.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/multi.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/pause.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/post.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/share.lua create mode 100644 watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua create mode 100644 watchdog/third_party/lua-curl/lakeconfig.lua create mode 100644 watchdog/third_party/lua-curl/lakefile create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec create mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec create mode 100644 watchdog/third_party/lua-curl/src/l52util.c create mode 100644 watchdog/third_party/lua-curl/src/l52util.h create mode 100644 watchdog/third_party/lua-curl/src/lceasy.c create mode 100644 watchdog/third_party/lua-curl/src/lceasy.h create mode 100644 watchdog/third_party/lua-curl/src/lcerr_easy.h create mode 100644 watchdog/third_party/lua-curl/src/lcerr_form.h create mode 100644 watchdog/third_party/lua-curl/src/lcerr_multi.h create mode 100644 watchdog/third_party/lua-curl/src/lcerr_share.h create mode 100644 watchdog/third_party/lua-curl/src/lcerr_url.h create mode 100644 watchdog/third_party/lua-curl/src/lcerror.c create mode 100644 watchdog/third_party/lua-curl/src/lcerror.h create mode 100644 watchdog/third_party/lua-curl/src/lcflags.h create mode 100644 watchdog/third_party/lua-curl/src/lchttppost.c create mode 100644 watchdog/third_party/lua-curl/src/lchttppost.h create mode 100644 watchdog/third_party/lua-curl/src/lcinfoeasy.h create mode 100644 watchdog/third_party/lua-curl/src/lcmime.c create mode 100644 watchdog/third_party/lua-curl/src/lcmime.h create mode 100644 watchdog/third_party/lua-curl/src/lcmulti.c create mode 100644 watchdog/third_party/lua-curl/src/lcmulti.h create mode 100644 watchdog/third_party/lua-curl/src/lcopteasy.h create mode 100644 watchdog/third_party/lua-curl/src/lcoptmulti.h create mode 100644 watchdog/third_party/lua-curl/src/lcoptshare.h create mode 100644 watchdog/third_party/lua-curl/src/lcopturl.h create mode 100644 watchdog/third_party/lua-curl/src/lcshare.c create mode 100644 watchdog/third_party/lua-curl/src/lcshare.h create mode 100644 watchdog/third_party/lua-curl/src/lcurl.c create mode 100644 watchdog/third_party/lua-curl/src/lcurl.h create mode 100644 watchdog/third_party/lua-curl/src/lcurlapi.c create mode 100644 watchdog/third_party/lua-curl/src/lcurlapi.h create mode 100644 watchdog/third_party/lua-curl/src/lcutils.c create mode 100644 watchdog/third_party/lua-curl/src/lcutils.h create mode 100644 watchdog/third_party/lua-curl/src/lua/cURL.lua create mode 100644 watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua create mode 100644 watchdog/third_party/lua-curl/src/lua/cURL/safe.lua create mode 100644 watchdog/third_party/lua-curl/src/lua/cURL/utils.lua create mode 100644 watchdog/third_party/lua-curl/test/.luacov create mode 100644 watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec create mode 100644 watchdog/third_party/lua-curl/test/lunit/console.lua create mode 100644 watchdog/third_party/lua-curl/test/run.lua create mode 100644 watchdog/third_party/lua-curl/test/server.lua create mode 100644 watchdog/third_party/lua-curl/test/test_curl.lua create mode 100644 watchdog/third_party/lua-curl/test/test_easy.lua create mode 100644 watchdog/third_party/lua-curl/test/test_form.lua create mode 100644 watchdog/third_party/lua-curl/test/test_mime.lua create mode 100644 watchdog/third_party/lua-curl/test/test_multi_callback.lua create mode 100644 watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua create mode 100644 watchdog/third_party/lua-curl/test/test_pause02.c.lua create mode 100644 watchdog/third_party/lua-curl/test/test_safe.lua create mode 100644 watchdog/third_party/lua-curl/test/test_urlapi.lua create mode 100644 watchdog/third_party/lua-curl/test/utils.lua diff --git a/.gitignore b/.gitignore index 46d6cb2..921651b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /target .deps/ -watchdog/third_party/lua-curl/* -!watchdog/third_party/lua-curl/UPSTREAM +# lua-curl sources are vendored under watchdog/third_party/lua-curl/ (see UPSTREAM). watchdog-e2e-*/ .env .env.fish diff --git a/scripts/watchdog-lua-deps.sh b/scripts/watchdog-lua-deps.sh index 1f947ee..2a72bfa 100755 --- a/scripts/watchdog-lua-deps.sh +++ b/scripts/watchdog-lua-deps.sh @@ -7,7 +7,7 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" out_dir="${root}/.deps/lua" out_so="${out_dir}/lcurl.so" vendor_dir="${root}/watchdog/third_party/lua-curl" -upstream_sha="9f8b6dba8b5ef1b26309a571ae75cda4034279e5" +upstream_sha="${LUA_CURL_UPSTREAM_SHA:-9f8b6dba8b5ef1b26309a571ae75cda4034279e5}" upstream_tar="https://github.com/Lua-cURL/Lua-cURLv3/archive/${upstream_sha}.tar.gz" mkdir -p "${out_dir}" diff --git a/watchdog/third_party/lua-curl/.appveyor/install.bat b/watchdog/third_party/lua-curl/.appveyor/install.bat new file mode 100644 index 0000000..4cef264 --- /dev/null +++ b/watchdog/third_party/lua-curl/.appveyor/install.bat @@ -0,0 +1,216 @@ +@echo off + +cd %APPVEYOR_BUILD_FOLDER% + +:: ========================================================= +:: Set some defaults. Infer some variables. +:: +:: These are set globally +if not "%LUA_VER%"=="" ( + set LUA=lua + set LUA_SHORTV=%LUA_VER:~0,3% +) else if not "%LJ_VER%"=="" ( + set LUA=luajit + set LJ_SHORTV=%LJ_VER:~0,3% + set LUA_SHORTV=5.1 +) else ( + echo Can not recognize needed Lua version + echo Please set LUA_VER or LJ_VER + exit /B 3 +) + +:: Now we declare a scope +Setlocal EnableDelayedExpansion EnableExtensions + +set LUAROCKS_SHORTV=%LUAROCKS_VER:~0,3% + +if not defined LUAROCKS_URL set LUAROCKS_URL=http://keplerproject.github.io/luarocks/releases +if not defined LUAROCKS_REPO set LUAROCKS_REPO=http://rocks.moonscript.org +if not defined LUA_URL set LUA_URL=http://www.lua.org/ftp +if not defined LUAJIT_GIT_REPO set LUAJIT_GIT_REPO=http://luajit.org/git/luajit-2.0.git +if not defined LUAJIT_URL set LUAJIT_URL=http://luajit.org/download + +if not defined LR_EXTERNAL set LR_EXTERNAL=c:\external +if not defined LUAROCKS_INSTALL set LUAROCKS_INSTALL=%ProgramFiles(x86)%\LuaRocks +if not defined LR_ROOT set LR_ROOT=%LUAROCKS_INSTALL%\%LUAROCKS_SHORTV% +if not defined LR_SYSTREE set LR_SYSTREE=%LUAROCKS_INSTALL%\systree +if /I "%platform%"=="x64" set LR_SYSTREE=%ProgramFiles%\LuaRocks\systree + +if not defined SEVENZIP set SEVENZIP=7z + +:: +:: ========================================================= + +:: first create some necessary directories: +mkdir downloads 2>NUL + +:: defines LUA_DIR so Cmake can find this Lua install +if "%LUA%"=="luajit" ( + set LUA_DIR=c:\lua\lj%LJ_SHORTV% +) else ( + set LUA_DIR=c:\lua\%LUA_VER% +) + +:: Download and compile Lua (or LuaJIT) +if "%LUA%"=="luajit" ( + if not exist %LUA_DIR% ( + if "%LJ_SHORTV%"=="2.1" ( + :: Clone repository and checkout 2.1 branch + set lj_source_folder=%APPVEYOR_BUILD_FOLDER%\downloads\luajit-%LJ_VER% + if not exist !lj_source_folder! ( + echo Cloning git repo %LUAJIT_GIT_REPO% !lj_source_folder! + git clone %LUAJIT_GIT_REPO% !lj_source_folder! || call :die "Failed to clone repository" + ) + cd !lj_source_folder!\src + git checkout v2.1 || call :die + ) else ( + set lj_source_folder=%APPVEYOR_BUILD_FOLDER%\downloads\luajit-%LJ_VER% + if not exist !lj_source_folder! ( + echo Downloading... %LUAJIT_URL%/LuaJIT-%LJ_VER%.tar.gz + curl --silent --fail --max-time 120 --connect-timeout 30 %LUAJIT_URL%/LuaJIT-%LJ_VER%.tar.gz | %SEVENZIP% x -si -so -tgzip | %SEVENZIP% x -si -ttar -aoa -odownloads + ) + cd !lj_source_folder!\src + ) + :: Compiles LuaJIT + call msvcbuild.bat + + mkdir %LUA_DIR% 2> NUL + for %%a in (bin include lib) do ( mkdir "%LUA_DIR%\%%a" ) + + for %%a in (luajit.exe lua51.dll) do ( move "!lj_source_folder!\src\%%a" "%LUA_DIR%\bin" ) + + move "!lj_source_folder!\src\lua51.lib" "%LUA_DIR%\lib" + for %%a in (lauxlib.h lua.h lua.hpp luaconf.h lualib.h luajit.h) do ( + copy "!lj_source_folder!\src\%%a" "%LUA_DIR%\include" + ) + + ) else ( + echo LuaJIT %LJ_VER% already installed at %LUA_DIR% + ) +) else ( + if not exist %LUA_DIR% ( + :: Download and compile Lua + if not exist downloads\lua-%LUA_VER% ( + curl --silent --fail --max-time 120 --connect-timeout 30 %LUA_URL%/lua-%LUA_VER%.tar.gz | %SEVENZIP% x -si -so -tgzip | %SEVENZIP% x -si -ttar -aoa -odownloads + ) + + mkdir downloads\lua-%LUA_VER%\etc 2> NUL + if not exist downloads\lua-%LUA_VER%\etc\winmake.bat ( + curl --silent --location --insecure --fail --max-time 120 --connect-timeout 30 https://github.com/Tieske/luawinmake/archive/master.tar.gz | %SEVENZIP% x -si -so -tgzip | %SEVENZIP% e -si -ttar -aoa -odownloads\lua-%LUA_VER%\etc luawinmake-master\etc\winmake.bat + ) + + cd downloads\lua-%LUA_VER% + call etc\winmake + call etc\winmake install %LUA_DIR% + ) else ( + echo Lua %LUA_VER% already installed at %LUA_DIR% + ) +) + +if not exist %LUA_DIR%\bin\%LUA%.exe ( + echo Missing Lua interpreter + exit /B 1 +) + +set PATH=%LUA_DIR%\bin;%PATH% +call %LUA% -v + +:: ========================================================= +:: LuaRocks +:: ========================================================= + +if not exist "%LR_ROOT%" ( + :: Downloads and installs LuaRocks + cd %APPVEYOR_BUILD_FOLDER% + + if not exist downloads\luarocks-%LUAROCKS_VER%-win32.zip ( + echo Downloading LuaRocks... + curl --silent --fail --max-time 120 --connect-timeout 30 --output downloads\luarocks-%LUAROCKS_VER%-win32.zip %LUAROCKS_URL%/luarocks-%LUAROCKS_VER%-win32.zip + %SEVENZIP% x -aoa -odownloads downloads\luarocks-%LUAROCKS_VER%-win32.zip + ) + + cd downloads\luarocks-%LUAROCKS_VER%-win32 + call install.bat /LUA %LUA_DIR% /Q /LV %LUA_SHORTV% /P "%LUAROCKS_INSTALL%" +) + +if not exist "%LR_ROOT%" ( + echo LuaRocks installation failed. + exit /B 2 +) + +set PATH=%LR_ROOT%;%LR_SYSTREE%\bin;%PATH% + +:: Lua will use just the system rocks +set LUA_PATH=%LR_ROOT%\lua\?.lua;%LR_ROOT%\lua\?\init.lua +set LUA_PATH=%LUA_PATH%;.\?.lua; +set LUA_PATH=%LUA_PATH%;%LR_SYSTREE%\share\lua\%LUA_SHORTV%\?.lua +set LUA_PATH=%LUA_PATH%;%LR_SYSTREE%\share\lua\%LUA_SHORTV%\?\init.lua +set LUA_CPATH=%LR_SYSTREE%\lib\lua\%LUA_SHORTV%\?.dll + +call luarocks --version || call :die "Error with LuaRocks installation" +call luarocks list + +if not exist "%LR_EXTERNAL%" ( + mkdir "%LR_EXTERNAL%" + mkdir "%LR_EXTERNAL%\lib" + mkdir "%LR_EXTERNAL%\include" +) + +set PATH=%LR_EXTERNAL%;%PATH% + +:: Exports the following variables: +:: (beware of whitespace between & and ^ below) +endlocal & set PATH=%PATH%&^ +set LUA_DIR=%LUA_DIR%&^ +set LR_SYSTREE=%LR_SYSTREE%&^ +set LUA_PATH=%LUA_PATH%&^ +set LUA_CPATH=%LUA_CPATH%&^ +set LR_EXTERNAL=%LR_EXTERNAL% + +echo ====================================================== +if "%LUA%"=="luajit" ( + echo Installation of LuaJIT %LJ_VER% and LuaRocks %LUAROCKS_VER% done. +) else ( + echo Installation of Lua %LUA_VER% and LuaRocks %LUAROCKS_VER% done. +) +echo Platform - %platform% +echo LUA - %LUA% +echo LUA_SHORTV - %LUA_SHORTV% +echo LJ_SHORTV - %LJ_SHORTV% +echo LUA_PATH - %LUA_PATH% +echo LUA_CPATH - %LUA_CPATH% +echo +echo LR_EXTERNAL - %LR_EXTERNAL% +echo ====================================================== + +goto :eof + + + + + + + + + + + + + + + + + + +:: This blank space is intentional. If you see errors like "The system cannot find the batch label specified 'foo'" +:: then try adding or removing blank lines lines above. +:: Yes, really. +:: http://stackoverflow.com/questions/232651/why-the-system-cannot-find-the-batch-label-specified-is-thrown-even-if-label-e + +:: helper functions: + +:: for bailing out when an error occurred +:die %1 +echo %1 +exit 1 +goto :eof diff --git a/watchdog/third_party/lua-curl/.appveyor/install_curl.bat b/watchdog/third_party/lua-curl/.appveyor/install_curl.bat new file mode 100644 index 0000000..c996d71 --- /dev/null +++ b/watchdog/third_party/lua-curl/.appveyor/install_curl.bat @@ -0,0 +1,49 @@ +SETLOCAL + +set PLAT_NAME=Win32 +set CURL_PLAT=Win32 +set CURL_URL=http://curl.haxx.se/download +set CURL_CFG=DLL Release - DLL Windows SSPI + +if /I "%platform%" == "x64" ( + set PLAT_NAME=Win64 + set CURL_PLAT=x64 +) + +cd %APPVEYOR_BUILD_FOLDER% + +echo ========================================= +echo External library path: %LR_EXTERNAL% +echo ========================================= + +if not exist %LR_EXTERNAL%\libcurl.dll ( + @echo Download %CURL_URL%/curl-%CURL_VER%.zip ... + + appveyor DownloadFile %CURL_URL%/curl-%CURL_VER%.zip + 7z x curl-%CURL_VER%.zip + cd curl-%CURL_VER% + + @echo Build curl %CURL_CFG% / %PLAT_NAME% ... + + if exist projects\Windows\VC12\curl.sln ( + msbuild projects\Windows\VC12\curl.sln /p:Configuration="%CURL_CFG%" /p:Platform=%CURL_PLAT% + ) else ( + msbuild projects\Windows\VC12\curl-all.sln /p:Configuration="%CURL_CFG%" /p:Platform=%CURL_PLAT% + ) + + @echo Build curl done + + if not exist %LR_EXTERNAL%\include\curl mkdir %LR_EXTERNAL%\include\curl + copy "include\curl\*.h" %LR_EXTERNAL%\include\curl + copy "build\%PLAT_NAME%\VC12\%CURL_CFG%\libcurl.lib" %LR_EXTERNAL%\lib\libcurl.lib + copy "build\%PLAT_NAME%\VC12\%CURL_CFG%\libcurl.lib" %LR_EXTERNAL%\libcurl.lib + copy "build\%PLAT_NAME%\VC12\%CURL_CFG%\libcurl.dll" %LR_EXTERNAL%\libcurl.dll +) + +if not exist %LR_EXTERNAL%\libcurl.dll ( + exit /B 1 +) + +appveyor PushArtifact %LR_EXTERNAL%\libcurl.dll -DeploymentName ext-deps + +cd %APPVEYOR_BUILD_FOLDER% diff --git a/watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat b/watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat new file mode 100644 index 0000000..bd52934 --- /dev/null +++ b/watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat @@ -0,0 +1,12 @@ +@setlocal + +@for /f "delims=" %%A in ('luarocks pack %1') do @set "rock_name=%%A" + +@set rock_name=%rock_name:Packed: =% + +@echo make rock file as artifact: %rock_name% + +@appveyor PushArtifact %rock_name% -DeploymentName %2 + +@endlocal + diff --git a/watchdog/third_party/lua-curl/.config b/watchdog/third_party/lua-curl/.config new file mode 100644 index 0000000..30c625a --- /dev/null +++ b/watchdog/third_party/lua-curl/.config @@ -0,0 +1,2 @@ +# You can freely define some Makefile variables here (if you don't want to call: +# $ make VAR=val diff --git a/watchdog/third_party/lua-curl/.gitignore b/watchdog/third_party/lua-curl/.gitignore new file mode 100644 index 0000000..82062cf --- /dev/null +++ b/watchdog/third_party/lua-curl/.gitignore @@ -0,0 +1,32 @@ +# Object files +*.o +*.ko +*.obj +*.elf + +# Libraries +*.lib +*.a + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +#MSVC +msvc/ + +#ldoc +doc/doc/ + +#lake deps +*.d diff --git a/watchdog/third_party/lua-curl/.travis.yml b/watchdog/third_party/lua-curl/.travis.yml new file mode 100644 index 0000000..194ba7c --- /dev/null +++ b/watchdog/third_party/lua-curl/.travis.yml @@ -0,0 +1,84 @@ +language: c + +sudo: false + +env: + global: + - LCURL_CC_FLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" + - LCURL_LD_FLAGS="-shared --coverage" + +matrix: + include: + - env: LUA="lua 5.1" + os: osx + - env: LUA="lua 5.1" + os: linux + - env: LUA="lua 5.2" + os: linux + - env: LUA="lua 5.3" + os: linux + - env: LUA="lua 5.4" + os: linux + - env: LUA="luajit 2.0" + os: linux + - env: LUA="luajit 2.1" + os: linux + +cache: + directories: + - here + - $HOME/.cache/pip + +branches: + only: + - master + - curl_mime + +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=$PATH:~/Library/Python/2.7/bin/; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export LCURL_LD_FLAGS="-bundle -undefined dynamic_lookup -all_load --coverage"; fi + - pip2 install --user cpp-coveralls + - pip2 install --user hererocks + - hererocks here -r^ --$LUA + - source here/bin/activate + - luarocks show lluv > /dev/null 2>&1 || bash .travis/setup_uv.sh + +install: + - luarocks make rockspecs/lua-curl-scm-0.rockspec CFLAGS="$LCURL_CC_FLAGS" LIBFLAG="$LCURL_LD_FLAGS" + +before_script: + - luarocks show luacov-coveralls > /dev/null 2>&1 || luarocks install luacov-coveralls + - luarocks show lunitx > /dev/null 2>&1 || luarocks install lunitx + - luarocks show luafilesystem > /dev/null 2>&1 || luarocks install luafilesystem + - luarocks show dkjson > /dev/null 2>&1 || luarocks install dkjson --deps-mode=none + - luarocks show luarocks-fetch-gitrec > /dev/null 2>&1 || luarocks install luarocks-fetch-gitrec + - luarocks show lua-http-parser > /dev/null 2>&1 || luarocks install lua-http-parser || luarocks install test/deps/lua-http-parser-2.7-1.rockspec + - luarocks show pegasus > /dev/null 2>&1 || luarocks install pegasus http.parser + --server=http://luarocks.org/manifests/moteus + - luarocks show pegasus-router > /dev/null 2>&1 || luarocks install pegasus-router + --server=http://luarocks.org/dev + - luarocks show lluv > /dev/null 2>&1 || luarocks install lluv UV_DIR=$TRAVIS_BUILD_DIR/libuv + --server=http://luarocks.org/dev + - luarocks show lluv-pegasus > /dev/null 2>&1 || luarocks install lluv-pegasus + --server=http://luarocks.org/dev --deps-mode=none + - lua test/server.lua & + - curl -s http://127.0.0.1:7090/get + +script: + - cd test + - lua -e "print(require 'cURL.utils'.find_ca_bundle())" + - lunit.sh run.lua + - lua test_pause02.c.lua + - lua test_multi_callback.lua + - lua test_multi_nested_callback.lua + +before_cache: + - coveralls -b .. -r .. --dump c.report.json + - luacov-coveralls -j c.report.json -v + - luarocks remove lua-curl + - rm -f /home/travis/.cache/pip/log/debug.log + +notifications: + email: + on_success: change + on_failure: always diff --git a/watchdog/third_party/lua-curl/.travis/platform.sh b/watchdog/third_party/lua-curl/.travis/platform.sh new file mode 100644 index 0000000..7259a7d --- /dev/null +++ b/watchdog/third_party/lua-curl/.travis/platform.sh @@ -0,0 +1,15 @@ +if [ -z "${PLATFORM:-}" ]; then + PLATFORM=$TRAVIS_OS_NAME; +fi + +if [ "$PLATFORM" == "osx" ]; then + PLATFORM="macosx"; +fi + +if [ -z "$PLATFORM" ]; then + if [ "$(uname)" == "Linux" ]; then + PLATFORM="linux"; + else + PLATFORM="macosx"; + fi; +fi diff --git a/watchdog/third_party/lua-curl/.travis/setup_uv.sh b/watchdog/third_party/lua-curl/.travis/setup_uv.sh new file mode 100644 index 0000000..34eb88e --- /dev/null +++ b/watchdog/third_party/lua-curl/.travis/setup_uv.sh @@ -0,0 +1,25 @@ +#! /bin/bash + +source .travis/platform.sh + +cd $TRAVIS_BUILD_DIR + +git clone https://github.com/libuv/libuv.git -b v1.x + +cd libuv + +git checkout v1.19.0 + +mkdir -p lib +mkdir -p build +git clone https://chromium.googlesource.com/external/gyp build/gyp + +if [ "$PLATFORM" == "macosx" ]; then + ./gyp_uv.py -f xcode && xcodebuild -ARCHS="x86_64" -project uv.xcodeproj -configuration Release -target All + cp ./build/Release/libuv.a ./lib; +else + ./gyp_uv.py -f make && BUILDTYPE=Release CFLAGS=-fPIC make -C out + cp ./out/Release/libuv.a ./lib; +fi + +cd $TRAVIS_BUILD_DIR diff --git a/watchdog/third_party/lua-curl/CMakeLists.txt b/watchdog/third_party/lua-curl/CMakeLists.txt new file mode 100644 index 0000000..fcd5b73 --- /dev/null +++ b/watchdog/third_party/lua-curl/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required (VERSION 2.6) +project (lcurl) + +file(GLOB lcurl_sources ./src/**.c) +SET(lcurl ${lcurl_sources}) + +ADD_LIBRARY (lcurl ${lcurl}) +TARGET_LINK_LIBRARIES (lcurl) \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/LICENSE b/watchdog/third_party/lua-curl/LICENSE new file mode 100644 index 0000000..3850127 --- /dev/null +++ b/watchdog/third_party/lua-curl/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2021 Alexey Melnichuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/watchdog/third_party/lua-curl/Makefile b/watchdog/third_party/lua-curl/Makefile new file mode 100644 index 0000000..3696549 --- /dev/null +++ b/watchdog/third_party/lua-curl/Makefile @@ -0,0 +1,82 @@ +include .config + +T=lcurl + +UNAME ?= $(shell uname) +DESTDIR ?= / +PKG_CONFIG ?= pkg-config +INSTALL ?= install +RM ?= rm +LUA_IMPL ?= lua +CC ?= $(MAC_ENV) gcc + +LUA_VERSION = $(shell $(PKG_CONFIG) --print-provides --silence-errors $(LUA_IMPL)) +ifeq ($(LUA_VERSION),) +LUA_CMOD ?= /usr/lib/lua/5.1 +LUA_LMOD ?= /usr/share/lua/5.1 +LIBDIR ?= /usr/lib +LUA_INC ?= /usr/include +CURL_LIBS = -L/usr/lib -lcurl +else +LUA_CMOD ?= $(shell $(PKG_CONFIG) --variable INSTALL_CMOD $(LUA_IMPL)) +LUA_LMOD ?= $(shell $(PKG_CONFIG) --variable INSTALL_LMOD $(LUA_IMPL)) +LIBDIR ?= $(shell $(PKG_CONFIG) --variable libdir $(LUA_IMPL)) +LUA_INC ?= $(shell $(PKG_CONFIG) --variable includedir $(LUA_IMPL)) +LUA_LIBS = $(shell $(PKG_CONFIG) --libs $(LUA_IMPL)) +CURL_LIBS = $(shell $(PKG_CONFIG) --libs libcurl) +endif + +ifeq ($(UNAME), Linux) +OS_FLAGS ?= -shared +endif +ifeq ($(UNAME), Darwin) +OS_FLAGS ?= -bundle -undefined dynamic_lookup +MAC_ENV ?= env MACOSX_DEPLOYMENT_TARGET='10.3' +endif + +ifneq ($(DEBUG),) +DBG = -ggdb +endif + +ifeq ($(DEV),) +WARN = -Wall -Wno-unused-value +else +WARN = -Wall -W -Waggregate-return -Wcast-align -Wmissing-prototypes -Wnested-externs -Wshadow -Wwrite-strings -pedantic +endif + +INCLUDES = -I$(LUA_INC) +DEFINES = +LIBS = $(CURL_LIBS) + +COMMONFLAGS = -O2 -g -pipe -fPIC $(OS_FLAGS) $(DBG) +LF = $(LIBS) $(LDFLAGS) +CF = $(INCLUDES) $(DEFINES) $(COMMONFLAGS) $(WARN) -DPTHREADS $(CFLAGS) + +SCR = src/lua/*.lua src/lua/cURL/*.lua src/lua/cURL/impl/*.lua +SRCS = src/*.c +OBJS = $(subst src/,,$(subst .c,.o,$(SRCS))) + +BIN = $(T).so +STATIC_LIB = $(T).a + +all: $(BIN) + +$(BIN): $(SRCS) + $(CC) $(CF) -o $@ $^ $(LF) + +$(OBJS): $(SRCS) + $(CC) $(CF) -c $^ $(LF) + +$(STATIC_LIB): $(OBJS) + ar rcs $@ $^ + +install: all + $(INSTALL) -d $(DESTDIR)$(LUA_CMOD) $(DESTDIR)$(LUA_LMOD)/cURL/impl + $(INSTALL) $(BIN) $(DESTDIR)$(LUA_CMOD) + $(INSTALL) src/lua/cURL.lua $(DESTDIR)$(LUA_LMOD) + $(INSTALL) src/lua/cURL/safe.lua $(DESTDIR)$(LUA_LMOD)/cURL + $(INSTALL) src/lua/cURL/utils.lua $(DESTDIR)$(LUA_LMOD)/cURL + $(INSTALL) src/lua/cURL/impl/cURL.lua $(DESTDIR)$(LUA_LMOD)/cURL/impl + +clean: + $(RM) -f $(BIN) $(OBJS) $(STATIC_LIB) diff --git a/watchdog/third_party/lua-curl/README.md b/watchdog/third_party/lua-curl/README.md new file mode 100644 index 0000000..e64d70e --- /dev/null +++ b/watchdog/third_party/lua-curl/README.md @@ -0,0 +1,147 @@ +# Lua binding to [libcurl](http://curl.haxx.se/libcurl) +[![Build Status](https://travis-ci.org/Lua-cURL/Lua-cURLv3.svg?branch=master)](https://travis-ci.org/Lua-cURL/Lua-cURLv3) +[![Build status](https://ci.appveyor.com/api/projects/status/oxiuv2u04ob391k0?svg=true)](https://ci.appveyor.com/project/moteus/lua-curlv3) +[![Coverage Status](https://coveralls.io/repos/Lua-cURL/Lua-cURLv3/badge.png?branch=master)](https://coveralls.io/r/Lua-cURL/Lua-cURLv3?branch=master) +[![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) + +## Status + +This module include three layer + +1. `lcurl` module provide low level pure C binding to libcurl.
+Almost ready and needs tests. I have no plans to change this API. + +2. `cURL` module provide compatibility for Lua-cURLv2 API.
+Almost ready and needs tests. + +3. `cURL` module provide new high level API.
+In fact for now it provide `lcurl` API directly and needed to redesign.
+ + +## Documentation +[lcurl API](http://lua-curl.github.io/lcurl/modules/lcurl.html)
+[Lua-cURLv2 API](http://lua-curl.github.io)
+[Lua-cURLv3 API](http://lua-curl.github.io/lcurl/modules/cURL.html) + +## + +Original Lua-cURLv2 binding has several problems: + +* it can not return error codes but just raise Lua errors (Fixed. use `cURL.safe` module) +* it raise Lua error from callback that may result resource leak in libcurl (Fixed.) +* it does not provide building multipart/formdata explicitly (Fixed.) +* it has memory leak when send multipart/formdata (Fixed.) +* it does not save string for curl options that may result crush in libcurl (Fixed.) +* there no way to get result for operations in multi interface (e.g. if one of easy operation fail you can not get result code/error message) (Fixed. But it does not very handy interface.) +* you can not use your own callback function to perform operation with multi interface (Could not be fixed without changing API.) +* you can not pass your context to callback functions (Could not be fixed without changing API.) + +## Installation + +Using LuaRocks: +``` +luarocks install Lua-cURL +``` + +Install current master: +``` +luarocks install Lua-cURL --server=https://luarocks.org/dev +``` + +## List of incompatibility with original [Lua-cURLv2](https://github.com/Lua-cURL/Lua-cURLv2) + +* objects are tables +* multi:perform() also returns ("done",code), ("error",error) and ("response",code) records +* writer callback does not recv string len (just string itself) +* on Lua > 5.2 errors are objects but not strings + +## Usage + +```Lua +-- HTTP Get +curl.easy{ + url = 'http://httpbin.org/get', + httpheader = { + "X-Test-Header1: Header-Data1", + "X-Test-Header2: Header-Data2", + }, + writefunction = io.stderr -- use io.stderr:write() + } + :perform() +:close() +``` + +```Lua +-- HTTP Post +curl.easy() + :setopt_url('http://posttestserver.com/post.php') + :setopt_writefunction(io.write) + :setopt_httppost(curl.form() -- Lua-cURL guarantee that form will be alive + :add_content("test_content", "some data", { + "MyHeader: SomeValue" + }) + :add_buffer("test_file", "filename", "text data", "text/plain", { + "Description: my file description" + }) + :add_file("test_file2", "BuildLog.htm", "application/octet-stream", { + "Description: my file description" + }) + ) + :perform() +:close() +``` + +```Lua +-- FTP Upload +local function get_bin_by(str,n) + local pos = 1 - n + return function() + pos = pos + n + return (str:sub(pos,pos+n-1)) + end +end + +curl.easy() + :setopt_url("ftp://moteus:123456@127.0.0.1/test.dat") + :setopt_upload(true) + :setopt_readfunction( + get_bin_by(("0123456789"):rep(4), 9) + ) + :perform() +:close() +``` + +```Lua +-- Multi FTP Upload + +-- We get error E_LOGIN_DENIED for this operation +e1 = curl.easy{url = "ftp://moteus:999999@127.0.0.1/test1.dat", upload = true} + :setopt_readfunction( + function(t) return table.remove(t) end, {"1111", "2222"} + ) + +e2 = curl.easy{url = "ftp://moteus:123456@127.0.0.1/test2.dat", upload = true} + :setopt_readfunction(get_bin_by(("e"):rep(1000), 5)) + +m = curl.multi() +m:add_handle(e1) +m:add_handle(e2) + +while m:perform() > 0 do m:wait() end + +while true do + h, ok, err = m:info_read() + if h == 0 then break end + + if h == e1 then + assert(ok == nil) + assert(err:name() == "LOGIN_DENIED") + assert(err:no() == curl.E_LOGIN_DENIED) + end + + if h == e2 then + assert(ok == true) + end +end +``` + diff --git a/watchdog/third_party/lua-curl/appveyor.yml b/watchdog/third_party/lua-curl/appveyor.yml new file mode 100644 index 0000000..33e885c --- /dev/null +++ b/watchdog/third_party/lua-curl/appveyor.yml @@ -0,0 +1,90 @@ +version: 0.3.2.{build} + +os: +- Windows Server 2012 R2 + +shallow_clone: true + +environment: + LR_EXTERNAL: c:\external + CURL_VER: 7.74.0 + + matrix: + - LUA: "lua 5.1" + - LUA: "lua 5.2" + - LUA: "lua 5.3" + - LUA: "lua 5.4" + +platform: + - x64 + - x86 + # - mingw + +cache: + - c:\hererocks -> appveyor.yml + - c:\external -> appveyor.yml + +install: + - set PATH=C:\Python27\Scripts;%LR_EXTERNAL%;%PATH% + - if /I "%platform%"=="x86" set HR_TARGET=vs_32 + - if /I "%platform%"=="x64" set HR_TARGET=vs_64 + - if /I "%platform%"=="mingw" set HR_TARGET=mingw + - if /I "%platform%"=="mingw" set PATH=C:\MinGW\bin;%PATH% + - if not exist "%LR_EXTERNAL%" ( + mkdir "%LR_EXTERNAL%" && + mkdir "%LR_EXTERNAL%\lib" && + mkdir "%LR_EXTERNAL%\include" + ) + - if not exist c:\hererocks ( + pip install hererocks && + hererocks c:\hererocks --%LUA% --target %HR_TARGET% -rlatest + ) + - call c:\hererocks\bin\activate + - luarocks show luarocks-fetch-gitrec >nul 2>&1 || luarocks install luarocks-fetch-gitrec + +before_build: + # external deps + - call .appveyor\install_curl.bat + +build_script: + - set LUA_CURL_VER=scm-0 + - if "%APPVEYOR_REPO_TAG%" == "true" set LUA_CURL_VER=%APPVEYOR_REPO_TAG_NAME:~1%-1 + - echo "Making lua-curl-%LUA_CURL_VER% ..." + - luarocks make rockspecs/lua-curl-%LUA_CURL_VER%.rockspec + +before_test: + # test deps + - if "%LUA%"=="lua 5.1" luarocks show bit32 >nul 2>&1 || luarocks install bit32 + - luarocks show lunitx >nul 2>&1 || luarocks install lunitx + - luarocks show dkjson >nul 2>&1 || luarocks install dkjson + - luarocks show luafilesystem >nul 2>&1 || luarocks install luafilesystem + - luarocks show lua-path >nul 2>&1 || luarocks install lua-path + - luarocks show luarocks-fetch-gitrec > /dev/null 2>&1 || luarocks install luarocks-fetch-gitrec + - luarocks show lua-http-parser > /dev/null 2>&1 || luarocks install lua-http-parser || luarocks install test/deps/lua-http-parser-2.7-1.rockspec + - luarocks show pegasus >nul 2>&1 || luarocks install pegasus http.parser + --server=http://luarocks.org/manifests/moteus + - luarocks show pegasus-router >nul 2>&1 || luarocks install pegasus-router + --server=http://luarocks.org/dev + - ps: $TestServer = Start-Process lua -ArgumentList test/server.lua -RedirectStandardOutput "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt" -RedirectStandardError "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt" -PassThru + - curl -s http://127.0.0.1:7090/get + +test_script: + - echo "Testing..." + - cd %APPVEYOR_BUILD_FOLDER%\test + - lua run.lua + - lua test_pause02.c.lua + - lua test_multi_callback.lua + - lua test_multi_nested_callback.lua + +after_test: + - cd %APPVEYOR_BUILD_FOLDER% + - .appveyor\pack_artifact.bat lua-curl bin-rock + +on_failure: + - ps: Stop-Process -Id $TestServer.Id + - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } + - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } + +on_finish: + - curl -s http://127.0.0.1:7090/get + - ps: Stop-Process -Id $TestServer.Id diff --git a/watchdog/third_party/lua-curl/doc/config.ld b/watchdog/third_party/lua-curl/doc/config.ld new file mode 100644 index 0000000..17a292b --- /dev/null +++ b/watchdog/third_party/lua-curl/doc/config.ld @@ -0,0 +1,41 @@ +project='Lua-cURLv3' +title='Lua-cURLv3 Docmentation' +description='Lua binding to libcurl' +format='discount' +backtick_references=false +file={ + './curl.ldoc', + './lcurl.ldoc', +} +dir='html' +readme='../README.md' +style='!pale' +kind_names={topic='Manual',script='Programs'} +examples = { + '../examples/lcurl', + '../examples/lcurl/file.lua', + '../examples/lcurl/pause.lua', + '../examples/lcurl/ftpupload.lua', + '../examples/lcurl/easy.obj.lua', + '../examples/lcurl/multi_iterator.lua', + '../examples/lcurl/multi.lua', + '../examples/lcurl/post.lua', + '../examples/lcurl/post_stream.lua', + '../examples/lcurl/share.lua', + + '../examples/cURLv2/file.lua', + '../examples/cURLv2/browser.lua', + '../examples/cURLv2/rss.lua', + '../examples/cURLv2/multi.lua', + '../examples/cURLv2/post_stream.lua', + '../examples/cURLv3/post_json.lua', + '../examples/cURLv3/post_form.lua', + + '../examples/cURLv3/pop3.lua', + '../examples/cURLv3/file.lua', + '../examples/cURLv3/rss.lua', + '../examples/cURLv3/multi3.lua', + '../examples/cURLv3/multi.lua', + '../examples/cURLv3/post_form.lua', + '../examples/cURLv3/multi2.lua', +} diff --git a/watchdog/third_party/lua-curl/doc/curl.ldoc b/watchdog/third_party/lua-curl/doc/curl.ldoc new file mode 100644 index 0000000..a915d4c --- /dev/null +++ b/watchdog/third_party/lua-curl/doc/curl.ldoc @@ -0,0 +1,175 @@ +--- +-- Provide same functions as `lcurl` module with some additions. +-- Here are the only functions that have been added or changed. +-- +-- @module cURL +do + +--- Create HTTP multipart/formdata object. +-- +-- @tparam FORM_DESCRIPTION form description +-- @treturn[1] Form new curl HTTP Post object context +-- +-- @see FORM_DESCRIPTION +function form() end + +end + +do + +--- Form description table. +-- +-- @table FORM_DESCRIPTION +-- @tfield string|form_stream|form_file|form_buffer|form_content content_name +-- +-- @usage +-- post_form = cURL.form{ +-- +-- -- file form filesystem +-- name01 = { +-- file = "post_form.lua", +-- type = "text/plain", +-- name = "post.lua", +-- }, +-- +-- -- file form string +-- name02 = { +-- data = "bold", +-- name = "dummy.html", +-- type = "text/html", +-- }, +-- +-- -- file form stream object +-- name03 = { +-- stream = Stream:new('8', 25), +-- name = "stream1.txt", +-- type = "text/plain", +-- headers = { +-- "X-Test-Char : 8", +-- "X-Test-length : 25", +-- } +-- }, +-- +-- -- file form stream function +-- name04 = { +-- stream = stream, +-- length = length, +-- name = "stream2.txt", +-- type = "text/plain", +-- }, +-- +-- -- content form string +-- name05 = 'value05', +-- +-- -- content form string +-- name06 = { 'value06', -- or content = 'value05' +-- type = "text/plain", +-- }, +-- +-- } + +--- Table describe stream part in form. +-- +-- @table form_stream +-- @tfield function|table|userdata stream stream function is same as in readfunction. +-- Also stream object may provide `length` method. +-- @tfield[opt=stream:length()] number length +-- @tfield[opt] string name file name +-- @tfield[opt] string type mime type (e.g. 'text/plain') +-- @tfield[opt] table headers array of headers +-- + +--- Table describe file part in form. +-- +-- @table form_file +-- @tfield string file path to file in local FS (e.g. 'path/to/some/file.txt') +-- @tfield[opt=basename(file)] string name file name +-- @tfield[opt] string type mime type (e.g. 'text/plain') +-- @tfield[opt] table headers array of headers +-- + +--- Table describe buffer part in form. +-- +-- @table form_buffer +-- @tfield string data file content +-- @tfield string name file name +-- @tfield[opt] string type mime type (e.g. 'text/plain') +-- @tfield[opt] table headers array of headers +-- + +--- Table describe content part in form. +-- +-- @table form_content +-- @tfield string content value (or first field in table) +-- @tfield[opt] string type mime type (e.g. 'text/plain') +-- @tfield[opt] table headers array of headers +-- + +end + +--- HTTP multipart/formdata object +-- @type httpform +-- +do + +--- Add new part to form. +-- +-- @tparam FORM_DESCRIPTION form description +-- @treturn[1] httpform self +-- +-- @see FORM_DESCRIPTION +function add() end + +end + +--- Easy curl object +-- @type easy +-- +do + +--- Perform a file transfer +-- +-- @tparam[opt] table options +-- @treturn[1] easy self +-- @usage +-- e:perfom{writefunction = assert(io.open("fname.txt", "w+b"))} +function perfom() end + +--- User data. +-- +-- Please use this field to associate any data with curl handle. +-- +-- @field data +-- +-- @usage +-- f = io.open("lua.org.download", "w+") +-- e = curl.easy{url = "http://lua.org", writefunction = f} +-- e.data = f + +end + +--- Muli curl object +-- @type multi +-- +do + +--- Iterator that returns the next data, type and corresponding easy handle. +-- +--
Returned data types: `response`, `header`, `data`, `error`, `done`. +--
Note. response data may appeare several times (e.g. if you proceed http request +-- with digest auth you should get 401 and 200 responses). +--
Easy handle is removed at the end of the operation (when get `done` or `error` row). +-- +-- @treturn Iterator iterator for generic for +-- +-- @usage +-- for data, type, easy in m:iperform() do ... end +function iperform() end + +--- User data. +-- +-- Please use this field to associate any data with curl handle. +-- +-- @field data + +end diff --git a/watchdog/third_party/lua-curl/doc/lcurl.ldoc b/watchdog/third_party/lua-curl/doc/lcurl.ldoc new file mode 100644 index 0000000..b997847 --- /dev/null +++ b/watchdog/third_party/lua-curl/doc/lcurl.ldoc @@ -0,0 +1,657 @@ +--- +-- @module lcurl +do + +--- Create HTTP multipart/formdata object. +-- +-- @treturn[1] httpform new curl HTTP Post object context +function form() end + +--- Create Easy object +-- +-- @tparam[opt] table options +-- @treturn[1] easy new curl easy object +-- +-- @usage +-- c = curl.easy{ +-- url = 'http://example.com', +-- [curl.OPT_VERBOSE] = true, +-- } +-- +function easy() end + +--- Create Multi object +-- +-- @tparam[opt] table options +-- @treturn[1] multi new curl multi object +-- +-- @usage +-- m = curl.multi{ +-- socketfunction = handle_socket; +-- timerfunction = start_timeout; +-- } +-- +function multi() end + +--- Create Share object +-- +-- @tparam[opt] table options +-- @treturn[1] share new curl share object +function share() end + +--- Returns libcurl version as human readable string +-- +function version() end + +--- Returns libcurl version as table +-- +-- @tparam[opt] string param specific version info +-- @treturn[1] table full version into if `param` was not specify +-- @return[2] specific info parameter +-- +-- @usage +-- proto = curl.version_info('protocols') +-- if proto.HTTP then ... -- libcurl support http protocol +function version_info() end + +--- File description table. +-- +-- Representation for `struct curl_fileinfo` +-- +-- @table curl_fileinfo +-- +-- @tfield string filename +-- @tfield integer filetype +-- @tfield integer time +-- @tfield integer perm +-- @tfield integer uid +-- @tfield integer gid +-- @tfield integer size +-- @tfield integer hardlinks +-- @tfield integer flags +-- @tfield table strings can have this string fields: `time`, `perm` , `user` , `group`, `target` + +end + +--- HTTP multipart/formdata object +-- @type httpform +-- +do + +--- Add new part to form. +-- +-- @tparam string name provide the name of this part +-- @tparam string content actual data to send +-- @tparam[opt] string type provides the content-type for this part +-- @tparam[opt] table headers specifies extra headers for the form POST section +-- @return[1] self +function add_content() end + +--- Add new part to form. +-- +-- @tparam string name provide the name of this part +-- @tparam string filename provides the filename field in the content header +-- @tparam string content actual data to send +-- @tparam[opt] string type provides the content-type for this part +-- @tparam[opt] table headers specifies extra headers for the form POST section +-- @return[1] self +function add_buffer() end + +--- Add new part to form. +-- +-- @tparam string name provide the name of this part +-- @tparam string path path to file that contain actual data to send +-- @tparam[opt] string type provides the content-type for this part +-- @tparam[opt] string filename provides the filename field in the content header. +-- By default it is basename of path. +-- @tparam[opt] table headers specifies extra headers for the form POST section +-- @return[1] self +function add_file() end + +--- Add new part to form. +-- +-- @tparam string name provide the name of this part +-- @tparam[opt] string filename provides the filename field in the content header. +-- @tparam[opt] string type provides the content-type for this part +-- @tparam[opt] table headers specifies extra headers for the form POST section +-- @tparam number size stream size in bytes +-- @tparam function/object reader +-- @param[opt] context reader context +-- @return[1] self +-- +-- @see easy:setopt_readfunction +function add_stream() end + +--- Serialize multipart/formdata HTTP POST chain. +-- +-- @return[1] string serialized data +-- +-- @usage print(post:get()) +-- +function get() end + +--- Serialize multipart/formdata HTTP POST chain. +-- +-- Writer function can return true or number of written bytes. +-- Also if function does not return anything is considered as success. +-- +-- @tparam function writer +-- @param[opt] context writer context +-- @return[1] self +-- +-- @usage +-- t = {} +-- post:get(table.insert, t) +-- print(table.concat(t)) +-- +function get() end + +--- Serialize multipart/formdata HTTP POST chain. +-- +-- This call same as httpform:get(writer.write, writer) +-- +-- @tparam object writer +-- @return[1] self +-- +-- @usage +-- f = io.open(...) +-- post:get(f) +-- +function get() end + +--- Free multipart/formdata. +-- +function free() end + +end + +--- Curl error object +-- @type error +-- +do + +--- Get the error category. +-- +-- @treturn string string of error category (curl.ERROR_XXX constants) +-- +-- @usage +-- if err:category() == curl.ERROR_EASY then +-- -- proceed easy error +-- end +function category ()end + +--- Get the number value of error. +-- +-- @treturn number number of error (curl.E_XXX constants) +function no ()end + +--- Get the error name. +-- +-- @treturn string error name (e.g. "UNSUPPORTED_PROTOCOL", "BAD_OPTION") +function name ()end + +--- Get the error description. +-- +-- @treturn string error description (e.g. "Login denied") +function msg ()end + +--- Get the full error description. +-- +-- @treturn string string that contain name, message and number of error +function __tostring ()end + +end + +--- Easy curl object +-- @type easy +-- +do + +--- Perform a file transfer +-- +-- @return[1] self +function perfom() end + +--- URL encodes the given string +-- +-- @tparam string url +-- @return[1] encoded url +function escape() end + +--- URL decodes the given string +-- +-- @tparam string url +-- @return[1] decoded url +function unescape() end + +--- Re-initializes all options previously set. +-- +-- @treturn easy self +function reset() end + +--- Pause and unpause a connection. +-- +-- @tparam number mask set of bits that sets the new state of the connection (one of PAUSE_XXX constant) +-- @treturn easy self +function pause() end + +--- End easy session +-- +function close() end + +--- Set options. +-- +-- @tparam number|table opt one of `curl.OPT_XXX` constant or options table +-- @param ... value +-- @treturn easy self +-- +-- @usage +-- c:setopt(curl.OPT_URL, "http://example.com") +-- c:setopt(curl.OPT_READFUNCTION, +-- function(t, n) return table.remove(t) end, +-- {"1111", "2222"} +-- ) +--c:setopt{ +-- url = 'http://example.com', +-- [curl.OPT_VERBOSE] = true, +-- } +function setopt() end + +--- Reset option to default value. +-- +-- @tparam number opt one of `curl.OPT_XXX` constant or options table +-- @treturn easy self +-- +-- @usage +-- c:unsetopt(curl.OPT_URL) +-- c:unsetopt(curl.OPT_READFUNCTION) +function unsetopt() end + +--- Get information. +-- +-- @tparam number info one of `curl.INFO_XXX` constant +-- @return value +-- +-- @usage +-- print(c:getinfo(curl.INFO_EFFECTIVE_URL)) +-- print(c:getinfo(curl.INFO_TOTAL_TIME)) +-- print(c:getinfo(curl.INFO_RESPONSE_CODE)) +function getinfo() end + +--- Set writer function. +-- +-- A callback accepting one or two parameters. +-- The first is the writer context if any, and the second is a string with the data to be written. +-- Function must return `true` (any non number true value) or full data length or nothing to continue operation. +-- Otherwise the transfer will be aborted with an error. +-- +-- @tparam function writer +-- @param[opt] context writer context +-- @return[1] self +-- +function setopt_writefunction() end + +--- Set writer function. +-- +-- This call same as easy:setopt_writefunction(writer.write, writer) +-- +-- @tparam object writer +-- @return[1] self +-- +function setopt_writefunction() end + +--- Set header function. +-- +-- A callback accepting one or two parameters. +-- The first is the writer context if any, and the second is a string with the data to be written. +-- Function must return `true` (any non number true value) or full data length or nothing to continue operation. +-- Otherwise the transfer will be aborted with an error. +-- +-- @tparam function writer +-- @param[opt] context writer context +-- @return[1] self +-- +function setopt_headerfunction() end + +--- Set header function. +-- +-- This call same as easy:setopt_headerfunction(writer.header, writer) +-- +-- @tparam object writer +-- @return[1] self +-- +function setopt_headerfunction() end + +--- Set reader function. +-- +-- A callback accepting one or two parameters. +-- The first is the reader context if any, and the second is the maximum amount of data to be read. +-- You can ignore second argument and pass as mach data as you need. lcurl can split data. +-- Function must return data to continue operation. To stop operation it must return empty string or nil or nothing. +-- Otherwise the transfer will be aborted with an error. +-- +-- +-- @tparam function reader +-- @param[opt] context reader context +-- @return[1] self +-- +-- @usage +-- local counter = 10 +-- c:setopt_readfunction(function() +-- if counter > 0 then +-- counter = counter - 1 +-- return 'a' +-- end +-- end) +function setopt_readfunction() end + +--- Set reader function. +-- +-- This call same as easy:setopt_readfunction(reader.read, reader) +-- +-- @tparam object reader +-- @return[1] self +-- +function setopt_readfunction() end + +--- Set progress function. +-- +-- A callback accepting four or five parameters. +-- The first is the reader context if any, the second is the total number +-- of bytes expected to be downloaded in this transfer, +-- the third is the number of bytes downloaded so far, +-- the fourth is the total number of bytes expected to be uploaded +-- in this transfer, and the fifth is the number of bytes uploaded so far. +-- Function must return `true` or `1` or nothing to continue operation. +-- Otherwise the transfer will be aborted with an error `ABORTED_BY_CALLBACK`.
+-- +-- !!! NOTE !!! This is differents form libcurl API. In libcurl returning a non-zero +-- value from this callback will cause libcurl to abort the transfer and return. +-- This done to be consisted with writefunction that should return `true` to continue. +-- +-- @tparam function progress +-- @param[opt] context progress context +-- @return[1] self +-- +function setopt_progressfunction() end + +--- Set progress function. +-- +-- This call same as easy:setopt_progressfunction(progress.progress, progress) +-- +-- @tparam object progress +-- @return[1] self +-- +function setopt_progressfunction() end + +--- Set seek callback function. +-- +-- A callback accepting two or three parameters. +-- The first is the seek context if any, the second is the `origin` value +-- same as in file.seek function from io library. Can be (`cur`, `set` and `end`). +-- The third parameter is offset or position in input stream. +-- If stream can not be seeking and need stop current transfer operation +-- function have to raise Lua error or returns `nil` and error message. +-- To indicate success function have to return any truthy value +-- In other case Function must return `true` or `1` or nothing to continue operation. +-- False value indicate that while the seek failed, libcurl is free to work +-- around the problem if possible. +-- +-- !!! NOTE !!! This is differents form libcurl API by swapping parameters in callback. +-- It done to be compatiable with Lua io library. +-- +-- @tparam function seek +-- @param[opt] context seek context +-- @return[1] self +-- +function setopt_seekfunction() end + +--- Set seek callback function. +-- +-- This call same as easy:setopt_seekfunction(stream.seek, seek) +-- +-- @tparam object stream +-- @return[1] self +-- +function setopt_seekfunction() end + +--- Set debug callback function. +-- +-- A callback accepting two or three parameters. +-- The first is the debug context if any, the second is the `data_type` value +-- which represent what kinde of data is passed. This value can be one of +-- constant `INFO_TEXT`, `INFO_HEADER_IN`, `INFO_HEADER_OUT`, `INFO_DATA_IN`, +-- `INFO_DATA_OUT`, `INFO_SSL_DATA_IN`, `INFO_SSL_DATA_OUT`. +-- The third parameter is data itself. +-- Any errors and returning values from this callback are ignored. +-- +-- @tparam function debug +-- @param[opt] context debug context +-- @return[1] self +-- +function setopt_debugfunction() end + +--- Set debug callback function. +-- +-- This call same as easy:setopt_debugfunction(debug.debug, debug) +-- +-- @tparam object debug +-- @return[1] self +-- +function setopt_debugfunction() end + +--- Set match callback function. +-- +-- A callback accepting two or three parameters. +-- The first is the match context if any, the second is the pattern string, +-- the third is the string to match to pattern. +-- Function must return truthy value to indicate that string match to pattern. +-- If function raise error or return nil and error message then it indicate +-- that curl should interrupt current transfer operation. +-- False value indicate that strind does not match to pattern +-- +-- @tparam function match +-- @param[opt] context match context +-- @return[1] self +-- +function setopt_fnmatch_function() end + +--- Set match callback function. +-- +-- This call same as easy:setopt_fnmatch_function(match.match, match) +-- +-- @tparam object match +-- @return[1] self +-- +function setopt_fnmatch_function() end + + +--- Set chunk begin function. +-- +-- A callback accepting two or three parameters. +-- The first is the chunk context if any, the second is the @{curl_fileinfo} table. +-- The third parameter contains number of chunks remaining per the transfer. +-- Function must return truthy value or nothing to indicate that transfer can begin. +-- If function raise error or return nil and error message then it indicate +-- that curl should interrupt current transfer operation. +-- False value indicate that curl should skip this chunk. +-- +-- @tparam function chunk_bgn +-- @param[opt] context chunk context +-- @return[1] self +-- +function setopt_chunk_bgn_function() end + +--- Set chunk begin function. +-- +-- This call same as easy:setopt_chunk_bgn_function(context.chunk_bgn, context) +-- +-- @tparam object context +-- @return[1] self +-- +function setopt_chunk_bgn_function() end + +--- Set chunk end function. +-- +-- A callback accepting zero or one parameter. +-- The only parameter is the chunk context if any. +-- Function must return truthy value or nothing to indicate that transfer can continue +-- otherwise it indicate that curl should interrupt current transfer operation. +-- +-- @tparam function chunk_end +-- @param[opt] context chunk context +-- @return[1] self +-- +function setopt_chunk_end_function() end + +--- Set chunk begin function. +-- +-- This call same as easy:setopt_chunk_end_function(context.chunk_end, context) +-- +-- @tparam object context +-- @return[1] self +-- +function setopt_chunk_end_function() end + +--- Set HTTP multipart/formdata. +-- +-- Caller does not have to save data. +-- +-- @tparam httpform data +-- @return[1] self +function setopt_httppost() end + +--- Set HTTP multipart/formdata. +-- +-- @tparam string data +-- @tparam[opt=#data] number length +-- @return[1] self +function setopt_postfields() end + +--- Set curl share object. +-- +-- Caller does not have to save data. +-- +-- @tparam share data +-- @return[1] self +function setopt_share() end + +end + +--- Muli curl object +-- @type multi +-- +do + +--- Add Easy object. +-- +-- Caller must ensure that easy object is alive until end of operation. +-- +-- @treturn multi self +-- @tparam easy handle +function add_handle() end + +--- Remove Easy object. +-- +-- @tparam easy handle +-- @treturn multi self +function remove_handle() end + +--- reads/writes available data from each easy handle. +-- +-- @treturn number handles number of active easy handles +function perfom() end + +--- Read multi stack informationals. +-- +-- Note. If curl_multi_remove_handle fail then there no error occure but handle jast stay in multi handle. +-- +-- @tparam[opt] remove remove easy handle if it done. +-- @treturn[1] number 0 there no informationals +-- @treturn[2] easy handle +-- @treturn[2] boolean true +-- @treturn[3] easy handle +-- @treturn[3] nil +-- @treturn[3] error error code +function info_read() end + +--- Set options. +-- +-- @tparam number|table opt one of `curl.OPT_MULTI_XXX` constant +-- @param ... value +-- @treturn multi self +-- +-- @usage +-- c:setopt(curl.OPT_MULTI_MAXCONNECTS, 10) +-- c:setopt{maxconnects = 10} +function setopt() end + +--- Perform socket action. +-- +-- @tparam[opt=curl.SOCKET_TIMEOUT] number socket +-- @tparam[opt=0] number mask +-- @treturn multi self +-- +-- @usage +-- c:socket_action() +-- c:socket_action(sock_fd, curl.CSELECT_IN) +-- c:socket_action(sock_fd, curl.CSELECT_OUT) +function socket_action() end + +--- Set timer callback. +-- +-- @tparam function timer timer callback +-- @param[opt] context timer context +-- @treturn multi self +-- +function setopt_timerfunction() end + +--- Set timer callback. +-- +-- This call same as easy:setopt_writefunction(timer.timer, timer) +-- +-- @tparam userdata|table timer timer object +-- @treturn multi self +-- +function setopt_timerfunction() end + +--- Polls on all easy objects in a multi object. +-- +-- @tparam[opt] number timeout milliseconds timeout. By default it is `multi:timeout()`. +-- @treturn number number of affected objects +-- +function wait() end + +--- How long to wait for action before proceeding. +-- +-- @treturn number timeout milliseconds timeout +-- +function timeout() end + +--- End multi session. +-- +function close() end + +end + +--- Share curl object +-- @type share +-- +do + +--- Set options. +-- +-- @tparam number|table opt one of `curl.OPT_SHARE_XXX` constant +-- @param ... value +-- @treturn share self +-- +-- @usage +-- c:setopt(curl.OPT_SHARE_SHARE, curl.LOCK_DATA_COOKIE) +-- c:setopt{share = curl.LOCK_DATA_COOKIE} +function setopt() end + +--- End share session. +-- +function close() end + +end diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/browser.lua b/watchdog/third_party/lua-curl/examples/cURLv2/browser.lua new file mode 100644 index 0000000..a73e151 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv2/browser.lua @@ -0,0 +1,266 @@ +----------------------------------------------------------------------------- +-- A Browser Class for easy Web Automation with Lua-cURL +-- Author: Kai Uwe Jesussek +-- RCS ID: $Id: browser.lua,v 0.1 2011/03/11 23:55:20 kai Exp $ +----------------------------------------------------------------------------- + +local cURL = require("cURL") +local string = require("string") +local table = require("table") +local base = _G + +USERAGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" --windows xp internet explorer 6.0 + +--this function joins 2 urls (absolute or relative) +function url_join(_base, _url) + assert(type(_url) == "string") + + if _base == nil or _base == "" then + return _url + end + + assert(type(_base) == "string") + local base = url_split(_base) + local url = url_split(_url) + + + local protocol = base.protocol + local host = base.host + + local path = "" + local port = "" + + if url.protocol ~= nil then + protocol = url.protocol + if url.path ~= nil then + path = url.path + end + if url.port ~= nil and url.port ~= "" then + port = url.port + end + if url.host ~= nil then + host = url.host + end + else + if _url:sub(1,2) == "//" then + --set host and path + host, port, path = _url:match("^//([^;/%?]+)(:?%d*)(/?.*)") + if path == nil then + path = "" + end + elseif _url:sub(1,1) == "/" then + port = base.port + --replace path + path = _url + else + --combine paths :( + path = base.path:match("^(.*)/[^/]*") + port = base.port + if path ~= nil then + path = path .. "/" .. _url + else + path = _url + end + end + + end + local ret = protocol .. "://" .. host .. port .. path + return ret +end + +--this function splits an url into its parts +function url_split(_url) + --print(_url) + local ret = {} + --test ipv6 + ret.protocol, ret.host, ret.port, ret.path = _url:match("^(https?)://(%[[0-9a-fA-F:]+%])(:?%d*)(.*)$") + + if ret.host == nil then + --fall back to ipv4 + ret.protocol, ret.host, ret.port, ret.path = _url:match("^(https?)://([^:/]+)(:?%d*)(.*)$") + end + return ret +end + + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +-- taken from Lua Socket and added underscore to ignore (MIT-License) +----------------------------------------------------------------------------- +function escape(s) + return string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end) +end + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +-- taken from Lua Socket +----------------------------------------------------------------------------- +function unescape(s) + return string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end) +end + +-- from encodes a key, value dictionary table +function tblencode (_arguments) + local ret = "" + if _arguments == nil or next(_arguments) == nil then -- no _arguments or empty _arguments? + return ret + end + + --iterate over each key -> value pairs and urlencode them + for key, vals in pairs(_arguments) do + if type(vals) ~= "table" then + vals = {vals} + end + for i,val in ipairs(vals) do + ret = ret .. "&"..key.. "=" ..escape(val) + end + end + + --cut off leadin '&' + return string.sub(ret,2) +end + + +--function helper for result +--taken from luasocket page (MIT-License) +local function build_w_cb(t) + return function(s) + table.insert(t, s) + return #s,nil + end +end + +--function helper for headers +--taken from luasocket page (MIT-License) +local function h_build_w_cb(t) + return function(s) + --stores the received data in the table t + --prepare header data + name, value = s:match("(.-): (.+)") + if name and value then + t.headers[name] = value:gsub("[\n\r]", "") + else + code, codemessage = string.match(s, "^HTTP/.* (%d+) (.+)$") + if code and codemessage then + t.code = tonumber(code) + t.codemessage = codemessage:gsub("[\n\r]", "") + end + end + return #s,nil + end +end + + +--the browser easy to use interface +browser = {} +function browser:new(_share) + if _share == nil then + _share = cURL.share_init() + _share:setopt_share("COOKIE") + _share:setopt_share("DNS") + end + local object = { url = nil, share = _share} + setmetatable(object, {__index = browser}) + return object +end + +--this function sets the proxy variables for the prepare function +function browser:setProxy(_proxy, _proxytype) + self.proxy = _proxy + self.proxytype = _proxytype + print("setting proxy", self.proxy, self.proxytype) +end + +--this function prepares a request +function browser:prepare(post_data, urlencoded) + local req = cURL.easy_init() + req:setopt_share(self.share) + req:setopt_url(self.url) + req:setopt_useragent(USERAGENT) + if self.proxy ~= nil and self.proxytype ~= nil then + req:setopt_proxy(self.proxy) + req:setopt_proxytype(self.proxytype) + end + if self.caInfoPath ~= nil then + req:setopt_cainfo(self.caInfoPath) + end + if post_data ~= nil then + if urlencoded and type(post_data) == "table" then + post_data = tblencode(post_data) + end + + if type(post_data) == "string" then + req:setopt_post(1) + req:setopt_postfields(post_data) + req:setopt_postfieldsize(#post_data) + else + req:post(post_data) + end + end + return req +end + +--this function sets the url +function browser:setUrl(url) + --appends a leading / to url if needed + if self.url and self.url:match("^(https?://[^/]+)$") then + self.url = self.url .. "/" + end + self.url = url_join(self.url or "", url) +end + +--opens a webpage :) only the first parameter is required +function browser:open(url, post_data, redirect, urlencoded) + local redirect = redirect or true + local urlencoded = urlencoded == nil + local ret = {} + response_body = {} + ret.headers = {} + + self:setUrl(url) + local req = self:prepare(post_data, urlencoded) + + req:perform({headerfunction=h_build_w_cb(ret), writefunction=build_w_cb(response_body)}) + + self:setUrl(url) + ret.body = table.concat(response_body) + + if redirect and ret.headers and (ret.headers.Location or ret.headers.location) and (ret.code == 301 or ret.code == 302) then + return self:open(url_join(self.url, ret.headers.Location or ret.headers.location), nil, extra_headers, redirect) + end + return ret + +end +--opens a webpage :) only the first and second parameters are required +function browser:save(url, filename, post_data) + local ret = {} + ret.headers = {} + self:setUrl(url) + local req = self:prepare(post_data, false) + file = io.open(filename) + req:perform({headerfunction=h_build_w_cb(ret), writefunction=function(str) file:write(str) end }) + file:close() +end + +function browser:setCaInfo(path) + self.caInfoPath = path +end + +--[[ usage examples: +-- b = browser:new() +-- resp = b:open("http://www.html-kit.com/tools/cookietester/") +-- print(resp.body) +-- table.foreach(resp.headers, print) +--]] diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/file.lua b/watchdog/third_party/lua-curl/examples/cURLv2/file.lua new file mode 100644 index 0000000..64c23a8 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv2/file.lua @@ -0,0 +1,16 @@ +local cURL = require("cURL") + +-- open output file +f = io.open("example_homepage", "w") + +c = cURL.easy_init() +-- setup url +c:setopt_url("http://www.example.com/") +-- perform, invokes callbacks +c:perform{writefunction = function(str) + f:write(str) +end} + +-- close output file +f:close() +print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/multi.lua b/watchdog/third_party/lua-curl/examples/cURLv2/multi.lua new file mode 100644 index 0000000..8052e93 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv2/multi.lua @@ -0,0 +1,22 @@ +local cURL = require("cURL") + +-- setup easy +c1 = cURL.easy_init() +c2 = cURL.easy_init() + +-- setup url +c1:setopt_url("http://www.lua.org/") +c2:setopt_url("http://luajit.org/") + +m = cURL.multi_init() +m:add_handle(c1) +m:add_handle(c2) + +local f1 = io.open("lua.html","a+") +local f2 = io.open("luajit.html","a+") + +for data, type, easy in m:perform() do +-- if (type == "header") then print(data) end + if (type == "data" and c1 == easy) then f1:write(data) end + if (type == "data" and c2 == easy) then f2:write(data) end +end \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua b/watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua new file mode 100644 index 0000000..94bbe05 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua @@ -0,0 +1,49 @@ +local cURL = require("cURL") + +-- returns size and reader +local function make_stream(ch, n, m) + local size = n * m + local i = -1 + return size, function() + i = i + 1 + if i < m then + return (tostring(ch)):rep(n - 2) .. '\r\n' + end + end +end + +c = cURL.easy_init() + +c:setopt_url("http://posttestserver.com/post.php") + +c:setopt_post(true) + +local length, reader = make_stream("a", 10, 4) + +c:post{-- post file from private read function + -- Lua-cURL compatiable + -- allows only one stream + name = { + file="stream.txt", + stream_length=length, + type="text/plain", + } +} + +c:perform{readfunction = reader} + + +local length, reader = make_stream("b", 10, 4) +c:post{-- post file from private read function + -- define stream callback + name = { + file = "stream.txt", + type = "text/plain", + stream_length = length, + stream = reader, + } +} + +c:perform{} + +print("Done") diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/rss.lua b/watchdog/third_party/lua-curl/examples/cURLv2/rss.lua new file mode 100644 index 0000000..e21d864 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv2/rss.lua @@ -0,0 +1,54 @@ +-- use LuaExpat and Lua-CuRL together for On-The-Fly XML parsing +local lxp = require("lxp") +local cURL = require("cURL") + +tags = {} +items = {} + +callback = {} + +function callback.StartElement(parser, tagname) + tags[#tags + 1] = tagname + if (tagname == "item") then + items[#items + 1] = {} + end +end + +function callback.CharacterData(parser, str) + if (tags[#tags -1] == "item") then + --we are parsing a item, get rid of trailing whitespace + items[#items][tags[#tags]] = string.gsub(str, "%s*$", "") + end +end + +function callback.EndElement(parser, tagname) + --assuming well formed xml + tags[#tags] = nil +end + +p = lxp.new(callback) + +-- create and setup easy handle +c = cURL.easy_init() +c:setopt_url("http://www.lua.org/news.rss") + +m = cURL.multi_init() +m:add_handle(c) + +for data,type in m:perform() do + -- ign "header" + if (type == "data") then + assert(p:parse(data)) + end +end + +--finish document +assert(p:parse()) +p:close() + +for i, item in ipairs(items) do + for k, v in pairs(item) do + print(k,v) + end + print() +end \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/file.lua b/watchdog/third_party/lua-curl/examples/cURLv3/file.lua new file mode 100644 index 0000000..4ddf256 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/file.lua @@ -0,0 +1,16 @@ +local cURL = require "cURL" + +-- open output file +f = io.open("example_homepage", "w") + +cURL.easy{ + url = "http://www.example.com/", + writefunction = f +} +:perform() +:close() + +-- close output file +f:close() + +print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/multi.lua b/watchdog/third_party/lua-curl/examples/cURLv3/multi.lua new file mode 100644 index 0000000..395a297 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/multi.lua @@ -0,0 +1,17 @@ +local cURL = require("cURL") + +-- setup easy and url +c1 = cURL.easy{url = "http://www.lua.org/"} +c2 = cURL.easy{url = "http://luajit.org/"} + +m = cURL.multi() + :add_handle(c1) + :add_handle(c2) + +local f1 = io.open("lua.html", "w+b") +local f2 = io.open("luajit.html", "w+b") + +for data, type, easy in m:iperform() do + if type == "data" and c1 == easy then f1:write(data) end + if type == "data" and c2 == easy then f2:write(data) end +end diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua b/watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua new file mode 100644 index 0000000..49850c2 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua @@ -0,0 +1,35 @@ +local cURL = require("cURL") + +local f1 = io.open("lua.html", "w+b") +local f2 = io.open("luajit.html", "w+b") + +-- setup easy and url +c1 = cURL.easy{url = "http://www.lua.org/", writefunction = f1} +c2 = cURL.easy{url = "http://luajit.org/", writefunction = f2} +c3 = cURL.easy{url = "****://luajit.org/"} -- UNSUPPORTED_PROTOCOL + +m = cURL.multi() + :add_handle(c1) + :add_handle(c2) + :add_handle(c3) + +local remain = 3 +while remain > 0 do + local last = m:perform() -- do some work + if last < remain then -- we have done some tasks + while true do -- proceed results/errors + local e, ok, err = m:info_read(true) -- get result and remove handle + if e == 0 then break end -- no more finished tasks + if ok then -- succeed + print(e:getinfo_effective_url(), '-', e:getinfo_response_code()) + else -- failure + print(e:getinfo_effective_url(), '-', err) + end + e:close() + end + end + remain = last + + -- wait while libcurl do io select + m:wait() +end diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua b/watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua new file mode 100644 index 0000000..fff826c --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua @@ -0,0 +1,28 @@ +local cURL = require("cURL") + +local urls = { + "http://httpbin.org/get?key=1", + "http://httpbin.org/get?key=2", + "http://httpbin.org/get?key=3", + "http://httpbin.org/get?key=4", +} + +local function next_easy() + local url = table.remove(urls, 1) + if url then return cURL.easy{url = url} end +end + +m = cURL.multi():add_handle(next_easy()) +for data, type, easy in m:iperform() do + + if type == "done" or type == "error" then + print("Done", easy:getinfo_effective_url(), ":", data) + easy:close() + easy = next_easy() + if easy then m:add_handle(easy) end + end + + if type == "data" then print(data) end + +end + diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua b/watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua new file mode 100644 index 0000000..3ecc401 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua @@ -0,0 +1,346 @@ +-- Simple pop3 wrapper +-- +-- @usage +-- local mbox = pop3.new('pop3s://pop3.yandex.ru') +-- +-- -- Yandex works only with tls +-- print('Open: ', mbox:open_tls('***', '***')) +-- print('NOOP: ', mbox:noop()) +-- print('RETR: ', mbox:retr(1)) +-- +-- -- use message class from lua-pop3 +-- -- https://github.com/moteus/lua-pop3 +-- for k, msg in mbox:messages() do +-- print"----------------------------------------------" +-- print("subject: ", msg:subject()) +-- print("from: ", msg:from()) +-- print("to: ", msg:to()) +-- for i,v in ipairs(msg:full_content()) do +-- if v.text then print(" ", i , "TEXT: ", v.type, #v.text) +-- else print(" ", i , "FILE: ", v.type, v.file_name or v.name, #v.data) end +-- end +-- end +-- +-- mbox:close() +-- + +local cURL = require "cURL.safe" +local find_ca_bundle = require "cURL.utils".find_ca_bundle +local message do local ok + ok, message = pcall(require, "pop3.message") + if not ok then message = nil end +end + +local function split(str, sep, plain) + local b, res = 1, {} + while b <= #str do + local e, e2 = string.find(str, sep, b, plain) + if e then + table.insert(res, (string.sub(str, b, e-1))) + b = e2 + 1 + else + table.insert(res, (string.sub(str, b))) + break + end + end + return res +end + +local function pars_response(resp) + local code, info = string.match(resp,"%s*(%S*)(.*)%s*") + -- SASL GET ONLY "+"/"-" + if code == '+OK' or code == '+' then + return true, info + elseif code == '-ERR' or code == '-' then + return false, info + end + return nil, resp +end + +local function split_2_numbers(data) + local n1, n2 = string.match(data, "%s*(%S+)%s+(%S+)") + return tonumber(n1), tonumber(n2) +end + +local function split_1_number(data) + local n1,s= string.match(data, "%s*(%S+)%s*(%S*)") + return tonumber(n1),s +end + +local crln = '\r\n' +local function writer(cb, ctx) + local tail + return function(str) + -- @check Is libcurl guarantee thant data will be arraived line by line? + -- if so we can just call `cb` + if str then + local t = split(tail and (tail .. str) or str, crln, true) + if str:sub(-2) == crln then tail = nil + else tail = table.remove(t) end + for _, s in ipairs(t) do cb(s, ctx) end + elseif tail then cb(tail, ctx) end + end +end + +local pop3 = {} do +pop3.__index = pop3 + +function pop3:new(host) + return setmetatable({ + _url = assert(host) + },self) +end + +local function open(self, user, password, ssl) + self._easy, err = cURL.easy{ + url = self._url, + username = user, + password = password, + customrequest = 'NOOP', + nobody = true, + headerfunction = function(h) self._response = h end + } + if not self._easy then return nil, err end + + if ssl then + -- For AVAST + -- http://www.avast.com/en-eu/faq.php?article=AVKB91#artTitle + local cainfo, capath = find_ca_bundle('MailShield.crt') + if not cainfo then + -- On Windows try find ca_bundle + cainfo, capath = find_ca_bundle() + end + local ok, err = self._easy:setopt{ + use_ssl = cURL.USESSL_ALL, + cainfo = cainfo, + capath = capath, + } + if not ok then + self:close() + return nil, err + end + end + + local ok, err = self._easy:perform() + if not ok then + self:close() + return nil, err + end + + return self +end + +local function exec(self) + self._response = nil + local ok, err = self._easy:perform() + if not ok then return nil, err end + assert(self._response) + self._response = self._response:gsub("%s+$", "") + return self:response() +end + +local function exec_no_body(self, cmd, url) + local ok, err = self._easy:setopt{ + url = self._url .. (url and ("/" .. url) or ""), + customrequest = cmd or '', + nobody = true, + } + if not ok then return nil, err end + return exec(self) +end + +local function exec_body_cb(self, cb, cmd, url) + local ok, err = self._easy:setopt{ + url = self._url .. (url and ("/" .. url) or ""), + customrequest = cmd or '', + nobody = false, + writefunction = writer(assert(cb)) + } + if not ok then return nil, err end + ok, err = exec(self) + if not ok then return nil, err end + return true +end + +local function exec_body(self, cmd, url) + local t = {} + local ok, err = exec_body_cb(self, + function(s) t[#t+1] = s end, + cmd, url + ) + if not ok then return nil, err end + return t +end + +local function exec_no_body_2_numbers(...) + local ok, data = exec_no_body(...) + if not ok then return ok, data end + return split_2_numbers(data) +end + +function pop3:open(user, password) + return open(self, user, password, false) +end + +function pop3:response() + if self._response then + return pars_response(self._response) + end +end + +function pop3:verbose(...) + local ok, err + if select("#", ...) == 0 then + ok, err = self._easy:setopt_verbose(true) + else + ok, err = self._easy:setopt_verbose(not not ...) + end + if not ok then return nil, ok end + return self +end + +function pop3:open_tls(user, password) + return open(self, user, password, true) +end + +function pop3:noop() + return exec_no_body(self, 'NOOP') +end + +function pop3:stat() + return exec_no_body_2_numbers(self, 'STAT') +end + +function pop3:dele(id) + return exec_no_body(self, 'DELE ' .. id) +end + +function pop3:rset() + return exec_no_body_2_numbers(self, 'RSET') +end + +function pop3:list(id) + if id then + return exec_no_body_2_numbers(self, 'LIST ' .. id) + end + + local t,i = {},0 + local fn = function(data) + local no, size = split_2_numbers(data) + if not (no and size) then + return nil, "Wrong Response: `" .. data .. "`" + end + t[no] = size + i = i + 1 + end + + local ok, err = exec_body_cb(self, fn, '') + if not ok then return nil, err end + if not ok then return nil, err end + return t, i +end + +function pop3:uidl(id) + if id then + local ok, data = exec_no_body(self, 'UIDL ' .. id) + if not ok then return ok, data end + local no, id = split_1_number(data) + if not (no and id) then + return nil, "Wrong Response:" .. data + end + return no,id + end + + local t,i = {},0 + local fn = function(data) + local no, id = split_1_number(data) + if not (no and id) then + return nil, "Wrong Response:" .. data + end + t[no]=id + i = i + 1 + return true + end + + local ok, err = exec_body_cb(self, fn, 'UIDL') + if not ok then return nil, err end + if not ok then return nil, err end + return t, i +end + +function pop3:retr(id) + assert(id) + return exec_body(self, '', tostring(id)) +end + +function pop3:top(id, n) + assert(id) + assert(type(n) == "number") + return exec_body(self, 'TOP ' .. id .. ' ' .. n) +end + +function pop3:make_iter(fn) + local lst, err = self:list() + if not lst then error(err) end + local k = nil + + local iter + iter = function () + k = next(lst, k) + if not k then return nil end + + -- skip deleted messages ? + local no, size = self:list(k) + if no == false then return iter() end -- next message + if not no then return error(size) end + assert(no == k) + + local data, err = fn(self, k, size) + if not data then error(err) end + + return k, data + end + + return iter +end + +if message then + +function pop3:message(msgid) + local msg, err = self:retr(msgid) + if not msg then return nil, err end + return message(msg) +end + +end + +function pop3:retrs() + return self:make_iter(self.retr) +end + +function pop3:tops(n) + return self:make_iter(function(self, msgid) + return self:top(msgid, n) + end) +end + +function pop3:messages() + return self:make_iter(self.message) +end + +function pop3:closed() + return not not self._easy +end + +function pop3:close() + if self._easy then + self._easy:close() + self._easy = false + end +end + +end + +return { + new = function(...) return pop3:new() end; +} diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua b/watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua new file mode 100644 index 0000000..ce3a8f8 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua @@ -0,0 +1,87 @@ +local cURL = require "cURL" + +-- Stream class +local Stream = {} do +Stream.__index = Stream + +function Stream:new(ch, length) + local o = setmetatable({}, self) + o._len = length + o._ch = ch + o._pos = 0 + o._step = 7 + return o +end + +function Stream:length() + return self._len +end + +function Stream:read() + local n = self._len - self._pos + if n <= 0 then return '' end -- eof + if n > self._step then n = self._step end + self._pos = self._pos + n + return self._ch:rep(n) +end + +end + +-- returns size and reader +local function make_stream(ch, n, m) + local size = n * m + local i = -1 + return size, function() + i = i + 1 + if i < m then + return (tostring(ch)):rep(n - 2) .. '\r\n' + end + return nil + end +end + +local length, stream = make_stream("a", 10, 4) + +c = cURL.easy{ + url = "http://posttestserver.com/post.php", + -- url = "http://httpbin.org/post", + post = true, + httppost = cURL.form{ + + -- file form filesystem + name01 = { + file = "post_form.lua", + type = "text/plain", + name = "post.lua", + }, + + -- file form string + name02 = { + data = "bold", + name = "dummy.html", + type = "text/html", + }, + + -- file form stream object + name03 = { + stream = Stream:new('8', 25), + name = "stream1.txt", + type = "text/plain", + headers = { + "X-Test-Char : 8", + "X-Test-length : 25", + } + }, + + -- file form stream function + name04 = { + stream = stream, + length = length, + name = "stream2.txt", + type = "text/plain", + }, + + }, +} + +c:perform() diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua b/watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua new file mode 100644 index 0000000..f41f8f0 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua @@ -0,0 +1,12 @@ +local cURL = require "cURL" + +c = cURL.easy{ + url = "http://posttestserver.com/post.php", + post = true, + httpheader = { + "Content-Type: application/json"; + }; + postfields = '{"hello": "world"}'; +} + +c:perform() \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua b/watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua new file mode 100644 index 0000000..fd041db --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua @@ -0,0 +1,47 @@ +local curl = require "cURL" + +-- Documentation does not restrict using the same mime with different easy handle. +-- In fact it does not mention this case at all. But in my experiments it works. +local easy = curl.easy() + +-- Create new mime object. +local mime = easy:mime() + +-- Add part. Source of data is file on disk. +-- If file not exists then perform returns `[CURL-EASY][READ_ERROR]` error +-- There no way to remove existed part from mime. +local part = mime:addpart{ + filedata = './post_mime.lua', + type = 'application/lua', + filename = 'post_mime.lua', + encoder = 'base64', +} + +local buffer = {} + +-- Use mime object as request body +easy:setopt{ + url = 'http://127.0.0.1:7090/post', + mimepost = mime, +} + +easy:setopt_writefunction(table.insert, buffer) + +local ok, err = easy:perform() +if ok then + local code, url, content = easy:getinfo_effective_url(), + easy:getinfo_response_code(), table.concat(buffer) + print(code, url) + print(content) +else + print(err) +end + +easy:close() + +-- Lua-cURL does not free mime when close `parent` easy. + +-- Explicitly free mime object and all its parts +-- There no way to reuse only some parts or submimes. +-- but it possible reuse entire mime again. +mime:free() diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/rss.lua b/watchdog/third_party/lua-curl/examples/cURLv3/rss.lua new file mode 100644 index 0000000..f29d914 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/rss.lua @@ -0,0 +1,47 @@ +-- use LuaExpat and Lua-CuRL together for On-The-Fly XML parsing +local lxp = require "lxp" +local cURL = require "cURL" + +-- create XML parser +items, tags = {}, {} +p = lxp.new{ + StartElement = function (parser, tagname) + tags[#tags + 1] = tagname + if (tagname == "item") then + items[#items + 1] = {} + end + end; + + CharacterData = function (parser, str) + if (tags[#tags -1] == "item") then + --we are parsing a item, get rid of trailing whitespace + items[#items][tags[#tags]] = string.gsub(str, "%s*$", "") + end + end; + + EndElement = function (parser, tagname) + --assuming well formed xml + tags[#tags] = nil + end; +} + +-- create and setup easy handle +c = cURL.easy{url = "http://www.lua.org/news.rss"} + +-- setup writer function with context +c:setopt_writefunction(p.parse, p) + +-- perform request and close easy handle +-- perform raise error if parser fail +c:perform():close() + +--finish document +assert(p:parse()) +p:close() + +for i, item in ipairs(items) do + for k, v in pairs(item) do + print(k,v) + end + print() +end \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua b/watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua new file mode 100644 index 0000000..2d6f1ac --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua @@ -0,0 +1,278 @@ +-- +-- implementation of uvwget example from +-- http://nikhilm.github.io/uvbook/index.html +-- + +local curl = require "cURL" +local uv = require "lluv" +local ut = require "lluv.utils" + +local fprintf = function(f, ...) f:write((string.format(...))) end + +local stderr = io.stderr + +local trace = false do + +trace = trace and print or function() end + +end + +local ACTION_NAMES = { + [curl.POLL_IN ] = "POLL_IN"; + [curl.POLL_INOUT ] = "POLL_INOUT"; + [curl.POLL_OUT ] = "POLL_OUT"; + [curl.POLL_NONE ] = "POLL_NONE"; + [curl.POLL_REMOVE ] = "POLL_REMOVE"; +} + +local POLL_IO_FLAGS = { + [ curl.POLL_IN ] = uv.READABLE; + [ curl.POLL_OUT ] = uv.WRITABLE; + [ curl.POLL_INOUT ] = uv.READABLE + uv.WRITABLE; +} + +local EVENT_NAMES = { + [ uv.READABLE ] = "READABLE"; + [ uv.WRITABLE ] = "WRITABLE"; + [ uv.READABLE + uv.WRITABLE ] = "READABLE + WRITABLE"; +} + +local FLAGS = { + [ uv.READABLE ] = curl.CSELECT_IN; + [ uv.WRITABLE ] = curl.CSELECT_OUT; + [ uv.READABLE + uv.WRITABLE ] = curl.CSELECT_IN + curl.CSELECT_OUT; +} + +local Context = ut.class() do + +function Context:__init(fd) + self._fd = assert(fd) + self._poll = uv.poll_socket(fd) + self._poll.data = {context = self} + + assert(self._poll:fileno() == fd) + + return self +end + +function Context:close() + if not self._poll then return end + self._poll.data = nil + self._poll:close() + self._poll, self._fd = nil +end + +function Context:poll(...) + self._poll:start(...) +end + +function Context:fileno() + return self._fd +end + +end + +local MAX_REQUESTS = 64 -- Number of parallel request +local timer, multi +local qtask = ut.Queue.new() -- wait tasks +local qfree = ut.Queue.new() -- avaliable easy handles +local qeasy = {} -- all easy handles + +local function on_begin(handle, url, num) + local filename = tostring(num) .. ".download" + local file = io.open(filename, "w") + if not file then + fprintf(stderr, "Error opening %s\n", filename) + return + end + handle.data.file = file + handle:setopt_writefunction(file) + + fprintf(stderr, "Added download %s -> %s\n", url, filename); + return true +end + +local function on_end(handle, err, url) + handle.data.file:close() + handle.data.file = nil + + if err then + fprintf(stderr, "%s ERROR - %s\n", url, tostring(err)); + else + fprintf(stderr, "%s DONE\n", url); + end +end + +local function cleanup() + timer:close() + + for i, easy in ipairs(qeasy) do + multi:remove_handle(easy) + if easy.data then + local context = easy.data.context + if context then context:close() end + + local file = easy.data.file + if file then on_end(easy, 'closed', easy:getinfo_effective_url()) end + end + easy:close() + end + + multi:close() +end + +local proceed_queue, add_download do + +proceed_queue = function() + while true do + if qtask:empty() then return end + + if qfree:empty() then + if #qeasy < MAX_REQUESTS then + local easy = assert(curl.easy()) + qeasy[#qeasy + 1] = easy + qfree:push(easy) + else + return + end + end + + local task = assert(qtask:pop()) + local url, num = task[1], task[2] + + local handle = assert(qfree:pop()) + + handle:setopt{ + url = url; + fresh_connect = true; + forbid_reuse = true; + } + + handle.data = {} + + if on_begin(handle, url, num) then + multi:add_handle(handle) + else + handle:reset().data = nil + qfree:push(handle) + end + end +end + +add_download = function(url, num) + qtask:push{url, num} + + proceed_queue() +end + +end + +local on_libuv_poll, on_libuv_timeout + +local on_curl_timeout, on_curl_action do + +on_curl_timeout = function(ms) + -- calls by curl -- + trace("CURL::TIMEOUT", ms) + + if not timer:active() then + if ms <= 0 then ms = 1 end + + timer:start(ms, 0, on_libuv_timeout) + end +end + +on_curl_action = function(easy, fd, action) + local ok, err = pcall(function() + trace("CURL::SOCKET", easy, fd, ACTION_NAMES[action] or action) + + local context = easy.data.context + + local flag = POLL_IO_FLAGS[action] + if flag then + if not context then + context = Context.new(fd) + easy.data.context = context + end + context:poll(flag, on_libuv_poll) + elseif action == curl.POLL_REMOVE then + if context then + easy.data.context = nil + context:close() + end + end + end) + + if not ok then uv.defer(function() error(err) end) end +end + +end + +-- on_libuv_poll, on_libuv_timeout +do + +local curl_check_multi_info = function() + while true do + local easy, ok, err = multi:info_read(true) + + if not easy then + multi:close() + error(err) + end + + if easy == 0 then break end + + local done_url = easy:getinfo_effective_url() + + local context = easy.data.context + if context then context:close() end + easy.data.context = nil + + if ok then on_end(easy, nil, done_url) else on_end(easy, err, done_url) end + + easy:reset() + easy.data = nil + qfree:push(easy) + end + + proceed_queue() +end + +on_libuv_poll = function(poller, err, events) + trace("UV::POLL", poller, err, EVENT_NAMES[events] or events) + + local flags = assert(FLAGS[events], ("unknown event:" .. events)) + + local context = poller.data.context + + multi:socket_action(context:fileno(), flags) + + curl_check_multi_info() +end + +on_libuv_timeout = function(timer) + trace("UV::TIMEOUT", timer) + + multi:socket_action() + + curl_check_multi_info() +end + +end + +timer = uv.timer() + +multi = curl.multi{ + timerfunction = on_curl_timeout; + socketfunction = on_curl_action; +} + +for i = 1, math.huge do + local url = arg[i] + if not url then break end + add_download(url, i) +end + +uv.run() + +cleanup() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua b/watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua new file mode 100644 index 0000000..e49419a --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua @@ -0,0 +1,36 @@ +local curl = require "lcurl" + +local function keys(t) + local s = {} + for k in pairs(t) do + s[#s + 1] = k + end + table.sort(s) + return s +end + +local function printf(...) + return print(string.format(...)) +end + +local exclude = {protocols = 1, features = 1, version = 1, version_num = 1} + +local info = curl.version_info() + +print(curl.version()) + +for _, key in ipairs(keys(info)) do if not exclude[key] then + printf("%15s: %s", key, info[key]) +end end + +print('Protocols:') +for _, protocol in ipairs(keys(info.protocols))do + local on = info.protocols[protocol] + printf(' [%s] %s', on and '+' or '-', protocol) +end + +print('Features:') +for _, feature in ipairs(keys(info.features))do + local on = info.features[feature] + printf(' [%s] %s', on and '+' or '-', feature) +end diff --git a/watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua b/watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua new file mode 100644 index 0000000..ddf57f4 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua @@ -0,0 +1,42 @@ +-- +-- convert `debug.c` example from libcurl examples +-- + +local curl = require "lcurl" + +local function printf(...) + io.stderr:write(string.format(...)) +end + +local function dump(title, data, n) + n = n or 16 + printf("%s, %10.10d bytes (0x%8.8x)\n", title, #data, #data) + for i = 1, #data do + if (i - 1) % n == 0 then printf("%4.4x: ", i-1) end + printf("%02x ", string.byte(data, i, i)) + if i % n == 0 then printf("\n") end + end + if #data % n ~= 0 then printf("\n") end +end + +local function my_trace(type, data) + local text + if type == curl.INFO_TEXT then printf("== Info: %s", data) end + if type == curl.INFO_HEADER_OUT then text = "=> Send header" end + if type == curl.INFO_DATA_OUT then text = "=> Send data" end + if type == curl.INFO_SSL_DATA_OUT then text = "=> Send SSL data" end + if type == curl.INFO_HEADER_IN then text = "<= Recv header" end + if type == curl.INFO_DATA_IN then text = "<= Recv data" end + if type == curl.INFO_SSL_DATA_IN then text = "<= Recv SSL data" end + if text then dump(text, data) end +end + +local easy = curl.easy{ + url = "http://google.com", + verbose = true, + debugfunction = my_trace, + followlocation = true, + writefunction = function()end, +} + +easy:perform() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua b/watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua new file mode 100644 index 0000000..e49419a --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua @@ -0,0 +1,36 @@ +local curl = require "lcurl" + +local function keys(t) + local s = {} + for k in pairs(t) do + s[#s + 1] = k + end + table.sort(s) + return s +end + +local function printf(...) + return print(string.format(...)) +end + +local exclude = {protocols = 1, features = 1, version = 1, version_num = 1} + +local info = curl.version_info() + +print(curl.version()) + +for _, key in ipairs(keys(info)) do if not exclude[key] then + printf("%15s: %s", key, info[key]) +end end + +print('Protocols:') +for _, protocol in ipairs(keys(info.protocols))do + local on = info.protocols[protocol] + printf(' [%s] %s', on and '+' or '-', protocol) +end + +print('Features:') +for _, feature in ipairs(keys(info.features))do + local on = info.features[feature] + printf(' [%s] %s', on and '+' or '-', feature) +end diff --git a/watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua b/watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua new file mode 100644 index 0000000..abee1f5 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua @@ -0,0 +1,147 @@ +--- High level wrapper around lcurl.easy +-- +-- Usage: +-- +-- local e = cURL.easy{ +-- url = 'http://example.com', +-- writefunction = io.stdout; +-- headerfunction = function(msg) io.stderr:write(msg) end; +-- } +-- e:perform() +-- + +local curl = require "lcurl.safe" + +local easy = {} do + +local LCURL_ERROR_EASY = curl.ERROR_EASY +local CURLE_UNKNOWN_OPTION = curl.E_UNKNOWN_OPTION + +easy.__index = function(self, k) + if k:sub(1, 4) == 'set_' then + local h = self:handle() + local name = k:sub(5) + if h['setopt_' .. name] then + easy[k] = function(self, value) return self:set(name, value) end + end + elseif k:sub(1, 4) == 'get_' then + local h = self:handle() + local name = k:sub(5) + if h['getinfo_' .. name] then + easy[k] = function(self) return self:get(name) end + end + end + return easy[k] +end + +local function setopt(handle, k, v, ...) + local fn = handle["setopt_" .. k] + if not fn then + return nil, curl.error(LCURL_ERROR_EASY, CURLE_UNKNOWN_OPTION) + end + local ok, err = fn(handle, v, ...) + if not ok then return nil, err end + return true +end + +function easy:new(opt) + local handle, err = curl.easy() + if not handle then return nil, err end + + if opt then for k, v in pairs(opt) do + local ok, err = setopt(handle, k, v) + if not ok then + handle:close() + return nil, err + end + end end + + local o = setmetatable({ + _handle = handle; + }, self) + + return o +end + +function easy:handle() + return self._handle +end + +function easy:set(k, v, ...) + local handle = self:handle() + + if type(k) == "table" then + assert(v == nil) + for k, v in pairs(k) do + local ok, err = setopt(handle, k, v) + if not ok then return nil, err, k end + end + return self + end + + assert(type(k) == 'string') + local ok, err = setopt(handle, k, v, ...) + if not ok then return nil, err end + return self +end + +function easy:perform(opt) + if opt then + local ok, err = self:set(opt) + if not ok then return nil, err end + end + + local e = self:handle() + + local ok, err = e:perform() + if ok then return nil, err end + + return self +end + +function easy:post(data) + local post = curl.form() + local ok, err + for k, v in pairs(data) do + if type(v) == "string" then + ok, err = post:add_content(k, v) + else + assert(type(v) == "table") + if v.data then + ok, err = post:add_buffer(k, v.file, v.data, v.type, v.headers) + else + ok, err = post:add_file(k, v.file, v.data, v.type, v.filename, v.headers) + end + end + if not ok then break end + end + if not ok then + post:free() + return nil, err + end + + local e = self:handle() + ok, err = e:setopt_httppost(post) + if not ok then + post:free() + return nil, err + end + + return self +end + +function easy:get(k, ...) + assert(type(k) == "string") + local handle = self:handle() + local fn = handle["getinfo_" .. k] + if not fn then return nil, "Unknown info: " .. k end + return fn(handle, ...) +end + +end + +local cURL = {} + +cURL.easy = function(...) return easy:new(...) end + +return cURL diff --git a/watchdog/third_party/lua-curl/examples/lcurl/file.lua b/watchdog/third_party/lua-curl/examples/lcurl/file.lua new file mode 100644 index 0000000..f5267bd --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/file.lua @@ -0,0 +1,18 @@ +local cURL = require("lcurl") + +-- open output file +f = assert(io.open("example_homepage.html", "w")) + +cURL.easy() + -- setup url + :setopt_url("http://www.example.com/") + -- setup file object as writer + :setopt_writefunction(f) + -- perform, invokes callbacks + :perform() +-- close easy +:close() + +-- close output file +f:close() +print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua b/watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua new file mode 100644 index 0000000..5e32fed --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua @@ -0,0 +1,47 @@ +-- +-- Example of how to download multiple files in single perform +-- + +local curl = require "lcurl" + +local function printf(...) + io.stderr:write(string.format(...)) +end + +local function pat2pat(s) + return "^" .. string.gsub(s, ".", { + ["*"] = '[^%.]*'; + ["."] = '%.'; + }) .. "$" +end + +local c = curl.easy{ + url = "ftp://moteus:123456@127.0.0.1/test.*"; + wildcardmatch = true; +} + +local data, n = 0, 0 +c:setopt_writefunction(function(chunk) data = #chunk + data end) + +-- called before each new file +c:setopt_chunk_bgn_function(function(info, remains) + data, n = 0, n + 1 + printf('\n======================================================\n') + printf('new file `%s` #%d, remains - %d\n', info.filename, n, remains or -1) +end) + +-- called after file download complite +c:setopt_chunk_end_function(function() + printf('total size %d[B]\n', data) + printf('------------------------------------------------------\n') +end) + +-- custom pattern matching function +c:setopt_fnmatch_function(function(pattern, name) + local p = pat2pat(pattern) + local r = not not string.match(name, p) + printf("%s %s %s\n", r and '+' or '-', pattern, name) + return r +end) + +c:perform() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua b/watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua new file mode 100644 index 0000000..d3c82c1 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua @@ -0,0 +1,17 @@ +-- simple "On the fly" fileupload +local cURL = require("lcurl") + +count=0 +cURL.easy() + :setopt_url("ftp://ftptest:secret0815@targethost/file.dat") + :setopt_upload(true) + :setopt_readfunction(function() + count = count + 1 + if count < 10 then + return "Line " .. count .. "\n" + end + end) + :perform() +:close() + +print("Fileupload done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/multi.lua b/watchdog/third_party/lua-curl/examples/lcurl/multi.lua new file mode 100644 index 0000000..666cc72 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/multi.lua @@ -0,0 +1,24 @@ +local cURL = require("lcurl") + +local f1 = assert(io.open("lua.html","a+")) + +local f2 = assert(io.open("luajit.html","a+")) + +c = cURL.easy() + :setopt_url("http://www.lua.org/") + :setopt_writefunction(f1) + +c2 = cURL.easy() + :setopt_url("http://luajit.org/") + :setopt_writefunction(f2) + +m = cURL.multi() + :add_handle(c) + :add_handle(c2) + +while m:perform() > 0 do + m:wait() +end + +print(m:info_read()) +print(m:info_read()) diff --git a/watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua b/watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua new file mode 100644 index 0000000..7513200 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua @@ -0,0 +1,78 @@ +-- +local function multi_iterator(...) + local curl = require "lcurl.safe" + + local buffers = {_ = {}} do + + function buffers:append(e, ...) + local b = self._[e] or {} + self._[e] = b + b[#b + 1] = {...} + end + + function buffers:next() + for e, t in pairs(self._) do + local m = table.remove(t, 1) + if m then return e, m end + end + end + + end + + local function multi_init(...) + local remain = 0 + local m = curl.multi() + for _, e in ipairs{...} do + e:setopt_writefunction(function(str) buffers:append(e, "data", str) end) + e:setopt_headerfunction(function(str) buffers:append(e, "header", str) end) + m:add_handle(e) + remain = remain + 1 + end + return m, remain + end + + local m, remain = multi_init(...) + m:perform() + return function() + while true do + local e, t = buffers:next() + if t then return e, unpack(t) end + if remain == 0 then break end + + m:wait() + + local n, err = m:perform() + if not n then m:close() error(err) end + + if n <= remain then + while true do + local e, ok, err = m:info_read() + if not e then m:close() error(err) end + if e == 0 then break end + if ok then buffers:append(e, "done", ok) + else buffers:append(e, "error", err) end + end + remain = n + end + + end + m:close() + end +end + +-- +local curl = require "lcurl" + +c1 = curl.easy() + :setopt_url("http://www.lua.org/") + +c2 = curl.easy() + :setopt_url("http://luajit.org/") + +for easy, type, data in multi_iterator(c1, c2) do + if type == 'header' then print(easy, type, (data:gsub("%s*$", ""))) + elseif type == 'data' then print(easy, type, #data) + elseif type == 'error' then print(easy, type, data) + elseif type == 'done' then print(easy, type, data) + end +end diff --git a/watchdog/third_party/lua-curl/examples/lcurl/pause.lua b/watchdog/third_party/lua-curl/examples/lcurl/pause.lua new file mode 100644 index 0000000..f293d2a --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/pause.lua @@ -0,0 +1,78 @@ +local curl = require "lcurl" + +local WAIT_COUNT = 1 + +local RESOURCE_URL = "http://www.rfc-editor.org/rfc/rfc2543.txt" + +local State = { + PAUSE = 0, -- write function should return CURL_WRITEFUNC_PAUSE + WAIT = 1, -- waiting for CURLPAUSE_CONT + WRITE = 2, -- write function should perform write + WRITTEN = 3, -- write function have performed write +} + +-- Current state +local state = State.PAUSE + +-- Countdown to continue writes +local waitCount = 0 + +-- Received data and data size +local data, datasize = {}, 0 + +local function WriteFunction(str) + if state == State.PAUSE then + state = State.WAIT + waitCount = WAIT_COUNT + print('Pause') + return curl.WRITEFUNC_PAUSE + end + + if state == State.WAIT then + -- callback shouldn't be called in this state + print("WARNING: write-callback called in STATE_WAIT") + return curl.WRITEFUNC_PAUSE + end + + if state == State.WRITE then + state = State.WRITTEN + end + + datasize = datasize + #str + data[#data + 1] = str +end + +local progress = 0 +local function ProgressFunction() + + progress = progress + 1 + + if state == State.WAIT then + -- Wait until unpause + waitCount = waitCount - 1 + if waitCount == 0 then + state = State.WRITE + print('Unpause :', progress) + easy:pause(curl.PAUSE_CONT) + end + end + + -- WriteFunction written some data + if state == State.WRITTEN then + -- send pause to WriteFunction + state = State.PAUSE + progress = 0 + end + +end + +easy = curl.easy{ + url = RESOURCE_URL, + accept_encoding = "gzip,deflate", + writefunction = WriteFunction, + progressfunction = ProgressFunction, + noprogress = false, + followlocation = true, +} + +easy:perform() \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/post.lua b/watchdog/third_party/lua-curl/examples/lcurl/post.lua new file mode 100644 index 0000000..6f7aa87 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/post.lua @@ -0,0 +1,69 @@ +local cURL = require("lcurl") + +local post = cURL.form() + -- post file from filesystem + :add_file ("name", "post.lua", "text/plain") + -- post file from data variable + :add_buffer("name2", "dummy.html", "bold", "text/html") + +cURL.easy() + :setopt_url("http://localhost") + :setopt_httppost(post) + :perform() +:close() + +-- Lua-cURL compatiable function +local function post(e, data) + local form = cURL.form() + local ok, err = true + + for k, v in pairs(data) do + if type(v) == "string" then + ok, err = form:add_content(k, v) + else + assert(type(v) == "table") + if v.stream_length then + form:free() + error("Stream does not support") + end + if v.data then + ok, err = form:add_buffer(k, v.file, v.data, v.type, v.headers) + else + ok, err = form:add_file(k, v.file, v.data, v.type, v.filename, v.headers) + end + end + if not ok then break end + end + + if not ok then + form:free() + return nil, err + end + + ok, err = e:setopt_httppost(form) + if not ok then + form:free() + return nil, err + end + + return e +end + +local e = cURL.easy() + :setopt_url("http://localhost") + +postdata = { + -- post file from filesystem + name = {file="post.lua", + type="text/plain" + }, + -- post file from data variable + name2 = {file="dummy.html", + data="bold", + type="text/html" + }, +} + +post(e, postdata):perform() + +print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua b/watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua new file mode 100644 index 0000000..b0f50c8 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua @@ -0,0 +1,26 @@ +local curl = require "lcurl" + +-- returns size and reader +local function make_stream(ch, n, m) + local size = n * m + local i = -1 + return size, function() + i = i + 1 + if i < m then + return (tostring(ch)):rep(n - 2) .. '\r\n' + end + end +end + +local form = curl.form() + :add_stream("test1", make_stream("a", 10, 4)) + :add_stream("test2", "test2.txt", make_stream("b", 10, 4)) + :add_stream("test3", "test3.txt", "text/plain", make_stream("c", 10, 4)) + +curl.easy{ + url = 'http://posttestserver.com/post.php', + writefunction = io.write, + httppost = form, + post = true, +}:perform() + diff --git a/watchdog/third_party/lua-curl/examples/lcurl/share.lua b/watchdog/third_party/lua-curl/examples/lcurl/share.lua new file mode 100644 index 0000000..7f49102 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/share.lua @@ -0,0 +1,23 @@ +-- Cookie data will be shared across the easy handles to do an authorized download +local cURL = require("lcurl") + +-- create share handle (share COOKIE and DNS Cache) +s = cURL.share() + :setopt_share(cURL.LOCK_DATA_COOKIE ) + :setopt_share(cURL.LOCK_DATA_DNS ) + +-- create first easy handle to do the login +c = cURL.easy() + :setopt_share(s) + :setopt_url("http://targethost/login.php?username=foo&password=bar") + +-- create second easy handle to do the download +c2 = cURL.easy() + :setopt_share(s) + :setopt_url("http://targethost/download.php?id=test") + +-- login +c:perform() + +-- download +c2:perform() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua b/watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua new file mode 100644 index 0000000..f829726 --- /dev/null +++ b/watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua @@ -0,0 +1,85 @@ +local curl = require "lcurl" + +local SMTP = { + url = "smtp://mail.example.com"; +} + +local FROM = "" +local TO = "" +local CC = "" +local FILE = "smtp-mime.lua" -- if you send this file do not forget it may have mail password +local CT_FILE = "application/lua" + +local DUMP_MIME = false + +local headers = { + "Date: Tue, 22 Aug 2017 14:08:43 +0100", + "To: " .. TO, + "From: " .. FROM .. " (Example User)", + "Cc: " .. CC .. " (Another example User)", + "Message-ID: ", + "Subject: example sending a MIME-formatted message", +} + +local inline_text = "" + .. "This is the inline text message of the e-mail.\r\n" + .. "\r\n" + .. " It could be a lot of lines that would be displayed in an e-mail\r\n" + .. "viewer that is not able to handle HTML.\r\n" + +local inline_html = "" + .. "\r\n" + .. "

This is the inline HTML message of the e-mail.

" + .. "
\r\n" + .. "

It could be a lot of HTML data that would be displayed by " + .. "e-mail viewers able to handle HTML.

" + .. "\r\n" + +local function dump_mime(type, data) + if type == curl.INFO_DATA_OUT then io.write(data) end +end + +local easy = curl.easy() + +local mime = easy:mime() do + local alt = easy:mime() + alt + :addpart() + :data(inline_html, "text/html") + alt + :addpart() + :data(inline_text) + mime:addpart() + :subparts(alt, "multipart/alternative", { + "Content-Disposition: inline" + }) + mime + :addpart() + :filedata(FILE, CT_FILE) +end + +easy:setopt{ + url = SMTP.url, + mail_from = FROM, + mail_rcpt = {TO, CC}, + httpheader = headers; + mimepost = mime; + ssl_verifyhost = false; + ssl_verifypeer = false; + username = SMTP.user; + password = SMTP.password; + upload = true; +} + +if DUMP_MIME then + easy:setopt{ + verbose = true; + debugfunction = dump_mime; + } +end + +easy:perform() + +easy:close() + +mime:free() diff --git a/watchdog/third_party/lua-curl/lakeconfig.lua b/watchdog/third_party/lua-curl/lakeconfig.lua new file mode 100644 index 0000000..ccbbb11 --- /dev/null +++ b/watchdog/third_party/lua-curl/lakeconfig.lua @@ -0,0 +1,260 @@ +local io = require "io" +io.stdout:setvbuf"no" +io.stderr:setvbuf"no" + +function vc_version() + local VER = lake.compiler_version() + MSVC_VER = ({ + [15] = '9'; + [16] = '10'; + })[VER.MAJOR] or '' + return MSVC_VER +end + +if not L then + +local function arkey(t) + assert(type(t) == 'table') + local keys = {} + for k in pairs(t) do + assert(type(k) == 'number') + table.insert(keys, k) + end + table.sort(keys) + return keys +end + +local function ikeys(t) + local keys = arkey(t) + local i = 0 + return function() + i = i + 1 + local k = keys[i] + if k == nil then return end + return k, t[k] + end +end + +local function expand(arr, t) + if t == nil then return arr end + + if type(t) ~= 'table' then + table.insert(arr, t) + return arr + end + + for _, v in ikeys(t) do + expand(arr, v) + end + + return arr +end + +function L(...) + return expand({}, {...}) +end + +end + +J = J or path.join + +IF = IF or lake.choose or choose + +DIR_SEP = package.config:sub(1,1) + +function prequire(...) + local ok, mod = pcall(require, ...) + if ok then return mod end +end + +function clone(t, o) + o = o or {} + for k, v in pairs(t) do + if o[k] == nil then o[k] = v end + end + return o +end + +function each_join(dir, list) + for i, v in ipairs(list) do + list[i] = path.join(dir, v) + end + return list +end + +function run(file, cwd) + print() + print("run " .. file) + if not TESTING then + if cwd then lake.chdir(cwd) end + local status, code = utils.execute( LUA_RUNNER .. ' ' .. file ) + if cwd then lake.chdir("<") end + print() + return status, code + end + return true, 0 +end + +function exec(file, cwd) + print() + print("exec " .. file) + if not TESTING then + if cwd then lake.chdir(cwd) end + local status, code = utils.execute( file ) + if cwd then lake.chdir("<") end + print() + return status, code + end + return true, 0 +end + +local TESTS = {} + +function run_test(name, params) + local test_dir = TESTDIR or J(ROOT, 'test') + local cmd = J(test_dir, name) + if params then cmd = cmd .. ' ' .. params end + local ok = run(cmd, test_dir) + + table.insert(TESTS, {cmd = cmd, result = ok}) + + print("TEST " .. name .. (ok and ' - pass!' or ' - fail!')) +end + +function exec_test(name, params) + local test_dir = TESTDIR or J(ROOT, 'test') + local cmd = J(test_dir, name) + if params then cmd = cmd .. ' ' .. params end + local ok = exec(cmd, test_dir) + + table.insert(TESTS, {cmd = cmd, result = ok}) + + print("TEST " .. name .. (ok and ' - pass!' or ' - fail!')) +end + +function test_summary() + local ok = true + print("") + print("------------------------------------") + print("Number of tests:", #TESTS) + for _, t in ipairs(TESTS) do + ok = ok and t.result + print((t.result and ' Pass' or ' Fail') .. " - TEST " .. t.cmd) + end + print("------------------------------------") + print("") + return ok +end + +--[[spawn]] if WINDOWS then + function spawn(file, cwd) + local winapi = prequire "winapi" + if not winapi then + quit('needs winapi for spawn!') + return false + end + + print("spawn " .. file) + if not TESTING then + if cwd then lake.chdir(cwd) end + assert(winapi.shell_exec(nil, LUA_RUNNER, file, cwd)) + if cwd then lake.chdir("<") end + print() + end + return true + end +else + function spawn(file, cwd) + print("spawn " .. file) + if not TESTING then + assert(run(file .. ' &', cwd)) + end + return true + end +end + +function as_bool(v,d) + if v == nil then return not not d end + local n = tonumber(v) + if n == 0 then return false end + if n then return true end + return false +end + +--- set global variables +-- LUA_NEED +-- LUA_DIR +-- LUA_RUNNER +-- ROOT +-- LUADIR +-- LIBDIR +-- TESTDIR +-- DOCDIR +-- DYNAMIC +function INITLAKEFILE() + if LUA_VER == '5.3' then + LUA_NEED = 'lua53' + LUA_DIR = ENV.LUA_DIR_5_3 or ENV.LUA_DIR + LUA_RUNNER = LUA_RUNNER or 'lua53' + elseif LUA_VER == '5.2' then + LUA_NEED = 'lua52' + LUA_DIR = ENV.LUA_DIR_5_2 or ENV.LUA_DIR + LUA_RUNNER = LUA_RUNNER or 'lua52' + elseif LUA_VER == '5.1' then + LUA_NEED = 'lua51' + LUA_DIR = ENV.LUA_DIR + LUA_RUNNER = LUA_RUNNER or 'lua' + else + LUA_NEED = 'lua' + LUA_DIR = ENV.LUA_DIR + LUA_RUNNER = LUA_RUNNER or 'lua' + end + ROOT = ROOT or J( LUA_DIR, 'libs', PROJECT ) + LUADIR = LUADIR or J( ROOT, 'share' ) + LIBDIR = LIBDIR or J( ROOT, 'share' ) + TESTDIR = TESTDIR or J( ROOT, 'test' ) + DOCDIR = DOCDIR or J( ROOT, 'doc' ) + DYNAMIC = as_bool(DYNAMIC, false) +end + +----------------------- +-- needs -- +----------------------- + +lake.define_need('lua53', function() + return { + incdir = J(ENV.LUA_DIR_5_3, 'include'); + libdir = J(ENV.LUA_DIR_5_3, 'lib'); + libs = {'lua53'}; + } +end) + +lake.define_need('lua52', function() + return { + incdir = J(ENV.LUA_DIR_5_2, 'include'); + libdir = J(ENV.LUA_DIR_5_2, 'lib'); + libs = {'lua52'}; + } +end) + +lake.define_need('lua51', function() + return { + incdir = J(ENV.LUA_DIR, 'include'); + libdir = J(ENV.LUA_DIR, 'lib'); + libs = {'lua5.1'}; + } +end) + +local CURL_DIR = CURL_DIR or ENV.CURL_DIR or J(ENV.CPPLIB_DIR, 'curl', '7.37.1') + +lake.define_need('libcurl', function() + return { + incdir = J(CURL_DIR, 'include'); + libdir = J(CURL_DIR, 'lib'); + libs = {'libcurl'}; + } +end) + +lake.define_need('winsock2', function() + return {libs = {"ws2_32"}} +end) diff --git a/watchdog/third_party/lua-curl/lakefile b/watchdog/third_party/lua-curl/lakefile new file mode 100644 index 0000000..aa83c99 --- /dev/null +++ b/watchdog/third_party/lua-curl/lakefile @@ -0,0 +1,44 @@ +PROJECT = 'cURL' + +INITLAKEFILE() + +DEFINES = L{DEFINES, + IF(WINDOWS, 'DLL_EXPORT', ''); + IF(DEBUG, 'LCURL_RESET_NULL_LUA', ''); +} + +cURL = c.shared{'lcurl', + base = 'src', + src = '*.c', + needs = {LUA_NEED, 'libcurl', IF(WINDOWS, 'winsock2', 'sockets')}, + defines = DEFINES, + dynamic = DYNAMIC, + strip = true, + cflags = IF(not MSVC, {'-Wno-unused-local-typedefs'}) +} + +target('build', cURL) + +install = target('install', { + file.group{odir=LIBDIR; src = cURL }; + file.group{odir=LIBDIR; src = J("src", "lua") ; recurse = true }; + file.group{odir=J(ROOT, 'examples'); src = 'examples'; recurse = true }; + file.group{odir=TESTDIR; src = 'test'; recurse = true }; +}) + +target('test', install, function() + run_test('test_easy.lua') + run_test('test_safe.lua') + run_test('test_form.lua') + run_test('test_pause02.c.lua') + run_test('test_curl.lua') + run_test('test_mime.lua') + run_test('test_multi_callback.lua') + run_test('test_multi_nested_callback.lua') + run_test('test_urlapi.lua') + + if not test_summary() then + quit("test fail") + end +end) + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec new file mode 100644 index 0000000..2dcf3d2 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.0-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.0.zip", + dir = "Lua-cURLv3-0.3.0", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.3" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec new file mode 100644 index 0000000..dbac658 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.1-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.1.zip", + dir = "Lua-cURLv3-0.3.1", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec new file mode 100644 index 0000000..6c39358 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec @@ -0,0 +1,75 @@ +package = "Lua-cURL" +version = "0.3.10-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.10.zip", + dir = "Lua-cURLv3-0.3.10", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + "src/lcurlapi.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec new file mode 100644 index 0000000..9aaa241 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec @@ -0,0 +1,75 @@ +package = "Lua-cURL" +version = "0.3.11-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.11.zip", + dir = "Lua-cURLv3-0.3.11", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + "src/lcurlapi.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec new file mode 100644 index 0000000..d828cb9 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec @@ -0,0 +1,75 @@ +package = "Lua-cURL" +version = "0.3.12-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.12.zip", + dir = "Lua-cURLv3-0.3.12", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.5" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + "src/lcurlapi.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec new file mode 100644 index 0000000..f5bed7a --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec @@ -0,0 +1,75 @@ +package = "Lua-cURL" +version = "0.3.13-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.13.zip", + dir = "Lua-cURLv3-0.3.13", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.5" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + "src/lcurlapi.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec new file mode 100644 index 0000000..28b69b9 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.2-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.2.zip", + dir = "Lua-cURLv3-0.3.2", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec new file mode 100644 index 0000000..bc9e124 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.3-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.3.zip", + dir = "Lua-cURLv3-0.3.3", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec new file mode 100644 index 0000000..d4d40b8 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.4-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.4.zip", + dir = "Lua-cURLv3-0.3.4", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec new file mode 100644 index 0000000..169f0ab --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.5-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.5.zip", + dir = "Lua-cURLv3-0.3.5", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec new file mode 100644 index 0000000..76aa110 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.6-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.6.zip", + dir = "Lua-cURLv3-0.3.6", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec new file mode 100644 index 0000000..6d55ef4 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.7-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.7.zip", + dir = "Lua-cURLv3-0.3.7", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec new file mode 100644 index 0000000..2fd2ca8 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.8-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.8.zip", + dir = "Lua-cURLv3-0.3.8", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec new file mode 100644 index 0000000..5e75398 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec @@ -0,0 +1,74 @@ +package = "Lua-cURL" +version = "0.3.8-2" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.8.zip", + dir = "Lua-cURLv3-0.3.8", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec new file mode 100644 index 0000000..f95e1e3 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec @@ -0,0 +1,75 @@ +package = "Lua-cURL" +version = "0.3.9-1" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.9.zip", + dir = "Lua-cURLv3-0.3.9", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.4" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + "src/lcurlapi.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec new file mode 100644 index 0000000..5aff1c6 --- /dev/null +++ b/watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec @@ -0,0 +1,75 @@ +package = "Lua-cURL" +version = "scm-0" + +source = { + url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/master.zip", + dir = "Lua-cURLv3-master", +} + +description = { + summary = "Lua binding to libcurl", + detailed = [[ + ]], + homepage = "https://github.com/Lua-cURL", + license = "MIT/X11" +} + +dependencies = { + "lua >= 5.1, < 5.5" +} + +external_dependencies = { + platforms = { + windows = { + CURL = { + header = "curl/curl.h", + library = "libcurl", + } + }; + unix = { + CURL = { + header = "curl/curl.h", + -- library = "curl", + } + }; + } +} + +build = { + copy_directories = {'doc', 'examples', 'test'}, + + type = "builtin", + + platforms = { + windows = { modules = { + lcurl = { + libraries = {"libcurl", "ws2_32"}, + } + }}, + unix = { modules = { + lcurl = { + libraries = {"curl"}, + } + }} + }, + + modules = { + ["cURL" ] = "src/lua/cURL.lua", + ["cURL.safe" ] = "src/lua/cURL/safe.lua", + ["cURL.utils" ] = "src/lua/cURL/utils.lua", + ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", + + lcurl = { + sources = { + "src/l52util.c", "src/lceasy.c", "src/lcerror.c", + "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", + "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", + "src/lcurlapi.c", + }, + incdirs = { "$(CURL_INCDIR)" }, + libdirs = { "$(CURL_LIBDIR)" } + }, + } +} + + diff --git a/watchdog/third_party/lua-curl/src/l52util.c b/watchdog/third_party/lua-curl/src/l52util.c new file mode 100644 index 0000000..6373687 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/l52util.c @@ -0,0 +1,178 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "l52util.h" + +#include +#include /* for memset */ +#include + +#if LUA_VERSION_NUM >= 502 + +int luaL_typerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, + luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} + +#ifndef luaL_register + +void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l){ + if(libname) lua_newtable(L); + luaL_setfuncs(L, l, 0); +} + +#endif + +#else + +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup){ + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + +void lua_rawgetp(lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); + lua_pushlightuserdata(L, (void *)p); + lua_rawget(L, index); +} + +void lua_rawsetp (lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); + lua_pushlightuserdata(L, (void *)p); + lua_insert(L, -2); + lua_rawset(L, index); +} + +#endif + +int lutil_newmetatablep (lua_State *L, const void *p) { + lua_rawgetp(L, LUA_REGISTRYINDEX, p); + if (!lua_isnil(L, -1)) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + + lua_newtable(L); /* create metatable */ + lua_pushvalue(L, -1); /* duplicate metatable to set*/ + + lua_pushliteral (L, "__type"); + lua_pushstring(L, p); // push meta name + lua_settable (L, -3); // set meta name + + lua_rawsetp(L, LUA_REGISTRYINDEX, p); + + return 1; +} + +void lutil_getmetatablep (lua_State *L, const void *p) { + lua_rawgetp(L, LUA_REGISTRYINDEX, p); +} + +void lutil_setmetatablep (lua_State *L, const void *p) { + lutil_getmetatablep(L, p); + assert(lua_istable(L,-1)); + lua_setmetatable (L, -2); +} + +int lutil_isudatap (lua_State *L, int ud, const void *p) { + if (lua_isuserdata(L, ud)){ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + int res; + lutil_getmetatablep(L,p); /* get correct metatable */ + res = lua_rawequal(L, -1, -2); /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return res; + } + } + return 0; +} + +void *lutil_checkudatap (lua_State *L, int ud, const void *p) { + void *up = lua_touserdata(L, ud); + if (up != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + lutil_getmetatablep(L,p); /* get correct metatable */ + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return up; + } + } + } + luaL_typerror(L, ud, p); /* else error */ + return NULL; /* to avoid warnings */ +} + +int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup) { + if (!lutil_newmetatablep(L, p)){ + lua_insert(L, -1 - nup); /* move mt prior upvalues */ + return 0; + } + + lua_insert(L, -1 - nup); /* move mt prior upvalues */ + luaL_setfuncs (L, methods, nup); /* define methods */ + lua_pushliteral (L, "__index"); /* define metamethods */ + lua_pushvalue (L, -2); + lua_settable (L, -3); + return 1; +} + +void *lutil_newudatap_impl(lua_State *L, size_t size, const void *p){ + void *obj = lua_newuserdata (L, size); + memset(obj, 0, size); + lutil_setmetatablep(L, p); + return obj; +} + +void lutil_pushint64(lua_State *L, int64_t v){ + if(sizeof(lua_Integer) >= sizeof(int64_t)){ + lua_pushinteger(L, (lua_Integer)v); + return; + } + lua_pushnumber(L, (lua_Number)v); +} + +void lutil_pushuint(lua_State *L, unsigned int v){ +#if LUA_VERSION_NUM >= 503 + lua_pushinteger(L, (lua_Integer)v); +#else + lua_pushnumber(L, (lua_Number)v); +#endif +} + +int64_t lutil_checkint64(lua_State *L, int idx){ + if(sizeof(lua_Integer) >= sizeof(int64_t)) + return luaL_checkinteger(L, idx); + return (int64_t)luaL_checknumber(L, idx); +} + +int64_t lutil_optint64(lua_State *L, int idx, int64_t v){ + if(sizeof(lua_Integer) >= sizeof(int64_t)) + return luaL_optinteger(L, idx, v); + return (int64_t)luaL_optnumber(L, idx, v); +} + +void lutil_pushnvalues(lua_State *L, int n){ + for(;n;--n) lua_pushvalue(L, -n); +} + +int lutil_is_null(lua_State *L, int i){ + return lua_islightuserdata(L, i) && 0 == lua_touserdata(L, i); +} + +void lutil_push_null(lua_State *L){ + lua_pushlightuserdata(L, (void*)0); +} diff --git a/watchdog/third_party/lua-curl/src/l52util.h b/watchdog/third_party/lua-curl/src/l52util.h new file mode 100644 index 0000000..97348a0 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/l52util.h @@ -0,0 +1,97 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _L52UTIL_H_ +#define _L52UTIL_H_ + +#include "lua.h" +#include "lauxlib.h" +#include + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + +#ifndef luaL_checkint +#define luaL_checkint luaL_checkinteger +#endif + +#ifndef luaL_checklong +#define luaL_checklong luaL_checkinteger +#endif + +#ifndef luaL_optint +#define luaL_optint luaL_optinteger +#endif + +#ifndef luaL_optlong +#define luaL_optlong luaL_optinteger +#endif + +#endif + +#if LUA_VERSION_NUM >= 502 /* Lua 5.2 */ + +/* lua_rawgetp */ +/* lua_rawsetp */ +/* luaL_setfuncs */ +/* lua_absindex */ + +#ifndef lua_objlen +#define lua_objlen lua_rawlen +#endif + +int luaL_typerror (lua_State *L, int narg, const char *tname); + +#ifndef luaL_register +void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l); +#endif + +#ifndef lua_equal +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#endif + +#else /* Lua 5.1 */ + +/* functions from lua 5.2 */ + +# define lua_absindex(L, i) (((i)>0)?(i):((i)<=LUA_REGISTRYINDEX?(i):(lua_gettop(L)+(i)+1))) +# define lua_rawlen lua_objlen + +void lua_rawgetp (lua_State *L, int index, const void *p); +void lua_rawsetp (lua_State *L, int index, const void *p); +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); + +#endif + +int lutil_newmetatablep (lua_State *L, const void *p); +void lutil_getmetatablep (lua_State *L, const void *p); +void lutil_setmetatablep (lua_State *L, const void *p); + +#define lutil_newudatap(L, TTYPE, TNAME) (TTYPE *)lutil_newudatap_impl(L, sizeof(TTYPE), TNAME) +int lutil_isudatap (lua_State *L, int ud, const void *p); +void *lutil_checkudatap (lua_State *L, int ud, const void *p); +int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup); + +void *lutil_newudatap_impl (lua_State *L, size_t size, const void *p); + +void lutil_pushuint(lua_State *L, unsigned int v); + +void lutil_pushint64(lua_State *L, int64_t v); + +int64_t lutil_checkint64(lua_State *L, int idx); + +int64_t lutil_optint64(lua_State *L, int idx, int64_t v); + +void lutil_pushnvalues(lua_State *L, int n); + +int lutil_is_null(lua_State *L, int i); + +void lutil_push_null(lua_State *L); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lceasy.c b/watchdog/third_party/lua-curl/src/lceasy.c new file mode 100644 index 0000000..ad48022 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lceasy.c @@ -0,0 +1,2469 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lceasy.h" +#include "lcerror.h" +#include "lcutils.h" +#include "lchttppost.h" +#include "lcshare.h" +#include "lcmulti.h" +#include "lcmime.h" +#include "lcurlapi.h" +#include + +static const char *LCURL_ERROR_TAG = "LCURL_ERROR_TAG"; + +#define LCURL_EASY_NAME LCURL_PREFIX" Easy" +static const char *LCURL_EASY = LCURL_EASY_NAME; + +#if LCURL_CURL_VER_GE(7,21,5) +# define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_OPTION +#else +# define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_TELNET_OPTION +#endif + +/* Before call curl_XXX function which can call any callback + * need set Current Lua thread pointer in easy/multi contexts. + * But it also possible that we already in callback call. + * E.g. `curl_easy_pause` function may be called from write callback. + * and it even may be called in different thread. + * ```Lua + * multi:add_handle(easy) + * easy:setopt_writefunction(function(...) + * coroutine.wrap(function() multi:add_handle(easy2) end)() + * end) + * ``` + * So we have to restore previews Lua state in callback contexts. + * But if previews Lua state is NULL then we can just do not set it back. + * But set it to NULL make easier to debug code. + */ +void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi){ + if(p->multi && assign_multi){ + lcurl__multi_assign_lua(L, p->multi, value, 1); + } + else{ + p->L = value; + if(p->post){ + p->post->L = value; + } +#if LCURL_CURL_VER_GE(7,56,0) + if(p->mime){ + lcurl_mime_set_lua(L, p->mime, value); + } +#endif + } +} + +//{ + +int lcurl_easy_create(lua_State *L, int error_mode){ + lcurl_easy_t *p; + int i; + + lua_settop(L, 1); /* options */ + + p = lutil_newudatap(L, lcurl_easy_t, LCURL_EASY); + + p->curl = curl_easy_init(); + + p->err_mode = error_mode; + if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->magic = LCURL_EASY_MAGIC; + p->L = NULL; + p->post = NULL; + p->multi = NULL; +#if LCURL_CURL_VER_GE(7,56,0) + p->mime = NULL; +#endif + p->storage = lcurl_storage_init(L); + p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; + p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; + p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; + p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; + p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; + p->match.cb_ref = p->match.ud_ref = LUA_NOREF; + p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; + p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; +#if LCURL_CURL_VER_GE(7,19,6) + p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,64,0) + p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; + p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; +#endif + p->rbuffer.ref = LUA_NOREF; + for(i = 0; i < LCURL_LIST_COUNT; ++i){ + p->lists[i] = LUA_NOREF; + } + + if(lua_type(L, 1) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); + if(ret) return ret; + assert(lua_gettop(L) == 2); + } + + return 1; +} + +lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i){ + lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, i, LCURL_EASY); + luaL_argcheck (L, p != NULL, 1, LCURL_EASY_NAME" object expected"); + return p; +} + +static int lcurl_easy_to_s(lua_State *L){ + lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, 1, LCURL_EASY); + lua_pushfstring(L, LCURL_EASY_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_easy_cleanup_storage(lua_State *L, lcurl_easy_t *p){ + int i; + + if(p->storage != LUA_NOREF){ + p->storage = lcurl_storage_free(L, p->storage); + } + + p->post = NULL; +#if LCURL_CURL_VER_GE(7,56,0) + p->mime = NULL; +#endif + + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.ud_ref); +#if LCURL_CURL_VER_GE(7,19,6) + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.ud_ref); +#endif +#if LCURL_CURL_VER_GE(7,64,0) + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.ud_ref); +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.ud_ref); +#endif + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + + p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; + p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; + p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; + p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; + p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; + p->match.cb_ref = p->match.ud_ref = LUA_NOREF; + p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; + p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; +#if LCURL_CURL_VER_GE(7,19,6) + p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,64,0) + p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; + p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; +#endif + p->rbuffer.ref = LUA_NOREF; + + for(i = 0; i < LCURL_LIST_COUNT; ++i){ + p->lists[i] = LUA_NOREF; + } +} + +static int lcurl_easy_cleanup(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lua_settop(L, 1); + + if(p->multi){ + LCURL_UNUSED_VAR CURLMcode code = lcurl__multi_remove_handle(L, p->multi, p); + + //! @todo what I can do if I can not remove it??? + } + + if(p->curl){ + lua_State *curL; + + // In my tests when I cleanup some easy handle. + // timerfunction called only for single multi handle. + // Also may be this function may call `close` callback + // for `curl_mimepart` structure. + curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); + curl_easy_cleanup(p->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 1); + + p->curl = NULL; + } + + lcurl_easy_cleanup_storage(L, p); + + lua_pushnil(L); + lua_rawset(L, LCURL_USERVALUES); + + return 0; +} + +static int lcurl_easy_perform(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + lua_State *curL; + int top = 1; + lua_settop(L, top); + + assert(p->rbuffer.ref == LUA_NOREF); + + // store reference to current coroutine to callbacks + // User should not call `perform` if handle assign to multi + curL = p->L; lcurl__easy_assign_lua(L, p, L, 0); + code = curl_easy_perform(p->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 0); + + if(p->rbuffer.ref != LUA_NOREF){ + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + p->rbuffer.ref = LUA_NOREF; + } + + if(code == CURLE_OK){ + lua_settop(L, 1); + return 1; + } + + if((lua_gettop(L) > top)&&(lua_touserdata(L, top + 1) == LCURL_ERROR_TAG)){ + return lua_error(L); + } + + if(code == CURLE_WRITE_ERROR){ + if(lua_gettop(L) > top){ + return lua_gettop(L) - top; + } + } + + if(code == CURLE_ABORTED_BY_CALLBACK){ + if(lua_gettop(L) > top){ + return lua_gettop(L) - top; + } + } + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); +} + +static int lcurl_easy_escape(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + size_t data_size; const char *data = luaL_checklstring(L, 2, &data_size); + const char *ret = curl_easy_escape(p->curl, data, (int)data_size); + if(!ret){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_OUT_OF_MEMORY); + } + lua_pushstring(L, ret); + curl_free((char*)ret); + return 1; +} + +static int lcurl_easy_unescape(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + size_t data_size; const char *data = luaL_checklstring(L, 2, &data_size); + int ret_size; const char *ret = curl_easy_unescape(p->curl, data, (int)data_size, &ret_size); + if(!ret){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_OUT_OF_MEMORY); + } + lua_pushlstring(L, ret, ret_size); + curl_free((char*)ret); + return 1; +} + +static int lcurl_easy_reset(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + curl_easy_reset(p->curl); + lua_settop(L, 1); + + lcurl_easy_cleanup_storage(L, p); + p->storage = lcurl_storage_init(L); + + return 1; +} + +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_mime(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_mime_create(L, p->err_mode); +} + +#endif + +#if LCURL_CURL_VER_GE(7,62,0) + +static int lcurl_easy_upkeep(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_upkeep(p->curl); + if(code == CURLE_OK){ + lua_settop(L, 1); + return 1; + } + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); +} + +#endif + +//{ OPTIONS + +//{ set + +static int lcurl_opt_set_long_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + long val; CURLcode code; + + if(lua_isboolean(L, 2)){ + val = lua_toboolean(L, 2); + if( val + && ( + (opt == CURLOPT_SSL_VERIFYHOST) +#if LCURL_CURL_VER_GE(7,52,0) + || (opt == CURLOPT_PROXY_SSL_VERIFYHOST) +#endif + ) + ){ + val = 2; + } + } + else{ + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); + val = luaL_checklong(L, 2); + } + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,59,0) + +static int lcurl_opt_set_off_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + curl_off_t val; CURLcode code; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number expected"); + val = lutil_checkint64(L, 2); + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#endif + +static int lcurl_opt_set_string_(lua_State *L, int opt, int store){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; const char *value; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); + + value = lua_tostring(L, 2); + code = curl_easy_setopt(p->curl, opt, value); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + if(store){ + if(value) + lcurl_storage_preserve_iv(L, p->storage, opt, 2); + else + lcurl_storage_remove_i(L, p->storage, opt); + } + + lua_settop(L, 1); + return 1; +} + +static int lcurl_opt_set_slist_(lua_State *L, int opt, int list_no){ + lcurl_easy_t *p = lcurl_geteasy(L); + struct curl_slist *list = lcurl_util_to_slist(L, 2); + CURLcode code; + int ref = p->lists[list_no]; + + luaL_argcheck(L, list || lua_istable(L, 2) || lutil_is_null(L, 2), 2, "array expected"); + + if(ref != LUA_NOREF){ + struct curl_slist *tmp = lcurl_storage_remove_slist(L, p->storage, ref); + curl_slist_free_all(tmp); + p->lists[list_no] = LUA_NOREF; + } + + code = curl_easy_setopt(p->curl, opt, list); + + if(code != CURLE_OK){ + curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + if (list) { + p->lists[list_no] = lcurl_storage_preserve_slist(L, p->storage, list); + } + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,73,0) + +static int lcurl_opt_set_blob_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; const char *value; size_t len; + struct curl_blob blob; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); + + value = lua_tolstring(L, 2, &len); + + blob.data = (void*)value; + blob.len = len; + blob.flags = CURL_BLOB_COPY; + + code = curl_easy_setopt(p->curl, opt, value); + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_settop(L, 1); + return 1; +} + +#endif + +#define LCURL_STR_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_string_(L, CURLOPT_##N, (S)); \ +} + +#define LCURL_LST_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_slist_(L, CURLOPT_##N, LCURL_##N##_LIST);\ +} + +#define LCURL_LNG_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_long_(L, CURLOPT_##N);\ +} + +#define LCURL_OFF_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_off_(L, CURLOPT_##N);\ +} + +#define LCURL_BLB_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ + return lcurl_opt_set_blob_(L, CURLOPT_##N); \ +} + +#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_OPT(N, S) + +#include "lcopteasy.h" + +#undef OPT_ENTRY + +static int lcurl_easy_set_POSTFIELDS(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + size_t len; const char *val = luaL_checklstring(L, 2, &len); + CURLcode code; + if(lua_isnumber(L, 3)){ + size_t n = (size_t)lua_tonumber(L, 3); + luaL_argcheck(L, len <= n, 3, "data length too big"); + len = n; + } + code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDS, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_POSTFIELDS, 2); + code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDSIZE, (long)len); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#undef LCURL_STR_OPT +#undef LCURL_LST_OPT +#undef LCURL_LNG_OPT +#undef LCURL_OFF_OPT +#undef LCURL_BLB_OPT + +static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg); + +static int lcurl_easy_set_HTTPPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_hpost_t *post = lcurl_gethpost_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HTTPPOST, post->post); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_HTTPPOST, 2); + + if(post->stream){ + curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, lcurl_hpost_read_callback); + } + + p->post = post; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_set_SHARE(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_share_t *sh = lcurl_getshare_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SHARE, sh->curl); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_SHARE, 2); + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,46,0) + +static int lcurl_easy_set_STREAM_DEPENDS_impl(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_easy_t *e = lcurl_geteasy_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, opt, e->curl); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, opt, 2); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_set_STREAM_DEPENDS(lua_State *L){ + return lcurl_easy_set_STREAM_DEPENDS_impl(L, CURLOPT_STREAM_DEPENDS); +} + +static int lcurl_easy_set_STREAM_DEPENDS_E(lua_State *L){ + return lcurl_easy_set_STREAM_DEPENDS_impl(L, CURLOPT_STREAM_DEPENDS_E); +} + +#endif + +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_set_MIMEPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_mime_t *mime = lcurl_getmime_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, mime->mime); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_MIMEPOST, 2); + + p->mime = mime; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,63,0) + +static int lcurl_easy_set_CURLU(lua_State *L) { + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_url_t *url = lcurl_geturl_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CURLU, url->url); + if (code != CURLE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_CURLU, 2); + + lua_settop(L, 1); + return 1; +} + +#endif + +//} + +//{ unset + +static int lcurl_opt_unset_long_(lua_State *L, int opt, long val){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,59,0) + +static int lcurl_opt_unset_off_(lua_State *L, int opt, curl_off_t val){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +#endif + +static int lcurl_opt_unset_string_(lua_State *L, int opt, const char *val){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, opt); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_opt_unset_slist_(lua_State *L, int opt, int list_no){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + int ref = p->lists[list_no]; + + code = curl_easy_setopt(p->curl, opt, NULL); + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + if(ref != LUA_NOREF){ + struct curl_slist *list = lcurl_storage_remove_slist(L, p->storage, ref); + curl_slist_free_all(list); + p->lists[list_no] = LUA_NOREF; + } + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,73,0) + +static int lcurl_opt_unset_blob_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code; + + code = curl_easy_setopt(p->curl, opt, 0); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_settop(L, 1); + return 1; +} + +#endif + +#define LCURL_STR_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_string_(L, CURLOPT_##N, (D)); \ +} + +#define LCURL_LST_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_slist_(L, CURLOPT_##N, LCURL_##N##_LIST);\ +} + +#define LCURL_LNG_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_long_(L, CURLOPT_##N, (D));\ +} + +#define LCURL_OFF_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_off_(L, CURLOPT_##N, (D));\ +} + +#define LCURL_BLB_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ + return lcurl_opt_unset_blob_(L, CURLOPT_##N);\ +} + +#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_OPT(N, S, D) + +#include "lcopteasy.h" + +#undef OPT_ENTRY + +#undef LCURL_STR_OPT +#undef LCURL_LST_OPT +#undef LCURL_LNG_OPT +#undef LCURL_OFF_OPT +#undef LCURL_BLB_OPT + +static int lcurl_easy_unset_HTTPPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HTTPPOST, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_get_i(L, p->storage, CURLOPT_HTTPPOST); + if(!lua_isnil(L, -1)){ + lcurl_hpost_t *form = lcurl_gethpost_at(L, -1); + if(form->stream){ + /* with stream we do not set CURLOPT_READDATA but + we also unset it to be sure that there no way to + call default curl reader with our READDATA + */ + curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, NULL); + curl_easy_setopt(p->curl, CURLOPT_READDATA, NULL); + } + lcurl_storage_remove_i(L, p->storage, CURLOPT_HTTPPOST); + } + + p->post = NULL; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_SHARE(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SHARE, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_SHARE); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_WRITEFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_WRITEFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_WRITEDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.ud_ref); + p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_READFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_READDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_HEADERFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HEADERFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_HEADERDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.ud_ref); + p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_PROGRESSFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_PROGRESSFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_PROGRESSDATA, NULL); + +#if LCURL_CURL_VER_GE(7,32,0) + curl_easy_setopt(p->curl, CURLOPT_XFERINFOFUNCTION, NULL); + curl_easy_setopt(p->curl, CURLOPT_XFERINFODATA, NULL); +#endif + + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.ud_ref); + p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_POSTFIELDS(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDS, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + curl_easy_setopt(p->curl, CURLOPT_POSTFIELDSIZE, -1); + lcurl_storage_remove_i(L, p->storage, CURLOPT_POSTFIELDS); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_SEEKFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SEEKFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_SEEKDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.ud_ref); + p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_DEBUGFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_DEBUGFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_DEBUGDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.ud_ref); + p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#if LCURL_CURL_VER_GE(7,19,6) + +static int lcurl_easy_unset_SSH_KEYFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SSH_KEYFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_SSH_KEYDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.ud_ref); + p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,21,0) + +static int lcurl_easy_unset_FNMATCH_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_FNMATCH_FUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_FNMATCH_DATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->match.ud_ref); + p->match.cb_ref = p->match.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_CHUNK_BGN_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CHUNK_BGN_FUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + if(p->chunk_end.cb_ref == LUA_NOREF){ + // if other callback not set + curl_easy_setopt(p->curl, CURLOPT_CHUNK_DATA, NULL); + } + + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.ud_ref); + p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_CHUNK_END_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CHUNK_END_FUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + if(p->chunk_bgn.cb_ref == LUA_NOREF){ + // if other callback not set + curl_easy_setopt(p->curl, CURLOPT_CHUNK_DATA, NULL); + } + + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.ud_ref); + p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,46,0) + +static int lcurl_easy_unset_STREAM_DEPENDS(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_STREAM_DEPENDS, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_STREAM_DEPENDS); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_STREAM_DEPENDS_E(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_STREAM_DEPENDS_E, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_STREAM_DEPENDS_E); + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_unset_MIMEPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_MIMEPOST); + + p->mime = NULL; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,63,0) + +static int lcurl_easy_unset_CURLU(lua_State *L) { + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CURLU, NULL); + if (code != CURLE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_CURLU); + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,64,0) + +static int lcurl_easy_unset_TRAILERFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_TRAILERFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_TRAILERDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.ud_ref); + p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + +static int lcurl_easy_unset_HSTSREADFUNCTION (lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HSTSREADFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_HSTSREADDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.ud_ref); + p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_unset_HSTSWRITEFUNCTION (lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HSTSWRITEFUNCTION, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, CURLOPT_HSTSWRITEDATA, NULL); + + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.ud_ref); + p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + return 1; +} + +#endif + +//} + +//} + +//{ info + +static int lcurl_info_get_long_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + long val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + if(sizeof(lua_Integer) >= sizeof(val)) + lua_pushinteger(L, (lua_Integer)val); + else +#endif + lua_pushnumber(L, val); + + return 1; +} + +static int lcurl_info_get_double_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + double val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_pushnumber(L, val); + return 1; +} + +#if LCURL_CURL_VER_GE(7,55,0) + +static int lcurl_info_get_offset_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + curl_off_t val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lutil_pushint64(L, val); + return 1; +} + +#endif + +static int lcurl_info_get_string_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + char *val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_pushstring(L, val); + return 1; +} + +static int lcurl_info_get_slist_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + struct curl_slist *val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_util_slist_to_table(L, val); + curl_slist_free_all(val); + + return 1; +} + +static int lcurl_info_get_certinfo_(lua_State *L, int opt){ + lcurl_easy_t *p = lcurl_geteasy(L); + int decode = lua_toboolean(L, 2); + struct curl_certinfo * val; CURLcode code; + + code = curl_easy_getinfo(p->curl, opt, &val); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lua_newtable(L); + { int i = 0; for(;inum_of_certs; ++i){ + struct curl_slist *slist = val->certinfo[i]; + if (decode) { + lua_newtable(L); + for(;slist; slist = slist->next){ + const char *ptr = strchr(slist->data, ':'); + if(ptr){ + lua_pushlstring(L, slist->data, ptr - slist->data); + lua_pushstring(L, ptr + 1); + lua_rawset(L, -3); + } + } + } + else{ + lcurl_util_slist_to_table(L, slist); + } + lua_rawseti(L, -2, i + 1); + }} + + return 1; +} + +#define LCURL_STR_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_string_(L, CURLINFO_##N); \ +} + +#define LCURL_LST_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_slist_(L, CURLINFO_##N);\ +} + +#define LCURL_LNG_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_long_(L, CURLINFO_##N);\ +} + +#define LCURL_DBL_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_double_(L, CURLINFO_##N);\ +} + +#define LCURL_OFF_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_offset_(L, CURLINFO_##N);\ +} + +#define LCURL_CERTINFO_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ + return lcurl_info_get_certinfo_(L, CURLINFO_##N);\ +} + +#define OPT_ENTRY(L, N, T, S) LCURL_##T##_INFO(N, S) + +#include "lcinfoeasy.h" + +#undef OPT_ENTRY + +#undef LCURL_STR_INFO +#undef LCURL_LST_INFO +#undef LCURL_LNG_INFO +#undef LCURL_DBL_INFO + +//} + +//{ CallBack + +static int lcurl_easy_set_callback(lua_State *L, + lcurl_easy_t *p, lcurl_callback_t *c, + int OPT_CB, int OPT_UD, + const char *method, void *func +) +{ + CURLcode code; + lcurl_set_callback(L, c, 2, method); + + code = curl_easy_setopt(p->curl, OPT_CB, (c->cb_ref == LUA_NOREF)?0:func); + if((code != CURLE_OK)&&(c->cb_ref != LUA_NOREF)){ + luaL_unref(L, LCURL_LUA_REGISTRY, c->cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, c->ud_ref); + c->cb_ref = c->ud_ref = LUA_NOREF; + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + curl_easy_setopt(p->curl, OPT_UD, (c->cb_ref == LUA_NOREF)?0:p); + + return 1; +} + +static size_t lcurl_write_callback_(lua_State*L, + lcurl_easy_t *p, lcurl_callback_t *c, + char *ptr, size_t size, size_t nmemb +){ + size_t ret = size * nmemb; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, c); + + lua_pushlstring(L, ptr, ret); + if(lua_pcall(L, n, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top+1); + return 0; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)){ + if(lua_gettop(L) == (top+1)) lua_settop(L, top); + return 0; + } + if(lua_isnumber(L, top + 1)){ + ret = (size_t)lua_tonumber(L, top + 1); + } + else{ + if(!lua_toboolean(L, top + 1)) ret = 0; + } + } + + lua_settop(L, top); + return ret; +} + +//{ Writer + +static size_t lcurl_write_callback(char *ptr, size_t size, size_t nmemb, void *arg){ + lcurl_easy_t *p = arg; + assert(NULL != p->L); + return lcurl_write_callback_(p->L, p, &p->wr, ptr, size, nmemb); +} + +static int lcurl_easy_set_WRITEFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->wr, + CURLOPT_WRITEFUNCTION, CURLOPT_WRITEDATA, + "write", lcurl_write_callback + ); +} + +//} + +//{ Reader + +size_t lcurl_read_callback(lua_State *L, + lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, + char *buffer, size_t size, size_t nitems +){ + const char *data; size_t data_size; + + size_t ret = size * nitems; + int n, top = lua_gettop(L); + + if(rbuffer->ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, rbuffer->ref); + data = luaL_checklstring(L, -1, &data_size); + lua_pop(L, 1); + + data = data + rbuffer->off; + data_size -= rbuffer->off; + + if(data_size > ret){ + data_size = ret; + memcpy(buffer, data, data_size); + rbuffer->off += data_size; + } + else{ + memcpy(buffer, data, data_size); + luaL_unref(L, LCURL_LUA_REGISTRY, rbuffer->ref); + rbuffer->ref = LUA_NOREF; + } + + lua_settop(L, top); + return data_size; + } + + // buffer is clean + assert(rbuffer->ref == LUA_NOREF); + + n = lcurl_util_push_cb(L, rd); + lua_pushinteger(L, ret); + if(lua_pcall(L, n, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top+1); + return CURL_READFUNC_ABORT; + } + + if(lua_gettop(L) == top){ + return 0; + } + + assert(lua_gettop(L) >= top); + + if(lua_type(L, top + 1) != LUA_TSTRING){ + if(lua_isnil(L, top + 1)){ + if(lua_gettop(L) == (top+1)){// only nil -> EOF + lua_settop(L, top); + return 0; + } + } + else{ + if(lua_type(L, top + 1) == LUA_TNUMBER){ + size_t ret = lua_tointeger(L, top + 1); + if(ret == (size_t)CURL_READFUNC_PAUSE){ + lua_settop(L, top); + return CURL_READFUNC_PAUSE; + } + } + lua_settop(L, top); + } + return CURL_READFUNC_ABORT; + } + + data = lua_tolstring(L, top + 1, &data_size); + assert(data); + if(data_size > ret){ + data_size = ret; + rbuffer->ref = luaL_ref(L, LCURL_LUA_REGISTRY); + rbuffer->off = data_size; + } + memcpy(buffer, data, data_size); + + lua_settop(L, top); + return data_size; +} + +static size_t lcurl_easy_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ + lcurl_easy_t *p = arg; + if(p->magic == LCURL_HPOST_STREAM_MAGIC){ + return lcurl_hpost_read_callback(buffer, size, nitems, arg); + } + assert(NULL != p->L); + return lcurl_read_callback(p->L, &p->rd, &p->rbuffer, buffer, size, nitems); +} + +static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ + lcurl_hpost_stream_t *p = arg; + assert(NULL != p->L); + return lcurl_read_callback(*p->L, &p->rd, &p->rbuffer, buffer, size, nitems); +} + +static int lcurl_easy_set_READFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->rd, + CURLOPT_READFUNCTION, CURLOPT_READDATA, + "read", lcurl_easy_read_callback + ); +} + +//} + +//{ Header + +static size_t lcurl_header_callback(char *ptr, size_t size, size_t nmemb, void *arg){ + lcurl_easy_t *p = arg; + assert(NULL != p->L); + return lcurl_write_callback_(p->L, p, &p->hd, ptr, size, nmemb); +} + +static int lcurl_easy_set_HEADERFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->hd, + CURLOPT_HEADERFUNCTION, CURLOPT_HEADERDATA, + "header", lcurl_header_callback + ); +} + +//} + +//{ Progress + +static int lcurl_xferinfo_callback(void *arg, curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) +{ + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int n, top, ret = 0; + + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->pr); + + lua_pushnumber( L, (lua_Number)dltotal ); + lua_pushnumber( L, (lua_Number)dlnow ); + lua_pushnumber( L, (lua_Number)ultotal ); + lua_pushnumber( L, (lua_Number)ulnow ); + + if(lua_pcall(L, n+3, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top+1); + return 1; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)){ + if(lua_gettop(L) == (top+1)) lua_settop(L, top); + return 1; + } + if(lua_isboolean(L, top + 1)) + ret = lua_toboolean(L, top + 1)?0:1; + else{ + ret = lua_tonumber(L, top + 1); + #if LCURL_CURL_VER_GE(7,68,0) + if(ret != (size_t)CURL_PROGRESSFUNC_CONTINUE) + #endif + if(ret == 0) ret = 1; else ret = 0; + } + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_progress_callback(void *arg, double dltotal, double dlnow, + double ultotal, double ulnow) +{ + return lcurl_xferinfo_callback(arg, + (curl_off_t)dltotal, + (curl_off_t)dlnow, + (curl_off_t)ultotal, + (curl_off_t)ulnow + ); +} + +static int lcurl_easy_set_PROGRESSFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + int n = lcurl_easy_set_callback(L, p, &p->pr, + CURLOPT_PROGRESSFUNCTION, CURLOPT_PROGRESSDATA, + "progress", lcurl_progress_callback + ); + +#if LCURL_CURL_VER_GE(7,32,0) + if(p->pr.cb_ref != LUA_NOREF){ + curl_easy_setopt(p->curl, CURLOPT_XFERINFOFUNCTION, lcurl_xferinfo_callback); + curl_easy_setopt(p->curl, CURLOPT_XFERINFODATA, p); + } +#endif + + return n; +} + +//} + +//{ Seek + +static int lcurl_seek_callback(void *arg, curl_off_t offset, int origin){ + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_SEEKFUNC_OK; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->seek); + + assert(NULL != p->L); + + if (SEEK_SET == origin) lua_pushliteral(L, "set"); + else if (SEEK_CUR == origin) lua_pushliteral(L, "cur"); + else if (SEEK_END == origin) lua_pushliteral(L, "end"); + else lua_pushinteger(L, origin); + lutil_pushint64(L, offset); + + if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_SEEKFUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_SEEKFUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_easy_set_SEEKFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->seek, + CURLOPT_SEEKFUNCTION, CURLOPT_SEEKDATA, + "seek", lcurl_seek_callback + ); +} + +//} + +//{ Debug + +static int lcurl_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *arg){ + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->debug); + + assert(NULL != p->L); + assert(handle == p->curl); + + lua_pushinteger(L, type); + lua_pushlstring(L, data, size); + + // just ignore all errors from Lua callback + lua_pcall(L, n + 1, LUA_MULTRET, 0); + lua_settop(L, top); + + return 0; +} + +static int lcurl_easy_set_DEBUGFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->debug, + CURLOPT_DEBUGFUNCTION, CURLOPT_DEBUGDATA, + "debug", lcurl_debug_callback + ); +} + +//} + +//{ Match + +#if LCURL_CURL_VER_GE(7,21,0) + +static int lcurl_match_callback(void *arg, const char *pattern, const char *string) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_FNMATCHFUNC_NOMATCH; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->match); + + assert(NULL != p->L); + + lua_pushstring(L, pattern); + lua_pushstring(L, string); + + if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_FNMATCHFUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_FNMATCHFUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_FNMATCHFUNC_MATCH : CURL_FNMATCHFUNC_NOMATCH; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_easy_set_FNMATCH_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->match, + CURLOPT_FNMATCH_FUNCTION, CURLOPT_FNMATCH_DATA, + "match", lcurl_match_callback + ); +} + +#endif + +//} + +//{ Chunk begin/end + +#if LCURL_CURL_VER_GE(7,21,0) + +static int lcurl_chunk_bgn_callback(struct curl_fileinfo *info, void *arg, int remains) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_CHUNK_BGN_FUNC_OK; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->chunk_bgn); + + assert(NULL != p->L); + + lua_newtable(L); + lua_pushstring (L, info->filename ); lua_setfield(L, -2, "filename" ); + lua_pushinteger(L, info->filetype ); lua_setfield(L, -2, "filetype" ); + lutil_pushint64(L, info->time ); lua_setfield(L, -2, "time" ); + lutil_pushint64(L, info->perm ); lua_setfield(L, -2, "perm" ); + lua_pushinteger(L, info->uid ); lua_setfield(L, -2, "uid" ); + lua_pushinteger(L, info->gid ); lua_setfield(L, -2, "gid" ); + lutil_pushint64(L, info->size ); lua_setfield(L, -2, "size" ); + lutil_pushint64(L, info->hardlinks ); lua_setfield(L, -2, "hardlinks" ); + lutil_pushint64(L, info->flags ); lua_setfield(L, -2, "flags" ); + + lua_newtable(L); + if(info->strings.time) { lua_pushstring (L, info->strings.time ); lua_setfield(L, -2, "time" ); } + if(info->strings.perm) { lua_pushstring (L, info->strings.perm ); lua_setfield(L, -2, "perm" ); } + if(info->strings.user) { lua_pushstring (L, info->strings.user ); lua_setfield(L, -2, "user" ); } + if(info->strings.group) { lua_pushstring (L, info->strings.group ); lua_setfield(L, -2, "group" ); } + if(info->strings.target){ lua_pushstring (L, info->strings.target); lua_setfield(L, -2, "target"); } + lua_setfield(L, -2, "strings"); + + lua_pushinteger(L, remains); + + if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_BGN_FUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_BGN_FUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_CHUNK_BGN_FUNC_OK : CURL_CHUNK_BGN_FUNC_SKIP; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_chunk_end_callback(void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int ret = CURL_CHUNK_END_FUNC_OK; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->chunk_end); + + assert(NULL != p->L); + + if (lua_pcall(L, n-1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_END_FUNC_FAIL; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ + lua_settop(L, top + 2); + lua_remove(L, top + 1); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_CHUNK_END_FUNC_FAIL; + } + ret = lua_toboolean(L, top + 1) ? CURL_CHUNK_END_FUNC_OK : CURL_CHUNK_END_FUNC_FAIL; + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_easy_set_CHUNK_BGN_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->chunk_bgn, + CURLOPT_CHUNK_BGN_FUNCTION, CURLOPT_CHUNK_DATA, + "chunk_bgn", lcurl_chunk_bgn_callback + ); +} + +static int lcurl_easy_set_CHUNK_END_FUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->chunk_end, + CURLOPT_CHUNK_END_FUNCTION, CURLOPT_CHUNK_DATA, + "chunk_end", lcurl_chunk_end_callback + ); +} + +#endif + +//} + +//{ Trailer + +#if LCURL_CURL_VER_GE(7,64,0) + +static int lcurl_trailer_callback(struct curl_slist **list, void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->trailer); + + if (lua_pcall(L, n - 1, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURL_TRAILERFUNC_ABORT; + } + + n = lua_gettop(L); + + if (n == top) { + return CURL_TRAILERFUNC_OK; + } + + /* libcurl will free the list */ + *list = lcurl_util_to_slist(L, top + 1); + if (*list) { + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + // empty array or NULL + if (lua_istable(L, top + 1) || lutil_is_null(L, top + 1)) { + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + // true + if((lua_type(L, top + 1) == LUA_TBOOLEAN) && (lua_toboolean(L, top + 1))){ + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + // single nil + if((n == (top + 1)) && lua_isnil(L, top + 1)){ + lua_settop(L, top); + return CURL_TRAILERFUNC_OK; + } + + lua_settop(L, top); + return CURL_TRAILERFUNC_ABORT; +} + +static int lcurl_easy_set_TRAILERFUNCTION (lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->trailer, + CURLOPT_TRAILERFUNCTION, CURLOPT_TRAILERDATA, + "trailer", lcurl_trailer_callback + ); +} + +#endif + +//} + +//{ HSTS Reader + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + +#define LCURL_HSTS_EXPIRE_LEN 18 + +static int lcurl_hstsread_callback(CURL *easy, struct curl_hstsentry *sts, void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->hstsread); + const char *name; size_t namelen; + const char *expire; size_t expirelen; + int type; + + assert(NULL != p->L); + + lua_pushinteger(L, sts->namelen); + if (lua_pcall(L, n, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURLSTS_FAIL; + } + + if (lua_gettop(L) == top) { + return CURLSTS_DONE; + } + + assert(lua_gettop(L) >= top); + + type = lua_type(L, top + 1); + if (type == LUA_TNIL) { + lua_settop(L, top); + return CURLSTS_DONE; + } + + if (type != LUA_TSTRING) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + name = lua_tolstring(L, top + 1, &namelen); + + if(namelen > sts->namelen) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + memcpy(sts->name, name, namelen + 1); + + type = lua_type(L, top + 2); + if (type == LUA_TNONE) { + lua_settop(L, top); + return CURLSTS_OK; + } + + if ((type != LUA_TBOOLEAN) && (type != LUA_TNIL)) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + if (type == LUA_TBOOLEAN) { + sts->includeSubDomains = lua_toboolean(L, top + 2) ? 0 : 1; + } + else if (type != LUA_TNIL) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + type = lua_type(L, top + 3); + if ((type == LUA_TNONE) || (type == LUA_TNIL)) { + lua_settop(L, top); + return CURLSTS_OK; + } + + if(type != LUA_TSTRING) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + expire = lua_tolstring(L, top + 3, &expirelen); + + if (expirelen != LCURL_HSTS_EXPIRE_LEN - 1) { + lua_settop(L, top); + return CURLSTS_FAIL; + } + + memcpy(sts->expire, expire, expirelen + 1); + + lua_settop(L, top); + return CURLSTS_OK; +} + +static int lcurl_easy_set_HSTSREADFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->hstsread, + CURLOPT_HSTSREADFUNCTION, CURLOPT_HSTSREADDATA, + "hstsread", lcurl_hstsread_callback + ); +} + +#endif + +//} + +//{ HSTS Writer + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + +static int lcurl_hstswrite_callback(CURL *easy, struct curl_hstsentry *sts, struct curl_index *count, void *arg) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->hstswrite); + int type; + + assert(NULL != p->L); + + lua_pushstring(L, sts->name); + lua_pushboolean(L, sts->includeSubDomains ? 1 : 0); + if (sts->expire[0]) { + lua_pushstring(L, sts->expire); + } else { + lua_pushnil(L); + } + lua_pushinteger(L, count->index); + lua_pushinteger(L, count->total); + + if (lua_pcall(L, n + 4, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURLSTS_FAIL; + } + + if (lua_gettop(L) == top) { + return CURLSTS_OK; + } + + assert(lua_gettop(L) >= top); + + type = lua_type(L, top + 1); + if (type == LUA_TNIL) { + type = lua_type(L, top + 2); + lua_settop(L, top); + if(type == LUA_TNONE){ + return CURLSTS_OK; + } + return CURLSTS_FAIL; + } + + if (type == LUA_TNUMBER) { + int ret = lua_tointeger(L, top + 1); + lua_settop(L, top); + return ret; + } + + if (type == LUA_TBOOLEAN) { + int ret = lua_toboolean(L, top + 1); + lua_settop(L, top); + return ret ? CURLSTS_OK : CURLSTS_DONE; + } + + lua_settop(L, top); + return CURLSTS_FAIL; +} + +static int lcurl_easy_set_HSTSWRITEFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->hstswrite, + CURLOPT_HSTSWRITEFUNCTION, CURLOPT_HSTSWRITEDATA, + "hstswrite", lcurl_hstswrite_callback + ); +} + +#endif + +//} + +//{ SSH key + +#if LCURL_CURL_VER_GE(7,19,6) + +static void lcurl_ssh_key_push(lua_State *L, const struct curl_khkey *key){ + if (!key) { + lua_pushnil(L); + return; + } + + lua_newtable(L); + + if(key->len){ + lua_pushliteral(L, "raw"); + lua_pushlstring(L, key->key, key->len); + } else { + lua_pushliteral(L, "base64"); + lua_pushstring(L, key->key); + } + lua_rawset(L, -3); + + lua_pushliteral(L, "type"); + lutil_pushuint(L, key->keytype); + lua_rawset(L, -3); +} + +static int lcurl_ssh_key_callback( + CURL *easy, + const struct curl_khkey *knownkey, + const struct curl_khkey *foundkey, + enum curl_khmatch khmatch, + void *arg +) { + lcurl_easy_t *p = arg; + lua_State *L = p->L; + int top = lua_gettop(L); + int n = lcurl_util_push_cb(L, &p->ssh_key); + + assert(NULL != p->L); + + lcurl_ssh_key_push(L, knownkey); + lcurl_ssh_key_push(L, foundkey); + lutil_pushuint(L, khmatch); + + if (lua_pcall(L, n + 2, LUA_MULTRET, 0)) { + assert(lua_gettop(L) >= top); + lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + lua_insert(L, top + 1); + return CURLKHSTAT_REJECT; + } + + if (lua_gettop(L) > top) { + int ret = lua_tointeger(L, top + 1); + lua_settop(L, top); + + switch (ret) +#if LCURL_CURL_VER_GE(7,73,0) + case CURLKHSTAT_FINE_REPLACE: +#endif + case CURLKHSTAT_FINE_ADD_TO_FILE: + case CURLKHSTAT_FINE: + case CURLKHSTAT_REJECT: + case CURLKHSTAT_DEFER: + return ret; + } + + return CURLKHSTAT_REJECT; +} + +static int lcurl_easy_set_SSH_KEYFUNCTION(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_easy_set_callback(L, p, &p->ssh_key, + CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA, + "ssh_key", lcurl_ssh_key_callback + ); +} + +#endif + +//} + +static int lcurl_easy_setopt(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + long opt; + + luaL_checkany(L, 2); + if(lua_type(L, 2) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); + if(ret) return ret; + lua_settop(L, 1); + return 1; + } + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S, D) case CURLOPT_##N: return lcurl_easy_set_##N(L); + switch(opt){ + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); +} + +static int lcurl_easy_unsetopt(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + long opt; + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S, D) case CURLOPT_##N: return lcurl_easy_unset_##N(L); + switch(opt){ + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); +} + +static int lcurl_easy_getinfo(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + long opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S) case CURLINFO_##N: return lcurl_easy_get_##N(L); + switch(opt){ + #include "lcinfoeasy.h" + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); +} + +static int lcurl_easy_pause(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lua_State *curL; + int mask = luaL_checkint(L, 2); + CURLcode code; + + curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); + code = curl_easy_pause(p->curl, mask); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 1); + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + lua_settop(L, 1); + return 1; +} + +static int lcurl_easy_setdata(lua_State *L){ + lua_settop(L, 2); + lua_pushvalue(L, 1); + lua_insert(L, 2); + lua_rawset(L, LCURL_USERVALUES); + return 1; +} + +static int lcurl_easy_getdata(lua_State *L){ + lua_settop(L, 1); + lua_rawget(L, LCURL_USERVALUES); + return 1; +} + +//} + +static const struct luaL_Reg lcurl_easy_methods[] = { + +#define OPT_ENTRY(L, N, T, S, D) { "setopt_"#L, lcurl_easy_set_##N }, + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif +#undef OPT_ENTRY + +#define OPT_ENTRY(L, N, T, S, D) { "unsetopt_"#L, lcurl_easy_unset_##N }, + #include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif +#undef OPT_ENTRY + +#define OPT_ENTRY(L, N, T, S) { "getinfo_"#L, lcurl_easy_get_##N }, + #include "lcinfoeasy.h" +#undef OPT_ENTRY + +#if LCURL_CURL_VER_GE(7,56,0) + { "mime", lcurl_easy_mime }, +#endif + + { "pause", lcurl_easy_pause }, + { "reset", lcurl_easy_reset }, + { "setopt", lcurl_easy_setopt }, + { "getinfo", lcurl_easy_getinfo }, + { "unsetopt", lcurl_easy_unsetopt }, + { "escape", lcurl_easy_escape }, + { "unescape", lcurl_easy_unescape }, + { "perform", lcurl_easy_perform }, +#if LCURL_CURL_VER_GE(7,62,0) + { "upkeep", lcurl_easy_upkeep }, +#endif + { "close", lcurl_easy_cleanup }, + { "__gc", lcurl_easy_cleanup }, + { "__tostring", lcurl_easy_to_s }, + + { "setdata", lcurl_easy_setdata }, + { "getdata", lcurl_easy_getdata }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_easy_opt[] = { + +#define OPT_ENTRY(L, N, T, S, D) { "OPT_"#N, CURLOPT_##N }, +#define FLG_ENTRY(N) { #N, CURL_##N }, +#include "lcopteasy.h" + OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) + OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) + OPT_ENTRY(share, SHARE, TTT, 0, 0) + OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) + OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) + OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) + OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) + OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) + OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) +#if LCURL_CURL_VER_GE(7,19,6) + OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) + OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,46,0) + OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) + OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,63,0) + OPT_ENTRY(curlu, CURLU, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,64,0) + OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) + OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) +#endif +#undef OPT_ENTRY +#undef FLG_ENTRY + +#define OPT_ENTRY(L, N, T, S) { "INFO_"#N, CURLINFO_##N }, +#include "lcinfoeasy.h" +#undef OPT_ENTRY + +#define OPT_ENTRY(N) { #N, CURL##N }, + // Debug message types not easy info + OPT_ENTRY(INFO_TEXT ) + OPT_ENTRY(INFO_HEADER_IN ) + OPT_ENTRY(INFO_HEADER_OUT ) + OPT_ENTRY(INFO_DATA_IN ) + OPT_ENTRY(INFO_DATA_OUT ) + OPT_ENTRY(INFO_SSL_DATA_OUT ) + OPT_ENTRY(INFO_SSL_DATA_IN ) + + // File types for CURL_CHUNK_BGN_FUNCTION +#if LCURL_CURL_VER_GE(7,21,0) + OPT_ENTRY(FILETYPE_DEVICE_BLOCK ) + OPT_ENTRY(FILETYPE_DEVICE_CHAR ) + OPT_ENTRY(FILETYPE_DIRECTORY ) + OPT_ENTRY(FILETYPE_DOOR ) + OPT_ENTRY(FILETYPE_FILE ) + OPT_ENTRY(FILETYPE_NAMEDPIPE ) + OPT_ENTRY(FILETYPE_SOCKET ) + OPT_ENTRY(FILETYPE_SYMLINK ) + OPT_ENTRY(FILETYPE_UNKNOWN ) +#endif + +#undef OPT_ENTRY + + {NULL, 0} +}; + +void lcurl_easy_initlib(lua_State *L, int nup){ + + /* Hack. We ensure that lcurl_easy_t and lcurl_hpost_stream_t + compatiable for readfunction + */ + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, magic, lcurl_hpost_stream_t, magic); + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, L, lcurl_hpost_stream_t, L); + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, rd, lcurl_hpost_stream_t, rd); + LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, rbuffer, lcurl_hpost_stream_t, rbuffer); + LCURL_ASSERT_SAME_FIELD_SIZE(lcurl_easy_t, rbuffer, lcurl_hpost_stream_t, rbuffer); + + if(!lutil_createmetap(L, LCURL_EASY, lcurl_easy_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_easy_opt); +} + +//} diff --git a/watchdog/third_party/lua-curl/src/lceasy.h b/watchdog/third_party/lua-curl/src/lceasy.h new file mode 100644 index 0000000..c03617c --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lceasy.h @@ -0,0 +1,127 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCEASY_H_ +#define _LCEASY_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include "lchttppost.h" + +#define LCURL_LST_INDEX(N) LCURL_##N##_LIST, +#define LCURL_STR_INDEX(N) +#define LCURL_LNG_INDEX(N) +#define LCURL_OFF_INDEX(N) +#define LCURL_BLB_INDEX(N) +#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_INDEX(N) + +enum { + LCURL_LIST_DUMMY = -1, + +#include"lcopteasy.h" + + LCURL_LIST_COUNT, +}; + +#undef LCURL_BLB_INDEX +#undef LCURL_OFF_INDEX +#undef LCURL_LST_INDEX +#undef LCURL_STR_INDEX +#undef LCURL_LNG_INDEX +#undef OPT_ENTRY + +#define LCURL_EASY_MAGIC 0xEA + +#if LCURL_CC_SUPPORT_FORWARD_TYPEDEF + typedef struct lcurl_multi_tag lcurl_multi_t; + #if LCURL_CURL_VER_GE(7,56,0) + typedef struct lcurl_mime_tag lcurl_mime_t; + #endif + #if LCURL_CURL_VER_GE(7,63,0) + typedef struct lcurl_url_tag lcurl_url_t; + #endif +#else + struct lcurl_multi_tag; + #define lcurl_multi_t struct lcurl_multi_tag + #if LCURL_CURL_VER_GE(7,56,0) + struct lcurl_mime_tag; + #define lcurl_mime_t struct lcurl_mime_tag + #endif + #if LCURL_CURL_VER_GE(7,63,0) + struct lcurl_url_tag; + #define lcurl_url_t struct lcurl_url_tag + #endif +#endif + +typedef struct lcurl_easy_tag{ + unsigned char magic; + + lua_State *L; + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + + lcurl_hpost_t *post; + + lcurl_multi_t *multi; + +#if LCURL_CURL_VER_GE(7,56,0) + lcurl_mime_t *mime; +#endif + + CURL *curl; + int storage; + int lists[LCURL_LIST_COUNT]; + int err_mode; + lcurl_callback_t wr; + lcurl_callback_t hd; + lcurl_callback_t pr; + lcurl_callback_t seek; + lcurl_callback_t debug; + lcurl_callback_t match; + lcurl_callback_t chunk_bgn; + lcurl_callback_t chunk_end; +#if LCURL_CURL_VER_GE(7,19,6) + lcurl_callback_t ssh_key; +#endif +#if LCURL_CURL_VER_GE(7,64,0) + lcurl_callback_t trailer; +#endif +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS + lcurl_callback_t hstsread; + lcurl_callback_t hstswrite; +#endif +}lcurl_easy_t; + +int lcurl_easy_create(lua_State *L, int error_mode); + +lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i); + +#define lcurl_geteasy(L) lcurl_geteasy_at((L),1) + +void lcurl_easy_initlib(lua_State *L, int nup); + +void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi); + +size_t lcurl_read_callback(lua_State *L, + lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, + char *buffer, size_t size, size_t nitems +); + +#if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF +#undef lcurl_multi_t +#ifdef lcurl_mime_t +#undef lcurl_mime_t +#endif +#ifdef lcurl_url_t +#undef lcurl_url_t +#endif +#endif + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_easy.h b/watchdog/third_party/lua-curl/src/lcerr_easy.h new file mode 100644 index 0000000..986026a --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_easy.h @@ -0,0 +1,146 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( UNSUPPORTED_PROTOCOL ) +ERR_ENTRY ( FAILED_INIT ) +ERR_ENTRY ( URL_MALFORMAT ) +#if LCURL_CURL_VER_GE(7,21,5) +ERR_ENTRY ( NOT_BUILT_IN ) +#endif +ERR_ENTRY ( COULDNT_RESOLVE_PROXY ) +ERR_ENTRY ( COULDNT_RESOLVE_HOST ) +ERR_ENTRY ( COULDNT_CONNECT ) +#if LCURL_CURL_VER_GE(7,51,0) +ERR_ENTRY ( WEIRD_SERVER_REPLY ) +#else +ERR_ENTRY ( FTP_WEIRD_SERVER_REPLY ) +#endif +ERR_ENTRY ( REMOTE_ACCESS_DENIED ) +#if LCURL_CURL_VER_GE(7,31,0) +ERR_ENTRY ( FTP_ACCEPT_FAILED ) +#endif +ERR_ENTRY ( FTP_WEIRD_PASS_REPLY ) +#if LCURL_CURL_VER_GE(7,24,0) +ERR_ENTRY ( FTP_ACCEPT_TIMEOUT ) +#endif +ERR_ENTRY ( FTP_WEIRD_PASV_REPLY ) +ERR_ENTRY ( FTP_WEIRD_227_FORMAT ) +ERR_ENTRY ( FTP_CANT_GET_HOST ) +ERR_ENTRY ( FTP_COULDNT_SET_TYPE ) +ERR_ENTRY ( PARTIAL_FILE ) +ERR_ENTRY ( FTP_COULDNT_RETR_FILE ) +ERR_ENTRY ( OBSOLETE20 ) +ERR_ENTRY ( QUOTE_ERROR ) +ERR_ENTRY ( HTTP_RETURNED_ERROR ) +ERR_ENTRY ( WRITE_ERROR ) +ERR_ENTRY ( OBSOLETE24 ) +ERR_ENTRY ( UPLOAD_FAILED ) +ERR_ENTRY ( READ_ERROR ) +ERR_ENTRY ( OUT_OF_MEMORY ) +ERR_ENTRY ( OPERATION_TIMEDOUT ) +ERR_ENTRY ( OBSOLETE29 ) +ERR_ENTRY ( FTP_PORT_FAILED ) +ERR_ENTRY ( FTP_COULDNT_USE_REST ) +ERR_ENTRY ( OBSOLETE32 ) +ERR_ENTRY ( RANGE_ERROR ) +ERR_ENTRY ( HTTP_POST_ERROR ) +ERR_ENTRY ( SSL_CONNECT_ERROR ) +ERR_ENTRY ( BAD_DOWNLOAD_RESUME ) +ERR_ENTRY ( FILE_COULDNT_READ_FILE ) +ERR_ENTRY ( LDAP_CANNOT_BIND ) +ERR_ENTRY ( LDAP_SEARCH_FAILED ) +ERR_ENTRY ( OBSOLETE40 ) +ERR_ENTRY ( FUNCTION_NOT_FOUND ) +ERR_ENTRY ( ABORTED_BY_CALLBACK ) +ERR_ENTRY ( BAD_FUNCTION_ARGUMENT ) +ERR_ENTRY ( OBSOLETE44 ) +ERR_ENTRY ( INTERFACE_FAILED ) +ERR_ENTRY ( OBSOLETE46 ) +ERR_ENTRY ( TOO_MANY_REDIRECTS ) +#if LCURL_CURL_VER_GE(7,21,5) +ERR_ENTRY ( UNKNOWN_OPTION ) +#else +ERR_ENTRY ( UNKNOWN_TELNET_OPTION ) /* User specified an unknown option */ +#endif +ERR_ENTRY ( TELNET_OPTION_SYNTAX ) +ERR_ENTRY ( OBSOLETE50 ) +ERR_ENTRY ( PEER_FAILED_VERIFICATION ) +ERR_ENTRY ( GOT_NOTHING ) +ERR_ENTRY ( SSL_ENGINE_NOTFOUND ) +ERR_ENTRY ( SSL_ENGINE_SETFAILED ) +ERR_ENTRY ( SEND_ERROR ) +ERR_ENTRY ( RECV_ERROR ) +ERR_ENTRY ( OBSOLETE57 ) +ERR_ENTRY ( SSL_CERTPROBLEM ) +ERR_ENTRY ( SSL_CIPHER ) +#if LCURL_CURL_VER_GE(7,62,0) +ERR_ENTRY ( OBSOLETE51 ) +#else +ERR_ENTRY ( SSL_CACERT ) +#endif +ERR_ENTRY ( BAD_CONTENT_ENCODING ) +ERR_ENTRY ( LDAP_INVALID_URL ) +ERR_ENTRY ( FILESIZE_EXCEEDED ) +ERR_ENTRY ( USE_SSL_FAILED ) +ERR_ENTRY ( SEND_FAIL_REWIND ) +ERR_ENTRY ( SSL_ENGINE_INITFAILED ) +ERR_ENTRY ( LOGIN_DENIED ) +ERR_ENTRY ( TFTP_NOTFOUND ) +ERR_ENTRY ( TFTP_PERM ) +ERR_ENTRY ( REMOTE_DISK_FULL ) +ERR_ENTRY ( TFTP_ILLEGAL ) +ERR_ENTRY ( TFTP_UNKNOWNID ) +ERR_ENTRY ( REMOTE_FILE_EXISTS ) +ERR_ENTRY ( TFTP_NOSUCHUSER ) +ERR_ENTRY ( CONV_FAILED ) +ERR_ENTRY ( CONV_REQD ) +ERR_ENTRY ( SSL_CACERT_BADFILE ) +ERR_ENTRY ( REMOTE_FILE_NOT_FOUND ) +ERR_ENTRY ( SSH ) +ERR_ENTRY ( SSL_SHUTDOWN_FAILED ) +ERR_ENTRY ( AGAIN ) +ERR_ENTRY ( SSL_CRL_BADFILE ) +ERR_ENTRY ( SSL_ISSUER_ERROR ) +#if LCURL_CURL_VER_GE(7,20,0) +ERR_ENTRY ( FTP_PRET_FAILED ) +#endif +#if LCURL_CURL_VER_GE(7,21,0) +ERR_ENTRY ( FTP_BAD_FILE_LIST ) +#endif +#if LCURL_CURL_VER_GE(7,20,0) +ERR_ENTRY ( RTSP_CSEQ_ERROR ) +ERR_ENTRY ( RTSP_SESSION_ERROR ) +#endif +#if LCURL_CURL_VER_GE(7,21,0) +ERR_ENTRY ( CHUNK_FAILED ) +#endif +#if LCURL_CURL_VER_GE(7,30,0) +ERR_ENTRY ( NO_CONNECTION_AVAILABLE ) +#endif +#if LCURL_CURL_VER_GE(7,38,0) +ERR_ENTRY ( HTTP2 ) +#else +ERR_ENTRY ( OBSOLETE16 ) +#endif +#if LCURL_CURL_VER_GE(7,39,0) +ERR_ENTRY ( SSL_PINNEDPUBKEYNOTMATCH ) +#endif +#if LCURL_CURL_VER_GE(7,41,0) +ERR_ENTRY ( SSL_INVALIDCERTSTATUS ) +#endif +#if LCURL_CURL_VER_GE(7,49,0) +ERR_ENTRY ( HTTP2_STREAM ) +#endif +#if LCURL_CURL_VER_GE(7,59,0) +ERR_ENTRY ( RECURSIVE_API_CALL ) +#endif +#if LCURL_CURL_VER_GE(7,66,0) +ERR_ENTRY ( AUTH_ERROR ) +#endif +#if LCURL_CURL_VER_GE(7,68,0) +ERR_ENTRY ( HTTP3 ) +#endif +#if LCURL_CURL_VER_GE(7,69,0) +ERR_ENTRY ( QUIC_CONNECT_ERROR ) +#endif +#if LCURL_CURL_VER_GE(7,73,0) +ERR_ENTRY ( PROXY ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_form.h b/watchdog/third_party/lua-curl/src/lcerr_form.h new file mode 100644 index 0000000..60ca8ce --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_form.h @@ -0,0 +1,8 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( MEMORY ) +ERR_ENTRY ( OPTION_TWICE ) +ERR_ENTRY ( NULL ) +ERR_ENTRY ( UNKNOWN_OPTION ) +ERR_ENTRY ( INCOMPLETE ) +ERR_ENTRY ( ILLEGAL_ARRAY ) +ERR_ENTRY ( DISABLED ) diff --git a/watchdog/third_party/lua-curl/src/lcerr_multi.h b/watchdog/third_party/lua-curl/src/lcerr_multi.h new file mode 100644 index 0000000..45322b7 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_multi.h @@ -0,0 +1,14 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( CALL_MULTI_PERFORM ) +ERR_ENTRY ( BAD_HANDLE ) +ERR_ENTRY ( BAD_EASY_HANDLE ) +ERR_ENTRY ( OUT_OF_MEMORY ) +ERR_ENTRY ( INTERNAL_ERROR ) +ERR_ENTRY ( BAD_SOCKET ) +ERR_ENTRY ( UNKNOWN_OPTION ) +#if LCURL_CURL_VER_GE(7,32,1) +ERR_ENTRY ( ADDED_ALREADY ) +#endif +#if LCURL_CURL_VER_GE(7,59,0) +ERR_ENTRY ( RECURSIVE_API_CALL ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_share.h b/watchdog/third_party/lua-curl/src/lcerr_share.h new file mode 100644 index 0000000..7027c2a --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_share.h @@ -0,0 +1,8 @@ +ERR_ENTRY ( OK ) +ERR_ENTRY ( BAD_OPTION ) +ERR_ENTRY ( IN_USE ) +ERR_ENTRY ( INVALID ) +ERR_ENTRY ( NOMEM ) +#if LCURL_CURL_VER_GE(7,23,0) +ERR_ENTRY ( NOT_BUILT_IN ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_url.h b/watchdog/third_party/lua-curl/src/lcerr_url.h new file mode 100644 index 0000000..f1002a1 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerr_url.h @@ -0,0 +1,20 @@ +#if LCURL_CURL_VER_GE(7,61,0) +ERR_ENTRY ( BAD_HANDLE ) +ERR_ENTRY ( BAD_PARTPOINTER ) +ERR_ENTRY ( BAD_PORT_NUMBER ) +ERR_ENTRY ( MALFORMED_INPUT ) +ERR_ENTRY ( NO_FRAGMENT ) +ERR_ENTRY ( NO_HOST ) +ERR_ENTRY ( NO_OPTIONS ) +ERR_ENTRY ( NO_PASSWORD ) +ERR_ENTRY ( NO_PORT ) +ERR_ENTRY ( NO_QUERY ) +ERR_ENTRY ( NO_SCHEME ) +ERR_ENTRY ( NO_USER ) +ERR_ENTRY ( OK ) +ERR_ENTRY ( OUT_OF_MEMORY ) +ERR_ENTRY ( UNKNOWN_PART ) +ERR_ENTRY ( UNSUPPORTED_SCHEME) +ERR_ENTRY ( URLDECODE ) +ERR_ENTRY ( USER_NOT_ALLOWED ) +#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcerror.c b/watchdog/third_party/lua-curl/src/lcerror.c new file mode 100644 index 0000000..11eea55 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerror.c @@ -0,0 +1,342 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcerror.h" +#include +#include "lcutils.h" + +#define LCURL_ERROR_NAME LCURL_PREFIX" Error" +static const char *LCURL_ERROR = LCURL_ERROR_NAME; + +#define LCURL_ERROR_EASY_NAME "CURL-EASY" +#define LCURL_ERROR_MULTI_NAME "CURL-MULTI" +#define LCURL_ERROR_SHARE_NAME "CURL-SHARE" +#define LCURL_ERROR_FORM_NAME "CURL-FORM" +#define LCURL_ERROR_URL_NAME "CURL-URL" + +typedef struct lcurl_error_tag{ + int tp; + int no; +}lcurl_error_t; + +//{ + +static const char* lcurl_err_easy_mnemo(int err){ +#define ERR_ENTRY(E) case CURLE_##E: return #E; + + switch (err){ + #include "lcerr_easy.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_multi_mnemo(int err){ +#define ERR_ENTRY(E) case CURLM_##E: return #E; + + switch (err){ + #include "lcerr_multi.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_share_mnemo(int err){ +#define ERR_ENTRY(E) case CURLSHE_##E: return #E; + + switch (err){ + #include "lcerr_share.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_form_mnemo(int err){ +#define ERR_ENTRY(E) case CURL_FORMADD_##E: return #E; + + switch (err){ + #include "lcerr_form.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* lcurl_err_url_mnemo(int err){ +#define ERR_ENTRY(E) case CURLUE_##E: return #E; + + switch (err){ + #include "lcerr_url.h" + } + return "UNKNOWN"; + +#undef ERR_ENTRY +} + +static const char* _lcurl_err_mnemo(int tp, int err){ + switch(tp){ + case LCURL_ERROR_EASY : return lcurl_err_easy_mnemo (err); + case LCURL_ERROR_MULTI: return lcurl_err_multi_mnemo(err); + case LCURL_ERROR_SHARE: return lcurl_err_share_mnemo(err); + case LCURL_ERROR_FORM : return lcurl_err_form_mnemo (err); + case LCURL_ERROR_URL : return lcurl_err_url_mnemo (err); + } + assert(0); + return ""; +} + +static const char* _lcurl_err_msg(int tp, int err){ + switch(tp){ + case LCURL_ERROR_EASY : return curl_easy_strerror (err); + case LCURL_ERROR_MULTI: return curl_multi_strerror(err); + case LCURL_ERROR_SHARE: return curl_share_strerror(err); + case LCURL_ERROR_FORM : return lcurl_err_form_mnemo(err); + case LCURL_ERROR_URL : return lcurl_err_url_mnemo(err); + } + assert(0); + return ""; +} + +static const char* _lcurl_err_category_name(int tp){ + assert( + (tp == LCURL_ERROR_EASY ) || + (tp == LCURL_ERROR_MULTI) || + (tp == LCURL_ERROR_SHARE) || + (tp == LCURL_ERROR_FORM ) || + (tp == LCURL_ERROR_URL ) || + 0 + ); + + switch(tp){ + case LCURL_ERROR_EASY: { + static const char *name = LCURL_ERROR_EASY_NAME; + return name; + } + case LCURL_ERROR_MULTI: { + static const char *name = LCURL_ERROR_MULTI_NAME; + return name; + } + case LCURL_ERROR_SHARE: { + static const char *name = LCURL_ERROR_SHARE_NAME; + return name; + } + case LCURL_ERROR_FORM: { + static const char *name = LCURL_ERROR_FORM_NAME; + return name; + } + case LCURL_ERROR_URL: { + static const char *name = LCURL_ERROR_URL_NAME; + return name; + } + } + + assert(0); + return NULL; +} + +static void _lcurl_err_pushstring(lua_State *L, int tp, int err){ + lua_pushfstring(L, "[%s][%s] %s (%d)", + _lcurl_err_category_name(tp), + _lcurl_err_mnemo(tp, err), + _lcurl_err_msg(tp, err), + err + ); +} + +//} + +//{ + +int lcurl_error_create(lua_State *L, int error_type, int no){ + lcurl_error_t *err = lutil_newudatap(L, lcurl_error_t, LCURL_ERROR); + + assert( + (error_type == LCURL_ERROR_EASY ) || + (error_type == LCURL_ERROR_MULTI) || + (error_type == LCURL_ERROR_SHARE) || + (error_type == LCURL_ERROR_FORM ) || + (error_type == LCURL_ERROR_URL ) || + 0 + ); + + err->tp = error_type; + err->no = no; + return 1; +} + +static lcurl_error_t *lcurl_geterror_at(lua_State *L, int i){ + lcurl_error_t *err = (lcurl_error_t *)lutil_checkudatap (L, i, LCURL_ERROR); + luaL_argcheck (L, err != NULL, 1, LCURL_PREFIX"error object expected"); + return err; +} + +#define lcurl_geterror(L) lcurl_geterror_at((L),1) + +static int lcurl_err_no(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushinteger(L, err->no); + return 1; +} + +static int lcurl_err_msg(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushstring(L, _lcurl_err_msg(err->tp, err->no)); + return 1; +} + +static int lcurl_err_mnemo(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushstring(L, _lcurl_err_mnemo(err->tp, err->no)); + return 1; +} + +static int lcurl_err_tostring(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + _lcurl_err_pushstring(L, err->tp, err->no); + return 1; +} + +static int lcurl_err_equal(lua_State *L){ + lcurl_error_t *lhs = lcurl_geterror_at(L, 1); + lcurl_error_t *rhs = lcurl_geterror_at(L, 2); + lua_pushboolean(L, ((lhs->no == rhs->no)&&(lhs->tp == rhs->tp))?1:0); + return 1; +} + +static int lcurl_err_category(lua_State *L){ + lcurl_error_t *err = lcurl_geterror(L); + lua_pushstring(L, _lcurl_err_category_name(err->tp)); + return 1; +} + +//} + +//{ + +int lcurl_fail_ex(lua_State *L, int mode, int error_type, int code){ + if(mode == LCURL_ERROR_RETURN){ + lua_pushnil(L); + lcurl_error_create(L, error_type, code); + return 2; + } + +#if LUA_VERSION_NUM >= 502 // lua 5.2 + lcurl_error_create(L, error_type, code); +#else + _lcurl_err_pushstring(L, error_type, code); +#endif + + assert(LCURL_ERROR_RAISE == mode); + + return lua_error(L); +} + +int lcurl_fail(lua_State *L, int error_type, int code){ + return lcurl_fail_ex(L, LCURL_ERROR_RETURN, error_type, code); +} + +//} + +static const int ERROR_CATEGORIES[] = { + LCURL_ERROR_EASY, + LCURL_ERROR_MULTI, + LCURL_ERROR_SHARE, + LCURL_ERROR_FORM, + LCURL_ERROR_URL, +}; + +static const char* ERROR_CATEGORIES_NAME[] = { + LCURL_ERROR_EASY_NAME, + LCURL_ERROR_MULTI_NAME, + LCURL_ERROR_SHARE_NAME, + LCURL_ERROR_FORM_NAME, + LCURL_ERROR_URL_NAME, + NULL +}; + +int lcurl_error_new(lua_State *L){ + int tp, no = luaL_checkint(L, 2); + if (lua_isnumber(L, 1)){ + tp = luaL_checkint(L, 2); + } + else{ + tp = luaL_checkoption(L, 1, NULL, ERROR_CATEGORIES_NAME); + tp = ERROR_CATEGORIES[tp]; + } + + //! @todo checks error type value + + lcurl_error_create(L, tp, no); + return 1; +} + +static const struct luaL_Reg lcurl_err_methods[] = { + {"no", lcurl_err_no }, + {"msg", lcurl_err_msg }, + {"name", lcurl_err_mnemo }, + {"mnemo", lcurl_err_mnemo }, + {"cat", lcurl_err_category }, + {"category", lcurl_err_category }, + {"__tostring", lcurl_err_tostring }, + {"__eq", lcurl_err_equal }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_error_codes[] = { + +#define ERR_ENTRY(N) { "E_"#N, CURLE_##N }, +#include "lcerr_easy.h" +#undef ERR_ENTRY + +/* libcurl rename CURLE_FTP_WEIRD_SERVER_REPLY to CURLE_WEIRD_SERVER_REPLY in version 7.51.0*/ +/* we can not have both codes in general because we have to be able convern error number to error name*/ +/* so we use newest name but add error code as alias.*/ +#if LCURL_CURL_VER_GE(7,51,0) + { "E_FTP_WEIRD_SERVER_REPLY", CURLE_FTP_WEIRD_SERVER_REPLY }, +#else + { "E_WEIRD_SERVER_REPLY", CURLE_FTP_WEIRD_SERVER_REPLY }, +#endif + +#define ERR_ENTRY(N) { "E_MULTI_"#N, CURLM_##N }, +#include "lcerr_multi.h" +#undef ERR_ENTRY + +#define ERR_ENTRY(N) { "E_SHARE_"#N, CURLSHE_##N }, +#include "lcerr_share.h" +#undef ERR_ENTRY + +#define ERR_ENTRY(N) { "E_FORM_"#N, CURL_FORMADD_##N }, +#include "lcerr_form.h" +#undef ERR_ENTRY + +#define ERR_ENTRY(N) { "E_URL_"#N, CURLUE_##N }, +#include "lcerr_url.h" +#undef ERR_ENTRY + + {NULL, 0} +}; + +void lcurl_error_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_ERROR, lcurl_err_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_error_codes); + + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_EASY ));lua_setfield(L, -2, "ERROR_EASY" ); + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_MULTI ));lua_setfield(L, -2, "ERROR_MULTI"); + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_SHARE ));lua_setfield(L, -2, "ERROR_SHARE"); + lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_FORM ));lua_setfield(L, -2, "ERROR_FORM" ); +} diff --git a/watchdog/third_party/lua-curl/src/lcerror.h b/watchdog/third_party/lua-curl/src/lcerror.h new file mode 100644 index 0000000..da4e1c5 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcerror.h @@ -0,0 +1,34 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCERROR_H_ +#define _LCERROR_H_ + +#include "lcurl.h" + +#define LCURL_ERROR_CURL 1 +#define LCURL_ERROR_EASY 1 +#define LCURL_ERROR_MULTI 2 +#define LCURL_ERROR_SHARE 3 +#define LCURL_ERROR_FORM 4 +#define LCURL_ERROR_URL 5 + +#define LCURL_ERROR_RETURN 1 +#define LCURL_ERROR_RAISE 2 + +int lcurl_fail(lua_State *L, int error_type, int code); + +int lcurl_fail_ex(lua_State *L, int mode, int error_type, int code); + +int lcurl_error_new(lua_State *L); + +void lcurl_error_initlib(lua_State *L, int nup); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcflags.h b/watchdog/third_party/lua-curl/src/lcflags.h new file mode 100644 index 0000000..0fbeff2 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcflags.h @@ -0,0 +1,283 @@ +/* Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options */ +FLG_ENTRY(AUTH_NONE ) +FLG_ENTRY(AUTH_BASIC ) +FLG_ENTRY(AUTH_DIGEST ) +FLG_ENTRY(AUTH_GSSNEGOTIATE ) +#if LCURL_CURL_VER_GE(7,38,0) +FLG_ENTRY(AUTH_NEGOTIATE ) +#endif +FLG_ENTRY(AUTH_NTLM ) +#if LCURL_CURL_VER_GE(7,19,3) +FLG_ENTRY(AUTH_DIGEST_IE ) +#endif +#if LCURL_CURL_VER_GE(7,19,6) +FLG_ENTRY(KHSTAT_FINE_ADD_TO_FILE ) +FLG_ENTRY(KHSTAT_FINE ) +FLG_ENTRY(KHSTAT_REJECT ) +FLG_ENTRY(KHSTAT_DEFER ) +FLG_ENTRY(KHMATCH_OK ) +FLG_ENTRY(KHMATCH_MISMATCH ) +FLG_ENTRY(KHMATCH_MISSING ) +FLG_ENTRY(KHTYPE_RSA1 ) +FLG_ENTRY(KHTYPE_RSA ) +FLG_ENTRY(KHTYPE_DSS ) +#endif +#if LCURL_CURL_VER_GE(7,58,0) +FLG_ENTRY(KHTYPE_ECDSA ) +FLG_ENTRY(KHTYPE_ED25519 ) +#endif +#if LCURL_CURL_VER_GE(7,73,0) +FLG_ENTRY(KHSTAT_FINE_REPLACE ) +#endif + +#if LCURL_CURL_VER_GE(7,22,0) +FLG_ENTRY(AUTH_NTLM_WB ) +#endif +#if LCURL_CURL_VER_GE(7,21,3) +FLG_ENTRY(AUTH_ONLY ) +#endif +FLG_ENTRY(AUTH_ANY ) +FLG_ENTRY(AUTH_ANYSAFE ) +#if LCURL_CURL_VER_GE(7,55,0) +FLG_ENTRY(AUTH_GSSAPI ) +#endif +#if LCURL_CURL_VER_GE(7,61,0) +FLG_ENTRY(AUTH_BEARER ) +#endif + +#ifdef CURLSSH_AUTH_ANY +FLG_ENTRY(SSH_AUTH_ANY ) +#endif +#ifdef CURLSSH_AUTH_NONE +FLG_ENTRY(SSH_AUTH_NONE ) +#endif +#ifdef CURLSSH_AUTH_PUBLICKEY +FLG_ENTRY(SSH_AUTH_PUBLICKEY ) +#endif +#ifdef CURLSSH_AUTH_PASSWORD +FLG_ENTRY(SSH_AUTH_PASSWORD ) +#endif +#ifdef CURLSSH_AUTH_HOST +FLG_ENTRY(SSH_AUTH_HOST ) +#endif +#ifdef CURLSSH_AUTH_GSSAPI +FLG_ENTRY(SSH_AUTH_GSSAPI ) +#endif +#ifdef CURLSSH_AUTH_KEYBOARD +FLG_ENTRY(SSH_AUTH_KEYBOARD ) +#endif +#ifdef CURLSSH_AUTH_AGENT +FLG_ENTRY(SSH_AUTH_AGENT ) +#endif +#ifdef CURLSSH_AUTH_DEFAULT +FLG_ENTRY(SSH_AUTH_DEFAULT ) +#endif + +#ifdef CURLGSSAPI_DELEGATION_NONE +FLG_ENTRY(GSSAPI_DELEGATION_NONE ) +#endif +#ifdef CURLGSSAPI_DELEGATION_POLICY_FLAG +FLG_ENTRY(GSSAPI_DELEGATION_POLICY_FLAG ) +#endif +#ifdef CURLGSSAPI_DELEGATION_FLAG +FLG_ENTRY(GSSAPI_DELEGATION_FLAG ) +#endif + +/* Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options */ +FLG_ENTRY(USESSL_NONE ) +FLG_ENTRY(USESSL_TRY ) +FLG_ENTRY(USESSL_CONTROL ) +FLG_ENTRY(USESSL_ALL ) + +/* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */ +#ifdef CURLSSLOPT_ALLOW_BEAST +FLG_ENTRY(SSLOPT_ALLOW_BEAST ) +#endif +#ifdef CURLSSLOPT_NO_REVOKE +FLG_ENTRY(SSLOPT_NO_REVOKE ) +#endif +#ifdef CURLSSLOPT_NO_PARTIALCHAIN +FLG_ENTRY(SSLOPT_NO_PARTIALCHAIN ) +#endif +#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT +FLG_ENTRY(SSLOPT_REVOKE_BEST_EFFORT ) +#endif +#ifdef CURLSSLOPT_NATIVE_CA +FLG_ENTRY(SSLOPT_NATIVE_CA ) +#endif + +/* parameter for the CURLOPT_FTP_SSL_CCC option */ +FLG_ENTRY(FTPSSL_CCC_NONE ) +FLG_ENTRY(FTPSSL_CCC_PASSIVE ) +FLG_ENTRY(FTPSSL_CCC_ACTIVE ) + +/* parameter for the CURLOPT_FTPSSLAUTH option */ +FLG_ENTRY(FTPAUTH_DEFAULT ) +FLG_ENTRY(FTPAUTH_SSL ) +FLG_ENTRY(FTPAUTH_TLS ) + +/* parameter for the CURLOPT_FTP_CREATE_MISSING_DIRS option */ +FLG_ENTRY(FTP_CREATE_DIR_NONE ) +FLG_ENTRY(FTP_CREATE_DIR ) +FLG_ENTRY(FTP_CREATE_DIR_RETRY ) +FLG_ENTRY(FTP_CREATE_DIR_LAST ) + +/* parameter for the CURLOPT_FTP_FILEMETHOD option */ +FLG_ENTRY(FTPMETHOD_DEFAULT ) +FLG_ENTRY(FTPMETHOD_MULTICWD ) +FLG_ENTRY(FTPMETHOD_NOCWD ) +FLG_ENTRY(FTPMETHOD_SINGLECWD ) + +/* bitmask defines for CURLOPT_HEADEROPT */ +#if LCURL_CURL_VER_GE(7,37,0) +FLG_ENTRY(HEADER_UNIFIED ) +FLG_ENTRY(HEADER_SEPARATE ) +#endif + +/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */ +FLG_ENTRY(PROTO_HTTP ) +FLG_ENTRY(PROTO_HTTPS ) +FLG_ENTRY(PROTO_FTP ) +FLG_ENTRY(PROTO_FTPS ) +FLG_ENTRY(PROTO_SCP ) +FLG_ENTRY(PROTO_SFTP ) +FLG_ENTRY(PROTO_TELNET ) +FLG_ENTRY(PROTO_LDAP ) +FLG_ENTRY(PROTO_LDAPS ) +FLG_ENTRY(PROTO_DICT ) +FLG_ENTRY(PROTO_FILE ) +FLG_ENTRY(PROTO_TFTP ) +#ifdef CURLPROTO_IMAP +FLG_ENTRY(PROTO_IMAP ) +#endif +#ifdef CURLPROTO_IMAPS +FLG_ENTRY(PROTO_IMAPS ) +#endif +#ifdef CURLPROTO_POP3 +FLG_ENTRY(PROTO_POP3 ) +#endif +#ifdef CURLPROTO_POP3S +FLG_ENTRY(PROTO_POP3S ) +#endif +#ifdef CURLPROTO_SMTP +FLG_ENTRY(PROTO_SMTP ) +#endif +#ifdef CURLPROTO_SMTPS +FLG_ENTRY(PROTO_SMTPS ) +#endif +#ifdef CURLPROTO_RTSP +FLG_ENTRY(PROTO_RTSP ) +#endif +#ifdef CURLPROTO_RTMP +FLG_ENTRY(PROTO_RTMP ) +#endif +#ifdef CURLPROTO_RTMPT +FLG_ENTRY(PROTO_RTMPT ) +#endif +#ifdef CURLPROTO_RTMPE +FLG_ENTRY(PROTO_RTMPE ) +#endif +#ifdef CURLPROTO_RTMPTE +FLG_ENTRY(PROTO_RTMPTE ) +#endif +#ifdef CURLPROTO_RTMPS +FLG_ENTRY(PROTO_RTMPS ) +#endif +#ifdef CURLPROTO_RTMPTS +FLG_ENTRY(PROTO_RTMPTS ) +#endif +#ifdef CURLPROTO_GOPHER +FLG_ENTRY(PROTO_GOPHER ) +#endif +#ifdef CURLPROTO_SMB +FLG_ENTRY(PROTO_SMB ) +#endif +#ifdef CURLPROTO_SMBS +FLG_ENTRY(PROTO_SMBS ) +#endif +#ifdef CURLPROTO_MQTT +FLG_ENTRY(PROTO_MQTT ) +#endif +FLG_ENTRY(PROTO_ALL ) + +FLG_ENTRY(PROXY_HTTP ) /* added in 7.10.0 */ +FLG_ENTRY(PROXY_HTTP_1_0 ) /* added in 7.19.4 */ +FLG_ENTRY(PROXY_SOCKS4 ) /* added in 7.15.2 */ +FLG_ENTRY(PROXY_SOCKS5 ) /* added in 7.10.0 */ +FLG_ENTRY(PROXY_SOCKS4A ) /* added in 7.18.0 */ +FLG_ENTRY(PROXY_SOCKS5_HOSTNAME ) /* added in 7.18.0 */ +#if LCURL_CURL_VER_GE(7,52,0) +FLG_ENTRY(PROXY_HTTPS ) +#endif + +FLG_ENTRY(PAUSE_ALL ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_CONT ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_RECV ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_RECV_CONT ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_SEND ) /* added in 7.18.0 */ +FLG_ENTRY(PAUSE_SEND_CONT ) /* added in 7.18.0 */ + +#if LCURL_CURL_VER_GE(7,64,1) +FLG_ENTRY(ALTSVC_H1) +FLG_ENTRY(ALTSVC_H2) +FLG_ENTRY(ALTSVC_H3) +FLG_ENTRY(ALTSVC_READONLYFILE) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +FLG_ENTRY(PX_OK) +FLG_ENTRY(PX_BAD_ADDRESS_TYPE) +FLG_ENTRY(PX_BAD_VERSION) +FLG_ENTRY(PX_CLOSED) +FLG_ENTRY(PX_GSSAPI) +FLG_ENTRY(PX_GSSAPI_PERMSG) +FLG_ENTRY(PX_GSSAPI_PROTECTION) +FLG_ENTRY(PX_IDENTD) +FLG_ENTRY(PX_IDENTD_DIFFER) +FLG_ENTRY(PX_LONG_HOSTNAME) +FLG_ENTRY(PX_LONG_PASSWD) +FLG_ENTRY(PX_LONG_USER) +FLG_ENTRY(PX_NO_AUTH) +FLG_ENTRY(PX_RECV_ADDRESS) +FLG_ENTRY(PX_RECV_AUTH) +FLG_ENTRY(PX_RECV_CONNECT) +FLG_ENTRY(PX_RECV_REQACK) +FLG_ENTRY(PX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED) +FLG_ENTRY(PX_REPLY_COMMAND_NOT_SUPPORTED) +FLG_ENTRY(PX_REPLY_CONNECTION_REFUSED) +FLG_ENTRY(PX_REPLY_GENERAL_SERVER_FAILURE) +FLG_ENTRY(PX_REPLY_HOST_UNREACHABLE) +FLG_ENTRY(PX_REPLY_NETWORK_UNREACHABLE) +FLG_ENTRY(PX_REPLY_NOT_ALLOWED) +FLG_ENTRY(PX_REPLY_TTL_EXPIRED) +FLG_ENTRY(PX_REPLY_UNASSIGNED) +FLG_ENTRY(PX_REQUEST_FAILED) +FLG_ENTRY(PX_RESOLVE_HOST) +FLG_ENTRY(PX_SEND_AUTH) +FLG_ENTRY(PX_SEND_CONNECT) +FLG_ENTRY(PX_SEND_REQUEST) +FLG_ENTRY(PX_UNKNOWN_FAIL) +FLG_ENTRY(PX_UNKNOWN_MODE) +FLG_ENTRY(PX_USER_REJECTED) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +FLG_ENTRY(OT_LONG) +FLG_ENTRY(OT_VALUES) +FLG_ENTRY(OT_OFF_T) +FLG_ENTRY(OT_OBJECT) +FLG_ENTRY(OT_STRING) +FLG_ENTRY(OT_SLIST) +FLG_ENTRY(OT_CBPTR) +FLG_ENTRY(OT_BLOB) +FLG_ENTRY(OT_FUNCTION) +FLG_ENTRY(OT_FLAG_ALIAS) +#endif + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS +FLG_ENTRY(HSTS_ENABLE) +FLG_ENTRY(HSTS_READONLYFILE) +FLG_ENTRY(STS_OK) +FLG_ENTRY(STS_DONE) +FLG_ENTRY(STS_FAIL) +#endif diff --git a/watchdog/third_party/lua-curl/src/lchttppost.c b/watchdog/third_party/lua-curl/src/lchttppost.c new file mode 100644 index 0000000..2d49323 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lchttppost.c @@ -0,0 +1,595 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lchttppost.h" +#include "lcerror.h" +#include "lcutils.h" + +#define LCURL_HTTPPOST_NAME LCURL_PREFIX" HTTPPost" +static const char *LCURL_HTTPPOST = LCURL_HTTPPOST_NAME; + + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + +/*! @fixme detect real types (e.g. float/int32_t) */ + +# define LCURL_USE_INTEGER + +#endif + +#ifdef LCURL_USE_INTEGER +# ifdef LUA_32BITS +# define LCURL_INT_SIZE_16 +# define LCURL_INT_SIZE_32 +# else +# define LCURL_INT_SIZE_16 +# define LCURL_INT_SIZE_32 +# define LCURL_INT_SIZE_64 +# endif +#endif + +#if LCURL_CURL_VER_GE(7,46,0) +# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTLEN +# define LCURL_LEN_TYPE curl_off_t +#else +# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTSLENGTH +# define LCURL_LEN_TYPE long +#endif + +/* 7.56.0 changed code for `curl_formget` if callback abort write. + * + * https://github.com/curl/curl/issues/1987#issuecomment-336139060 + * ... not sure its worth the effort to document its return codes to + * any further extent then it currently is. This function is very + * rarely used, and the new mime API doesn't even have a version of it. + **/ +#if LCURL_CURL_VER_GE(7,56,0) +# define LCURL_GET_CB_ERROR CURLE_READ_ERROR +#else +# define LCURL_GET_CB_ERROR (CURLcode)-1 +#endif + +//{ stream + +static lcurl_hpost_stream_t *lcurl_hpost_stream_add(lua_State *L, lcurl_hpost_t *p){ + lcurl_hpost_stream_t *ptr = p->stream; + lcurl_hpost_stream_t *stream = malloc(sizeof(lcurl_hpost_stream_t)); + if(!stream) return NULL; + + stream->magic = LCURL_HPOST_STREAM_MAGIC; + stream->L = &p->L; + stream->rbuffer.ref = LUA_NOREF; + stream->rd.cb_ref = stream->rd.ud_ref = LUA_NOREF; + stream->next = NULL; + if(!p->stream) p->stream = stream; + else{ + while(ptr->next) ptr = ptr->next; + ptr->next = stream; + } + return stream; +} + +static void lcurl_hpost_stream_free(lua_State *L, lcurl_hpost_stream_t *ptr){ + if(ptr){ + luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rbuffer.ref); + luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.ud_ref); + free(ptr); + } +} + +static void lcurl_hpost_stream_free_last(lua_State *L, lcurl_hpost_t *p){ + lcurl_hpost_stream_t *ptr = p->stream; + if(!ptr) return; + if(!ptr->next){ + lcurl_hpost_stream_free(L, ptr); + p->stream = 0; + } + + while(ptr->next->next) ptr = ptr->next; + lcurl_hpost_stream_free(L, ptr->next); + ptr->next = NULL; +} + +static void lcurl_hpost_stream_free_all(lua_State *L, lcurl_hpost_t *p){ + lcurl_hpost_stream_t *ptr = p->stream; + while(ptr){ + lcurl_hpost_stream_t *next = ptr->next; + lcurl_hpost_stream_free(L, ptr); + ptr = next; + } + p->stream = 0; +} + +//} + +//{ HTTPPost + +int lcurl_hpost_create(lua_State *L, int error_mode){ + lcurl_hpost_t *p = lutil_newudatap(L, lcurl_hpost_t, LCURL_HTTPPOST); + p->post = p->last = 0; + p->storage = lcurl_storage_init(L); + p->err_mode = error_mode; + p->stream = 0; + + return 1; +} + +lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i){ + lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, i, LCURL_HTTPPOST); + luaL_argcheck (L, p != NULL, 1, LCURL_HTTPPOST_NAME" object expected"); + return p; +} + +static int lcurl_hpost_to_s(lua_State *L){ + lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, 1, LCURL_HTTPPOST); + lua_pushfstring(L, LCURL_HTTPPOST_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_hpost_add_content(lua_State *L){ + // add_buffer(name, data, [type,] [headers]) + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + size_t cont_len; const char *cont = luaL_checklstring(L, 3, &cont_len); + const char *type = lua_tostring(L, 4); + struct curl_slist *list = lcurl_util_to_slist(L, type?5:4); + struct curl_forms forms[3]; + CURLFORMcode code; + + int i = 0; + if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } + if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } + forms[i].option = CURLFORM_END; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_PTRCONTENTS, cont, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)cont_len, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + if(code != CURL_FORMADD_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + lcurl_storage_preserve_value(L, p->storage, 3); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_buffer(lua_State *L){ + // add_buffer(name, filename, data, [type,] [headers]) + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + const char *buff = luaL_checkstring(L, 3); + size_t cont_len; const char *cont = luaL_checklstring(L, 4, &cont_len); + const char *type = lua_tostring(L, 5); + struct curl_slist *list = lcurl_util_to_slist(L, ((!type)&&(lua_isnone(L,6)))?5:6); + struct curl_forms forms[3]; + CURLFORMcode code; + + int i = 0; + if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } + if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } + forms[i].option = CURLFORM_END; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_BUFFER, buff, + CURLFORM_BUFFERPTR, cont, CURLFORM_BUFFERLENGTH, cont_len, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + if(code != CURL_FORMADD_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + lcurl_storage_preserve_value(L, p->storage, 4); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_file(lua_State *L){ + // add_file(name, path, [type, [fname,]] [headers]) + // add_file("Picture", "c:\\image.jpg") + // add_file("Picture", "c:\\image.jpg", "image/jpeg") + // add_file("Picture", "c:\\image.jpg", "image/jpeg", {"XDescript: my image"}) + // add_file("Picture", "c:\\image.jpg", "image/jpeg", "avatar.jpeg", {"XDescript: my image"}) + // add_file("Picture", "c:\\image.jpg", nil, "avatar.jpeg", {"XDescript: my image"}) + + int top = lua_gettop(L); + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + const char *path = luaL_checkstring(L, 3); + const char *type = 0, *fname = 0; + struct curl_slist *list = NULL; + struct curl_forms forms[4]; + CURLFORMcode code; + int i = 0; + + if(top == 4){ /* name, path, type | headers */ + if(lua_istable(L, 4)) + list = lcurl_util_to_slist(L, 4); + else + type = lua_tostring(L, 4); + } + else if(top > 4){ /* name, path, type, fname | [fname, headers] */ + type = lua_tostring(L, 4); + if(top == 5){ /* name, path, type, fname | headers */ + if(lua_istable(L, 5)) + list = lcurl_util_to_slist(L, 5); + else + fname = lua_tostring(L, 5); + } + else{ /* name, path, type, fname, headers */ + fname = lua_tostring(L, 5); + list = lcurl_util_to_slist(L, 6); + } + } + + if(fname){ forms[i].option = CURLFORM_FILENAME; forms[i++].value = fname; } + if(type) { forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } + if(list) { forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } + forms[i].option = CURLFORM_END; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_FILE, path, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + if(code != CURL_FORMADD_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_stream(lua_State *L){ + static const char *EMPTY = ""; + + // add_stream(name, [filename, [type,]] [headers,] size, reader [,context]) + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + struct curl_slist *list = NULL; int ilist = 0; + const char *type = 0, *fname = 0; + size_t len; + CURLFORMcode code; + lcurl_callback_t rd = {LUA_NOREF, LUA_NOREF}; + lcurl_hpost_stream_t *stream; + int n = 0, i = 3; + struct curl_forms forms[4]; + + while(1){ // [filename, [type,]] [headers,] + if(lua_isnone(L, i)){ + lua_pushliteral(L, "stream size required"); + lua_error(L); + } + if(lua_type(L, i) == LUA_TNUMBER){ + break; + } + if(lua_type(L, i) == LUA_TTABLE){ + ilist = i++; + break; + } + else if(!fname){ + if(lua_isnil(L, i)) fname = EMPTY; + else fname = luaL_checkstring(L, i); + } + else if(!type){ + if(lua_isnil(L, i)) type = EMPTY; + else type = luaL_checkstring(L, i); + } + else{ + if(lua_isnil(L, i) && (!ilist)){ + ++i; // empty headers + break; + } + lua_pushliteral(L, "stream size required"); + lua_error(L); + } + ++i; + } + +#if defined(LCURL_INT_SIZE_64) && LCURL_CURL_VER_GE(7,46,0) + len = luaL_checkinteger(L, i); +#else + len = luaL_checklong(L, i); +#endif + + lcurl_set_callback(L, &rd, i + 1, "read"); + + luaL_argcheck(L, rd.cb_ref != LUA_NOREF, i + 1, "function expected"); + + if(ilist) list = lcurl_util_to_slist(L, ilist); + if(fname == EMPTY) fname = NULL; + if(type == EMPTY) type = NULL; + + n = 0; + if(fname){ forms[n].option = CURLFORM_FILENAME; forms[n++].value = fname; } + if(type) { forms[n].option = CURLFORM_CONTENTTYPE; forms[n++].value = type; } + if(list) { forms[n].option = CURLFORM_CONTENTHEADER; forms[n++].value = (char*)list; } + forms[n].option = CURLFORM_END; + + stream = lcurl_hpost_stream_add(L, p); + if(!stream){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY); + } + + stream->rd = rd; + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_STREAM, stream, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)len, + CURLFORM_ARRAY, forms, + CURLFORM_END + ); + + if(code != CURL_FORMADD_OK){ + lcurl_hpost_stream_free_last(L, p); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lcurl_storage_preserve_value(L, p->storage, 2); + if(list) lcurl_storage_preserve_slist (L, p->storage, list); + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_add_files(lua_State *L){ + lcurl_hpost_t *p = lcurl_gethpost(L); + size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); + int i; int opt_count = 0; + int arr_count = lua_rawlen(L, 3); + struct curl_forms *forms; + CURLFORMcode code; + + lua_settop(L, 3); + if(lua_type(L, -1) != LUA_TTABLE){ + //! @fixme use library specific error codes + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); + } + + for(i = 1; i <= arr_count; ++i){ + int n; + lua_rawgeti(L, 3, i); + + if((lua_type(L, -1) != LUA_TTABLE) && (lua_type(L, -1) != LUA_TSTRING)){ + //! @fixme use library specific error codes + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); + } + + n = (lua_type(L, -1) == LUA_TSTRING) ? 1: lua_rawlen(L, -1); + if(n == 1) opt_count += 1; // name + else if(n == 2) opt_count += 2; // name and type + else if(n == 3) opt_count += 3; // name, type and filename + else{ + //! @fixme use library specific error codes + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); + } + + lua_pop(L, 1); + } + + if(opt_count == 0){ + lua_settop(L, 1); + return 1; + } + + forms = calloc(opt_count + 1, sizeof(struct curl_forms)); + if(!forms){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY); + } + forms[opt_count].option = CURLFORM_END; + + opt_count = 0; + for(i = 1; i <= arr_count; ++i){ + int n; + + lua_rawgeti(L, 3, i); + if (lua_type(L, -1) == LUA_TSTRING){ + forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1); + } + else{ + n = lua_rawlen(L, -1); + lua_rawgeti(L, -1, 1); + forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1); + lua_pop(L, 1); + if(n > 1){ + lua_rawgeti(L, -1, 2); + forms[opt_count].option = CURLFORM_CONTENTTYPE; forms[opt_count++].value = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + if(n > 2){ + lua_rawgeti(L, -1, 3); + forms[opt_count].option = CURLFORM_FILENAME; forms[opt_count++].value = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + } + + lua_pop(L, 1); + } + + code = curl_formadd(&p->post, &p->last, + CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, + CURLFORM_ARRAY, forms, + CURLFORM_END); + + free(forms); + + if(code != CURL_FORMADD_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); + } + + lua_settop(L, 1); + return 1; +} + +static size_t lcurl_hpost_getter_by_buffer(void *arg, const char *buf, size_t len){ + luaL_Buffer *b = arg; + luaL_addlstring(b, buf, len); + return len; +} + +static size_t call_writer(lua_State *L, int fn, int ctx, const char *buf, size_t len){ + int top = lua_gettop(L); + int n = 1; // number of args + lua_Number ret = (lua_Number)len; + + lua_pushvalue(L, fn); + if(ctx){ + lua_pushvalue(L, ctx); + n += 1; + } + lua_pushlstring(L, buf, len); + + if(lua_pcall(L, n, LUA_MULTRET, 0)) return 0; + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)) return 0; + if(lua_isboolean(L, top + 1)){ + if(!lua_toboolean(L, top + 1)) ret = 0; + } + else ret = lua_tonumber(L, top + 1); + } + lua_settop(L, top); + + return (size_t)ret; +} + +static size_t lcurl_hpost_getter_by_callback1(void *arg, const char *buf, size_t len){ + lua_State *L = arg; + assert(2 == lua_gettop(L)); + return call_writer(L, 2, 0, buf, len); +} + +static size_t lcurl_hpost_getter_by_callback2(void *arg, const char *buf, size_t len){ + lua_State *L = arg; + assert(3 == lua_gettop(L)); + return call_writer(L, 2, 3, buf, len); +} + +static int lcurl_hpost_get(lua_State *L){ + // get() + // get(fn [, ctx]) + // get(object) + lcurl_hpost_t *p = lcurl_gethpost(L); + CURLcode code; + int top; + + if(lua_isnoneornil(L, 2)){ + luaL_Buffer b; + luaL_buffinit(L, &b); + + code = curl_formget(p->post, &b, lcurl_hpost_getter_by_buffer); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code); + } + + luaL_pushresult(&b); + return 1; + } + + if(lua_isfunction(L, 2)){ + if(lua_gettop(L) == 2){ + top = 2; + code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback1); + } + else{ + top = 3; + lua_settop(L, 3); + code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback2); + } + } + else if(lua_isuserdata(L, 2) || lua_istable(L, 2)){ + lua_settop(L, 2); + lua_getfield(L, 2, "write"); + luaL_argcheck(L, lua_isfunction(L, -1), 2, "write method not found in object"); + assert(3 == lua_gettop(L)); + lua_insert(L, -2); + top = 3; + code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback2); + } + else{ + lua_pushliteral(L, "invalid writer type"); + return lua_error(L); + } + + if(LCURL_GET_CB_ERROR == code){ + if(((lua_gettop(L) == top+1))&&(lua_isstring(L, -1))){ + return lua_error(L); + } + return lua_gettop(L) - top; + } + + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code); + } + + lua_settop(L, 1); + return 1; +} + +static int lcurl_hpost_free(lua_State *L){ + lcurl_hpost_t *p = lcurl_gethpost(L); + if(p->post){ + curl_formfree(p->post); + p->post = p->last = 0; + } + + if(p->storage != LUA_NOREF){ + p->storage = lcurl_storage_free(L, p->storage); + } + + lcurl_hpost_stream_free_all(L, p); + + return 0; +} + +//} + +static const struct luaL_Reg lcurl_hpost_methods[] = { + {"add_content", lcurl_hpost_add_content }, + {"add_buffer", lcurl_hpost_add_buffer }, + {"add_file", lcurl_hpost_add_file }, + {"add_stream", lcurl_hpost_add_stream }, + + {"add_files", lcurl_hpost_add_files }, + + {"get", lcurl_hpost_get }, + {"free", lcurl_hpost_free }, + {"__gc", lcurl_hpost_free }, + {"__tostring", lcurl_hpost_to_s }, + + {NULL,NULL} +}; + +void lcurl_hpost_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_HTTPPOST, lcurl_hpost_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); +} + diff --git a/watchdog/third_party/lua-curl/src/lchttppost.h b/watchdog/third_party/lua-curl/src/lchttppost.h new file mode 100644 index 0000000..3299069 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lchttppost.h @@ -0,0 +1,47 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCHTTPPOST_H_ +#define _LCHTTPPOST_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +#define LCURL_HPOST_STREAM_MAGIC 0xAA + +typedef struct lcurl_hpost_stream_tag{ + unsigned char magic; + + lua_State **L; + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + struct lcurl_hpost_stream_tag *next; +}lcurl_hpost_stream_t; + +typedef struct lcurl_hpost_tag{ + lua_State *L; + struct curl_httppost *post; + struct curl_httppost *last; + int storage; + int err_mode; + lcurl_hpost_stream_t *stream; +}lcurl_hpost_t; + +int lcurl_hpost_create(lua_State *L, int error_mode); + +void lcurl_hpost_initlib(lua_State *L, int nup); + +lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i); + +#define lcurl_gethpost(L) lcurl_gethpost_at((L),1) + + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcinfoeasy.h b/watchdog/third_party/lua-curl/src/lcinfoeasy.h new file mode 100644 index 0000000..6cbf8a9 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcinfoeasy.h @@ -0,0 +1,94 @@ +OPT_ENTRY( effective_url, EFFECTIVE_URL, STR, 0) +OPT_ENTRY( response_code, RESPONSE_CODE, LNG, 0) +OPT_ENTRY( http_connectcode, HTTP_CONNECTCODE, LNG, 0) +OPT_ENTRY( filetime, FILETIME, LNG, 0) +OPT_ENTRY( total_time, TOTAL_TIME, DBL, 0) +OPT_ENTRY( namelookup_time, NAMELOOKUP_TIME, DBL, 0) +OPT_ENTRY( connect_time, CONNECT_TIME, DBL, 0) +OPT_ENTRY( appconnect_time, APPCONNECT_TIME, DBL, 0) +OPT_ENTRY( pretransfer_time, PRETRANSFER_TIME, DBL, 0) +OPT_ENTRY( starttransfer_time, STARTTRANSFER_TIME, DBL, 0) +OPT_ENTRY( redirect_time, REDIRECT_TIME, DBL, 0) +OPT_ENTRY( redirect_count, REDIRECT_COUNT, LNG, 0) +OPT_ENTRY( redirect_url, REDIRECT_URL, STR, 0) +OPT_ENTRY( size_upload, SIZE_UPLOAD, DBL, 0) +OPT_ENTRY( size_download, SIZE_DOWNLOAD, DBL, 0) +OPT_ENTRY( speed_download, SPEED_DOWNLOAD, DBL, 0) +OPT_ENTRY( speed_upload, SPEED_UPLOAD, DBL, 0) +OPT_ENTRY( header_size, HEADER_SIZE, LNG, 0) +OPT_ENTRY( request_size, REQUEST_SIZE, LNG, 0) +OPT_ENTRY( ssl_verifyresult, SSL_VERIFYRESULT, LNG, 0) +OPT_ENTRY( ssl_engines, SSL_ENGINES, LST, 0) +OPT_ENTRY( content_length_download, CONTENT_LENGTH_DOWNLOAD, DBL, 0) +OPT_ENTRY( content_length_upload, CONTENT_LENGTH_UPLOAD, DBL, 0) +OPT_ENTRY( content_type, CONTENT_TYPE, STR, 0) +OPT_ENTRY( httpauth_avail, HTTPAUTH_AVAIL, LNG, 0) +OPT_ENTRY( proxyauth_avail, PROXYAUTH_AVAIL, LNG, 0) +OPT_ENTRY( os_errno, OS_ERRNO, LNG, 0) +OPT_ENTRY( num_connects, NUM_CONNECTS, LNG, 0) +OPT_ENTRY( primary_ip, PRIMARY_IP, STR, 0) +OPT_ENTRY( certinfo, CERTINFO, CERTINFO, 0) +#if LCURL_CURL_VER_GE(7,21,0) +OPT_ENTRY( primary_port, PRIMARY_PORT, LNG, 0) +OPT_ENTRY( local_ip, LOCAL_IP, STR, 0) +OPT_ENTRY( local_port, LOCAL_PORT, LNG, 0) +#endif +OPT_ENTRY( cookielist, COOKIELIST, LST, 0) +OPT_ENTRY( lastsocket, LASTSOCKET, LNG, 0) +OPT_ENTRY( ftp_entry_path, FTP_ENTRY_PATH, STR, 0) +OPT_ENTRY( condition_unmet, CONDITION_UNMET, LNG, 0) +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( rtsp_session_id, RTSP_SESSION_ID, STR, 0) +OPT_ENTRY( rtsp_client_cseq, RTSP_CLIENT_CSEQ, LNG, 0) +OPT_ENTRY( rtsp_server_cseq, RTSP_SERVER_CSEQ, LNG, 0) +OPT_ENTRY( rtsp_cseq_recv, RTSP_CSEQ_RECV, LNG, 0) +#endif + +#if LCURL_CURL_VER_GE(7,50,1) +OPT_ENTRY( http_version, HTTP_VERSION, LNG, 0) +#endif + +#if LCURL_CURL_VER_GE(7,52,0) +OPT_ENTRY( proxy_ssl_verifyresult, PROXY_SSL_VERIFYRESULT, LNG, 0) +OPT_ENTRY( protocol, PROTOCOL, LNG, 0) +OPT_ENTRY( scheme, SCHEME, STR, 0) +#endif + +#if LCURL_CURL_VER_GE(7,55,0) +OPT_ENTRY( content_length_download_t, CONTENT_LENGTH_DOWNLOAD_T, OFF, 0) +OPT_ENTRY( content_length_upload_t, CONTENT_LENGTH_UPLOAD_T, OFF, 0) +OPT_ENTRY( size_download_t, SIZE_DOWNLOAD_T, OFF, 0) +OPT_ENTRY( size_upload_t, SIZE_UPLOAD_T, OFF, 0) +OPT_ENTRY( speed_download_t, SPEED_DOWNLOAD_T, OFF, 0) +OPT_ENTRY( speed_upload_t, SPEED_UPLOAD_T, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,59,0) +OPT_ENTRY( filetime_t, FILETIME_T, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,61,0) +OPT_ENTRY(appconnect_time_t, APPCONNECT_TIME_T, OFF, 0) +OPT_ENTRY(connect_time_t, CONNECT_TIME_T, OFF, 0) +OPT_ENTRY(namelookup_time_t, NAMELOOKUP_TIME_T, OFF, 0) +OPT_ENTRY(pretransfer_time_t, PRETRANSFER_TIME_T, OFF, 0) +OPT_ENTRY(redirect_time_t, REDIRECT_TIME_T, OFF, 0) +OPT_ENTRY(starttransfer_time_t, STARTTRANSFER_TIME_T, OFF, 0) +OPT_ENTRY(total_time_t, TOTAL_TIME_T, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,66,0) +OPT_ENTRY(retry_after, RETRY_AFTER, OFF, 0) +#endif + +#if LCURL_CURL_VER_GE(7,72,0) +OPT_ENTRY(effective_method, EFFECTIVE_METHOD, STR, 0) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +OPT_ENTRY(proxy_error, PROXY_ERROR, LNG, 0) +#endif + +// OPT_ENTRY( PRIVATE, void ) +// OPT_ENTRY( TLS_SSL_PTR, struct curl_tlssessioninfo ** +// OPT_ENTRY( TLS_SESSION, struct curl_tlssessioninfo * diff --git a/watchdog/third_party/lua-curl/src/lcmime.c b/watchdog/third_party/lua-curl/src/lcmime.c new file mode 100644 index 0000000..90fd6b4 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmime.c @@ -0,0 +1,686 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2017-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcmime.h" +#include "lceasy.h" +#include "lcerror.h" +#include "lcutils.h" + +/* API Notes. + * 1. Each mime can be root or child. If mime is a child (subpart) then curl free it + * when parent mime is freed or when remove this part from parent. There no way reuse same mime. + * Its not clear is it possible use mime created by one easy handle when do preform in another. + * `m=e1:mime() e2:setopt_httpmime(m) e1:close() e2:perform()` + * + * // Attach child to root (root also can have parent) + * curl_mime_subparts(root, child); + * + * // curl free `child` and all its childs + * curl_mime_subparts(root, other_child_or_null); + * + * // forbidden + * curl_mime_free(child); + */ + +#if LCURL_CURL_VER_GE(7,56,0) + +#define LCURL_MIME_NAME LCURL_PREFIX" MIME" +static const char *LCURL_MIME = LCURL_MIME_NAME; + +#define LCURL_MIME_PART_NAME LCURL_PREFIX" MIME Part" +static const char *LCURL_MIME_PART = LCURL_MIME_PART_NAME; + +//{ Free mime and subparts + +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it); + +static lcurl_mime_t* lcurl_mime_part_get_subparts(lua_State *L, lcurl_mime_part_t *part){ + lcurl_mime_t *sub = NULL; + + if(LUA_NOREF != part->subpart_ref){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, part->subpart_ref); + sub = lcurl_getmime_at(L, -1); + lua_pop(L, 1); + } + + return sub; +} + +static int lcurl_mime_part_reset(lua_State *L, lcurl_mime_part_t *p){ + p->part = NULL; + + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + + p->headers_ref = p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + + /*free only if we have no parents*/ + lcurl_mime_part_remove_subparts(L, p, 0); + + return 0; +} + +static int lcurl_mime_reset(lua_State *L, lcurl_mime_t *p){ + lcurl_mime_part_t *ptr; + + /* reset all parts*/ + for(ptr = p->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_reset(L, ptr); + } + + if(LUA_NOREF != p->storage){ + p->storage = lcurl_storage_free(L, p->storage); + } + + p->parts = p->parent = NULL; + p->mime = NULL; + + /* remove weak reference to easy */ + lua_pushnil(L); + lua_rawsetp(L, LCURL_MIME_EASY, p); + + return 0; +} + +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, p); + if(sub){ + assert(LUA_NOREF != p->subpart_ref); + /* detach `subpart` mime from current mime part */ + /* if set `sub->parent = NULL` then gc for mime will try free curl_mime_free. */ + + luaL_unref(L, LCURL_LUA_REGISTRY, p->subpart_ref); + p->subpart_ref = LUA_NOREF; + + if(p->part && free_it){ + curl_mime_subparts(p->part, NULL); + } + + /* seems curl_mime_subparts(h, NULL) free asubparts. + so we have to invalidate all reference to all nested objects (part/mime). + NOTE. All resources already feed. So just need set all pointers to NULL + and free all Lua resources (like references and storages) + */ + { + lcurl_mime_part_t *ptr; + /* reset all parts*/ + for(ptr = sub->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_remove_subparts(L, p, 0); + } + lcurl_mime_reset(L, sub); + } + } +} + +//} + +int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ + lcurl_mime_part_t *part; + for(part = p->parts; part; part=part->next){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, part); + if(sub) lcurl_mime_set_lua(L, sub, v); + part->L = v; + } + return 0; +} + +#define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) +#define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE +#define IS_FALSE(L, i) ((lua_type(L, i) == LUA_TBOOLEAN) && (!lua_toboolean(L, i))) || lutil_is_null(L,i) +#define IS_OPTSTR(L, i) (IS_FALSE(L, i)) || (IS_NILORSTR(L, i)) + +static int lutil_isarray(lua_State *L, int i){ + int ret = 0; + i = lua_absindex(L, i); + lua_pushnil(L); + if(lua_next(L, i)){ + ret = lua_isnumber(L, -2); + lua_pop(L, 2); + } + return ret; +} + +static int lcurl_mime_part_assign(lua_State *L, int part, const char *method){ + int top = lua_gettop(L); + + lua_pushvalue(L, part); + lua_insert(L, -2); + lua_getfield(L, -2, method); + lua_insert(L, -3); + lua_call(L, 2, LUA_MULTRET); + + return lua_gettop(L) - top + 1; +} + +static const char *lcurl_mime_part_fields[] = { + "data", "filedata", "name", "filename", "headers", "encoder", "type", NULL +}; + +static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ + int top = lua_gettop(L); + const char *method; int i; + + part = lua_absindex(L, part); + t = lua_absindex(L, t); + + if(lutil_isarray(L, t)){ + int ret; + lua_pushvalue(L, t); + ret = lcurl_mime_part_assign(L, part, "headers"); + if(ret != 1) return ret; + + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + } + else{ + for(i=0;method = lcurl_mime_part_fields[i]; ++i){ + lua_getfield(L, t, method); + if(!lua_isnil(L, -1)){ + int ret = lcurl_mime_part_assign(L, part, method); + if(ret != 1) return ret; + } + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + } + + lua_getfield(L, t, "subparts"); + if(!lua_isnil(L, -1)){ + if(IS_FALSE(L, -1) || lcurl_getmime_at(L, -1)){ + int ret = lcurl_mime_part_assign(L, part, "subparts"); + if(ret != 1) return ret; + } + } + lua_pop(L, 1); + assert(top == lua_gettop(L)); + } + + return 0; +} + +//{ MIME + +static lcurl_mime_part_t* lcurl_mime_parts_append(lcurl_mime_t *m, lcurl_mime_part_t *p){ + if(!m->parts) m->parts = p; + else{ + lcurl_mime_part_t *ptr = m->parts; + while(ptr->next)ptr = ptr->next; + ptr->next = p; + } + return p; +} + +static lcurl_mime_part_t* lcurl_mime_parts_find(lcurl_mime_t *m, lcurl_mime_part_t *p){ + lcurl_mime_part_t *ptr; + + for(ptr = m->parts; ptr; ptr = ptr->next){ + if(ptr == p) return p; + } + + return NULL; +} + +int lcurl_mime_create(lua_State *L, int error_mode){ + //! @todo make this function as method of easy handle + lcurl_easy_t *e = lcurl_geteasy(L); + + lcurl_mime_t *p = lutil_newudatap(L, lcurl_mime_t, LCURL_MIME); + + p->mime = curl_mime_init(e->curl); + + //! @todo return more accurate error category/code + if(!p->mime) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->storage = lcurl_storage_init(L); + p->err_mode = error_mode; + p->parts = p->parent = NULL; + + /* weak reference from mime to easy handle */ + lua_pushvalue(L, 1); + lua_rawsetp(L, LCURL_MIME_EASY, (void*)p); + + return 1; +} + +lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, i, LCURL_MIME); + luaL_argcheck (L, p != NULL, i, LCURL_MIME_NAME" object expected"); + luaL_argcheck (L, p->mime != NULL, i, LCURL_MIME_NAME" object freed"); + return p; +} + +static int lcurl_mime_to_s(lua_State *L){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); + + lua_pushfstring(L, LCURL_MIME_NAME" (%p)%s", (void*)p, + p->mime ? (p->parent ? " (subpart)" : "") : " (freed)" + ); + return 1; +} + +static int lcurl_mime_free(lua_State *L){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); + + if((p->mime) && (NULL == p->parent)){ + curl_mime_free(p->mime); + } + + return lcurl_mime_reset(L, p); +} + +static int lcurl_mime_addpart(lua_State *L){ + lcurl_mime_t *p = lcurl_getmime(L); + int ret; + + lua_settop(L, 2); + + ret = lcurl_mime_part_create(L, p->err_mode); + if(ret != 1) return ret; + + /* store mime part in storage */ + lcurl_storage_preserve_value(L, p->storage, lua_absindex(L, -1)); + lcurl_mime_parts_append(p, lcurl_getmimepart_at(L, -1)); + + if(lua_istable(L, 2)){ + ret = lcurl_mime_part_assing_table(L, 3, 2); + if(ret) return ret; + } + + return 1; +} + +static int lcurl_mime_easy(lua_State *L){ + lcurl_mime_t *p = lcurl_getmime(L); + lua_rawgetp(L, LCURL_MIME_EASY, p); + return 1; +} + +//} + +//{ MIME Part + +int lcurl_mime_part_create(lua_State *L, int error_mode){ + //! @todo make this function as method of mime handle + lcurl_mime_t *m = lcurl_getmime(L); + + lcurl_mime_part_t *p = lutil_newudatap(L, lcurl_mime_part_t, LCURL_MIME_PART); + + p->part = curl_mime_addpart(m->mime); + + //! @todo return more accurate error category/code + if(!p->part) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->err_mode = error_mode; + p->subpart_ref = p->headers_ref = LUA_NOREF; + p->parent = m; + + return 1; +} + +lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, i, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, i, LCURL_MIME_PART_NAME" object expected"); + luaL_argcheck (L, p->part != NULL, i, LCURL_MIME_PART_NAME" object freed"); + return p; +} + +static int lcurl_mime_part_to_s(lua_State *L){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); + + lua_pushfstring(L, LCURL_MIME_PART_NAME" (%p)%s", (void*)p, p->part ? "" : " (freed)"); + return 1; +} + +static int lcurl_mime_part_free(lua_State *L){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); + + lcurl_mime_part_reset(L, p); + + return 0; +} + +static int lcurl_mime_part_assing_ext(lua_State *L, int part, int i){ +#define UNSET_VALUE (const char*)-1 + + const char *mime_type = NULL, *mime_name = NULL, *mime_fname = NULL; + int headers = 0; + CURLcode ret; + lcurl_mime_part_t *p = lcurl_getmimepart_at(L, part); + + if(IS_TABLE(L, i)) headers = i; + else if (IS_OPTSTR(L, i)) { + mime_type = IS_FALSE(L, i) ? UNSET_VALUE : lua_tostring(L, i); + if(IS_TABLE(L, i+1)) headers = i+1; + else if(IS_OPTSTR(L, i+1)){ + mime_name = IS_FALSE(L, i+1) ? UNSET_VALUE : lua_tostring(L, i+1); + if(IS_TABLE(L, i+2)) headers = i+2; + else if(IS_OPTSTR(L, i+2)){ + mime_fname = IS_FALSE(L, i+2) ? UNSET_VALUE : lua_tostring(L, i+2); + if(IS_TABLE(L, i+3)) headers = i+3; + else if(IS_FALSE(L, i+3)){ + headers = -1; + } + } + } + } + + if(mime_type){ + ret = curl_mime_type(p->part, mime_type == UNSET_VALUE ? NULL : mime_type); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(mime_name){ + ret = curl_mime_name(p->part, mime_name == UNSET_VALUE ? NULL : mime_name); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(mime_fname){ + ret = curl_mime_filename(p->part, mime_fname == UNSET_VALUE ? NULL : mime_fname); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(headers){ + if(-1 == headers){ + ret = curl_mime_headers(p->part, NULL, 0); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + else + return lcurl_mime_part_assing_table(L, part, headers); + } + + return 0; + +#undef UNSET_VALUE +} + +// part:data(str[, type[, name[, filename]]][, headers]) +static int lcurl_mime_part_data(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + size_t len; const char *data; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + data = NULL; + len = 0; + } + else{ + data = luaL_checklstring(L, 2, &len); + /*string too long*/ + if(len == CURL_ZERO_TERMINATED){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } + } + + /* curl_mime_data copies data */ + ret = curl_mime_data(p->part, data, len); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, 1, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:subparts(mime[, type[, name]][, headers]) +static int lcurl_mime_part_subparts(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + lcurl_mime_t *mime = lcurl_getmime_at(L, 2); + CURLcode ret; + + /* we can attach mime to only one part */ + if(mime->parent){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } + + /* if we already have one subpart then libcurl free it so we can not use any references to it */ + lcurl_mime_part_remove_subparts(L, p, 1); + + ret = curl_mime_subparts(p->part, mime->mime); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_pushvalue(L, 2); + p->subpart_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + mime->parent = p; + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, 1, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:filedata(path[, type[, name[, filename]]][, headers]) +static int lcurl_mime_part_filedata(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *data = luaL_checkstring(L, 2); + CURLcode ret; + + ret = curl_mime_filedata(p->part, data); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, 1, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:headers(t) +static int lcurl_mime_part_headers(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + struct curl_slist *list; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + list = NULL; + } + else{ + list = lcurl_util_to_slist(L, 2); + luaL_argcheck(L, list || IS_TABLE(L, 2), 2, "array or null expected"); + } + + ret = curl_mime_headers(p->part, list, 1); + + if(ret != CURLE_OK){ + if(list) curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:type(t) +static int lcurl_mime_part_type(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_type; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_type = NULL; + } + else{ + mime_type = luaL_checkstring(L, 2); + } + + ret = curl_mime_type(p->part, mime_type); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:name(t) +static int lcurl_mime_part_name(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_name; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_name = NULL; + } + else{ + mime_name = luaL_checkstring(L, 2); + } + ret = curl_mime_name(p->part, mime_name); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:filename(t) +static int lcurl_mime_part_filename(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_name; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_name = NULL; + } + else{ + mime_name = luaL_checkstring(L, 2); + } + ret = curl_mime_filename(p->part, mime_name); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:encoder(t) +static int lcurl_mime_part_encoder(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_encode; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_encode = NULL; + } + else{ + mime_encode = luaL_checkstring(L, 2); + } + ret = curl_mime_encoder(p->part, mime_encode); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +//} + +static const struct luaL_Reg lcurl_mime_methods[] = { + + {"addpart", lcurl_mime_addpart }, + {"easy", lcurl_mime_easy }, + + {"free", lcurl_mime_free }, + {"__gc", lcurl_mime_free }, + {"__tostring", lcurl_mime_to_s }, + + {NULL,NULL} +}; + +static const struct luaL_Reg lcurl_mime_part_methods[] = { + + {"subparts", lcurl_mime_part_subparts }, + {"data", lcurl_mime_part_data }, + {"filedata", lcurl_mime_part_filedata }, + {"headers", lcurl_mime_part_headers }, + {"name", lcurl_mime_part_name }, + {"filename", lcurl_mime_part_filename }, + {"type", lcurl_mime_part_type }, + {"encoder", lcurl_mime_part_encoder }, + + + {"free", lcurl_mime_part_free }, + {"__gc", lcurl_mime_part_free }, + {"__tostring", lcurl_mime_part_to_s }, + + {NULL,NULL} +}; + +static int lcurl_pushvalues(lua_State *L, int nup) { + assert(lua_gettop(L) >= nup); + + if (nup > 0) { + int b = lua_absindex(L, -nup); + int e = lua_absindex(L, -1); + int i; + + lua_checkstack(L, nup); + + for(i = b; i <= e; ++i) + lua_pushvalue(L, i); + } + + return nup; +} + +#endif + +void lcurl_mime_initlib(lua_State *L, int nup){ +#if LCURL_CURL_VER_GE(7,56,0) + lcurl_pushvalues(L, nup); + + if(!lutil_createmetap(L, LCURL_MIME, lcurl_mime_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + if(!lutil_createmetap(L, LCURL_MIME_PART, lcurl_mime_part_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + +#else + lua_pop(L, nup); +#endif +} + diff --git a/watchdog/third_party/lua-curl/src/lcmime.h b/watchdog/third_party/lua-curl/src/lcmime.h new file mode 100644 index 0000000..6dbe9c5 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmime.h @@ -0,0 +1,66 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2017-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCMIME_H_ +#define _LCMIME_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +void lcurl_mime_initlib(lua_State *L, int nup); + +#if LCURL_CURL_VER_GE(7,56,0) + +typedef struct lcurl_mime_part_tag{ + lua_State *L; + + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + + curl_mimepart *part; + + struct lcurl_mime_tag *parent; /*always set and can not be changed*/ + + int subpart_ref; + int headers_ref; + + int err_mode; + + struct lcurl_mime_part_tag *next; +}lcurl_mime_part_t; + +typedef struct lcurl_mime_tag{ + curl_mime *mime; + + int storage; + int err_mode; + + lcurl_mime_part_t *parts; + lcurl_mime_part_t *parent; /*after set there no way change it*/ +}lcurl_mime_t; + +int lcurl_mime_create(lua_State *L, int error_mode); + +lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i); + +#define lcurl_getmime(L) lcurl_getmime_at((L), 1) + +int lcurl_mime_part_create(lua_State *L, int error_mode); + +lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i); + +#define lcurl_getmimepart(L) lcurl_getmimepart_at((L), 1) + +int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v); + +#endif + +#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcmulti.c b/watchdog/third_party/lua-curl/src/lcmulti.c new file mode 100644 index 0000000..b040083 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmulti.c @@ -0,0 +1,670 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#if defined(_WINDOWS) || defined(_WIN32) +# define LCURL_WINDOWS +#endif + +#ifdef LCURL_WINDOWS +# include +#else +# include +#endif + +#include "lcurl.h" +#include "lceasy.h" +#include "lcmulti.h" +#include "lcerror.h" +#include "lcutils.h" +#include "lchttppost.h" + +#define LCURL_MULTI_NAME LCURL_PREFIX" Multi" +static const char *LCURL_MULTI = LCURL_MULTI_NAME; + +#if defined(DEBUG) || defined(_DEBUG) +static void lcurl__multi_validate_sate(lua_State *L, lcurl_multi_t *p){ + int top = lua_gettop(L); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + assert(lua_istable(L, -1)); + + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + void *ptr = lua_touserdata(L, -2); + + assert(e->curl == ptr); + assert(e->multi == p); + assert(e->L == p->L); + + lua_pop(L, 1); + } + + lua_pop(L, 1); + assert(lua_gettop(L) == top); +} +#else +# define lcurl__multi_validate_sate(L, p) (void*)(0) +#endif + +void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy){ + lcurl__multi_validate_sate(L, p); + + if((assign_easy)&&(p->L != value)){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + lcurl__easy_assign_lua(L, e, value, 0); + lua_pop(L, 1); + } + lua_pop(L, 1); + } + + p->L = value; +} + +//{ + +int lcurl_multi_create(lua_State *L, int error_mode){ + lcurl_multi_t *p; + + lua_settop(L, 1); + + p = lutil_newudatap(L, lcurl_multi_t, LCURL_MULTI); + p->curl = curl_multi_init(); + p->err_mode = error_mode; + if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_INTERNAL_ERROR); + p->L = NULL; + lcurl_util_new_weak_table(L, "v"); + p->h_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; + p->sc.cb_ref = p->sc.ud_ref = LUA_NOREF; + + if(lua_type(L, 1) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); + if(ret) return ret; + assert(lua_gettop(L) == 2); + } + + return 1; +} + +lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i){ + lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, i, LCURL_MULTI); + luaL_argcheck (L, p != NULL, 1, LCURL_MULTI_NAME" object expected"); + return p; +} + +static int lcurl_multi_to_s(lua_State *L){ + lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, 1, LCURL_MULTI); + lua_pushfstring(L, LCURL_MULTI_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_multi_cleanup(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + if(p->curl){ + curl_multi_cleanup(p->curl); + p->curl = NULL; + } + + if(p->h_ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + e->multi = NULL; + lua_pop(L, 1); + } + lua_pop(L, 1); + luaL_unref(L, LCURL_LUA_REGISTRY, p->h_ref); + p->h_ref = LUA_NOREF; + } + + luaL_unref(L, LCURL_LUA_REGISTRY, p->tm.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->tm.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->sc.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->sc.ud_ref); + p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; + p->sc.cb_ref = p->sc.ud_ref = LUA_NOREF; + + lua_settop(L, 1); + lua_pushnil(L); + lua_rawset(L, LCURL_USERVALUES); + + return 0; +} + +static int lcurl_multi_add_handle(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + lcurl_easy_t *e = lcurl_geteasy_at(L, 2); + CURLMcode code; + lua_State *curL; + + if(e->multi){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, +#if LCURL_CURL_VER_GE(7,32,1) + CURLM_ADDED_ALREADY +#else + CURLM_BAD_EASY_HANDLE +#endif + ); + } + + // From doc: + // If you have CURLMOPT_TIMERFUNCTION set in the multi handle, + // that callback will be called from within this function to ask + // for an updated timer so that your main event loop will get + // the activity on this handle to get started. + // + // So we should add easy before this call + // call chain may be like => timerfunction->socket_action->socketfunction + lua_settop(L, 2); + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushvalue(L, 2); + lua_rawsetp(L, -2, e->curl); + lua_settop(L, 1); + + // all `esay` handles have to have same L + lcurl__easy_assign_lua(L, e, p->L, 0); + + e->multi = p; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_add_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + // remove + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + lua_rawsetp(L, -2, e->curl); + e->multi = NULL; + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + return 1; +} + +static int lcurl_multi_remove_handle(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + lcurl_easy_t *e = lcurl_geteasy_at(L, 2); + CURLMcode code = lcurl__multi_remove_handle(L, p, e); + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + + lua_settop(L, 1); + return 1; +} + +CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e){ + CURLMcode code; + lua_State *curL; + + if(e->multi != p){ + // cURL returns CURLM_OK for such call so we do the same. + // tested on 7.37.1 + return CURLM_OK; + } + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_remove_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code == CURLM_OK){ + e->multi = NULL; + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + lua_rawsetp(L, -2, e->curl); + lua_pop(L, 1); + } + + return code; +} + +static int lcurl_multi_perform(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int running_handles = 0; + CURLMcode code; + lua_State *curL; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + while((code = curl_multi_perform(p->curl, &running_handles)) == CURLM_CALL_MULTI_PERFORM); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushnumber(L, running_handles); + return 1; +} + +static int lcurl_multi_info_read(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int msgs_in_queue = 0; + CURLMsg *msg = curl_multi_info_read(p->curl, &msgs_in_queue); + int remove = lua_toboolean(L, 2); + + lcurl_easy_t *e; + if(!msg){ + lua_pushnumber(L, msgs_in_queue); + return 1; + } + + if(msg->msg == CURLMSG_DONE){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_rawgetp(L, -1, msg->easy_handle); + e = lcurl_geteasy_at(L, -1); + if(remove){ + //! @fixme We ignore any errors + CURLMcode code; + lua_State *curL; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_remove_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(CURLM_OK == code){ + e->multi = NULL; + lua_pushnil(L); + lua_rawsetp(L, -3, e->curl); + } + } + if(msg->data.result == CURLE_OK){ + lua_pushboolean(L, 1); + return 2; + } + return 1 + lcurl_fail_ex(L, LCURL_ERROR_RETURN, LCURL_ERROR_EASY, msg->data.result); + } + + // @todo handle unknown message + lua_pushboolean(L, 0); + return 1; +} + +static int lcurl_multi_wait(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + CURLMcode code; + int maxfd; long ms; + + if(lua_isnoneornil(L, 2)){ + code = curl_multi_timeout(p->curl, &ms); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + } + else{ + ms = luaL_checklong(L, 2); + } + + if(ms < 0){ + /* if libcurl returns a -1 timeout here, it just means that libcurl + currently has no stored timeout value. You must not wait too long + (more than a few seconds perhaps) before you call + curl_multi_perform() again. + */ + ms = 1000; + } + +#if LCURL_CURL_VER_GE(7,28,0) + //! @todo supports extra_fds + code = curl_multi_wait(p->curl, 0, 0, ms, &maxfd); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushnumber(L, maxfd); + return 1; +#else + { + fd_set fdread, fdwrite, fdexcep; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + code = curl_multi_fdset(p->curl, &fdread, &fdwrite, &fdexcep, &maxfd); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + + //if(maxfd > 0) + { + struct timeval tv; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + + maxfd = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv); + if(maxfd < 0){ + //! @fixme return error + } + } + + lua_pushnumber(L, maxfd); + return 1; + } +#endif +} + +static int lcurl_multi_timeout(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + long n; + CURLMcode code = curl_multi_timeout(p->curl, &n); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushnumber(L, n); + return 1; +} + +static int lcurl_multi_socket_action(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + curl_socket_t s = lcurl_opt_os_socket(L, 2, CURL_SOCKET_TIMEOUT); + CURLMcode code; int n, mask; + lua_State *curL; + + if(s == CURL_SOCKET_TIMEOUT) mask = lutil_optint64(L, 3, 0); + else mask = lutil_checkint64(L, 3); + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_socket_action(p->curl, s, mask, &n); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_pushinteger(L, n); + return 1; +} + +//{ OPTIONS +static int lcurl_opt_set_long_(lua_State *L, int opt){ + lcurl_multi_t *p = lcurl_getmulti(L); + long val; CURLMcode code; + + if(lua_isboolean(L, 2)) val = lua_toboolean(L, 2); + else{ + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); + val = luaL_checklong(L, 2); + } + + code = curl_multi_setopt(p->curl, opt, val); + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + lua_settop(L, 1); + return 1; +} + +static int lcurl_opt_set_string_array_(lua_State *L, int opt){ + lcurl_multi_t *p = lcurl_getmulti(L); + CURLMcode code; + int n; + + if (lutil_is_null(L, 2)) { + n = 0; + } + else { + luaL_argcheck(L, lua_type(L, 2) == LUA_TTABLE, 2, "array expected"); + n = lua_rawlen(L, 2); + } + + if(n == 0){ + code = curl_multi_setopt(p->curl, opt, 0); + } + else{ + int i; + char const**val = malloc(sizeof(char*) * (n + 1)); + if(!val){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_OUT_OF_MEMORY); + } + for(i = 1; i <= n; ++i){ + lua_rawgeti(L, 2, i); + val[i-1] = lua_tostring(L, -1); + lua_pop(L, 1); + } + val[n] = NULL; + code = curl_multi_setopt(p->curl, opt, val); + free((void*)val); + } + + if(code != CURLM_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } + + lua_settop(L, 1); + return 1; +} + +#define LCURL_LNG_OPT(N, S) static int lcurl_multi_set_##N(lua_State *L){\ + return lcurl_opt_set_long_(L, CURLMOPT_##N);\ +} + +#define LCURL_STR_ARR_OPT(N, S) static int lcurl_multi_set_##N(lua_State *L){\ + return lcurl_opt_set_string_array_(L, CURLMOPT_##N);\ +} + +#define OPT_ENTRY(L, N, T, S) LCURL_##T##_OPT(N, S) + +#include "lcoptmulti.h" + +#undef OPT_ENTRY +#undef LCURL_LNG_OPT +#undef LCURL_STR_ARR_OPT + +//} + +//{ CallBack + +static int lcurl_multi_set_callback(lua_State *L, + lcurl_multi_t *p, lcurl_callback_t *c, + int OPT_CB, int OPT_UD, + const char *method, void *func +) +{ + lcurl_set_callback(L, c, 2, method); + + curl_multi_setopt(p->curl, OPT_CB, (c->cb_ref == LUA_NOREF)?0:func); + curl_multi_setopt(p->curl, OPT_UD, (c->cb_ref == LUA_NOREF)?0:p); + + return 1; +} + +//{ Timer + +int lcurl_multi_timer_callback(CURLM *multi, long ms, void *arg){ + lcurl_multi_t *p = arg; + lua_State *L = p->L; + int n, top, ret = 0; + + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->tm); + + lua_pushnumber(L, ms); + if(lua_pcall(L, n, LUA_MULTRET, 0)){ + assert(lua_gettop(L) >= top); + lua_settop(L, top); //! @todo + // lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); + // lua_insert(L, top+1); + return -1; + } + + if(lua_gettop(L) > top){ + if(lua_isnil(L, top + 1)){ + lua_settop(L, top); + return -1; + } + + if(lua_isboolean(L, top + 1)) + ret = lua_toboolean(L, top + 1)?0:-1; + else ret = lua_tointeger(L, top + 1); + } + + lua_settop(L, top); + return ret; +} + +static int lcurl_multi_set_TIMERFUNCTION(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + return lcurl_multi_set_callback(L, p, &p->tm, + CURLMOPT_TIMERFUNCTION, CURLMOPT_TIMERDATA, + "timer", lcurl_multi_timer_callback + ); +} + +//} + +//{ Socket + +static int lcurl_multi_socket_callback(CURL *easy, curl_socket_t s, int what, void *arg, void *socketp){ + lcurl_multi_t *p = arg; + lua_State *L = p->L; + lcurl_easy_t *e; + int n, top; + + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->sc); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_rawgetp(L, -1, easy); + e = lcurl_geteasy_at(L, -1); + lua_remove(L, -2); + lcurl_push_os_socket(L, s); + lua_pushinteger(L, what); + + if(lua_pcall(L, n+2, 0, 0)){ + assert(lua_gettop(L) >= top); + lua_settop(L, top); + return -1; //! @todo break perform + } + + lua_settop(L, top); + return 0; +} + +static int lcurl_multi_set_SOCKETFUNCTION(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + return lcurl_multi_set_callback(L, p, &p->sc, + CURLMOPT_SOCKETFUNCTION, CURLMOPT_SOCKETDATA, + "socket", lcurl_multi_socket_callback + ); +} + +//} + +//} + +static int lcurl_multi_setopt(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int opt; + + luaL_checkany(L, 2); + + if(lua_type(L, 2) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); + if(ret) return ret; + lua_settop(L, 1); + return 1; + } + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S) case CURLMOPT_##N: return lcurl_multi_set_##N(L); + switch(opt){ + #include "lcoptmulti.h" + OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) + OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); +} + +static int lcurl_multi_setdata(lua_State *L){ + lua_settop(L, 2); + lua_pushvalue(L, 1); + lua_insert(L, 2); + lua_rawset(L, LCURL_USERVALUES); + return 1; +} + +static int lcurl_multi_getdata(lua_State *L){ + lua_settop(L, 1); + lua_rawget(L, LCURL_USERVALUES); + return 1; +} + +//} + +static const struct luaL_Reg lcurl_multi_methods[] = { + {"add_handle", lcurl_multi_add_handle }, + {"remove_handle", lcurl_multi_remove_handle }, + {"perform", lcurl_multi_perform }, + {"info_read", lcurl_multi_info_read }, + {"setopt", lcurl_multi_setopt }, + {"wait", lcurl_multi_wait }, + {"timeout", lcurl_multi_timeout }, + {"socket_action", lcurl_multi_socket_action }, + { "__tostring", lcurl_multi_to_s }, + +#define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_multi_set_##N }, + #include "lcoptmulti.h" + OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) + OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) +#undef OPT_ENTRY + + { "setdata", lcurl_multi_setdata }, + { "getdata", lcurl_multi_getdata }, + + {"close", lcurl_multi_cleanup }, + {"__gc", lcurl_multi_cleanup }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_multi_opt[] = { +#define OPT_ENTRY(L, N, T, S) { "OPT_MULTI_"#N, CURLMOPT_##N }, + #include "lcoptmulti.h" + OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) + OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) +#undef OPT_ENTRY + + {NULL, 0} +}; + +void lcurl_multi_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_MULTI, lcurl_multi_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_multi_opt); +} diff --git a/watchdog/third_party/lua-curl/src/lcmulti.h b/watchdog/third_party/lua-curl/src/lcmulti.h new file mode 100644 index 0000000..89c8723 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcmulti.h @@ -0,0 +1,50 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCMULTI_H_ +#define _LCMULTI_H_ + +#include "lcurl.h" +#include "lcutils.h" + +typedef struct lcurl_multi_tag{ + CURLM *curl; + lua_State *L; + int err_mode; + int h_ref; + lcurl_callback_t tm; + lcurl_callback_t sc; +}lcurl_multi_t; + + +#if LCURL_CC_SUPPORT_FORWARD_TYPEDEF +typedef struct lcurl_multi_tag lcurl_multi_t; +#else +struct lcurl_easy_tag; +#define lcurl_easy_t struct lcurl_easy_tag +#endif + +int lcurl_multi_create(lua_State *L, int error_mode); + +lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i); + +#define lcurl_getmulti(L) lcurl_getmulti_at((L),1) + +void lcurl_multi_initlib(lua_State *L, int nup); + +void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy); + +CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e); + +#if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF +#undef lcurl_easy_t +#endif + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcopteasy.h b/watchdog/third_party/lua-curl/src/lcopteasy.h new file mode 100644 index 0000000..8155667 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcopteasy.h @@ -0,0 +1,557 @@ +/* Before version 7.17.0, strings were not copied. + Instead the user was forced keep them available + until libcurl no longer needed them. +*/ + +#ifndef LCURL_STORE_STRING +# if LCURL_CURL_VER_GE(7,17,0) +# define LCURL_STORE_STRING 0 +# else +# define LCURL_STORE_STRING 1 +# endif +#endif + +#ifndef OPT_ENTRY +# define OPT_ENTRY(a,b,c,d,e) +# define OPT_ENTRY_IS_NULL +#endif + +#ifndef FLG_ENTRY +# define FLG_ENTRY(a) +# define FLG_ENTRY_IS_NULL +#endif + +#ifndef LCURL_DEFAULT_VALUE +# define LCURL_DEFAULT_VALUE 0 +#endif + +//{ Reset system macros + +#ifdef TCP_FASTOPEN +# define LCURL__TCP_FASTOPEN TCP_FASTOPEN +# undef TCP_FASTOPEN +#endif + +#ifdef TCP_KEEPIDLE +# define LCURL__TCP_KEEPIDLE TCP_KEEPIDLE +# undef TCP_KEEPIDLE +#endif + +#ifdef TCP_KEEPINTVL +# define LCURL__TCP_KEEPINTVL TCP_KEEPINTVL +# undef TCP_KEEPINTVL +#endif + +#ifdef TCP_NODELAY +# define LCURL__TCP_NODELAY TCP_NODELAY +# undef TCP_NODELAY +#endif + +#ifdef TCP_KEEPALIVE +# define LCURL__TCP_KEEPALIVE TCP_KEEPALIVE +# undef TCP_KEEPALIVE +#endif + +#ifdef BUFFERSIZE +# define LCURL__BUFFERSIZE BUFFERSIZE +# undef BUFFERSIZE +#endif + +#ifdef INTERFACE +# define LCURL__INTERFACE INTERFACE +# undef INTERFACE +#endif + +//} + +OPT_ENTRY( verbose, VERBOSE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( header, HEADER, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( noprogress, NOPROGRESS, LNG, 0, 1 ) +OPT_ENTRY( nosignal, NOSIGNAL, LNG, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,21,0) +OPT_ENTRY( wildcardmatch, WILDCARDMATCH, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif + +OPT_ENTRY( url, URL, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( failonerror, FAILONERROR, LNG, 0, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( protocols, PROTOCOLS, LNG, 0, CURLPROTO_ALL ) +OPT_ENTRY( redir_protocols, REDIR_PROTOCOLS, LNG, 0, CURLPROTO_ALL ) /*! @fixme All protocols except for FILE and SCP */ +OPT_ENTRY( proxy, PROXY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxyport, PROXYPORT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxytype, PROXYTYPE, LNG, 0, CURLPROXY_HTTP ) +OPT_ENTRY( noproxy, NOPROXY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( httpproxytunnel, HTTPPROXYTUNNEL, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( socks5_gssapi_service, SOCKS5_GSSAPI_SERVICE, STR, LCURL_STORE_STRING, "rcmd/server-fqdn" ) +OPT_ENTRY( socks5_gssapi_nec, SOCKS5_GSSAPI_NEC, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ +OPT_ENTRY( interface, INTERFACE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( localport, LOCALPORT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( localportrange, LOCALPORTRANGE, LNG, 0, 1 ) +OPT_ENTRY( dns_cache_timeout, DNS_CACHE_TIMEOUT, LNG, 0, 60 ) + +#if !LCURL_CURL_VER_GE(7,65,0) +OPT_ENTRY( dns_use_global_cache, DNS_USE_GLOBAL_CACHE, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif + +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( dns_servers, DNS_SERVERS, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( buffersize, BUFFERSIZE, LNG, 0, CURL_MAX_WRITE_SIZE ) +OPT_ENTRY( port, PORT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tcp_nodelay, TCP_NODELAY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( address_scope, ADDRESS_SCOPE, LNG, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( tcp_keepalive, TCP_KEEPALIVE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tcp_keepidle, TCP_KEEPIDLE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ +OPT_ENTRY( tcp_keepintvl, TCP_KEEPINTVL, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ +#endif + +OPT_ENTRY( netrc, NETRC, LNG, 0, CURL_NETRC_IGNORED ) +OPT_ENTRY( netrc_file, NETRC_FILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( userpwd, USERPWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxyuserpwd, PROXYUSERPWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( username, USERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( password, PASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,31,0) +OPT_ENTRY( login_options, LOGIN_OPTIONS, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( proxyusername, PROXYUSERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxypassword, PROXYPASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( httpauth, HTTPAUTH, LNG, 0, CURLAUTH_BASIC ) +#if LCURL_CURL_VER_GE(7,21,4) +OPT_ENTRY( tlsauth_username, TLSAUTH_USERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tlsauth_password, TLSAUTH_PASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( tlsauth_type, TLSAUTH_TYPE, STR, 0, "" ) +#endif +OPT_ENTRY( proxyauth, PROXYAUTH, LNG, 0, CURLAUTH_BASIC ) +#if LCURL_CURL_VER_GE(7,31,0) +OPT_ENTRY( sasl_ir, SASL_IR, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,33,0) +OPT_ENTRY( xoauth2_bearer, XOAUTH2_BEARER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif + +OPT_ENTRY( autoreferer, AUTOREFERER, LNG, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,21,6) +OPT_ENTRY( accept_encoding, ACCEPT_ENCODING, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( transfer_encoding, TRANSFER_ENCODING, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( followlocation, FOLLOWLOCATION, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( unrestricted_auth, UNRESTRICTED_AUTH, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( maxredirs, MAXREDIRS, LNG, 0, -1 ) +OPT_ENTRY( postredir, POSTREDIR, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( put, PUT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( post, POST, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( referer, REFERER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( useragent, USERAGENT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,37,0) +OPT_ENTRY( headeropt, HEADEROPT, LNG, 0, CURLHEADER_UNIFIED ) +#endif +OPT_ENTRY( httpheader, HTTPHEADER, LST, 0, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,37,0) +OPT_ENTRY( proxyheader, PROXYHEADER, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( http200aliases, HTTP200ALIASES, LST, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookie, COOKIE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookiefile, COOKIEFILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookiejar, COOKIEJAR, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookiesession, COOKIESESSION, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( cookielist, COOKIELIST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( httpget, HTTPGET, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( http_version, HTTP_VERSION, LNG, 0, CURL_HTTP_VERSION_NONE ) +OPT_ENTRY( ignore_content_length, IGNORE_CONTENT_LENGTH, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( http_content_decoding, HTTP_CONTENT_DECODING, LNG, 0, 1 ) +OPT_ENTRY( http_transfer_decoding, HTTP_TRANSFER_DECODING, LNG, 0, 1 ) +#if LCURL_CURL_VER_GE(7,36,0) +OPT_ENTRY( expect_100_timeout_ms, EXPECT_100_TIMEOUT_MS, LNG, 0, 1000 ) +#endif + +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( mail_from, MAIL_FROM, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) /*! @check doc says `blank` */ +OPT_ENTRY( mail_rcpt, MAIL_RCPT, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( mail_auth, MAIL_AUTH, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif + +OPT_ENTRY( tftp_blksize, TFTP_BLKSIZE, LNG, 0, 512 ) + +OPT_ENTRY( ftpport, FTPPORT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( quote, QUOTE, LST, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( postquote, POSTQUOTE, LST, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( prequote, PREQUOTE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( dirlistonly, DIRLISTONLY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( append, APPEND, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftp_use_eprt, FTP_USE_EPRT, LNG, 0, LCURL_DEFAULT_VALUE )/*! @check doc says nothing */ +OPT_ENTRY( ftp_use_epsv, FTP_USE_EPSV, LNG, 0, 1 ) +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( ftp_use_pret, FTP_USE_PRET, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( ftp_create_missing_dirs, FTP_CREATE_MISSING_DIRS, LNG, 0, CURLFTP_CREATE_DIR_NONE ) +OPT_ENTRY( ftp_response_timeout, FTP_RESPONSE_TIMEOUT, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( ftp_alternative_to_user, FTP_ALTERNATIVE_TO_USER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftp_skip_pasv_ip, FTP_SKIP_PASV_IP, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftpsslauth, FTPSSLAUTH, LNG, 0, CURLFTPAUTH_DEFAULT ) +OPT_ENTRY( ftp_ssl_ccc, FTP_SSL_CCC, LNG, 0, CURLFTPSSL_CCC_NONE ) +OPT_ENTRY( ftp_account, FTP_ACCOUNT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( ftp_filemethod, FTP_FILEMETHOD, LNG, 0, CURLFTPMETHOD_MULTICWD ) + +OPT_ENTRY( transfertext, TRANSFERTEXT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( proxy_transfer_mode, PROXY_TRANSFER_MODE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( crlf, CRLF, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( range, RANGE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( resume_from, RESUME_FROM, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( resume_from_large, RESUME_FROM_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( customrequest, CUSTOMREQUEST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( filetime, FILETIME, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( nobody, NOBODY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( infilesize, INFILESIZE, LNG, 0, LCURL_DEFAULT_VALUE )/*! @fixme doc says `Unset` */ +OPT_ENTRY( infilesize_large, INFILESIZE_LARGE, LNG, 0, LCURL_DEFAULT_VALUE )/*! @fixme doc says `Unset` */ +OPT_ENTRY( upload, UPLOAD, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( maxfilesize, MAXFILESIZE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( maxfilesize_large, MAXFILESIZE_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( timecondition, TIMECONDITION, LNG, 0, CURL_TIMECOND_NONE ) +OPT_ENTRY( timevalue, TIMEVALUE, LNG, 0, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( timeout, TIMEOUT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( timeout_ms, TIMEOUT_MS, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( low_speed_limit, LOW_SPEED_LIMIT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( low_speed_time, LOW_SPEED_TIME, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( max_send_speed_large, MAX_SEND_SPEED_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( max_recv_speed_large, MAX_RECV_SPEED_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( maxconnects, MAXCONNECTS, LNG, 0, 5 ) +OPT_ENTRY( fresh_connect, FRESH_CONNECT, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( forbid_reuse, FORBID_REUSE, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( connecttimeout, CONNECTTIMEOUT, LNG, 0, 300 ) +OPT_ENTRY( connecttimeout_ms, CONNECTTIMEOUT_MS, LNG, 0, 300000 ) +OPT_ENTRY( ipresolve, IPRESOLVE, LNG, 0, CURL_IPRESOLVE_WHATEVER ) +OPT_ENTRY( connect_only, CONNECT_ONLY, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( use_ssl, USE_SSL, LNG, 0, CURLUSESSL_NONE ) +#if LCURL_CURL_VER_GE(7,21,3) +OPT_ENTRY( resolve, RESOLVE, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,33,0) +OPT_ENTRY( dns_interface, DNS_INTERFACE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( dns_local_ip4, DNS_LOCAL_IP4, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( dns_local_ip6, DNS_LOCAL_IP6, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( accepttimeout_ms, ACCEPTTIMEOUT_MS, LNG, 0, 60000 ) +#endif + +OPT_ENTRY( ssh_auth_types, SSH_AUTH_TYPES, LNG, 0, LCURL_DEFAULT_VALUE) /*! @fixme doc says `None` */ +OPT_ENTRY( ssh_host_public_key_md5, SSH_HOST_PUBLIC_KEY_MD5, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( ssh_public_keyfile, SSH_PUBLIC_KEYFILE, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( ssh_private_keyfile, SSH_PRIVATE_KEYFILE, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( ssh_knownhosts, SSH_KNOWNHOSTS, STR, 0, LCURL_DEFAULT_VALUE) + +OPT_ENTRY( new_file_perms, NEW_FILE_PERMS, LNG, 0, 0644) +OPT_ENTRY( new_directory_perms, NEW_DIRECTORY_PERMS, LNG, 0, 0755) + +OPT_ENTRY( telnetoptions, TELNETOPTIONS, LST, 0, LCURL_DEFAULT_VALUE) + +OPT_ENTRY( random_file, RANDOM_FILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( egdsocket, EGDSOCKET, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( issuercert, ISSUERCERT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( krblevel, KRBLEVEL, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( cainfo, CAINFO, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `Built-in system specific` */ +OPT_ENTRY( capath, CAPATH, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( certinfo, CERTINFO, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( crlfile, CRLFILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) + +OPT_ENTRY( sslcert, SSLCERT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( sslcerttype, SSLCERTTYPE, STR, LCURL_STORE_STRING, "PEM" ) +OPT_ENTRY( sslengine, SSLENGINE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( sslengine_default, SSLENGINE_DEFAULT, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ +OPT_ENTRY( sslkey, SSLKEY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( sslkeytype, SSLKEYTYPE, STR, LCURL_STORE_STRING, "PEM" ) +OPT_ENTRY( sslversion, SSLVERSION, LNG, 0, CURL_SSLVERSION_DEFAULT ) +OPT_ENTRY( ssl_cipher_list, SSL_CIPHER_LIST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#if LCURL_CURL_VER_GE(7,36,0) +OPT_ENTRY( ssl_enable_alpn, SSL_ENABLE_ALPN, LNG, 0, 1 ) +OPT_ENTRY( ssl_enable_npn, SSL_ENABLE_NPN, LNG, 0, 1 ) +#endif +#if LCURL_CURL_VER_GE(7,25,0) +OPT_ENTRY( ssl_options, SSL_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +OPT_ENTRY( ssl_sessionid_cache, SSL_SESSIONID_CACHE, LNG, 0, 1 ) +OPT_ENTRY( ssl_verifyhost, SSL_VERIFYHOST, LNG, 0, 2 ) +OPT_ENTRY( ssl_verifypeer, SSL_VERIFYPEER, LNG, 0, 1 ) +OPT_ENTRY( keypasswd, KEYPASSWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) + +#if LCURL_CURL_VER_GE(7,20,0) +OPT_ENTRY( rtsp_client_cseq, RTSP_CLIENT_CSEQ, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_request, RTSP_REQUEST, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_server_cseq, RTSP_SERVER_CSEQ, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_session_id, RTSP_SESSION_ID, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_stream_uri, RTSP_STREAM_URI, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( rtsp_transport, RTSP_TRANSPORT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) +#endif + +#if LCURL_CURL_VER_GE(7,22,0) +OPT_ENTRY( gssapi_delegation, GSSAPI_DELEGATION, LNG, 0, CURLGSSAPI_DELEGATION_NONE ) +#endif + +FLG_ENTRY( SSLVERSION_DEFAULT ) +FLG_ENTRY( SSLVERSION_TLSv1 ) +FLG_ENTRY( SSLVERSION_SSLv2 ) +FLG_ENTRY( SSLVERSION_SSLv3 ) +#if LCURL_CURL_VER_GE(7,34,0) +FLG_ENTRY( SSLVERSION_TLSv1_0 ) +FLG_ENTRY( SSLVERSION_TLSv1_1 ) +FLG_ENTRY( SSLVERSION_TLSv1_2 ) +#endif +#if LCURL_CURL_VER_GE(7,52,0) +FLG_ENTRY( SSLVERSION_TLSv1_3 ) +#endif + +#if LCURL_CURL_VER_GE(7,54,0) +FLG_ENTRY( SSLVERSION_MAX_NONE ) +FLG_ENTRY( SSLVERSION_MAX_DEFAULT ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_0 ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_1 ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_2 ) +FLG_ENTRY( SSLVERSION_MAX_TLSv1_3 ) +#endif + +#if LCURL_CURL_VER_GE(7,21,4) +FLG_ENTRY( TLSAUTH_SRP ) +#endif + +FLG_ENTRY( HTTP_VERSION_NONE ) +FLG_ENTRY( HTTP_VERSION_1_0 ) +FLG_ENTRY( HTTP_VERSION_1_1 ) +#if LCURL_CURL_VER_GE(7,33,0) +FLG_ENTRY( HTTP_VERSION_2_0 ) +#endif +#if LCURL_CURL_VER_GE(7,43,0) +FLG_ENTRY( HTTP_VERSION_2 ) +#endif +#if LCURL_CURL_VER_GE(7,47,0) +FLG_ENTRY( HTTP_VERSION_2TLS ) +#endif +#if LCURL_CURL_VER_GE(7,49,0) +FLG_ENTRY( HTTP_VERSION_2_PRIOR_KNOWLEDGE ) +#endif +#if LCURL_CURL_VER_GE(7,66,0) +FLG_ENTRY( HTTP_VERSION_3 ) +#endif + +FLG_ENTRY( READFUNC_PAUSE ) /*7.18.0*/ +FLG_ENTRY( WRITEFUNC_PAUSE ) /*7.18.0*/ + +FLG_ENTRY( POLL_IN ) /*7.14.0*/ +FLG_ENTRY( POLL_INOUT ) /*7.14.0*/ +FLG_ENTRY( POLL_NONE ) /*7.14.0*/ +FLG_ENTRY( POLL_OUT ) /*7.14.0*/ +FLG_ENTRY( POLL_REMOVE ) /*7.14.0*/ +FLG_ENTRY( SOCKET_TIMEOUT ) /*7.14.0*/ + +FLG_ENTRY( CSELECT_ERR ) /*7.16.3*/ +FLG_ENTRY( CSELECT_IN ) /*7.16.3*/ +FLG_ENTRY( CSELECT_OUT ) /*7.16.3*/ + +FLG_ENTRY( IPRESOLVE_WHATEVER ) /*7.10.8*/ +FLG_ENTRY( IPRESOLVE_V4 ) /*7.10.8*/ +FLG_ENTRY( IPRESOLVE_V6 ) /*7.10.8*/ + +#if LCURL_CURL_VER_GE(7,20,0) +FLG_ENTRY( RTSPREQ_OPTIONS ) +FLG_ENTRY( RTSPREQ_DESCRIBE ) +FLG_ENTRY( RTSPREQ_ANNOUNCE ) +FLG_ENTRY( RTSPREQ_SETUP ) +FLG_ENTRY( RTSPREQ_PLAY ) +FLG_ENTRY( RTSPREQ_PAUSE ) +FLG_ENTRY( RTSPREQ_TEARDOWN ) +FLG_ENTRY( RTSPREQ_GET_PARAMETER ) +FLG_ENTRY( RTSPREQ_SET_PARAMETER ) +FLG_ENTRY( RTSPREQ_RECORD ) +FLG_ENTRY( RTSPREQ_RECEIVE ) +#endif + +#if LCURL_CURL_VER_GE(7,39,0) +OPT_ENTRY( pinnedpublickey, PINNEDPUBLICKEY, STR, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,40,0) +OPT_ENTRY( unix_socket_path, UNIX_SOCKET_PATH, STR, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,41,0) +OPT_ENTRY( ssl_verifystatus, SSL_VERIFYSTATUS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,42,0) +OPT_ENTRY( ssl_falsestart, SSL_FALSESTART, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( path_as_is, PATH_AS_IS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,43,0) +OPT_ENTRY( proxy_service_name, PROXY_SERVICE_NAME, STR, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( service_name, SERVICE_NAME, STR, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( pipewait, PIPEWAIT, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,45,0) +OPT_ENTRY( default_protocol, DEFAULT_PROTOCOL, STR, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,46,0) +OPT_ENTRY( stream_weight, STREAM_WEIGHT, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,48,0) +OPT_ENTRY( tftp_no_options, TFTP_NO_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,49,0) +OPT_ENTRY( tcp_fastopen, TCP_FASTOPEN, LNG, 0, LCURL_DEFAULT_VALUE ) +OPT_ENTRY( connect_to, CONNECT_TO, LST, 0, LCURL_DEFAULT_VALUE ) +#endif +#if LCURL_CURL_VER_GE(7,51,0) +OPT_ENTRY( keep_sending_on_error, KEEP_SENDING_ON_ERROR, LNG, 0, LCURL_DEFAULT_VALUE ) +#endif + +#if LCURL_CURL_VER_GE(7,52,0) +OPT_ENTRY( proxy_cainfo, PROXY_CAINFO, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_capath, PROXY_CAPATH, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_ssl_verifypeer, PROXY_SSL_VERIFYPEER, LNG, 0, 1) +OPT_ENTRY( proxy_ssl_verifyhost, PROXY_SSL_VERIFYHOST, LNG, 0, 2) +OPT_ENTRY( proxy_sslversion, PROXY_SSLVERSION, LNG, 0, CURL_SSLVERSION_DEFAULT) +OPT_ENTRY( proxy_tlsauth_username, PROXY_TLSAUTH_USERNAME, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_tlsauth_password, PROXY_TLSAUTH_PASSWORD, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_tlsauth_type, PROXY_TLSAUTH_TYPE, STR, 0, "") +OPT_ENTRY( proxy_sslcert, PROXY_SSLCERT, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_sslcerttype, PROXY_SSLCERTTYPE, STR, 0, "PEM") +OPT_ENTRY( proxy_sslkey, PROXY_SSLKEY, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_sslkeytype, PROXY_SSLKEYTYPE, STR, 0, "PEM") /* default value not defined. Use same as for `SSLKEYTYPE` */ +OPT_ENTRY( proxy_keypasswd, PROXY_KEYPASSWD, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_ssl_cipher_list, PROXY_SSL_CIPHER_LIST, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_crlfile, PROXY_CRLFILE, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_ssl_options, PROXY_SSL_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( pre_proxy, PRE_PROXY, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( proxy_pinnedpublickey, PROXY_PINNEDPUBLICKEY, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,53,0) +OPT_ENTRY( abstract_unix_socket, ABSTRACT_UNIX_SOCKET, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,54,0) +OPT_ENTRY( suppress_connect_headers, SUPPRESS_CONNECT_HEADERS, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,55,0) +OPT_ENTRY( request_target, REQUEST_TARGET, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY( socks5_auth, SOCKS5_AUTH, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,56,0) +OPT_ENTRY( ssh_compression, SSH_COMPRESSION, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,59,0) +OPT_ENTRY( happy_eyeballs_timeout_ms,HAPPY_EYEBALLS_TIMEOUT_MS,LNG, 0, CURL_HET_DEFAULT) +OPT_ENTRY( timevalue_large, TIMEVALUE_LARGE ,OFF, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,60,0) +OPT_ENTRY(dns_shuffle_addresses, DNS_SHUFFLE_ADDRESSES, LNG, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(haproxyprotocol, HAPROXYPROTOCOL, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,61,0) +OPT_ENTRY(disallow_username_in_url, DISALLOW_USERNAME_IN_URL, LNG, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(proxy_tls13_ciphers, PROXY_TLS13_CIPHERS, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(tls13_ciphers, TLS13_CIPHERS, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,62,0) +OPT_ENTRY(upkeep_interval_ms, UPKEEP_INTERVAL_MS, LNG, 0, CURL_UPKEEP_INTERVAL_DEFAULT) +OPT_ENTRY(doh_url, DOH_URL, STR, 0, LCURL_DEFAULT_VALUE) +// thre no named value for default value. It just defined as 64kB in documentation +OPT_ENTRY(upload_buffersize, UPLOAD_BUFFERSIZE, LNG, 0, 64 * 1024) +#endif + +#if LCURL_CURL_VER_GE(7,64,0) +OPT_ENTRY(http09_allowed, HTTP09_ALLOWED, LNG, 0, 0) +#endif + +#if LCURL_CURL_VER_GE(7,64,1) +OPT_ENTRY(altsvc, ALTSVC, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(altsvc_ctrl, ALTSVC_CTRL, LNG, 0, 0) +#endif + +#if LCURL_CURL_VER_GE(7,65,0) +OPT_ENTRY(maxage_conn, MAXAGE_CONN, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,66,0) +OPT_ENTRY(sasl_authzid, SASL_AUTHZID, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,68,0) +FLG_ENTRY( PROGRESSFUNC_CONTINUE ) +#endif + +#if LCURL_CURL_VER_GE(7,69,0) +OPT_ENTRY(mail_rcpt_alllowfails, MAIL_RCPT_ALLLOWFAILS, LNG, 0, 1) +#endif + +#if LCURL_CURL_VER_GE(7,71,0) +OPT_ENTRY(sslcert_blob, SSLCERT_BLOB, BLB, 0, 0) +OPT_ENTRY(sslkey_blob, SSLKEY_BLOB, BLB, 0, 0) +OPT_ENTRY(proxy_sslcert_blob, PROXY_SSLCERT_BLOB, BLB, 0, 0) +OPT_ENTRY(proxy_sslkey_blob, PROXY_SSLKEY_BLOB, BLB, 0, 0) +OPT_ENTRY(issuercert_blob, ISSUERCERT_BLOB, BLB, 0, 0) + +OPT_ENTRY(proxy_issuercert, PROXY_ISSUERCERT, STR, 0, LCURL_DEFAULT_VALUE) +OPT_ENTRY(proxy_issuercert_blob, PROXY_ISSUERCERT_BLOB, BLB, 0, 0) +#endif + +#if LCURL_CURL_VER_GE(7,73,0) +OPT_ENTRY(ssl_ec_curves, SSL_EC_CURVES, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS +OPT_ENTRY(hsts_ctrl, HSTS_CTRL, LNG, 0, 0) +OPT_ENTRY(hsts, HSTS, STR, 0, LCURL_DEFAULT_VALUE) +#endif + +//{ Restore system macros + +#ifdef LCURL__TCP_FASTOPEN +# define TCP_FASTOPEN LCURL__TCP_FASTOPEN +# undef LCURL__TCP_FASTOPEN +#endif + +#ifdef LCURL__TCP_KEEPIDLE +# define TCP_KEEPIDLE LCURL__TCP_KEEPIDLE +# undef LCURL__TCP_KEEPIDLE +#endif + +#ifdef LCURL__TCP_KEEPINTVL +# define TCP_KEEPINTVL LCURL__TCP_KEEPINTVL +# undef LCURL__TCP_KEEPINTVL +#endif + +#ifdef LCURL__TCP_NODELAY +# define TCP_NODELAY LCURL__TCP_NODELAY +# undef LCURL__TCP_NODELAY +#endif + +#ifdef LCURL__TCP_KEEPALIVE +# define TCP_KEEPALIVE LCURL__TCP_KEEPALIVE +# undef LCURL__TCP_KEEPALIVE +#endif + +#ifdef LCURL__BUFFERSIZE +# define BUFFERSIZE LCURL__BUFFERSIZE +# undef LCURL__BUFFERSIZE +#endif + +#ifdef LCURL__INTERFACE +# define INTERFACE LCURL__INTERFACE +# undef LCURL__INTERFACE +#endif + +//} + +#ifdef OPT_ENTRY_IS_NULL +# undef OPT_ENTRY +#endif + +#ifdef FLG_ENTRY_IS_NULL +# undef FLG_ENTRY +#endif diff --git a/watchdog/third_party/lua-curl/src/lcoptmulti.h b/watchdog/third_party/lua-curl/src/lcoptmulti.h new file mode 100644 index 0000000..8f7fd71 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcoptmulti.h @@ -0,0 +1,17 @@ + +OPT_ENTRY(pipelining, PIPELINING, LNG, 0 ) +OPT_ENTRY(maxconnects, MAXCONNECTS, LNG, 0 ) + +#if LCURL_CURL_VER_GE(7,30,0) +OPT_ENTRY(max_host_connections, MAX_HOST_CONNECTIONS, LNG, 0 ) +OPT_ENTRY(max_pipeline_length, MAX_PIPELINE_LENGTH, LNG, 0 ) +OPT_ENTRY(content_length_penalty_size, CONTENT_LENGTH_PENALTY_SIZE, LNG, 0 ) +OPT_ENTRY(chunk_length_penalty_size, CHUNK_LENGTH_PENALTY_SIZE, LNG, 0 ) +OPT_ENTRY(pipelining_site_bl, PIPELINING_SITE_BL, STR_ARR, 0 ) +OPT_ENTRY(pipelining_server_bl, PIPELINING_SERVER_BL, STR_ARR, 0 ) +OPT_ENTRY(max_total_connections, MAX_TOTAL_CONNECTIONS, LNG, 0 ) +#endif + +#if LCURL_CURL_VER_GE(7,67,0) +OPT_ENTRY(max_concurrent_streams, MAX_CONCURRENT_STREAMS, LNG, 0 ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcoptshare.h b/watchdog/third_party/lua-curl/src/lcoptshare.h new file mode 100644 index 0000000..95960bb --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcoptshare.h @@ -0,0 +1,27 @@ +#ifndef OPT_ENTRY +# define OPT_ENTRY(a,b,c,d) +# define OPT_ENTRY_IS_NULL +#endif + +#ifndef FLG_ENTRY +# define FLG_ENTRY(a) +# define FLG_ENTRY_IS_NULL +#endif + +OPT_ENTRY(share, SHARE, LNG, 0 ) +OPT_ENTRY(unshare, UNSHARE, LNG, 0 ) + +FLG_ENTRY( LOCK_DATA_COOKIE ) +FLG_ENTRY( LOCK_DATA_DNS ) +FLG_ENTRY( LOCK_DATA_SSL_SESSION ) +FLG_ENTRY( LOCK_DATA_CONNECT ) + +#ifdef OPT_ENTRY_IS_NULL +# undef OPT_ENTRY +# undef OPT_ENTRY_IS_NULL +#endif + +#ifdef FLG_ENTRY_IS_NULL +# undef FLG_ENTRY +# undef FLG_ENTRY_IS_NULL +#endif diff --git a/watchdog/third_party/lua-curl/src/lcopturl.h b/watchdog/third_party/lua-curl/src/lcopturl.h new file mode 100644 index 0000000..20fee9e --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcopturl.h @@ -0,0 +1,29 @@ +ENTRY_PART(fragment, UPART_FRAGMENT , CURLUE_NO_FRAGMENT ) +ENTRY_PART(host, UPART_HOST , CURLUE_NO_HOST ) +ENTRY_PART(options, UPART_OPTIONS , CURLUE_NO_OPTIONS ) +ENTRY_PART(password, UPART_PASSWORD , CURLUE_NO_PASSWORD ) +ENTRY_PART(path, UPART_PATH , CURLUE_OK ) +ENTRY_PART(port, UPART_PORT , CURLUE_NO_PORT ) +ENTRY_PART(query, UPART_QUERY , CURLUE_NO_QUERY ) +ENTRY_PART(scheme, UPART_SCHEME , CURLUE_NO_SCHEME ) +ENTRY_PART(url, UPART_URL , CURLUE_OK ) +ENTRY_PART(user, UPART_USER , CURLUE_NO_USER ) + +#if LCURL_CURL_VER_GE(7,65,0) +ENTRY_PART(zoneid, UPART_ZONEID , CURLUE_UNKNOWN_PART ) +#endif + +ENTRY_FLAG(DEFAULT_PORT ) +ENTRY_FLAG(NO_DEFAULT_PORT ) +ENTRY_FLAG(DEFAULT_SCHEME ) +ENTRY_FLAG(NON_SUPPORT_SCHEME ) +ENTRY_FLAG(PATH_AS_IS ) +ENTRY_FLAG(DISALLOW_USER ) +ENTRY_FLAG(URLDECODE ) +ENTRY_FLAG(URLENCODE ) +ENTRY_FLAG(APPENDQUERY ) +ENTRY_FLAG(GUESS_SCHEME ) + +#if LCURL_CURL_VER_GE(7,67,0) +ENTRY_FLAG(NO_AUTHORITY ) +#endif diff --git a/watchdog/third_party/lua-curl/src/lcshare.c b/watchdog/third_party/lua-curl/src/lcshare.c new file mode 100644 index 0000000..c7ac489 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcshare.c @@ -0,0 +1,152 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcshare.h" +#include "lcerror.h" +#include "lcutils.h" +#include "lchttppost.h" + +#define LCURL_SHARE_NAME LCURL_PREFIX" Share" +static const char *LCURL_SHARE = LCURL_SHARE_NAME; + +//{ +int lcurl_share_create(lua_State *L, int error_mode){ + lcurl_share_t *p; + + lua_settop(L, 1); + + p = lutil_newudatap(L, lcurl_share_t, LCURL_SHARE); + p->curl = curl_share_init(); + p->err_mode = error_mode; + if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_NOMEM); + + if(lua_type(L, 1) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); + if(ret) return ret; + assert(lua_gettop(L) == 2); + } + + return 1; +} + +lcurl_share_t *lcurl_getshare_at(lua_State *L, int i){ + lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, i, LCURL_SHARE); + luaL_argcheck (L, p != NULL, 1, LCURL_SHARE_NAME" object expected"); + return p; +} + +static int lcurl_easy_to_s(lua_State *L){ + lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, 1, LCURL_SHARE); + lua_pushfstring(L, LCURL_SHARE_NAME" (%p)", (void*)p); + return 1; +} + +static int lcurl_share_cleanup(lua_State *L){ + lcurl_share_t *p = lcurl_getshare(L); + if(p->curl){ + curl_share_cleanup(p->curl); + p->curl = NULL; + } + + return 0; +} + +//{ OPTIONS + +static int lcurl_opt_set_long_(lua_State *L, int opt){ + lcurl_share_t *p = lcurl_getshare(L); + long val; CURLSHcode code; + + if(lua_isboolean(L, 2)) val = lua_toboolean(L, 2); + else{ + luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); + val = luaL_checklong(L, 2); + } + + code = curl_share_setopt(p->curl, opt, val); + if(code != CURLSHE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, code); + } + lua_settop(L, 1); + return 1; +} + +#define LCURL_LNG_OPT(N, S) static int lcurl_share_set_##N(lua_State *L){\ + return lcurl_opt_set_long_(L, CURLSHOPT_##N);\ +} + +#define OPT_ENTRY(L, N, T, S) LCURL_##T##_OPT(N, S) + +#include "lcoptshare.h" + +#undef OPT_ENTRY +#undef LCURL_LNG_OPT + +//} + +static int lcurl_share_setopt(lua_State *L){ + lcurl_share_t *p = lcurl_getshare(L); + int opt; + + luaL_checkany(L, 2); + if(lua_type(L, 2) == LUA_TTABLE){ + int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); + if(ret) return ret; + lua_settop(L, 1); + return 1; + } + + opt = luaL_checklong(L, 2); + lua_remove(L, 2); + +#define OPT_ENTRY(l, N, T, S) case CURLSHOPT_##N: return lcurl_share_set_##N(L); + switch(opt){ + #include "lcoptshare.h" + } +#undef OPT_ENTRY + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); +} + +//} + +static const struct luaL_Reg lcurl_share_methods[] = { + { "__tostring", lcurl_easy_to_s }, + {"setopt", lcurl_share_setopt }, + +#define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_share_set_##N }, + #include "lcoptshare.h" +#undef OPT_ENTRY + + {"close", lcurl_share_cleanup }, + {"__gc", lcurl_share_cleanup }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_share_opt[] = { + +#define OPT_ENTRY(L, N, T, S) { "OPT_SHARE_"#N, CURLSHOPT_##N }, +#define FLG_ENTRY(N) { #N, CURL_##N }, +# include "lcoptshare.h" +#undef OPT_ENTRY +#undef FLG_ENTRY + + {NULL, 0} +}; + +void lcurl_share_initlib(lua_State *L, int nup){ + if(!lutil_createmetap(L, LCURL_SHARE, lcurl_share_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_share_opt); +} diff --git a/watchdog/third_party/lua-curl/src/lcshare.h b/watchdog/third_party/lua-curl/src/lcshare.h new file mode 100644 index 0000000..a1018c9 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcshare.h @@ -0,0 +1,30 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCSHARE_H_ +#define _LCSHARE_H_ + +#include "lcurl.h" +#include "lcutils.h" + +typedef struct lcurl_share_tag{ + CURLM *curl; + int err_mode; +}lcurl_share_t; + +int lcurl_share_create(lua_State *L, int error_mode); + +lcurl_share_t *lcurl_getshare_at(lua_State *L, int i); + +#define lcurl_getshare(L) lcurl_getshare_at((L),1) + +void lcurl_share_initlib(lua_State *L, int nup); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcurl.c b/watchdog/third_party/lua-curl/src/lcurl.c new file mode 100644 index 0000000..e70680d --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurl.c @@ -0,0 +1,487 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lceasy.h" +#include "lcmulti.h" +#include "lcshare.h" +#include "lcerror.h" +#include "lchttppost.h" +#include "lcmime.h" +#include "lcurlapi.h" +#include "lcutils.h" + +/*export*/ +#ifdef _WIN32 +# define LCURL_EXPORT_API __declspec(dllexport) +#else +# define LCURL_EXPORT_API LUALIB_API +#endif + +static const char* LCURL_REGISTRY = "LCURL Registry"; +static const char* LCURL_USERVAL = "LCURL Uservalues"; +#if LCURL_CURL_VER_GE(7,56,0) +static const char* LCURL_MIME_EASY_MAP = "LCURL Mime easy"; +#endif + +#if LCURL_CURL_VER_GE(7,56,0) +#define NUP 3 +#else +#define NUP 2 +#endif + +static volatile int LCURL_INIT = 0; + +static int lcurl_init_in_mode(lua_State *L, long init_mode, int error_mode){ + if(!LCURL_INIT){ + /* Note from libcurl documentation. + * + * The environment it sets up is constant for the life of the program + * and is the same for every program, so multiple calls have the same + * effect as one call. ... This function is not thread safe. + */ + CURLcode code = curl_global_init(init_mode); + if (code != CURLE_OK) { + return lcurl_fail_ex(L, error_mode, LCURL_ERROR_CURL, code); + } + LCURL_INIT = 1; + } + return 0; +} + +static int lcurl_init(lua_State *L, int error_mode){ + long init_mode = CURL_GLOBAL_DEFAULT; + if (L != NULL) { + int type = lua_type(L, 1); + if (type == LUA_TNUMBER) { + init_mode = lua_tonumber(L, 1); + } + } + return lcurl_init_in_mode(L, init_mode, error_mode); +} + +static int lcurl_init_default(lua_State *L){ + return lcurl_init_in_mode(L, CURL_GLOBAL_DEFAULT, LCURL_ERROR_RAISE); +} + +static int lcurl_init_unsafe(lua_State *L){ + return lcurl_init(L, LCURL_ERROR_RAISE); +} + +static int lcurl_init_safe(lua_State *L){ + return lcurl_init(L, LCURL_ERROR_RETURN); +} + +static int lcurl_easy_new_safe(lua_State *L){ + return lcurl_easy_create(L, LCURL_ERROR_RETURN); +} + +static int lcurl_multi_new_safe(lua_State *L){ + return lcurl_multi_create(L, LCURL_ERROR_RETURN); +} + +static int lcurl_share_new_safe(lua_State *L){ + return lcurl_share_create(L, LCURL_ERROR_RETURN); +} + +static int lcurl_hpost_new_safe(lua_State *L) { + return lcurl_hpost_create(L, LCURL_ERROR_RETURN); +} + +#if LCURL_CURL_VER_GE(7,62,0) + +static int lcurl_url_new_safe(lua_State *L) { + return lcurl_url_create(L, LCURL_ERROR_RETURN); +} + +#endif + +static int lcurl_easy_new(lua_State *L){ + return lcurl_easy_create(L, LCURL_ERROR_RAISE); +} + +static int lcurl_multi_new(lua_State *L){ + return lcurl_multi_create(L, LCURL_ERROR_RAISE); +} + +static int lcurl_share_new(lua_State *L){ + return lcurl_share_create(L, LCURL_ERROR_RAISE); +} + +static int lcurl_hpost_new(lua_State *L){ + return lcurl_hpost_create(L, LCURL_ERROR_RAISE); +} + +#if LCURL_CURL_VER_GE(7,62,0) + +static int lcurl_url_new(lua_State *L) { + return lcurl_url_create(L, LCURL_ERROR_RAISE); +} + +#endif + +#if LCURL_CURL_VER_GE(7,73,0) + +static void lcurl_easy_option_push(lua_State *L, const struct curl_easyoption *opt) { + lua_newtable(L); + lua_pushliteral(L, "id"); lutil_pushuint(L, opt->id); lua_rawset(L, -3); + lua_pushliteral(L, "name"); lua_pushstring(L, opt->name); lua_rawset(L, -3); + lua_pushliteral(L, "type"); lutil_pushuint(L, opt->type); lua_rawset(L, -3); + lua_pushliteral(L, "flags"); lutil_pushuint(L, opt->flags); lua_rawset(L, -3); + lua_pushliteral(L, "flags_set"); lua_newtable(L); + lua_pushliteral(L, "alias"); lua_pushboolean(L, opt->flags & CURLOT_FLAG_ALIAS); lua_rawset(L, -3); + lua_rawset(L, -3); + lua_pushliteral(L, "type_name"); + switch(opt->type){ + case CURLOT_LONG : lua_pushliteral(L, "LONG" ); break; + case CURLOT_VALUES : lua_pushliteral(L, "VALUES" ); break; + case CURLOT_OFF_T : lua_pushliteral(L, "OFF_T" ); break; + case CURLOT_OBJECT : lua_pushliteral(L, "OBJECT" ); break; + case CURLOT_STRING : lua_pushliteral(L, "STRING" ); break; + case CURLOT_SLIST : lua_pushliteral(L, "SLIST" ); break; + case CURLOT_CBPTR : lua_pushliteral(L, "CBPTR" ); break; + case CURLOT_BLOB : lua_pushliteral(L, "BLOB" ); break; + case CURLOT_FUNCTION: lua_pushliteral(L, "FUNCTION"); break; + default: lua_pushliteral(L, "UNKNOWN"); + } + lua_rawset(L, -3); +} + +static int lcurl_easy_option_next(lua_State *L) { + const struct curl_easyoption *opt; + + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); + + lua_rawgeti(L, 1, 1); + opt = lua_touserdata(L, -1); + lua_settop(L, 1); + + opt = curl_easy_option_next(opt); + if (!opt) { + return 0; + } + + lcurl_easy_option_push(L, opt); + + lua_pushlightuserdata(L, (void*)opt); + lua_rawseti(L, 1, 1); + + return 1; +} + +static int lcurl_easy_option_by_id(lua_State *L) { + const struct curl_easyoption *opt = NULL; + lua_Integer id = luaL_checkinteger(L, 1); + + lua_settop(L, 0); + opt = curl_easy_option_by_id(id); + if (!opt) { + return 0; + } + + lcurl_easy_option_push(L, opt); + + return 1; +} + +static int lcurl_easy_option_by_name(lua_State *L) { + const struct curl_easyoption *opt = NULL; + const char *name = luaL_checkstring(L, 1); + + lua_settop(L, 0); + opt = curl_easy_option_by_name(name); + if (!opt) { + return 0; + } + + lcurl_easy_option_push(L, opt); + + return 1; +} + +static int lcurl_easy_option_iter(lua_State *L) { + lua_pushcfunction(L, lcurl_easy_option_next); + lua_newtable(L); + return 2; +} + +#endif + +static int lcurl_version(lua_State *L){ + lua_pushstring(L, curl_version()); + return 1; +} + +static int lcurl_debug_getregistry(lua_State *L) { + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); + return 1; +} + +static int push_upper(lua_State *L, const char *str){ + char buffer[128]; + size_t i, n = strlen(str); + char *ptr = (n < sizeof(buffer))?&buffer[0]:malloc(n + 1); + if (!ptr) return 1; + for(i = 0; i < n; ++i){ + if( (str[i] > 96 ) && (str[i] < 123) ) ptr[i] = str[i] - 'a' + 'A'; + else ptr[i] = str[i]; + } + lua_pushlstring(L, ptr, n); + if(ptr != &buffer[0]) free(ptr); + return 0; +} + +static int lcurl_version_info(lua_State *L){ + const char * const*p; + curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); + + lua_newtable(L); + lua_pushstring(L, data->version); lua_setfield(L, -2, "version"); /* LIBCURL_VERSION */ + lutil_pushuint(L, data->version_num); lua_setfield(L, -2, "version_num"); /* LIBCURL_VERSION_NUM */ + lua_pushstring(L, data->host); lua_setfield(L, -2, "host"); /* OS/host/cpu/machine when configured */ + + lua_newtable(L); + lua_pushliteral(L, "IPV6"); lua_pushboolean(L, data->features & CURL_VERSION_IPV6 ); lua_rawset(L, -3); + lua_pushliteral(L, "KERBEROS4"); lua_pushboolean(L, data->features & CURL_VERSION_KERBEROS4 ); lua_rawset(L, -3); + lua_pushliteral(L, "SSL"); lua_pushboolean(L, data->features & CURL_VERSION_SSL ); lua_rawset(L, -3); + lua_pushliteral(L, "LIBZ"); lua_pushboolean(L, data->features & CURL_VERSION_LIBZ ); lua_rawset(L, -3); + lua_pushliteral(L, "NTLM"); lua_pushboolean(L, data->features & CURL_VERSION_NTLM ); lua_rawset(L, -3); + lua_pushliteral(L, "GSSNEGOTIATE"); lua_pushboolean(L, data->features & CURL_VERSION_GSSNEGOTIATE); lua_rawset(L, -3); +#if LCURL_CURL_VER_GE(7,38,0) + lua_pushliteral(L, "GSSAPI"); lua_pushboolean(L, data->features & CURL_VERSION_GSSAPI ); lua_rawset(L, -3); +#endif + lua_pushliteral(L, "DEBUG"); lua_pushboolean(L, data->features & CURL_VERSION_DEBUG ); lua_rawset(L, -3); + lua_pushliteral(L, "ASYNCHDNS"); lua_pushboolean(L, data->features & CURL_VERSION_ASYNCHDNS ); lua_rawset(L, -3); + lua_pushliteral(L, "SPNEGO"); lua_pushboolean(L, data->features & CURL_VERSION_SPNEGO ); lua_rawset(L, -3); + lua_pushliteral(L, "LARGEFILE"); lua_pushboolean(L, data->features & CURL_VERSION_LARGEFILE ); lua_rawset(L, -3); + lua_pushliteral(L, "IDN"); lua_pushboolean(L, data->features & CURL_VERSION_IDN ); lua_rawset(L, -3); + lua_pushliteral(L, "SSPI"); lua_pushboolean(L, data->features & CURL_VERSION_SSPI ); lua_rawset(L, -3); + lua_pushliteral(L, "CONV"); lua_pushboolean(L, data->features & CURL_VERSION_CONV ); lua_rawset(L, -3); + lua_pushliteral(L, "CURLDEBUG"); lua_pushboolean(L, data->features & CURL_VERSION_CURLDEBUG ); lua_rawset(L, -3); +#if LCURL_CURL_VER_GE(7,21,4) + lua_pushliteral(L, "TLSAUTH_SRP"); lua_pushboolean(L, data->features & CURL_VERSION_TLSAUTH_SRP ); lua_rawset(L, -3); +#endif +#if LCURL_CURL_VER_GE(7,22,0) + lua_pushliteral(L, "NTLM_WB"); lua_pushboolean(L, data->features & CURL_VERSION_NTLM_WB ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HTTP2 + lua_pushliteral(L, "HTTP2"); lua_pushboolean(L, data->features & CURL_VERSION_HTTP2 ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HTTPS_PROXY + lua_pushliteral(L, "HTTPS_PROXY"); lua_pushboolean(L, data->features & CURL_VERSION_HTTPS_PROXY ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_MULTI_SSL + lua_pushliteral(L, "MULTI_SSL"); lua_pushboolean(L, data->features & CURL_VERSION_MULTI_SSL ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_BROTLI + lua_pushliteral(L, "BROTLI"); lua_pushboolean(L, data->features & CURL_VERSION_BROTLI ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_ALTSVC + lua_pushliteral(L, "ALTSVC"); lua_pushboolean(L, data->features & CURL_VERSION_ALTSVC ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HTTP3 + lua_pushliteral(L, "HTTP3"); lua_pushboolean(L, data->features & CURL_VERSION_HTTP3 ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_ZSTD + lua_pushliteral(L, "ZSTD"); lua_pushboolean(L, data->features & CURL_VERSION_ZSTD ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_UNICODE + lua_pushliteral(L, "UNICODE"); lua_pushboolean(L, data->features & CURL_VERSION_UNICODE ); lua_rawset(L, -3); +#endif +#ifdef CURL_VERSION_HSTS + lua_pushliteral(L, "HSTS"); lua_pushboolean(L, data->features & CURL_VERSION_HSTS ); lua_rawset(L, -3); +#endif + + lua_setfield(L, -2, "features"); /* bitmask, see defines below */ + + if(data->ssl_version){lua_pushstring(L, data->ssl_version); lua_setfield(L, -2, "ssl_version");} /* human readable string */ + lutil_pushuint(L, data->ssl_version_num); lua_setfield(L, -2, "ssl_version_num"); /* not used anymore, always 0 */ + if(data->libz_version){lua_pushstring(L, data->libz_version); lua_setfield(L, -2, "libz_version");} /* human readable string */ + + /* protocols is terminated by an entry with a NULL protoname */ + lua_newtable(L); + for(p = data->protocols; *p; ++p){ + push_upper(L, *p); lua_pushboolean(L, 1); lua_rawset(L, -3); + } + lua_setfield(L, -2, "protocols"); + + if(data->age >= CURLVERSION_SECOND){ + if(data->ares){lua_pushstring(L, data->ares); lua_setfield(L, -2, "ares");} + lutil_pushuint(L, data->ares_num); lua_setfield(L, -2, "ares_num"); + } + + if(data->age >= CURLVERSION_THIRD){ /* added in 7.12.0 */ + if(data->libidn){lua_pushstring(L, data->libidn); lua_setfield(L, -2, "libidn");} + } + +#if LCURL_CURL_VER_GE(7,16,1) + if(data->age >= CURLVERSION_FOURTH){ + lutil_pushuint(L, data->iconv_ver_num); lua_setfield(L, -2, "iconv_ver_num"); + if(data->libssh_version){lua_pushstring(L, data->libssh_version);lua_setfield(L, -2, "libssh_version");} + } +#endif + +#if LCURL_CURL_VER_GE(7,57,0) + if(data->age >= CURLVERSION_FOURTH){ + lutil_pushuint(L, data->brotli_ver_num); lua_setfield(L, -2, "brotli_ver_num"); + if(data->brotli_version){lua_pushstring(L, data->brotli_version);lua_setfield(L, -2, "brotli_version");} + } +#endif + +#if LCURL_CURL_VER_GE(7,66,0) + if(data->age >= CURLVERSION_SIXTH){ + lutil_pushuint(L, data->nghttp2_ver_num); lua_setfield(L, -2, "nghttp2_ver_num"); + if(data->nghttp2_version){lua_pushstring(L, data->nghttp2_version);lua_setfield(L, -2, "nghttp2_version");} + if(data->quic_version){lua_pushstring(L, data->quic_version);lua_setfield(L, -2, "quic_version");} + } +#endif + +#if LCURL_CURL_VER_GE(7,70,0) + if(data->age >= CURLVERSION_SEVENTH){ + if(data->cainfo){lua_pushstring(L, data->cainfo);lua_setfield(L, -2, "cainfo");} + if(data->capath){lua_pushstring(L, data->capath);lua_setfield(L, -2, "capath");} + } +#endif + +#if LCURL_CURL_VER_GE(7,72,0) + if(data->age >= CURLVERSION_EIGHTH){ + lutil_pushuint(L, data->zstd_ver_num); lua_setfield(L, -2, "zstd_ver_num"); + if(data->zstd_version){lua_pushstring(L, data->zstd_version);lua_setfield(L, -2, "zstd_version");} + } +#endif + + if(lua_isstring(L, 1)){ + lua_pushvalue(L, 1); lua_rawget(L, -2); + } + + return 1; +} + +static const struct luaL_Reg lcurl_functions[] = { + {"init", lcurl_init_unsafe }, + {"error", lcurl_error_new }, + {"form", lcurl_hpost_new }, + {"easy", lcurl_easy_new }, + {"multi", lcurl_multi_new }, + {"share", lcurl_share_new }, +#if LCURL_CURL_VER_GE(7,62,0) + {"url", lcurl_url_new }, +#endif + {"version", lcurl_version }, + {"version_info", lcurl_version_info }, +#if LCURL_CURL_VER_GE(7,73,0) + {"ieasy_options", lcurl_easy_option_iter }, + {"easy_option_by_id", lcurl_easy_option_by_id }, + {"easy_option_by_name", lcurl_easy_option_by_name }, +#endif + + {"__getregistry", lcurl_debug_getregistry}, + + {NULL,NULL} +}; + +static const struct luaL_Reg lcurl_functions_safe[] = { + {"init", lcurl_init_safe }, + {"error", lcurl_error_new }, + {"form", lcurl_hpost_new_safe }, + {"easy", lcurl_easy_new_safe }, + {"multi", lcurl_multi_new_safe }, + {"share", lcurl_share_new_safe }, +#if LCURL_CURL_VER_GE(7,62,0) + {"url", lcurl_url_new_safe }, +#endif + {"version", lcurl_version }, + {"version_info", lcurl_version_info }, +#if LCURL_CURL_VER_GE(7,73,0) + {"ieasy_options", lcurl_easy_option_iter }, + {"easy_option_by_id", lcurl_easy_option_by_id }, + {"easy_option_by_name", lcurl_easy_option_by_name }, +#endif + + { "__getregistry", lcurl_debug_getregistry }, + + {NULL,NULL} +}; + +static const lcurl_const_t lcurl_flags[] = { + +#define FLG_ENTRY(N) { #N, CURL##N }, +#include "lcflags.h" +#undef FLG_ENTRY + + {NULL, 0} +}; + +#if LCURL_CURL_VER_GE(7,56,0) +#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); +#else +#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); +#endif + +static int luaopen_lcurl_(lua_State *L, const struct luaL_Reg *func){ + if (getenv("LCURL_NO_INIT") == NULL) { // do not initialize curl if env variable LCURL_NO_INIT defined + lcurl_init_default(L); + } + + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); + if(!lua_istable(L, -1)){ /* registry */ + lua_pop(L, 1); + lua_newtable(L); + } + + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); + if(!lua_istable(L, -1)){ /* usevalues */ + lua_pop(L, 1); + lcurl_util_new_weak_table(L, "k"); + } + +#if LCURL_CURL_VER_GE(7,56,0) + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); + if(!lua_istable(L, -1)){ /* Mime->Easy */ + lua_pop(L, 1); + lcurl_util_new_weak_table(L, "v"); + } +#endif + + lua_newtable(L); /* library */ + + LCURL_PUSH_NUP(L); luaL_setfuncs(L, func, NUP); + LCURL_PUSH_NUP(L); lcurl_error_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_hpost_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_easy_initlib (L, NUP); + LCURL_PUSH_NUP(L); lcurl_mime_initlib (L, NUP); + LCURL_PUSH_NUP(L); lcurl_multi_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_share_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_url_initlib (L, NUP); + + LCURL_PUSH_NUP(L); + +#if LCURL_CURL_VER_GE(7,56,0) + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); +#endif + + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); + + lcurl_util_set_const(L, lcurl_flags); + + lutil_push_null(L); + lua_setfield(L, -2, "null"); + + return 1; +} + +LCURL_EXPORT_API +int luaopen_lcurl(lua_State *L){ return luaopen_lcurl_(L, lcurl_functions); } + +LCURL_EXPORT_API +int luaopen_lcurl_safe(lua_State *L){ return luaopen_lcurl_(L, lcurl_functions_safe); } + diff --git a/watchdog/third_party/lua-curl/src/lcurl.h b/watchdog/third_party/lua-curl/src/lcurl.h new file mode 100644 index 0000000..8f43320 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurl.h @@ -0,0 +1,31 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCURL_H_ +#define _LCURL_H_ + +#include "l52util.h" +#include "curl/curl.h" +#include "curl/easy.h" +#include "curl/multi.h" + +#include +#include + +#define LCURL_PREFIX "LcURL" + +#define LCURL_LUA_REGISTRY lua_upvalueindex(1) + +#define LCURL_USERVALUES lua_upvalueindex(2) + +/* only for `mime` API */ +#define LCURL_MIME_EASY lua_upvalueindex(3) + +#endif diff --git a/watchdog/third_party/lua-curl/src/lcurlapi.c b/watchdog/third_party/lua-curl/src/lcurlapi.c new file mode 100644 index 0000000..2a8d503 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurlapi.c @@ -0,0 +1,218 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurlapi.h" +#include "lcurl.h" +#include "lcerror.h" +#include "lcutils.h" +#include + +#define LCURL_URL_NAME LCURL_PREFIX" URL" +static const char *LCURL_URL = LCURL_URL_NAME; + +#if LCURL_CURL_VER_GE(7,62,0) + +#define lcurl_geturl(L) lcurl_geturl_at(L, 1) + +int lcurl_url_create(lua_State *L, int error_mode){ + lcurl_url_t *p; + + p = lutil_newudatap(L, lcurl_url_t, LCURL_URL); + + p->url = curl_url(); + if(!p->url) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, CURLUE_OUT_OF_MEMORY); + + p->err_mode = error_mode; + + if (lua_gettop(L) > 1) { + const char *url = luaL_checkstring(L, 1); + unsigned int flags = 0; + CURLUcode code; + + if (lua_gettop(L) > 2) { + flags = (unsigned int)lutil_optint64(L, 2, 0); + } + + code = curl_url_set(p->url, CURLUPART_URL, url, flags); + if (code != CURLUE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); + } + } + + return 1; +} + +lcurl_url_t *lcurl_geturl_at(lua_State *L, int i){ + lcurl_url_t *p = (lcurl_url_t *)lutil_checkudatap (L, i, LCURL_URL); + luaL_argcheck (L, p != NULL, 1, LCURL_URL_NAME" object expected"); + return p; +} + +static int lcurl_url_cleanup(lua_State *L){ + lcurl_url_t *p = lcurl_geturl(L); + + if (p->url){ + curl_url_cleanup(p->url); + p->url = NULL; + } + + return 0; +} + +static int lcurl_url_dup(lua_State *L) { + lcurl_url_t *r = lcurl_geturl(L); + lcurl_url_t *p = lutil_newudatap(L, lcurl_url_t, LCURL_URL); + + p->url = curl_url_dup(r->url); + if (!p->url) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, CURLUE_OUT_OF_MEMORY); + + p->err_mode = r->err_mode; + + return 1; +} + +static int lcurl_url_set(lua_State *L, CURLUPart what){ + lcurl_url_t *p = lcurl_geturl(L); + CURLUcode code; + const char *part; + unsigned int flags = 0; + + luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); + + part = lua_tostring(L, 2); + flags = (unsigned int)lutil_optint64(L, 3, 0); + + code = curl_url_set(p->url, what, part, flags); + if (code != CURLUE_OK) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); + } + + lua_settop(L, 1); + return 1; +} + +static int lcurl_url_get(lua_State *L, CURLUPart what, CURLUcode empty) { + lcurl_url_t *p = lcurl_geturl(L); + CURLUcode code; + char *part = NULL; + unsigned int flags = 0; + + flags = (unsigned int)lutil_optint64(L, 2, 0); + + code = curl_url_get(p->url, what, &part, flags); + if (code != CURLUE_OK) { + if (part) { + curl_free(part); + part = NULL; + } + + if (code != empty) { + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); + } + } + + if (part == NULL) { + lutil_push_null(L); + } + else { + lua_pushstring(L, part); + curl_free(part); + } + + return 1; +} + +static int lcurl_url_to_s(lua_State *L) { + lcurl_url_t *p = lcurl_geturl(L); + char *part = NULL; + + CURLUcode code = curl_url_get(p->url, CURLUPART_URL, &part, 0); + + if (code != CURLUE_OK) { + if (part) { + curl_free(part); + } + + return lcurl_fail_ex(L, LCURL_ERROR_RAISE, LCURL_ERROR_URL, code); + } + + if (part == NULL) { + lua_pushliteral(L, ""); + } + else { + lua_pushstring(L, part); + curl_free(part); + } + + return 1; +} + +#define ENTRY_PART(N, S, E) static int lcurl_url_set_##N(lua_State *L){\ + return lcurl_url_set(L, CURL##S);\ +} +#define ENTRY_FLAG(S) + +#include "lcopturl.h" + +#undef ENTRY_PART +#undef ENTRY_FLAG + +#define ENTRY_PART(N, S, E) static int lcurl_url_get_##N(lua_State *L){\ + return lcurl_url_get(L, CURL##S, E);\ +} +#define ENTRY_FLAG(S) + +#include "lcopturl.h" + +#undef ENTRY_PART +#undef ENTRY_FLAG + +static const struct luaL_Reg lcurl_url_methods[] = { + #define ENTRY_PART(N, S, E) { "set_"#N, lcurl_url_set_##N }, + #define ENTRY_FLAG(S) + #include "lcopturl.h" + #undef ENTRY_PART + #undef ENTRY_FLAG + + #define ENTRY_PART(N, S, E) { "get_"#N, lcurl_url_get_##N }, + #define ENTRY_FLAG(S) + #include "lcopturl.h" + #undef ENTRY_PART + #undef ENTRY_FLAG + + { "dup", lcurl_url_dup }, + { "cleanup", lcurl_url_cleanup }, + { "__gc", lcurl_url_cleanup }, + { "__tostring", lcurl_url_to_s }, + + { NULL,NULL } +}; + +static const lcurl_const_t lcurl_url_opt[] = { + #define ENTRY_PART(N, S, E) { #S, CURL##S }, + #define ENTRY_FLAG(S) { "U_"#S, CURLU_##S }, + #include "lcopturl.h" + #undef ENTRY_PART + #undef ENTRY_FLAG + {NULL, 0} +}; +#endif + +void lcurl_url_initlib(lua_State *L, int nup){ +#if LCURL_CURL_VER_GE(7,62,0) + if(!lutil_createmetap(L, LCURL_URL, lcurl_url_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + lcurl_util_set_const(L, lcurl_url_opt); +#else + lua_pop(L, nup); +#endif +} diff --git a/watchdog/third_party/lua-curl/src/lcurlapi.h b/watchdog/third_party/lua-curl/src/lcurlapi.h new file mode 100644 index 0000000..4dd4672 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcurlapi.h @@ -0,0 +1,34 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2018 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LURL_H_ +#define _LURL_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +void lcurl_url_initlib(lua_State *L, int nup); + +#if LCURL_CURL_VER_GE(7,62,0) + +typedef struct lcurl_url_tag { + CURLU *url; + + int err_mode; +}lcurl_url_t; + +int lcurl_url_create(lua_State *L, int error_mode); + +lcurl_url_t *lcurl_geturl_at(lua_State *L, int i); + +#endif + +#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcutils.c b/watchdog/third_party/lua-curl/src/lcutils.c new file mode 100644 index 0000000..6a406b4 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcutils.c @@ -0,0 +1,408 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcutils.h" +#include "lcerror.h" + +#define LCURL_STORAGE_SLIST 1 +#define LCURL_STORAGE_KV 2 + +int lcurl_storage_init(lua_State *L){ + lua_newtable(L); + return luaL_ref(L, LCURL_LUA_REGISTRY); +} + +void lcurl_storage_preserve_value(lua_State *L, int storage, int i){ + assert(i > 0 && i <= lua_gettop(L)); + luaL_checkany(L, i); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_pushvalue(L, i); lua_pushboolean(L, 1); lua_rawset(L, -3); + lua_pop(L, 1); +} + +void lcurl_storage_remove_value(lua_State *L, int storage, int i){ + assert(i > 0 && i <= lua_gettop(L)); + luaL_checkany(L, i); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_pushvalue(L, i); lua_pushnil(L); lua_rawset(L, -3); + lua_pop(L, 1); +} + +static void lcurl_storage_ensure_t(lua_State *L, int t){ + lua_rawgeti(L, -1, t); + if(!lua_istable(L, -1)){ + lua_pop(L, 1); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, -3, t); + } +} + +int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list){ + int r; + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lcurl_storage_ensure_t(L, LCURL_STORAGE_SLIST); + lua_pushlightuserdata(L, list); + r = luaL_ref(L, -2); + lua_pop(L, 2); + return r; +} + +void lcurl_storage_preserve_iv(lua_State *L, int storage, int i, int v){ + v = lua_absindex(L, v); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lcurl_storage_ensure_t(L, LCURL_STORAGE_KV); + lua_pushvalue(L, v); + lua_rawseti(L, -2, i); + lua_pop(L, 2); +} + +void lcurl_storage_remove_i(lua_State *L, int storage, int i){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_KV); + if(lua_istable(L, -1)){ + lua_pushnil(L); + lua_rawseti(L, -2, i); + } + lua_pop(L, 2); +} + +void lcurl_storage_get_i(lua_State *L, int storage, int i){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_KV); + if(lua_istable(L, -1)){ + lua_rawgeti(L, -1, i); + lua_remove(L, -2); + } + lua_remove(L, -2); +} + +struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx){ + struct curl_slist* list = NULL; + assert(idx != LUA_NOREF); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_SLIST); // list storage + if(lua_istable(L, -1)){ + lua_rawgeti(L, -1, idx); + list = lua_touserdata(L, -1); + assert(list); + luaL_unref(L, -2, idx); + lua_pop(L, 1); + } + lua_pop(L, 2); + return list; +} + +int lcurl_storage_free(lua_State *L, int storage){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_rawgeti(L, -1, LCURL_STORAGE_SLIST); // list storage + if(lua_istable(L, -1)){ + lua_pushnil(L); + while(lua_next(L, -2) != 0){ + struct curl_slist * list = lua_touserdata(L, -1); + curl_slist_free_all(list); + lua_pushvalue(L, -2); lua_pushnil(L); + lua_rawset(L, -5); + lua_pop(L, 1); + } + } + luaL_unref(L, LCURL_LUA_REGISTRY, storage); + lua_pop(L, 2); + return LUA_NOREF; +} + +struct curl_slist* lcurl_util_array_to_slist(lua_State *L, int t){ + struct curl_slist *list = NULL; + int i, n = lua_rawlen(L, t); + + assert(lua_type(L, t) == LUA_TTABLE); + + for(i = 1; i <= n; ++i){ + lua_rawgeti(L, t, i); + list = curl_slist_append(list, lua_tostring(L, -1)); + lua_pop(L, 1); + } + return list; +} + +struct curl_slist* lcurl_util_to_slist(lua_State *L, int t){ + if(lua_type(L, t) == LUA_TTABLE){ + return lcurl_util_array_to_slist(L, t); + } + return 0; +} + +void lcurl_util_slist_set(lua_State *L, int t, struct curl_slist* list){ + int i; + t = lua_absindex(L, t); + for(i = 0;list;list = list->next){ + lua_pushstring(L, list->data); + lua_rawseti(L, t, ++i); + } +} + +void lcurl_util_slist_to_table(lua_State *L, struct curl_slist* list){ + lua_newtable(L); + lcurl_util_slist_set(L, -1, list); +} + +void lcurl_util_set_const(lua_State *L, const lcurl_const_t *reg){ + const lcurl_const_t *p; + for(p = reg; p->name; ++p){ + lua_pushstring(L, p->name); + lua_pushnumber(L, p->value); + lua_settable(L, -3); + } +} + +int lcurl_set_callback(lua_State *L, lcurl_callback_t *c, int i, const char *method){ + int top = lua_gettop(L); + i = lua_absindex(L, i); + + luaL_argcheck(L, !lua_isnoneornil(L, i), i, "no function present"); + luaL_argcheck(L, (top < (i + 2)), i + 2, "no arguments expected"); + + assert((top == i)||(top == (i + 1))); + + if(c->ud_ref != LUA_NOREF){ + luaL_unref(L, LCURL_LUA_REGISTRY, c->ud_ref); + c->ud_ref = LUA_NOREF; + } + + if(c->cb_ref != LUA_NOREF){ + luaL_unref(L, LCURL_LUA_REGISTRY, c->cb_ref); + c->cb_ref = LUA_NOREF; + } + + if(lutil_is_null(L, i)){ + if(top == (i + 1)){ + // Do we can just ignore this? + luaL_argcheck(L, + lua_isnoneornil(L, i + 1) || lutil_is_null(L, i + 1) + ,i + 1, "no context allowed when set callback to null" + ); + } + lua_pop(L, top - i + 1); + + return 1; + } + + if(lua_gettop(L) == (i + 1)){// function + context + c->ud_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + + assert(top == (2 + lua_gettop(L))); + return 1; + } + + assert(top == i); + + if(lua_isfunction(L, i)){ // function + c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + + assert(top == (1 + lua_gettop(L))); + return 1; + } + + if(lua_isuserdata(L, i) || lua_istable(L, i)){ // object + lua_getfield(L, i, method); + + luaL_argcheck(L, lua_isfunction(L, -1), 2, "method not found in object"); + + c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + c->ud_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + + assert(top == (1 + lua_gettop(L))); + return 1; + } + + lua_pushliteral(L, "invalid object type"); + return lua_error(L); +} + +int lcurl_util_push_cb(lua_State *L, lcurl_callback_t *c){ + assert(c->cb_ref != LUA_NOREF); + lua_rawgeti(L, LCURL_LUA_REGISTRY, c->cb_ref); + if(c->ud_ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, c->ud_ref); + return 2; + } + return 1; +} + +int lcurl_util_new_weak_table(lua_State*L, const char *mode){ + int top = lua_gettop(L); + lua_newtable(L); + lua_newtable(L); + lua_pushstring(L, mode); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L,-2); + assert((top+1) == lua_gettop(L)); + return 1; +} + +int lcurl_util_pcall_method(lua_State *L, const char *name, int nargs, int nresults, int errfunc){ + int obj_index = -nargs - 1; + lua_getfield(L, obj_index, name); + lua_insert(L, obj_index - 1); + return lua_pcall(L, nargs + 1, nresults, errfunc); +} + +static void lcurl_utils_pcall_close(lua_State *L, int obj){ + int top = lua_gettop(L); + lua_pushvalue(L, obj); + lcurl_util_pcall_method(L, "close", 0, 0, 0); + lua_settop(L, top); +} + +int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, + int error_mode, int error_type, int error_code +){ + int top = lua_gettop(L); + opt = lua_absindex(L, opt); + obj = lua_absindex(L, obj); + + lua_pushnil(L); + while(lua_next(L, opt) != 0){ + int n; + assert(lua_gettop(L) == (top + 2)); + + if(lua_type(L, -2) == LUA_TNUMBER){ /* [curl.OPT_URL] = "http://localhost" */ + lua_pushvalue(L, -2); + lua_insert(L, -2); /*Stack : opt, obj, k, k, v */ + lua_pushliteral(L, "setopt"); /*Stack : opt, obj, k, k, v, "setopt" */ + n = 2; + } + else if(lua_type(L, -2) == LUA_TSTRING){ /* url = "http://localhost" */ + lua_pushliteral(L, "setopt_"); lua_pushvalue(L, -3); lua_concat(L, 2); + /*Stack : opt, obj, k, v, "setopt_XXX" */ + n = 1; + } + else{ + lua_pop(L, 1); + continue; + } + /*Stack : opt, obj, k,[ k,] v, `setoptXXX` */ + + lua_gettable(L, obj); /* get e["settop_XXX]*/ + + if(lua_isnil(L, -1)){ /* unknown option */ + if(do_close) lcurl_utils_pcall_close(L, obj); + lua_settop(L, top); + return lcurl_fail_ex(L, error_mode, error_type, error_code); + } + + lua_insert(L, -n-1); /*Stack : opt, obj, k, setoptXXX, [ k,] v */ + lua_pushvalue(L, obj); /*Stack : opt, obj, k, setoptXXX, [ k,] v, obj */ + lua_insert(L, -n-1); /*Stack : opt, obj, k, setoptXXX, obj, [ k,] v */ + + if(lua_pcall(L, n+1, 2, 0)){ + if(do_close) lcurl_utils_pcall_close(L, obj); + return lua_error(L); + } + + if(lua_isnil(L, -2)){ + if(do_close) lcurl_utils_pcall_close(L, obj); + lua_settop(L, top); + return 2; + } + + /*Stack : opt, obj, k, ok, nil*/ + lua_pop(L, 2); + assert(lua_gettop(L) == (top+1)); + } + assert(lua_gettop(L) == top); + return 0; +} + +void lcurl_stack_dump (lua_State *L){ + int i = 1, top = lua_gettop(L); + + fprintf(stderr, " ---------------- Stack Dump ----------------\n" ); + while( i <= top ) { + int t = lua_type(L, i); + switch (t) { + case LUA_TSTRING: + fprintf(stderr, "%d(%d):`%s'\n", i, i - top - 1, lua_tostring(L, i)); + break; + case LUA_TBOOLEAN: + fprintf(stderr, "%d(%d): %s\n", i, i - top - 1,lua_toboolean(L, i) ? "true" : "false"); + break; + case LUA_TNUMBER: + fprintf(stderr, "%d(%d): %g\n", i, i - top - 1, lua_tonumber(L, i)); + break; + default: + lua_getglobal(L, "tostring"); + lua_pushvalue(L, i); + lua_call(L, 1, 1); + fprintf(stderr, "%d(%d): %s(%s)\n", i, i - top - 1, lua_typename(L, t), lua_tostring(L, -1)); + lua_pop(L, 1); + break; + } + i++; + } + fprintf(stderr, " ------------ Stack Dump Finished ------------\n" ); +} + +curl_socket_t lcurl_opt_os_socket(lua_State *L, int idx, curl_socket_t def) { + if (lua_islightuserdata(L, idx)) + return (curl_socket_t)lua_touserdata(L, idx); + + return (curl_socket_t)lutil_optint64(L, idx, def); +} + +void lcurl_push_os_socket(lua_State *L, curl_socket_t fd) { +#if !defined(_WIN32) + lutil_pushint64(L, fd); +#else /*_WIN32*/ + /* Assumes that compiler can optimize constant conditions. MSVC do this. */ + + /*On Lua 5.3 lua_Integer type can be represented exactly*/ +#if LUA_VERSION_NUM >= 503 + if (sizeof(curl_socket_t) <= sizeof(lua_Integer)) { + lua_pushinteger(L, (lua_Integer)fd); + return; + } +#endif + +#if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) + /*! @todo test DBL_MANT_DIG, FLT_MANT_DIG */ + + if (sizeof(lua_Number) == 8) { /*we have 53 bits for integer*/ + if ((sizeof(curl_socket_t) <= 6)) { + lua_pushnumber(L, (lua_Number)fd); + return; + } + + if(((UINT_PTR)fd & 0x1FFFFFFFFFFFFF) == (UINT_PTR)fd) + lua_pushnumber(L, (lua_Number)fd); + else + lua_pushlightuserdata(L, (void*)fd); + + return; + } + + if (sizeof(lua_Number) == 4) { /*we have 24 bits for integer*/ + if (((UINT_PTR)fd & 0xFFFFFF) == (UINT_PTR)fd) + lua_pushnumber(L, (lua_Number)fd); + else + lua_pushlightuserdata(L, (void*)fd); + return; + } +#endif + + lutil_pushint64(L, fd); + if (lcurl_opt_os_socket(L, -1, 0) != fd) + lua_pushlightuserdata(L, (void*)fd); + +#endif /*_WIN32*/ +} diff --git a/watchdog/third_party/lua-curl/src/lcutils.h b/watchdog/third_party/lua-curl/src/lcutils.h new file mode 100644 index 0000000..cfad1af --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lcutils.h @@ -0,0 +1,108 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2014-2021 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of Lua-cURL library. +******************************************************************************/ + +#ifndef _LCUTILS_H_ +#define _LCUTILS_H_ + +#include "lcurl.h" + +#if defined(_MSC_VER) || defined(__cplusplus) +# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 1 +#elif defined(__STDC_VERSION__) +# if __STDC_VERSION__ >= 201112 +# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 1 +# endif +#endif + +#ifndef LCURL_CC_SUPPORT_FORWARD_TYPEDEF +# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 0 +#endif + +#ifdef __GNUC__ + #define LCURL_UNUSED_TYPEDEF __attribute__ ((unused)) +#else + #define LCURL_UNUSED_TYPEDEF +#endif + +#define LCURL_UNUSED_VAR LCURL_UNUSED_TYPEDEF + +#define LCURL_MAKE_VERSION(MIN, MAJ, PAT) ((MIN<<16) + (MAJ<<8) + PAT) +#define LCURL_CURL_VER_GE(MIN, MAJ, PAT) (LIBCURL_VERSION_NUM >= LCURL_MAKE_VERSION(MIN, MAJ, PAT)) + +#define LCURL_CONCAT_STATIC_ASSERT_IMPL_(x, y) LCURL_CONCAT1_STATIC_ASSERT_IMPL_ (x, y) +#define LCURL_CONCAT1_STATIC_ASSERT_IMPL_(x, y) LCURL_UNUSED_TYPEDEF x##y +#define LCURL_STATIC_ASSERT(expr) typedef char LCURL_CONCAT_STATIC_ASSERT_IMPL_(static_assert_failed_at_line_, __LINE__) [(expr) ? 1 : -1] + +#define LCURL_ASSERT_SAME_SIZE(a, b) LCURL_STATIC_ASSERT( sizeof(a) == sizeof(b) ) +#define LCURL_ASSERT_SAME_OFFSET(a, am, b, bm) LCURL_STATIC_ASSERT( (offsetof(a,am)) == (offsetof(b,bm)) ) +#define LCURL_ASSERT_SAME_FIELD_SIZE(a, am, b, bm) LCURL_ASSERT_SAME_SIZE(((a*)0)->am, ((b*)0)->bm) + +typedef struct lcurl_const_tag{ + const char *name; + long value; +}lcurl_const_t; + +typedef struct lcurl_callback_tag{ + int cb_ref; + int ud_ref; +}lcurl_callback_t; + +typedef struct lcurl_read_buffer_tag{ + int ref; + size_t off; +}lcurl_read_buffer_t; + +int lcurl_storage_init(lua_State *L); + +void lcurl_storage_preserve_value(lua_State *L, int storage, int i); + +void lcurl_storage_remove_value(lua_State *L, int storage, int i); + +int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list); + +struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx); + +void lcurl_storage_preserve_iv(lua_State *L, int storage, int i, int v); + +void lcurl_storage_remove_i(lua_State *L, int storage, int i); + +void lcurl_storage_get_i(lua_State *L, int storage, int i); + +int lcurl_storage_free(lua_State *L, int storage); + +struct curl_slist* lcurl_util_array_to_slist(lua_State *L, int t); + +struct curl_slist* lcurl_util_to_slist(lua_State *L, int t); + +void lcurl_util_slist_set(lua_State *L, int t, struct curl_slist* list); + +void lcurl_util_slist_to_table(lua_State *L, struct curl_slist* list); + +void lcurl_util_set_const(lua_State *L, const lcurl_const_t *reg); + +int lcurl_set_callback(lua_State *L, lcurl_callback_t *c, int i, const char *method); + +int lcurl_util_push_cb(lua_State *L, lcurl_callback_t *c); + +int lcurl_util_new_weak_table(lua_State*L, const char *mode); + +int lcurl_util_pcall_method(lua_State *L, const char *name, int nargs, int nresults, int errfunc); + +int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, + int error_mode, int error_type, int error_code + ); + +void lcurl_stack_dump (lua_State *L); + +curl_socket_t lcurl_opt_os_socket(lua_State *L, int idx, curl_socket_t def); + +void lcurl_push_os_socket(lua_State *L, curl_socket_t fd); + +#endif diff --git a/watchdog/third_party/lua-curl/src/lua/cURL.lua b/watchdog/third_party/lua-curl/src/lua/cURL.lua new file mode 100644 index 0000000..c546cf4 --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lua/cURL.lua @@ -0,0 +1,14 @@ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2014-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENSE' document +-- +-- This file is part of Lua-cURL library. +-- + +local curl = require "lcurl" +local impl = require "cURL.impl.cURL" + +return impl(curl) diff --git a/watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua b/watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua new file mode 100644 index 0000000..a7b141d --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua @@ -0,0 +1,739 @@ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2014-2021 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENSE' document +-- +-- This file is part of Lua-cURL library. +-- + +local module_info = { + _NAME = "Lua-cURL"; + _VERSION = "0.3.13"; + _LICENSE = "MIT"; + _COPYRIGHT = "Copyright (c) 2014-2021 Alexey Melnichuk"; +} + +local function hash_id(str) + local id = string.match(str, "%((.-)%)") or string.match(str, ': (%x+)$') + return id +end + +local function clone(t, o) + o = o or {} + for k,v in pairs(t) do o[k]=v end + return o +end + +local function wrap_function(k) + return function(self, ...) + local ok, err = self._handle[k](self._handle, ...) + if ok == self._handle then return self end + return ok, err + end +end + +local function wrap_setopt_flags(k, flags) + k = "setopt_" .. k + local flags2 = clone(flags) + for k, v in pairs(flags) do flags2[v] = v end + + return function(self, v) + v = assert(flags2[v], "Unsupported value " .. tostring(v)) + local ok, err = self._handle[k](self._handle, v) + if ok == self._handle then return self end + return ok, err + end +end + +local function new_buffers() + local buffers = {resp = {}, _ = {}} + + function buffers:append(e, ...) + local resp = assert(e:getinfo_response_code()) + if not self._[e] then self._[e] = {} end + + local b = self._[e] + + if self.resp[e] ~= resp then + b[#b + 1] = {"response", resp} + self.resp[e] = resp + end + + b[#b + 1] = {...} + end + + function buffers:next() + for e, t in pairs(self._) do + local m = table.remove(t, 1) + if m then return e, m end + end + end + + return buffers +end + +local function make_iterator(self, perform) + local curl = require "lcurl.safe" + + local buffers = new_buffers() + + -- reset callbacks to all easy handles + local function reset_easy(self) + if not self._easy_mark then -- that means we have some new easy handles + for h, e in pairs(self._easy) do if h ~= 'n' then + e:setopt_writefunction (function(str) buffers:append(e, "data", str) end) + e:setopt_headerfunction(function(str) buffers:append(e, "header", str) end) + end end + self._easy_mark = true + end + return self._easy.n + end + + if 0 == reset_easy(self) then return end + + assert(perform(self)) + + return function() + -- we can add new handle during iteration + local remain = reset_easy(self) + + -- wait next event + while true do + local e, t = buffers:next() + if t then return t[2], t[1], e end + if remain == 0 then break end + + self:wait() + + local n = assert(perform(self)) + + if n <= remain then + while true do + local e, ok, err = assert(self:info_read()) + if e == 0 then break end + if ok then + ok = e:getinfo_response_code() or ok + buffers:append(e, "done", ok) + else buffers:append(e, "error", err) end + self:remove_handle(e) + e:unsetopt_headerfunction() + e:unsetopt_writefunction() + end + end + + remain = n + end + end +end + +-- name = //// +-- +-- = { +-- stream = function/object +-- length = ?number +-- name = ?string +-- type = ?string +-- headers = ?table +-- } +-- +-- = { +-- file = string +-- type = ?string +-- name = ?string +-- headers = ?table +-- } +-- +-- = { +-- data = string +-- name = string +-- type = ?string +-- headers = ?table +-- } +-- +-- = { +-- content = string -- or first key in table +-- type = ?string +-- headers = ?table +-- } +-- + +local function form_add_element(form, name, value) + local vt = type(value) + if vt == "string" then return form:add_content(name, value) end + + assert(type(name) == "string") + assert(vt == "table") + assert((value.name == nil) or (type(value.name ) == 'string')) + assert((value.type == nil) or (type(value.type ) == 'string')) + assert((value.headers == nil) or (type(value.headers) == 'table' )) + + if value.stream then + local vst = type(value.stream) + + if vst == 'function' then + assert(type(value.length) == 'number') + local length = value.length + return form:add_stream(name, value.name, value.type, value.headers, length, value.stream) + end + + if (vst == 'table') or (vst == 'userdata') then + local length = value.length or assert(value.stream:length()) + assert(type(length) == 'number') + return form:add_stream(name, value.name, value.type, value.headers, length, value.stream) + end + + error("Unsupported stream type: " .. vst) + end + + if value.file then + assert(type(value.file) == 'string') + return form:add_file(name, value.file, value.type, value.filename, value.headers) + end + + if value.data then + assert(type(value.data) == 'string') + assert(type(value.name) == 'string') + return form:add_buffer(name, value.name, value.data, value.type, value.headers) + end + + local content = value[1] or value.content + if content then + assert(type(content) == 'string') + if value.type then + return form:add_content(name, content, value.type, value.headers) + end + return form:add_content(name, content, value.headers) + end + + return form +end + +local function form_add(form, data) + for k, v in pairs(data) do + local ok, err = form_add_element(form, k, v) + if not ok then return nil, err end + end + + return form +end + +local function class(ctor, type_name) + local C = {} + C.__type = type_name or "LcURL Unknown" + C.__index = function(self, k) + local fn = C[k] + + if not fn and self._handle[k] then + fn = wrap_function(k) + C[k] = fn + end + return fn + end + + function C:new(...) + local h, err = ctor() + if not h then return nil, err end + + local o = setmetatable({ + _handle = h + }, self) + + if self.__init then return self.__init(o, ...) end + + return o + end + + function C:handle() + return self._handle + end + + return C +end + +local function Load_cURLv2(cURL, curl) + +------------------------------------------- +local Easy = class(curl.easy, "LcURL Easy") do + +local perform = wrap_function("perform") +local setopt_share = wrap_function("setopt_share") +local setopt_readfunction = wrap_function("setopt_readfunction") + +local NONE = {} + +function Easy:_call_readfunction(...) + if self._rd_ud == NONE then + return self._rd_fn(...) + end + return self._rd_fn(self._rd_ud, ...) +end + +function Easy:setopt_readfunction(fn, ...) + assert(fn) + + if select('#', ...) == 0 then + if type(fn) == "function" then + self._rd_fn = fn + self._rd_ud = NONE + else + self._rd_fn = assert(fn.read) + self._rd_ud = fn + end + else + self._rd_fn = fn + self._ud_fn = ... + end + + return setopt_readfunction(self, fn, ...) +end + +function Easy:perform(opt) + opt = opt or {} + + local oerror = opt.errorfunction or function(err) return nil, err end + + if opt.readfunction then + local ok, err = self:setopt_readfunction(opt.readfunction) + if not ok then return oerror(err) end + end + + if opt.writefunction then + local ok, err = self:setopt_writefunction(opt.writefunction) + if not ok then return oerror(err) end + end + + if opt.headerfunction then + local ok, err = self:setopt_headerfunction(opt.headerfunction) + if not ok then return oerror(err) end + end + + local ok, err = perform(self) + if not ok then return oerror(err) end + + return self +end + +function Easy:post(data) + local form = curl.form() + local ok, err = true + + for k, v in pairs(data) do + if type(v) == "string" then + ok, err = form:add_content(k, v) + else + assert(type(v) == "table") + if v.stream_length then + local len = assert(tonumber(v.stream_length)) + assert(v.file) + if v.stream then + ok, err = form:add_stream(k, v.file, v.type, v.headers, len, v.stream) + else + ok, err = form:add_stream(k, v.file, v.type, v.headers, len, self._call_readfunction, self) + end + elseif v.data then + ok, err = form:add_buffer(k, v.file, v.data, v.type, v.headers) + else + ok, err = form:add_file(k, v.file, v.type, v.filename, v.headers) + end + end + if not ok then break end + end + + if not ok then + form:free() + return nil, err + end + + ok, err = self:setopt_httppost(form) + if not ok then + form:free() + return nil, err + end + + return self +end + +function Easy:setopt_share(s) + return setopt_share(self, s:handle()) +end + +Easy.setopt_proxytype = wrap_setopt_flags("proxytype", { + ["HTTP" ] = curl.PROXY_HTTP; + ["HTTP_1_0" ] = curl.PROXY_HTTP_1_0; + ["SOCKS4" ] = curl.PROXY_SOCKS4; + ["SOCKS5" ] = curl.PROXY_SOCKS5; + ["SOCKS4A" ] = curl.PROXY_SOCKS4A; + ["SOCKS5_HOSTNAME" ] = curl.PROXY_SOCKS5_HOSTNAME; + ["HTTPS" ] = curl.PROXY_HTTPS; +}) + +Easy.setopt_httpauth = wrap_setopt_flags("httpauth", { + ["NONE" ] = curl.AUTH_NONE; + ["BASIC" ] = curl.AUTH_BASIC; + ["DIGEST" ] = curl.AUTH_DIGEST; + ["GSSNEGOTIATE" ] = curl.AUTH_GSSNEGOTIATE; + ["NEGOTIATE" ] = curl.AUTH_NEGOTIATE; + ["NTLM" ] = curl.AUTH_NTLM; + ["DIGEST_IE" ] = curl.AUTH_DIGEST_IE; + ["GSSAPI" ] = curl.AUTH_GSSAPI; + ["NTLM_WB" ] = curl.AUTH_NTLM_WB; + ["ONLY" ] = curl.AUTH_ONLY; + ["ANY" ] = curl.AUTH_ANY; + ["ANYSAFE" ] = curl.AUTH_ANYSAFE; + ["BEARER" ] = curl.AUTH_BEARER; +}) + +Easy.setopt_ssh_auth_types = wrap_setopt_flags("ssh_auth_types", { + ["NONE" ] = curl.SSH_AUTH_NONE; + ["ANY" ] = curl.SSH_AUTH_ANY; + ["PUBLICKEY" ] = curl.SSH_AUTH_PUBLICKEY; + ["PASSWORD" ] = curl.SSH_AUTH_PASSWORD; + ["HOST" ] = curl.SSH_AUTH_HOST; + ["GSSAPI" ] = curl.SSH_AUTH_GSSAPI; + ["KEYBOARD" ] = curl.SSH_AUTH_KEYBOARD; + ["AGENT" ] = curl.SSH_AUTH_AGENT; + ["DEFAULT" ] = curl.SSH_AUTH_DEFAULT; +}) + +end +------------------------------------------- + +------------------------------------------- +local Multi = class(curl.multi, "LcURL Multi") do + +local perform = wrap_function("perform") +local add_handle = wrap_function("add_handle") +local remove_handle = wrap_function("remove_handle") + +function Multi:__init() + self._easy = {n = 0} + return self +end + +function Multi:perform() + return make_iterator(self, perform) +end + +function Multi:add_handle(e) + assert(self._easy.n >= 0) + + local h = e:handle() + if self._easy[h] then return self end + + local ok, err = add_handle(self, h) + if not ok then return nil, err end + self._easy[h], self._easy.n = e, self._easy.n + 1 + self._easy_mark = nil + + return self +end + +function Multi:remove_handle(e) + local h = e:handle() + + if self._easy[h] then + self._easy[h], self._easy.n = nil, self._easy.n - 1 + end + assert(self._easy.n >= 0) + + return remove_handle(self, h) +end + +function Multi:info_read(...) + while true do + local h, ok, err = self:handle():info_read(...) + if not h then return nil, ok end + if h == 0 then return h end + + local e = self._easy[h] + if e then + if ... then + self._easy[h], self._easy.n = nil, self._easy.n - 1 + end + return e, ok, err + end + end +end + +end +------------------------------------------- + +------------------------------------------- +local Share = class(curl.share, "LcURL Share") do + +Share.setopt_share = wrap_setopt_flags("share", { + [ "COOKIE" ] = curl.LOCK_DATA_COOKIE; + [ "DNS" ] = curl.LOCK_DATA_DNS; + [ "SSL_SESSION" ] = curl.LOCK_DATA_SSL_SESSION; +}) + +end +------------------------------------------- + +assert(cURL.easy_init == nil) +function cURL.easy_init() return Easy:new() end + +assert(cURL.multi_init == nil) +function cURL.multi_init() return Multi:new() end + +assert(cURL.share_init == nil) +function cURL.share_init() return Share:new() end + +end + +local function Load_cURLv3(cURL, curl) + +------------------------------------------- +local Form = class(curl.form, "LcURL Form") do + +function Form:__init(opt) + if opt then return self:add(opt) end + return self +end + +function Form:add(data) + return form_add(self, data) +end + +function Form:__tostring() + local id = hash_id(tostring(self._handle)) + return string.format("%s %s (%s)", module_info._NAME, 'Form', id) +end + +end +------------------------------------------- + +------------------------------------------- +local Easy = class(curl.easy, "LcURL Easy") do + +function Easy:__init(opt) + if opt then return self:setopt(opt) end + return self +end + +local perform = wrap_function("perform") +function Easy:perform(opt) + if opt then + local ok, err = self:setopt(opt) + if not ok then return nil, err end + end + + return perform(self) +end + +local setopt_httppost = wrap_function("setopt_httppost") +function Easy:setopt_httppost(form) + return setopt_httppost(self, form:handle()) +end + +if curl.OPT_STREAM_DEPENDS then + +local setopt_stream_depends = wrap_function("setopt_stream_depends") +function Easy:setopt_stream_depends(easy) + return setopt_stream_depends(self, easy:handle()) +end + +local setopt_stream_depends_e = wrap_function("setopt_stream_depends_e") +function Easy:setopt_stream_depends_e(easy) + return setopt_stream_depends_e(self, easy:handle()) +end + +end + +local setopt = wrap_function("setopt") +local custom_setopt = { + [curl.OPT_HTTPPOST or true] = 'setopt_httppost'; + [curl.OPT_STREAM_DEPENDS or true] = 'setopt_stream_depends'; + [curl.OPT_STREAM_DEPENDS_E or true] = 'setopt_stream_depends_e'; +} +custom_setopt[true] = nil + +function Easy:setopt(k, v) + if type(k) == 'table' then + local t = k + + local t2 + local hpost = t.httppost or t[curl.OPT_HTTPPOST] + if hpost and hpost._handle then + t = t2 or clone(t); t2 = t; + if t.httppost then t.httppost = hpost:handle() end + if t[curl.OPT_HTTPPOST] then t[curl.OPT_HTTPPOST] = hpost:handle() end + end + + local easy = t.stream_depends or t[curl.OPT_STREAM_DEPENDS] + if easy and easy._handle then + t = t2 or clone(t); t2 = t; + if t.stream_depends then t.stream_depends = easy:handle() end + if t[curl.OPT_STREAM_DEPENDS] then t[curl.OPT_STREAM_DEPENDS] = easy:handle() end + end + + local easy = t.stream_depends_e or t[curl.OPT_STREAM_DEPENDS_E] + if easy and easy._handle then + t = t2 or clone(t); t2 = t; + if t.stream_depends_e then t.stream_depends_e = easy:handle() end + if t[curl.OPT_STREAM_DEPENDS_E] then t[curl.OPT_STREAM_DEPENDS_E] = easy:handle() end + end + + return setopt(self, t) + end + + local setname = custom_setopt[k] + if setname then + return self[setname](self, v) + end + + return setopt(self, k, v) +end + +function Easy:__tostring() + local id = hash_id(tostring(self._handle)) + return string.format("%s %s (%s)", module_info._NAME, 'Easy', id) +end + +end +------------------------------------------- + +------------------------------------------- +local Multi = class(curl.multi, "LcURL Multi") do + +local add_handle = wrap_function("add_handle") +local remove_handle = wrap_function("remove_handle") + +function Multi:__init(opt) + self._easy = {n = 0} + if opt then self:setopt(opt) end + return self +end + +function Multi:iperform() + return make_iterator(self, self.perform) +end + +function Multi:add_handle(e) + assert(self._easy.n >= 0) + + local h = e:handle() + if self._easy[h] then + return nil, curl.error(curl.ERROR_MULTI, curl.E_MULTI_ADDED_ALREADY or curl.E_MULTI_BAD_EASY_HANDLE) + end + + local ok, err = add_handle(self, h) + if not ok then return nil, err end + self._easy[h], self._easy.n = e, self._easy.n + 1 + self._easy_mark = nil + + return self +end + +function Multi:remove_handle(e) + local h = e:handle() + + if self._easy[h] then + self._easy[h], self._easy.n = nil, self._easy.n - 1 + end + assert(self._easy.n >= 0) + + return remove_handle(self, h) +end + +function Multi:info_read(...) + while true do + local h, ok, err = self:handle():info_read(...) + if not h then return nil, ok end + if h == 0 then return h end + + local e = self._easy[h] + if e then + if ... then + self._easy[h], self._easy.n = nil, self._easy.n - 1 + end + return e, ok, err + end + end +end + +local function wrap_callback(...) + local n = select("#", ...) + local fn, ctx, has_ctx + if n >= 2 then + has_ctx, fn, ctx = true, assert(...) + else + fn = assert(...) + if type(fn) ~= "function" then + has_ctx, fn, ctx = true, assert(fn.socket), fn + end + end + if has_ctx then + return function(...) return fn(ctx, ...) end + end + return function(...) return fn(...) end +end + +local function wrap_socketfunction(self, cb) + local ptr = setmetatable({value = self},{__mode = 'v'}) + return function(h, ...) + local e = ptr.value._easy[h] + if e then return cb(e, ...) end + return 0 + end +end + +local setopt_socketfunction = wrap_function("setopt_socketfunction") +function Multi:setopt_socketfunction(...) + local cb = wrap_callback(...) + + return setopt_socketfunction(self, wrap_socketfunction(self, cb)) +end + +local setopt = wrap_function("setopt") +function Multi:setopt(k, v) + if type(k) == 'table' then + local t = k + + local socketfunction = t.socketfunction or t[curl.OPT_SOCKETFUNCTION] + if socketfunction then + t = clone(t) + local fn = wrap_socketfunction(self, socketfunction) + if t.socketfunction then t.socketfunction = fn end + if t[curl.OPT_SOCKETFUNCTION] then t[curl.OPT_SOCKETFUNCTION] = fn end + end + + return setopt(self, t) + end + + if k == curl.OPT_SOCKETFUNCTION then + return self:setopt_socketfunction(v) + end + + return setopt(self, k, v) +end + +function Multi:__tostring() + local id = hash_id(tostring(self._handle)) + return string.format("%s %s (%s)", module_info._NAME, 'Multi', id) +end + +end +------------------------------------------- + +setmetatable(cURL, {__index = curl}) + +function cURL.form(...) return Form:new(...) end + +function cURL.easy(...) return Easy:new(...) end + +function cURL.multi(...) return Multi:new(...) end + +end + +return function(curl) + local cURL = clone(module_info) + + Load_cURLv3(cURL, curl) + + Load_cURLv2(cURL, curl) + + return cURL +end diff --git a/watchdog/third_party/lua-curl/src/lua/cURL/safe.lua b/watchdog/third_party/lua-curl/src/lua/cURL/safe.lua new file mode 100644 index 0000000..cc46cfb --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lua/cURL/safe.lua @@ -0,0 +1,14 @@ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2014-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENSE' document +-- +-- This file is part of Lua-cURL library. +-- + +local curl = require "lcurl.safe" +local impl = require "cURL.impl.cURL" + +return impl(curl) diff --git a/watchdog/third_party/lua-curl/src/lua/cURL/utils.lua b/watchdog/third_party/lua-curl/src/lua/cURL/utils.lua new file mode 100644 index 0000000..ea7dcae --- /dev/null +++ b/watchdog/third_party/lua-curl/src/lua/cURL/utils.lua @@ -0,0 +1,79 @@ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2014-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENSE' document +-- +-- This file is part of Lua-cURL library. +-- + +--- Returns path to cURL ca bundle +-- +-- @tparam[opt="curl-ca-bundle.crt"] string name name of bundle +-- @treturn string path to file (CURLOPT_CAINFO) +-- @treturn string path to ssl dir path (CURLOPT_CAPATH) +-- +-- @usage +-- local file, path = find_ca_bundle() +-- if file then e:setopt_cainfo(file) end +-- if path then e:setopt_capath(path) end +-- +local function find_ca_bundle(name) + name = name or "curl-ca-bundle.crt" + + local path = require "path" + local env = setmetatable({},{__index = function(_, name) return os.getenv(name) end}) + + local function split(str, sep, plain) + local b, res = 1, {} + while b <= #str do + local e, e2 = string.find(str, sep, b, plain) + if e then + table.insert(res, (string.sub(str, b, e-1))) + b = e2 + 1 + else + table.insert(res, (string.sub(str, b))) + break + end + end + return res + end + + if env.CURL_CA_BUNDLE and path.isfile(env.CURL_CA_BUNDLE) then + return env.CURL_CA_BUNDLE + end + + if env.SSL_CERT_DIR and path.isdir(env.SSL_CERT_DIR) then + return nil, env.SSL_CERT_DIR + end + + if env.SSL_CERT_FILE and path.isfile(env.SSL_CERT_FILE) then + return env.SSL_CERT_FILE + end + + if not path.IS_WINDOWS then return end + + local paths = { + '.', + path.join(env.windir, "System32"), + path.join(env.windir, "SysWOW64"), + env.windir, + } + for _, p in ipairs(split(env.path, ';')) do paths[#paths + 1] = p end + + for _, p in ipairs(paths) do + p = path.fullpath(p) + if path.isdir(p) then + p = path.join(p, name) + if path.isfile(p) then + return p + end + end + end +end + +return { + find_ca_bundle = find_ca_bundle; +} + diff --git a/watchdog/third_party/lua-curl/test/.luacov b/watchdog/third_party/lua-curl/test/.luacov new file mode 100644 index 0000000..e6e0a49 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/.luacov @@ -0,0 +1,38 @@ +return { + configfile = ".luacov", + + -- filename to store stats collected + statsfile = "luacov.stats.out", + + -- filename to store report + reportfile = "luacov.report.json", + + -- Run reporter on completion? (won't work for ticks) + runreport = false, + + -- Delete stats file after reporting? + deletestats = false, + + -- Patterns for files to include when reporting + -- all will be included if nothing is listed + -- (exclude overrules include, do not include + -- the .lua extension) + include = { + "/cURL$", + "/cURL/.+$", + }, + + -- Patterns for files to exclude when reporting + -- all will be included if nothing is listed + -- (exclude overrules include, do not include + -- the .lua extension) + exclude = { + }, + + -- configuration for luacov-coveralls reporter + coveralls = { + pathcorrect = { + {"^.-[/\\]share[/\\]lua[/\\]5.%d", "src/lua"}; + }, + }, +} \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec b/watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec new file mode 100644 index 0000000..5edc3c2 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec @@ -0,0 +1,28 @@ +#!/usr/bin/env lua + +package = 'lua-http-parser' +version = '2.7-1' +source = { + url = 'gitrec+https://github.com/brimworks/lua-http-parser' +} +description = { + summary = "A Lua binding to Ryan Dahl's http request/response parser.", + detailed = '', + homepage = 'http://github.com/brimworks/lua-http-parser', + license = 'MIT', --as with Ryan's +} +dependencies = { + 'lua >= 5.1, < 5.5', + 'luarocks-fetch-gitrec', +} +build = { + type = 'builtin', + modules = { + ['http.parser'] = { + sources = { + "http-parser/http_parser.c", + "lua-http-parser.c" + } + } + } +} diff --git a/watchdog/third_party/lua-curl/test/lunit/console.lua b/watchdog/third_party/lua-curl/test/lunit/console.lua new file mode 100644 index 0000000..97a4d1b --- /dev/null +++ b/watchdog/third_party/lua-curl/test/lunit/console.lua @@ -0,0 +1,132 @@ + +--[[-------------------------------------------------------------------------- + + This file is part of lunit 0.6. + + For Details about lunit look at: http://www.mroth.net/lunit/ + + Author: Michael Roth + + Copyright (c) 2006-2008 Michael Roth + + 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. + +--]]-------------------------------------------------------------------------- + + + +--[[ + + begin() + run(testcasename, testname) + err(fullname, message, traceback) + fail(fullname, where, message, usermessage) + pass(testcasename, testname) + done() + + Fullname: + testcase.testname + testcase.testname:setupname + testcase.testname:teardownname + +--]] + + +local lunit = require "lunit" +local string = require "string" +local io = require "io" +local table = require "table" + +local _M = {} + +local function rfill(str, wdt, ch) + if wdt > #str then str = str .. (ch or ' '):rep(wdt - #str) end + return str +end + +local function printformat(format, ...) + io.write( string.format(format, ...) ) +end + +local columns_printed = 0 + +local function writestatus(char) + if columns_printed == 0 then + io.write(" ") + end + if columns_printed == 60 then + io.write("\n ") + columns_printed = 0 + end + io.write(char) + io.flush() + columns_printed = columns_printed + 1 +end + +local msgs = {} + +function _M.begin() + local total_tc = 0 + local total_tests = 0 + + msgs = {} -- e + + for tcname in lunit.testcases() do + total_tc = total_tc + 1 + for testname, test in lunit.tests(tcname) do + total_tests = total_tests + 1 + end + end + + printformat("Loaded testsuite with %d tests in %d testcases.\n\n", total_tests, total_tc) +end + +function _M.run(testcasename, testname) + io.write(rfill(testcasename .. '.' .. testname, 70)) io.flush() +end + +function _M.err(fullname, message, traceback) + io.write(" - error!\n") + io.write("Error! ("..fullname.."):\n"..message.."\n\t"..table.concat(traceback, "\n\t"), "\n") +end + +function _M.fail(fullname, where, message, usermessage) + io.write(" - fail!\n") + io.write(string.format("Failure (%s): %s\n%s: %s", fullname, usermessage or "", where, message), "\n") +end + +function _M.skip(fullname, where, message, usermessage) + io.write(" - skip!\n") + io.write(string.format("Skip (%s): %s\n%s: %s", fullname, usermessage or "", where, message), "\n") +end + +function _M.pass(testcasename, testname) + io.write(" - pass!\n") +end + +function _M.done() + printformat("\n\n%d Assertions checked.\n", lunit.stats.assertions ) + print() + + printformat("Testsuite finished (%d passed, %d failed, %d errors, %d skipped).\n", + lunit.stats.passed, lunit.stats.failed, lunit.stats.errors, lunit.stats.skipped ) +end + +return _M diff --git a/watchdog/third_party/lua-curl/test/run.lua b/watchdog/third_party/lua-curl/test/run.lua new file mode 100644 index 0000000..65394be --- /dev/null +++ b/watchdog/third_party/lua-curl/test/run.lua @@ -0,0 +1,34 @@ +local RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end + +lunit = require "lunit" + +local ok, curl = pcall(require, "cURL") +local version if ok then + version = curl.version() +else + io.stderr:write('can not load cURL:' .. curl) + os.exit(-1) +end + +print("------------------------------------") +print("Module name: " .. curl._NAME); +print("Module version: " .. curl._VERSION); +print("Lua version: " .. (_G.jit and _G.jit.version or _G._VERSION)) +print("cURL version: " .. version) +print("------------------------------------") +print("") + +require "test_safe" +require "test_easy" +require "test_form" +require "test_mime" +require "test_curl" +require "test_urlapi" + +RUN() diff --git a/watchdog/third_party/lua-curl/test/server.lua b/watchdog/third_party/lua-curl/test/server.lua new file mode 100644 index 0000000..a3053da --- /dev/null +++ b/watchdog/third_party/lua-curl/test/server.lua @@ -0,0 +1,121 @@ +local function prequire(m) + local ok, err = pcall(require, m) + if not ok then return nil, err end + return err +end + +local uv = prequire "lluv" +local Pegasus = require (uv and "lluv.pegasus" or "pegasus") +local Router = require "pegasus.plugins.router" +local json = require "dkjson" +-- local pp = require "pp" + +local function decode_form(form) + return string.match(form, '\r\nContent%-Disposition:%s*form%-data;%s*name="(.-)".-\r\n\r\n(.-)\r\n') +end + +local function decode_params(str) + local params = {} + for k, v in string.gmatch(str, '([^=]+)=([^&]+)&?') do + params[k] = v + end + return params +end + +local function rand_bytes(n) + local res = {} + for i = 1, n do + res[#res + 1] = string.char(math.random(254)) + end + return table.concat(res) +end + +local r = Router:new() + +local server = Pegasus:new{ + plugins = { r }; + host = '127.0.0.1', port = 7090, timout = 10 +} + +local function recvFullBody(request, T1) + local body, counter = {}, 0 + + local result, status + while true do + result, status = request:receiveBody() + if result then + counter = 0 + body[#body + 1] = result + elseif status ~= 'timeout' then + break + else + counter = counter + 1 + if counter > T1 then break end + end + end + + return table.concat(body), status +end + +local function buildResponse(request) + local headers = request:headers() + local params = request:params() + local path = request:path() + local ip = request.ip + local host = headers and headers.Host or '127.0.0.1' + local url = string.format('http://%s%s', host, path) + + return { + args = params; + headers = headers; + origin = ip; + url = url; + } +end + +r:get('/get', function(request, response) + local result = buildResponse(request) + result.body = recvFullBody(request, 15) + + response:statusCode(200) + response:contentType('application/json') + response:write(json.encode(result, {indent = true})) +end) + +r:post('/post', function(request, response, params) + local result = buildResponse(request) + + local body, status = recvFullBody(request, 15) + + local name, data = decode_form(body) + if name then + result.form = {[name] = data} + else + result.form = decode_params(body) + end + + response:statusCode(200) + response:contentType('application/json') + response:write(json.encode(result, {indent = true})) +end) + +r:get('/bytes/:size', function(request, response, params) + local headers = request:headers() + local size = tonumber(params.size) or 1024 + local result = rand_bytes(size) + + response:statusCode(200) + response:addHeader('Connection', 'close') + response:contentType('application/octet-stream') + response:write(result) +end) + +server:start(function(request, response) + local headers = request:headers() + + response:statusCode(200) + response:addHeader('Content-Type', 'text/plain') + response:write('Hello from Pegasus') +end) + +if uv then uv.run() end diff --git a/watchdog/third_party/lua-curl/test/test_curl.lua b/watchdog/third_party/lua-curl/test/test_curl.lua new file mode 100644 index 0000000..ce6a44a --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_curl.lua @@ -0,0 +1,451 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local _,luacov = pcall(require, "luacov") +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end + +local curl = require "cURL" +local scurl = require "cURL.safe" +local json = require "dkjson" +local fname = "./test.download" + +local utils = require "utils" + +local weak_ptr, gc_collect, is_curl_ge, is_curl_eq, read_file, stream, Stream, dump_request = + utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'is_curl_eq', 'read_file', 'stream', 'Stream', 'dump_request') + +-- Bug. libcurl 7.56.0 does not add `Content-Type: text/plain` +local text_plain = is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' + +local GET_URL = "http://127.0.0.1:7090/get" + +local ENABLE = true + +local _ENV = TEST_CASE'version' if ENABLE then + +function test() + assert_match("^%d+%.%d+%.%d+%-?", curl._VERSION) + assert_equal("Lua-cURL", curl._NAME ) +end + +end + +local _ENV = TEST_CASE'easy' if ENABLE then + +local e1, e2 +function teardown() + if e1 then e1:close() end + if e2 then e2:close() end + e1, e2 = nil +end + +if curl.OPT_STREAM_DEPENDS then + +function test_easy_setopt_stream_depends_1() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt_stream_depends(e2) + end) +end + +function test_easy_setopt_stream_depends_2() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt(curl.OPT_STREAM_DEPENDS, e2) + end) +end + +function test_easy_setopt_stream_depends_3() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt{[curl.OPT_STREAM_DEPENDS] = e2} + end) +end + +function test_easy_setopt_stream_depends_4() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt{stream_depends = e2} + end) +end + +function test_easy_setopt_stream_depends_e_1() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt_stream_depends_e(e2) + end) +end + +function test_easy_setopt_stream_depends_e_2() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt(curl.OPT_STREAM_DEPENDS_E, e2) + end) +end + +function test_easy_setopt_stream_depends_e_3() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt{[curl.OPT_STREAM_DEPENDS_E] = e2} + end) +end + +function test_easy_setopt_stream_depends_e_4() + e1 = assert(scurl.easy()) + e2 = assert(scurl.easy()) + assert_pass(function() + e1:setopt{stream_depends_e = e2} + end) +end + +end + +function test_easy_setopt_share() + e1 = assert(scurl.easy()) + e2 = assert(scurl.share()) + assert_pass(function() + e1:setopt_share(e2) + end) +end + +end + +local _ENV = TEST_CASE'multi_iterator' if ENABLE then + +local url = GET_URL + +local c, t, m + +local function json_data() + return json.decode(table.concat(t)) +end + +function setup() + t = {} + m = assert(scurl.multi()) +end + +function teardown() + if m then m:close() end + if c then c:close() end + m, c, t = nil +end + +function test_add_handle() + local base_url = url .. '?key=' + local urls = { + base_url .. "1", + base_url .. "2", + "###" .. base_url .. "3", + base_url .. "4", + base_url .. "5", + } + + local i = 0 + local function next_easy() + i = i + 1 + local url = urls[i] + if url then + c = assert(scurl.easy{url = url}) + t = {} + return c + end + end + + assert_equal(m, m:add_handle(next_easy())) + + for data, type, easy in m:iperform() do + + if type == "done" or type == "error" then + assert_equal(urls[i], easy:getinfo_effective_url()) + assert_equal(easy, c) + easy:close() + c = nil + + if i == 3 then + if is_curl_ge(7, 62,0) then + assert_equal(curl.error(curl.ERROR_EASY, curl.E_URL_MALFORMAT), data) + else + assert_equal(curl.error(curl.ERROR_EASY, curl.E_UNSUPPORTED_PROTOCOL), data) + end + else + local data = json_data() + assert_table(data.args) + assert_equal(tostring(i), data.args.key) + end + + easy = next_easy() + if easy then m:add_handle(easy) end + end + + if type == "data" then table.insert(t, data) end + + end + + assert_equal(#urls + 1, i) + assert_nil(c) +end + +function test_info_read() + + local url = GET_URL .. '?key=1' + + c = assert(curl.easy{url=url, writefunction=function() end}) + assert_equal(m, m:add_handle(c)) + + while m:perform() > 0 do m:wait() end + + local h, ok, err = m:info_read() + assert_equal(c, h) + + local h, ok, err = m:info_read() + assert_equal(0, h) +end + +end + +local _ENV = TEST_CASE'multi memory leak' if ENABLE then + +local m + +function teardown() + if m then m:close() end + m = nil +end + +function test_basic() + local ptr do + local multi = assert(scurl.multi()) + ptr = weak_ptr(multi) + end + gc_collect() + + assert_nil(ptr.value) +end + +function test_socket_action() + local ptr do + local multi = assert(scurl.multi()) + multi:setopt_socketfunction(function() end) + ptr = weak_ptr(multi) + end + gc_collect() + + assert_nil(ptr.value) +end + +end + +local _ENV = TEST_CASE'form' if ENABLE then + +local post + +function teardown() + if post then post:free() end + post = nil +end + +function test_content_01() + post = assert(scurl.form{name01 = 'value01'}) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue01\r\n", data) + assert_match('name="name01"', data) +end + +function test_content_02() + post = assert(scurl.form{name02 = {'value02', type = text_plain}}) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue02\r\n", data) + assert_match('name="name02"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_content_03() + post = assert(scurl.form{name03 = {content = 'value03', headers = {"Content-Encoding: gzip"}}}) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue03\r\n", data) + assert_match('name="name03"', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_content_04() + post = assert(scurl.form{name04 = {'value04', type = text_plain, headers = {"Content-Encoding: gzip"}}}) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue04\r\n", data) + assert_match('name="name04"', data) + assert_match('Content%-Encoding: gzip\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_buffer_01() + post = assert(scurl.form{name01 = { + name = 'file01', + data = 'value01', + }}) + + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue01\r\n", data) + assert_match('name="name01"', data) + assert_match('filename="file01"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) +end + +function test_buffer_02() + post = assert(scurl.form{name02 = { + name = 'file02', + data = 'value02', + type = text_plain, + }}) + + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue02\r\n", data) + assert_match('name="name02"', data) + assert_match('filename="file02"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) +end + +function test_buffer_03() + post = assert(scurl.form{name03 = { + name = 'file03', + data = 'value03', + headers = {"Content-Encoding: gzip"}, + }}) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue03\r\n", data) + assert_match('name="name03"', data) + assert_match('filename="file03"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_buffer_04() + post = assert(scurl.form{name04 = { + name = 'file04', + data = 'value04', + type = text_plain, + headers = {"Content-Encoding: gzip"}, + }}) + + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue04\r\n", data) + assert_match('name="name04"', data) + assert_match('filename="file04"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_stream_01() + post = assert(scurl.form{name01 = { + stream = function() end, + length = 128, + }}) + local data = assert_string(post:get()) + assert_match('name="name01"', data) + assert_not_match('filename', data) +end + +function test_stream_02() + post = assert(scurl.form{name02 = { + name = 'file02', + stream = function() end, + length = 128, + }}) + local data = assert_string(post:get()) + assert_match('name="name02"', data) + assert_match('filename="file02"', data) +end + +function test_stream_03() + post = assert(scurl.form{name03 = { + name = 'file03', + stream = function() end, + length = 128, + type = text_plain, + }}) + + local data = assert_string(post:get()) + assert_match('name="name03"', data) + assert_match('filename="file03"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_stream_04() + post = assert(scurl.form{name04 = { + name = 'file04', + stream = function() end, + length = 128, + type = text_plain, + headers = {"Content-Encoding: gzip"}, + }}) + local data = assert_string(post:get()) + assert_match('name="name04"', data) + assert_match('filename="file04"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_stream_05() + post = assert(scurl.form{name05 = { + stream = { + length = function() return 128 end; + read = function() end; + } + }}) + local data = assert_string(post:get()) + assert_match('name="name05"', data) + assert_not_match('filename', data) +end + +function test_error() + assert_error(function() post = scurl.form{name = {content = 1}} end) + assert_error(function() post = scurl.form{name = {1}} end) + assert_error(function() post = scurl.form{name = {data = {}}} end) + assert_error(function() post = scurl.form{name = {file = true}} end) + assert_error(function() post = scurl.form{name = {stream = function() end}} end) + assert_error(function() post = scurl.form{name = {stream = {}}} end) + assert_error(function() post = scurl.form{name = {stream = { + read=function()end;length=function()end + }}}end) + assert_error(function() post = scurl.form{name = {stream = { + read=function()end;length=function() return "123" end + }}}end) + assert_error(function() post = scurl.form{name = {stream = { + read=function()end;length=function() return "hello" end + }}}end) +end + +function test_ignore_unknown() + post = assert(scurl.form{ + name01 = {}, + name02 = {name = "helo"}, + }) + local data = assert_string(post:get()) + assert_not_match('name="name01"', data) + assert_not_match('name="name02"', data) +end + +function test_empty() + post = assert(scurl.form{}) +end + +end + +RUN() diff --git a/watchdog/third_party/lua-curl/test/test_easy.lua b/watchdog/third_party/lua-curl/test/test_easy.lua new file mode 100644 index 0000000..2710764 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_easy.lua @@ -0,0 +1,1309 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end + +local curl = require "lcurl" +local scurl = require "lcurl.safe" +local json = require "dkjson" +local path = require "path" +local upath = require "path".new('/') +local utils = require "utils" +local fname = "./test.download" + +-- local GET_URL = "http://example.com" +-- local POST_URL = "http://httpbin.org/post" +local GET_URL = "http://127.0.0.1:7090/get" +local POST_URL = "http://127.0.0.1:7090/post" + +-- print("------------------------------------") +-- print("Lua version: " .. (_G.jit and _G.jit.version or _G._VERSION)) +-- print("cURL version: " .. curl.version()) +-- print("------------------------------------") +-- print("") + +local weak_ptr, gc_collect, is_curl_ge, read_file, stream, Stream, dump_request = + utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'read_file', 'stream', 'Stream', 'dump_request') + +local null = curl.null + +local ENABLE = true + +local _ENV = TEST_CASE'curl error' if ENABLE then + +function test_eq_with_same_cat() + local e1 = curl.error(curl.ERROR_EASY, curl.E_OK) + local e2 = curl.error(curl.ERROR_EASY, curl.E_OK) + assert_equal(e1, e2) +end + +function test_eq_with_different_cat() + local e1 = curl.error(curl.ERROR_EASY, curl.E_OK) + local e2 = curl.error(curl.ERROR_FORM, curl.E_OK) + + assert_equal(e1:no(), e2:no()) + assert_not_equal(e1, e2) +end + +function test_ctor_cat() + local e + + e = curl.error(curl.ERROR_EASY, curl.E_OK) + assert_equal(e:category(), curl.ERROR_EASY) + assert_equal(e:no(), curl.E_OK) + + e = curl.error(curl.ERROR_MULTI, curl.E_OK) + assert_equal(e:category(), curl.ERROR_MULTI) + assert_equal(e:no(), curl.E_OK) + + e = curl.error(curl.ERROR_SHARE, curl.E_OK) + assert_equal(e:category(), curl.ERROR_SHARE) + assert_equal(e:no(), curl.E_OK) + + e = curl.error(curl.ERROR_FORM, curl.E_OK) + assert_equal(e:category(), curl.ERROR_FORM) + assert_equal(e:no(), curl.E_OK) + + assert_error(function() + curl.error(nil, curl.E_OK) + end) + + assert_error(function() + curl.error('UNKNOWN STRING', curl.E_OK) + end) + +end + +end + +local _ENV = TEST_CASE'write_callback' if ENABLE then + +local c, f + +function teardown() + if f then f:close() end + os.remove(fname) + if c then c:close() end + f, c = nil +end + +function test_write_to_file() + f = assert(io.open(fname, "w+b")) + c = assert(curl.easy{ + url = GET_URL; + writefunction = f; + }) + + assert_equal(c, c:perform()) +end + +function test_write_abort_01() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = function(str) return #str - 1 end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) +end + +function test_write_abort_02() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = function(str) return false end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) +end + +function test_write_abort_03() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = function(str) return nil, "WRITEERROR" end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal("WRITEERROR", e) +end + +function test_write_abort_04() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = function(str) return nil end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) +end + +function test_reset_write_callback() + f = assert(io.open(fname, "w+b")) + c = assert(curl.easy{url = url}) + assert_equal(c, c:setopt_writefunction(f)) + assert_equal(c, c:setopt_writefunction(f.write, f)) + assert_equal(c, c:setopt_writefunction(print)) + assert_equal(c, c:setopt_writefunction(print, null)) + assert_equal(c, c:setopt_writefunction(null)) + assert_equal(c, c:setopt_writefunction(null, nil)) + assert_equal(c, c:setopt_writefunction(null, null)) + assert_error(function()c:setopt_writefunction()end) + assert_error(function()c:setopt_writefunction(nil)end) + assert_error(function()c:setopt_writefunction(nil, f)end) + assert_error(function()c:setopt_writefunction(null, {})end) + assert_error(function()c:setopt_writefunction(print, {}, nil)end) + assert_error(function()c:setopt_writefunction(print, {}, null)end) +end + +function test_write_pass_01() + c = assert(curl.easy{ + url = GET_URL; + writefunction = function(s) return #s end + }) + + assert_equal(c, c:perform()) +end + +function test_write_pass_02() + c = assert(curl.easy{ + url = GET_URL; + writefunction = function() return end + }) + + assert_equal(c, c:perform()) +end + +function test_write_pass_03() + c = assert(curl.easy{ + url = GET_URL; + writefunction = function() return true end + }) + + assert_equal(c, c:perform()) +end + +function test_write_coro() + local co1, co2 + local called + + co1 = coroutine.create(function() + c = assert(curl.easy{ + url = GET_URL; + writefunction = function() + called = coroutine.running() + return true + end + }) + coroutine.yield() + end) + + co2 = coroutine.create(function() + assert_equal(c, c:perform()) + end) + + coroutine.resume(co1) + coroutine.resume(co2) + + assert_equal(co2, called) +end + +function test_write_pass_null_context() + c = assert(curl.easy{ + url = GET_URL; + }) + + local context + assert_equal(c, c:setopt_writefunction(function(ctx) + context = ctx + return true + end, null)) + + assert_equal(c, c:perform()) + assert_equal(null, context) +end + +function test_write_pass_nil_context() + c = assert(curl.easy{ + url = GET_URL; + }) + + local context, called + assert_equal(c, c:setopt_writefunction(function(ctx) + context = ctx + called = true + return true + end, nil)) + + assert_equal(c, c:perform()) + assert_true(called) + assert_nil(context) +end + +end + +local _ENV = TEST_CASE'progress_callback' if ENABLE then + +local c + +local function pass() end + +function teardown() + if f then f:close() end + os.remove(fname) + if c then c:close() end + f, c = nil +end + +function test_abort_01() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() return false end + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_02() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() return 0 end + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_03() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() return nil end + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_04() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() return nil, "PROGRESSERROR" end + }) + + local _, e = assert_nil(c:perform()) + assert_equal("PROGRESSERROR", e) +end + +function test_abort_05() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() error( "PROGRESSERROR" )end + }) + + assert_error_match("PROGRESSERROR", function() + c:perform() + end) +end + +function test_pass_01() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() end + }) + + assert_equal(c, c:perform()) +end + +function test_pass_02() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() return true end + }) + + assert_equal(c, c:perform()) +end + +function test_pass_03() + c = assert(scurl.easy{ + url = GET_URL, + writefunction = pass, + noprogress = false, + progressfunction = function() return 1 end + }) + + assert_equal(c, c:perform()) +end + +end + +local _ENV = TEST_CASE'header_callback' if ENABLE then + +local c, f +local function dummy() end + +function teardown() + if c then c:close() end + f, c = nil +end + +function test_header_abort_01() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function(str) return #str - 1 end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) +end + +function test_header_abort_02() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function(str) return false end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) +end + +function test_header_abort_03() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function(str) return nil, "WRITEERROR" end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal("WRITEERROR", e) +end + +function test_header_abort_04() + c = assert(scurl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function(str) return nil end; + }) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) +end + +function test_reset_header_callback() + f = {header = function() end} + c = assert(curl.easy{url = url}) + assert_equal(c, c:setopt_headerfunction(f)) + assert_equal(c, c:setopt_headerfunction(f.header, f)) + assert_equal(c, c:setopt_headerfunction(print)) + assert_equal(c, c:setopt_headerfunction(null)) + assert_equal(c, c:setopt_headerfunction(null, nil)) + assert_equal(c, c:setopt_headerfunction(null, null)) + assert_error(function()c:setopt_headerfunction()end) + assert_error(function()c:setopt_headerfunction(nil)end) + assert_error(function()c:setopt_headerfunction(nil, f)end) + assert_error(function()c:setopt_headerfunction(null, {})end) + assert_error(function()c:setopt_headerfunction(print, {}, nil)end) +end + +function test_header_pass_01() + c = assert(curl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function(s) return #s end + }) + + assert_equal(c, c:perform()) +end + +function test_header_pass_02() + c = assert(curl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function() return end + }) + + assert_equal(c, c:perform()) +end + +function test_header_pass_03() + c = assert(curl.easy{ + url = GET_URL; + writefunction = dummy, + headerfunction = function() return true end + }) + + assert_equal(c, c:perform()) +end + + +end + +local _ENV = TEST_CASE'read_stream_callback' if ENABLE and is_curl_ge(7,30,0) then + +-- tested on WinXP(x32)/Win8(x64) libcurl/7.37.1 / libcurl/7.30.0 + +local url = POST_URL + +local m, c, f, t + +local function json_data() + return json.decode(table.concat(t)) +end + +function setup() + t = {} + f = assert(scurl.form()) + c = assert(scurl.easy{ + url = url, + timeout = 60, + }) + assert_equal(c, c:setopt_writefunction(table.insert, t)) +end + +function teardown() + if f then f:free() end + if c then c:close() end + if m then m:close() end + t, f, c, m = nil +end + +function test() + assert_equal(f, f:add_stream('SSSSS', stream('X', 128, 13))) + assert_equal(c, c:setopt_httppost(f)) + + -- should be called only stream callback + local read_called + assert_equal(c, c:setopt_readfunction(function() + read_called = true + end)) + + assert_equal(c, c:perform()) + + assert_nil(read_called) + + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + assert_table(data.form) + assert_equal(('X'):rep(128), data.form.SSSSS) +end + +function test_object() + local s = Stream('X', 128, 13) + + assert_equal(f, f:add_stream('SSSSS', s:size(), s)) + assert_equal(c, c:setopt_httppost(f)) + assert_equal(c, c:perform()) + + assert_equal(s, s.called_ctx) + + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + assert_table(data.form) + assert_equal(('X'):rep(128), data.form.SSSSS) +end + +function test_co_multi() + local s = Stream('X', 128, 13) + assert_equal(f, f:add_stream('SSSSS', s:size(), s)) + assert_equal(c, c:setopt_httppost(f)) + + m = assert(scurl.multi()) + assert_equal(m, m:add_handle(c)) + + co = coroutine.create(function() + while 1== m:perform() do end + end) + + coroutine.resume(co) + + assert_equal(co, s.called_co) + + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + assert_table(data.form) + assert_equal(('X'):rep(128), data.form.SSSSS) +end + +function test_co() + local s = Stream('X', 128, 13) + + assert_equal(f, f:add_stream('SSSSS', s:size(), s)) + assert_equal(c, c:setopt_httppost(f)) + + co = coroutine.create(function() + assert_equal(c, c:perform()) + end) + + coroutine.resume(co) + + assert_equal(co, s.called_co) + + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + assert_table(data.form) + assert_equal(('X'):rep(128), data.form.SSSSS) + +end + +function test_abort_01() + assert_equal(f, f:add_stream('SSSSS', 128 * 1024, function() end)) + assert_equal(c, c:setopt_timeout(5)) + assert_equal(c, c:setopt_httppost(f)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_OPERATION_TIMEDOUT), e) +end + +function test_abort_02() + assert_equal(f, f:add_stream('SSSSS', 128, function() return nil, "READERROR" end)) + assert_equal(c, c:setopt_httppost(f)) + + local _, e = assert_nil(c:perform()) + assert_equal("READERROR", e) +end + +function test_abort_03() + assert_equal(f, f:add_stream('SSSSS', 128, function() return 1 end)) + assert_equal(c, c:setopt_httppost(f)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_04() + assert_equal(f, f:add_stream('SSSSS', 128, function() return true end)) + assert_equal(c, c:setopt_httppost(f)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_05() + assert_equal(f, f:add_stream('SSSSS', 128, function() error("READERROR") end)) + assert_equal(c, c:setopt_httppost(f)) + + assert_error_match("READERROR", function() c:perform() end) +end + +function test_abort_06() + assert_equal(f, f:add_stream('SSSSS', 128, function() return false end)) + assert_equal(c, c:setopt_httppost(f)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_pass_01() + assert_equal(c, c:setopt_timeout(10)) + assert_equal(f, f:add_stream('SSSSS', 128, function() return nil end)) + assert_equal(c, c:setopt_httppost(f)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_OPERATION_TIMEDOUT), e) +end + +function test_pause() + local counter = 0 + assert_equal(f, f:add_stream('SSSSS', 128, function() + if counter == 0 then + counter = counter + 1 + return curl.READFUNC_PAUSE + end + if counter == 1 then + counter = counter + 1 + return ('X'):rep(128) + end + return '' + end)) + + assert_equal(c, c:setopt_progressfunction(function() + if counter == 1 then + c:pause(curl.PAUSE_CONT) + end + end)) + + assert_equal(c, c:setopt_noprogress(false)) + + assert_equal(c, c:setopt_httppost(f)) + + assert_equal(c, c:perform()) + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + assert_table(data.form) + assert_equal(('X'):rep(128), data.form.SSSSS) +end + +end + +local _ENV = TEST_CASE'read_callback' if ENABLE then + +local uname = upath:normalize(path.fullpath(fname)) + +local url = "FILE:///" .. uname + +local c + +function setup() + c = assert(scurl.easy{ + url = url, + upload = true, + }) +end + +function teardown() + os.remove(fname) + if c then c:close() end + c = nil +end + +function test_abort_01() +-- assert_equal(c, c:setopt_readfunction(function() end)) +-- +-- local _, e = assert_nil(c:perform()) +-- assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_02() + assert_equal(c, c:setopt_readfunction(function() return nil, "READERROR" end)) + + local _, e = assert_nil(c:perform()) + assert_equal("READERROR", e) +end + +function test_abort_03() + assert_equal(c, c:setopt_readfunction(function() return 1 end)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_04() + assert_equal(c, c:setopt_readfunction(function() return true end)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_abort_05() + assert_equal(c, c:setopt_readfunction(function() error("READERROR") end)) + + assert_error_match("READERROR", function() c:perform() end) +end + +function test_abort_06() + assert_equal(c, c:setopt_readfunction(function() return false end)) + + local _, e = assert_nil(c:perform()) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) +end + +function test_pause() + +-- BUG? +-- c:perform() returns curl.E_READ_ERROR after readfunction return curl.READFUNC_PAUSE +-- +-- OS version : Linux Mint 17 (x86_64) +-- cURL version : libcurl/7.35.0 OpenSSL/1.0.1f zlib/1.2.8 libidn/1.28 librtmp/2.3 +-- version_info("host"): x86_64-pc-linux-gnu +-- +-- OS version : Windows XP (x86_64) +-- cURL version : libcurl/7.38.0 OpenSSL/1.0.1c zlib/1.2.7 WinIDN +-- cURL version : libcurl/7.37.1 OpenSSL/1.0.1c zlib/1.2.7 WinIDN +-- version_info("host"): i386-pc-win32 +-- +-- Works correctly on +-- (same binary as with libcurl 7.38.0/7.37.1) +-- +-- OS version : Windows XP (x86_64) +-- cURL version : libcurl/7.30.0 OpenSSL/0.9.8y zlib/1.2.7 +-- version_info("host"): i386-pc-win32 +-- + + local counter = 0 + assert_equal(c, c:setopt_readfunction(function() + if counter == 0 then + counter = counter + 1 + return curl.READFUNC_PAUSE + end + if counter == 1 then + counter = counter + 1 + return ('X'):rep(128) + end + return '' + end)) + + assert_equal(c, c:setopt_progressfunction(function() + if counter == 1 then + c:pause(curl.PAUSE_CONT) + end + end)) + + assert_equal(c, c:setopt_noprogress(false)) + + local ok, err = c:perform() + + if (not ok) and err:name() == "READ_ERROR" then + skip("TODO check pause on readfunction") + end + + assert_equal(c, ok, err) + + assert_equal(0, c:getinfo_response_code()) +end + +function test_readbuffer() + local flag = false + local N + assert_equal(c, c:setopt_readfunction(function(n) + if not flag then + flag = true + N = math.floor(n*2 + n/3) + assert(N > n) + return ("s"):rep(N) + end + return '' + end)) + + assert_equal(c, c:perform()) + c:close() + local data = read_file(fname) + assert_equal(N, #data) + assert_equal(("s"):rep(N), data) +end + +function test_pass_01() + -- We need this to support file:read() method which returns nil as EOF + assert_equal(c, c:setopt_readfunction(function() return nil end)) + + assert_equal(c, c:perform()) + c:close() + local data = read_file(fname) + assert_equal(0, #data) +end + +function test_pass_02() + local counter = 10 + assert_equal(c, c:setopt_readfunction(function() + if counter > 0 then + counter = counter - 1 + return 'a' + end + end)) + + assert_equal(c, c:perform()) + c:close() + local data = read_file(fname) + assert_equal(('a'):rep(10), data) +end + +end + +local _ENV = TEST_CASE'escape' if ENABLE then + +local c + +function teardown() + if c then c:close() end + f, c = nil +end + +function test() + local e = "This%2Bis%2Ba%2Bsimple%2B%2526%2Bshort%2Btest." + local d = "This+is+a+simple+%26+short+test." + c = assert(curl.easy()) + assert_equal(e, c:escape(d)) + assert_equal(d, c:unescape(e)) +end + +end + +local _ENV = TEST_CASE'setopt_form' if ENABLE then + +local c + +function teardown() + if c then c:close() end + c = nil +end + +function test() + local pfrom, e + do + local form = curl.form() + e = curl.easy{httppost = form} + pfrom = weak_ptr(form) + end + + gc_collect() + assert(pfrom.value) + + e:setopt_httppost(curl.form()) + + gc_collect() + assert(not pfrom.value) +end + +function test_unset() + local pfrom, e + do + local form = curl.form() + e = curl.easy{httppost = form} + pfrom = weak_ptr(form) + end + + gc_collect() + assert(pfrom.value) + + assert_equal(e, e:unsetopt_httppost()) + + gc_collect() + assert(not pfrom.value) +end + +function test_reset() + local pfrom, e + do + local form = curl.form() + e = curl.easy{httppost = form} + pform = weak_ptr(form) + end + + gc_collect() + assert(pform.value) + + assert_equal(e, e:reset()) + + gc_collect() + assert(not pform.value) +end + +end + +local _ENV = TEST_CASE'setopt_postfields' if ENABLE then + +local c + +function teardown() + if c then c:close() end + c = nil +end + +function test() + + do local fields = {} + for i = 1, 100 do fields[#fields + 1] = string.format('key%d=value%d', i, i) end + fields = table.concat(fields, '&') + c = assert(curl.easy{ + url = POST_URL, + postfields = fields, + writefunction = function()end, + }) + end + + -- call gc to try clear `fields` string + for i = 1, 4 do collectgarbage"collect" end + + c:perform() +end + +function test_unset() + local pfields + + do local fields = {} + for i = 1, 100 do fields[#fields + 1] = string.format('key%d=value%d', i, i) end + fields = table.concat(fields, '&') + c = assert(curl.easy{ + url = POST_URL, + postfields = fields, + writefunction = function()end, + }) + pfields = weak_ptr(fields) + end + + -- call gc to try clear `fields` string + for i = 1, 4 do collectgarbage"collect" end + assert_string(pfields.value) + + assert_equal(c, c:unsetopt_postfields()) + + -- @todo check internal storage because gc really do not clear `weak` string + -- for i = 1, 4 do collectgarbage"collect" end + -- assert_nil(pfields.value) + + -- c:perform() +end + +end + +local _ENV = TEST_CASE'setopt_user_data' if ENABLE then + +local c + +function teardown() + if c then c:close() end + c = nil +end + +function test_data() + c = assert(curl.easy()) + assert_nil(c:getdata()) + c:setdata("hello") + assert_equal("hello", c:getdata()) +end + +function test_cleanup() + local ptr do + local t = {} + local e = curl.easy():setdata(t) + ptr = weak_ptr(t) + gc_collect() + + assert_equal(t, ptr.value) + end + + gc_collect() + assert_nil(ptr.value) +end + +end + +local _ENV = TEST_CASE'multi_add_remove' if ENABLE then + +local m, c + +function setup() + m = assert(scurl.multi()) +end + +function teardown() + if c then c:close() end + if m then m:close() end + m, c = nil +end + +function test_remove_unknow_easy() + c = assert(scurl.easy()) + assert_equal(m, m:remove_handle(c)) +end + +function test_double_remove_easy() + c = assert(scurl.easy()) + assert_equal(m, m:add_handle(c)) + assert_equal(m, m:remove_handle(c)) + assert_equal(m, m:remove_handle(c)) +end + +function test_double_add_easy() + c = assert(scurl.easy()) + assert_equal(m, m:add_handle(c)) + assert_nil(m:add_handle(c)) +end + +end + +local _ENV = TEST_CASE'unset_callback_ctx' if ENABLE then + +local HSTS = curl.version_info().features.HSTS + +local c + +function setup() + c = assert(scurl.easy()) +end + +function teardown() + if c then c:close() end + c = nil +end + +local function test_cb(name) + local set, unset = 'setopt_' .. name, 'unsetopt_' .. name + + set = assert_function(c[set], set) + unset = assert_function(c[unset], unset) + + local pctx + do local ctx = {} + pctx = weak_ptr(ctx) + assert(set(c, function() end, ctx)) + end + + gc_collect() + assert_table(pctx.value) + + unset(c) + + gc_collect() + assert_nil(pctx.value) + + do local ctx = {} + pctx = weak_ptr(ctx) + assert(set(c, function() end, ctx)) + end + + gc_collect() + assert_table(pctx.value) + + c:reset() + + gc_collect() + assert_nil(pctx.value) +end + +function test_read() test_cb('readfunction') end +function test_write() test_cb('writefunction') end +function test_header() test_cb('headerfunction') end +function test_progress() test_cb('progressfunction') end +function test_seek() test_cb('seekfunction') end +function test_debug() test_cb('debugfunction') end +function test_fnmatch() test_cb('fnmatch_function') end +function test_chunk_bgn() test_cb('chunk_bgn_function') end +function test_chunk_end() test_cb('chunk_end_function') end + +if curl.OPT_HSTSREADFUNCTION and HSTS then +function test_hstsreadfunction() test_cb('hstsreadfunction') end +function test_hstswritefunction() test_cb('hstswritefunction') end +end + +end + +local _ENV = TEST_CASE'set_slist' if ENABLE then + +local c + +function teardown() + if c then c:close() end + c = nil +end + +function test_set() + c = curl.easy() + c:setopt_httpheader({'X-Custom: value'}) + local body, headers = assert_string(dump_request(c)) + assert_match("X%-Custom:%s*value\r\n", headers) +end + +function test_unset() + c = curl.easy() + c:setopt_httpheader({'X-Custom: value'}) + c:unsetopt_httpheader() + local body, headers = assert_string(dump_request(c)) + assert_not_match("X%-Custom:%s*value\r\n", headers) +end + +function test_set_empty_array() + c = curl.easy() + c:setopt_httpheader({'X-Custom: value'}) + c:setopt_httpheader({}) + local body, headers = assert_string(dump_request(c)) + assert_not_match("X%-Custom:%s*value\r\n", headers) +end + +function test_reset_slist() + c = curl.easy { + httpheader = {'X-Foo: 1'}, + resolve = {'example.com:80:127.0.0.1'} + } + + c:reset() + + c:setopt{ + httpheader = {'X-Foo: 2'}, + resolve = {'example.com:80:127.0.0.1'} + } + + local body, headers = assert_string(dump_request(c)) + assert_match("X%-Foo:%s2\r\n", headers) +end + +end + +local _ENV = TEST_CASE'set_null' if ENABLE then + +local c, m + +function teardown() + if c then c:close() end + if m then m:close() end + m, c = nil +end + +function test_string() + c = curl.easy() + c:setopt_accept_encoding('gzip') + local body, headers = assert_string(dump_request(c)) + assert_match("Accept%-Encoding:%s*gzip", headers) + + c:setopt_accept_encoding(null) + body, headers = assert_string(dump_request(c)) + assert_not_match("Accept%-Encoding:%s*gzip", headers) +end + +function test_string_via_table() + c = curl.easy() + c:setopt_accept_encoding('gzip') + local body, headers = assert_string(dump_request(c)) + assert_match("Accept%-Encoding:%s*gzip", headers) + + c:setopt{ accept_encoding = null } + body, headers = assert_string(dump_request(c)) + assert_not_match("Accept%-Encoding:%s*gzip", headers) +end + +function test_slist() + c = curl.easy() + c:setopt_httpheader({'X-Custom: value'}) + c:setopt_httpheader(null) + local body, headers = assert_string(dump_request(c)) + assert_not_match("X%-Custom:%s*value\r\n", headers) +end + +function test_slist_via_table() + c = curl.easy() + c:setopt_httpheader({'X-Custom: value'}) + c:setopt{httpheader = null} + local body, headers = assert_string(dump_request(c)) + assert_not_match("X%-Custom:%s*value\r\n", headers) +end + +function test_multi_set_array() + m = curl.multi() + m:setopt_pipelining_site_bl{ + '127.0.0.1' + } + assert_equal(m, m:setopt_pipelining_site_bl(null)) +end + +end + +local _ENV = TEST_CASE'trailer_callback' if ENABLE and is_curl_ge(7,64,0) then + +local url = POST_URL + +local m, c, t + +local function json_data() + return json.decode(table.concat(t)) +end + +local treader = function(t) + local i = 0 + return function() + i = i + 1 + return t[i] + end +end + +function setup() + t = {} + c = assert(scurl.easy{ + url = url, + post = true, + httpheader = {"Transfer-Encoding: chunked"}, + readfunction = treader {'a=1&', 'b=2&'}, + timeout = 60, + }) + assert_equal(c, c:setopt_writefunction(table.insert, t)) +end + +function teardown() + if c then c:close() end + if m then m:close() end + t, c, m = nil +end + +local empty_responses = { + {'no_response', function() end}, + {'nil_response', function() return nil end}, + {'null_response', function() return curl.null end}, + {'true_response', function() return true end}, + {'empty_array', function() return {} end}, +} + +local abort_responses = { + {'false_response', function() return false end}, + {'nil_with_error_response', function() return nil, 'error message' end}, + {'numeric_response_0', function() return 0 end}, + {'numeric_response_1', function() return 1 end}, +} + +for _, response in ipairs(empty_responses) do + _ENV[ 'test_' .. response[1] ] = function() + local trailer_called = 0 + assert_equal(c, c:setopt_trailerfunction(function() + trailer_called = trailer_called + 1 + return response[2]() + end)) + + assert_equal(c, c:perform()) + + assert_equal(1, trailer_called) + + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + + assert_equal('1', data.form.a) + assert_equal('2', data.form.b) + end +end + +for _, response in ipairs(abort_responses) do + _ENV[ 'test_' .. response[1] ] = function() + local trailer_called = 0 + assert_equal(c, c:setopt_trailerfunction(function() + trailer_called = trailer_called + 1 + return response[2]() + end)) + + local ok, err = assert_nil(c:perform()) + assert_equal(1, trailer_called) + assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), err) + end +end + +function test_send_header() + local trailer_called = 0 + assert_equal(c, c:setopt_trailerfunction(function() + trailer_called = trailer_called + 1 + return {'x-trailer-header: value'} + end)) + + assert_equal(c, c:perform()) + + assert_equal(1, trailer_called) + + assert_equal(200, c:getinfo_response_code()) + local data = assert_table(json_data()) + + assert_equal('1', data.form.a) + assert_equal('2', data.form.b) + assert_equal('value', data.headers['x-trailer-header']) +end + +end + +RUN() diff --git a/watchdog/third_party/lua-curl/test/test_form.lua b/watchdog/third_party/lua-curl/test/test_form.lua new file mode 100644 index 0000000..08836cc --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_form.lua @@ -0,0 +1,490 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end + +local utils = require "utils" + +-- Bug. libcurl 7.56.0 does not add `Content-Type: text/plain` +local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' + +local curl = require "lcurl" + +local _ENV = TEST_CASE'add_content' do + +local post + +function setup() + post = curl.form() +end + +function teardown() + if post then post:free() end + post = nil +end + +local function F(...) + local data = assert_string(post:get()) + post:free() + post = nil + return data +end + +function test_01() + assert_equal(post, post:add_content('name01', 'value01')) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue01\r\n", data) + assert_match('name="name01"', data) +end + +function test_02() + assert_equal(post, post:add_content('name02', 'value02', text_plain)) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue02\r\n", data) + assert_match('name="name02"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_03() + assert_equal(post, post:add_content('name03', 'value03', {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue03\r\n", data) + assert_match('name="name03"', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_04() + assert_equal(post, post:add_content('name04', 'value04', text_plain, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue04\r\n", data) + assert_match('name="name04"', data) + assert_match('Content%-Encoding: gzip\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_error() + assert_error(function() post:add_content() end) + assert_error(function() post:add_stream(nil) end) + assert_error(function() post:add_stream('name01') end) +end + +end + +local _ENV = TEST_CASE'add_buffer' do + +local post + +function setup() + post = curl.form() +end + +function teardown() + if post then post:free() end + post = nil +end + +function test_01() + assert_equal(post, post:add_buffer('name01', 'file01', 'value01')) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue01\r\n", data) + assert_match('name="name01"', data) + assert_match('filename="file01"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) +end + +function test_02() + assert_equal(post, post:add_buffer('name02', 'file02', 'value02', text_plain)) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue02\r\n", data) + assert_match('name="name02"', data) + assert_match('filename="file02"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) +end + +function test_03() + assert_equal(post, post:add_buffer('name03', 'file03', 'value03', {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue03\r\n", data) + assert_match('name="name03"', data) + assert_match('filename="file03"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_04() + assert_equal(post, post:add_buffer('name04', 'file04', 'value04', text_plain, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue04\r\n", data) + assert_match('name="name04"', data) + assert_match('filename="file04"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_05() + assert_equal(post, post:add_buffer('name05', 'file05', 'value05', nil, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + assert_match("\r\n\r\nvalue05\r\n", data) + assert_match('name="name05"', data) + assert_match('filename="file05"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_error() + assert_error(function() post:add_buffer() end) + assert_error(function() post:add_buffer(nil) end) + assert_error(function() post:add_buffer('name01') end) + assert_error(function() post:add_buffer('name02', 'file02') end) + assert_error(function() post:add_buffer('name03', 'file03', nil) end) + assert_error(function() post:add_buffer('name04', nil, 'value04') end) +end + +end + +local _ENV = TEST_CASE'add_stream' do + +local post + +local dummy = function()end + +local stream = function() return 128, dummy end + +function setup() + post = curl.form() +end + +function teardown() + if post then post:free() end + post = nil +end + +function test_01() + assert_equal(post, post:add_stream('name01', stream())) + local data = assert_string(post:get()) + assert_match('name="name01"', data) + assert_not_match('filename', data) +end + +function test_02() + assert_equal(post, post:add_stream('name02', 'file02', stream())) + local data = assert_string(post:get()) + assert_match('name="name02"', data) + assert_match('filename="file02"', data) +end + +function test_03() + assert_equal(post, post:add_stream('name03', 'file03', text_plain, stream())) + local data = assert_string(post:get()) + assert_match('name="name03"', data) + assert_match('filename="file03"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_04() + assert_equal(post, post:add_stream('name04', 'file04', text_plain, {"Content-Encoding: gzip"}, stream())) + local data = assert_string(post:get()) + assert_match('name="name04"', data) + assert_match('filename="file04"', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_05() + assert_equal(post, post:add_stream('name05', 'file05', {"Content-Encoding: gzip"}, stream())) + local data = assert_string(post:get()) + assert_match('name="name05"', data) + assert_match('filename="file05"', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_06() + assert_equal(post, post:add_stream('name06', {"Content-Encoding: gzip"}, stream())) + local data = assert_string(post:get()) + assert_match('name="name06"', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_07() + assert_equal(post, post:add_stream('name07', 'file07', nil, {"Content-Encoding: gzip"}, stream())) + local data = assert_string(post:get()) + assert_match('name="name07"', data) + assert_match('filename="file07"', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_08() + assert_equal(post, post:add_stream('name08', nil, nil, {"Content-Encoding: gzip"}, stream())) + local data = assert_string(post:get()) + assert_match('name="name08"', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_09() + assert_equal(post, post:add_stream('name09', nil, nil, nil, stream())) + local data = assert_string(post:get()) + assert_match('name="name09"', data) +end + +function test_error() + assert_error(function() post:add_stream('name01', dummy) end) + assert_error(function() post:add_stream('name02', 10) end) + assert_error(function() post:add_stream('name03', "10", dummy) end) +end + +function test_add_several() + assert_equal(post, post:add_stream('name01', stream())) + assert_equal(post, post:add_stream('name02', 'file02', stream())) + local data = assert_string(post:get()) + assert_match('name="name01"', data) + assert_match('name="name02"', data) + assert_match('filename="file02"', data) +end + +end + +local _ENV = TEST_CASE'add_file' do + +local post + +local form_file = "file.form" +local form_path = "./" .. form_file +local form_data = "some values" + +local function mkfile(P, data) + local f, e = io.open(P, "w+b") + if not f then return nil, err end + if data then assert(f:write(data)) end + f:close() + return P +end + +local function check_form(data) + assert_match('name="nameXX"', data) + assert_match("\r\n\r\n" .. form_data .."\r\n", data) +end + +function setup() + post = curl.form() + assert(mkfile(form_path, form_data)) +end + +function teardown() + if post then post:free() end + post = nil + os.remove(form_file) +end + +function test_01() + assert_equal(post, post:add_file('nameXX', form_path)) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. form_file .. '"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) +end + +function test_02() + assert_equal(post, post:add_file('nameXX', form_path, text_plain)) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. form_file .. '"', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_03() + assert_equal(post, post:add_file('nameXX', form_path, text_plain, 'renamedfile')) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. 'renamedfile' .. '"', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_04() + assert_equal(post, post:add_file('nameXX', form_path, nil, 'renamedfile')) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. 'renamedfile' .. '"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) +end + +function test_05() + assert_equal(post, post:add_file('nameXX', form_path, text_plain, 'renamedfile', {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. 'renamedfile' .. '"', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_05() + assert_equal(post, post:add_file('nameXX', form_path, text_plain, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. form_file .. '"', data) + assert_not_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_06() + assert_equal(post, post:add_file('nameXX', form_path, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. form_file .. '"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_07() + assert_equal(post, post:add_file('nameXX', form_path, nil, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. form_file .. '"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_08() + assert_equal(post, post:add_file('nameXX', form_path, nil, nil, {"Content-Encoding: gzip"})) + local data = assert_string(post:get()) + check_form(data) + assert_match('filename="' .. form_file .. '"', data) + assert_match('Content%-Type: application/octet%-stream\r\n', data) + assert_match('Content%-Encoding: gzip\r\n', data) +end + +function test_error() + assert_error(function() + assert_equal(post, post:add_file('nameXX', text_plain, form_path)) + post:get() + end) +end + +end + +local _ENV = TEST_CASE'get' do + +local post + +function setup() + post = curl.form() +end + +function teardown() + if post then post:free() end + post = nil +end + +local function check_form(data) + assert_match("\r\n\r\nvalueXX\r\n", data) + assert_match('name="nameXX"', data) + assert_match('Content%-Encoding: gzip\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_writer_01() + local t = {} + local function writer(str, ...) + assert_equal(0, select("#", ...)) + t[#t+1] = str + end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + assert_equal(post, post:get(writer)) + check_form(table.concat(t)) +end + +function test_writer_02() + local t = {} + local function writer(str, ...) + assert_equal(0, select("#", ...)) + t[#t+1] = str + return true + end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + assert_equal(post, post:get(writer)) + check_form(table.concat(t)) +end + +function test_writer_03() + local t = {} + local function writer(str, ...) + assert_equal(0, select("#", ...)) + t[#t+1] = str + return #str + end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + assert_equal(post, post:get(writer)) + check_form(table.concat(t)) +end + +function test_writer_context() + local t = {} + local function writer(T, str, ...) + assert_equal(t, T) + assert_equal(0, select("#", ...)) + T[#T+1] = str + end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + assert_equal(post, post:get(writer, t)) + local data = table.concat(t) + assert_match("\r\n\r\nvalueXX\r\n", data) + assert_match('name="nameXX"', data) + assert_match('Content%-Encoding: gzip\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) +end + +function test_abort_01() + local err = {} + local function writer() return nil, err end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + local _, e = assert_nil(post:get(writer)) + assert_equal(err, e) +end + +function test_abort_02() + local function writer() return 0 end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + local _, e = assert_nil(post:get(writer)) + assert_nil(e) +end + +function test_abort_03() + local function writer() return nil end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + local _, e = assert_nil(post:get(writer)) + assert_nil(e) +end + +function test_abort_04() + local function writer() return false end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + local _, e = assert_nil(post:get(writer)) + assert_nil(e) +end + +function test_error() + local err = {} + local function writer() error("WRITEERROR") end + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) + assert_error_match("WRITEERROR", function() + post:get(writer) + end) +end + +end + +RUN() diff --git a/watchdog/third_party/lua-curl/test/test_mime.lua b/watchdog/third_party/lua-curl/test/test_mime.lua new file mode 100644 index 0000000..c3d5ef2 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_mime.lua @@ -0,0 +1,631 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end + +local curl = require "lcurl" +local utils = require "utils" + +local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime') + +local GET_URL = 'http://127.0.0.1:7090/get' + +local null = curl.null + +local function is_freed(c) + return not not string.find(tostring(c), '%(freed%)') +end + +local _ENV = TEST_CASE'mime lifetime' if not curl.OPT_MIMEPOST then +function test() skip("MIMI API supports since cURL 7.56.0") end +else + +local easy, mime + +function setup() + easy = curl.easy() +end + +function teardown() + if easy then easy:close() end + if mime then mime:free() end + easy, mime = nil +end + +function test_preserve_mime_part_reference() + -- mime part stores references to all parts + + local mime, part = easy:mime() do + part = weak_ptr(mime:addpart()) + end + gc_collect() + + assert_not_nil(part.value) + + mime = nil + gc_collect() + + assert_nil(part.value) + + easy:close() +end + +function test_free_mime_subparts() + -- when free root free all nodes + + -- mime + -- +- part3 + -- +- alt + -- +- part1 + -- +- part2 + + local mime, a, p1, p2, p3 = easy:mime() do + + local alt = easy:mime() + + local part1 = alt:addpart() + + local part2 = alt:addpart() + + local part3 = mime:addpart() + part3:subparts(alt, "multipart/alternative") + + a = weak_ptr(alt) + p1 = weak_ptr(part1) + p2 = weak_ptr(part2) + p3 = weak_ptr(part3) + end + + gc_collect() + + assert_not_nil(a.value) + assert_not_nil(p1.value) + assert_not_nil(p2.value) + assert_not_nil(p3.value) + + -- reamove reference to root node + mime = nil + gc_collect(4) + + assert_nil(a.value) + assert_nil(p1.value) + assert_nil(p2.value) + assert_nil(p3.value) + + easy:close() +end + +function test_preserve_mime_subparts() + -- if we have references to subnode but we free root + -- then all references have to become to invalid + + -- mime + -- +- part3 + -- +- alt + -- +- part1 + -- +- part2 + + local easy = curl.easy() + + local mime, a, p1, p2, p3 = easy:mime() do + + local alt = easy:mime() + + local part1 = alt:addpart() + + local part2 = alt:addpart() + + local part3 = mime:addpart() + part3:subparts(alt, "multipart/alternative") + + a = weak_ptr(alt) + p1 = weak_ptr(part1) + p2 = weak_ptr(part2) + p3 = weak_ptr(part3) + end + + gc_collect() + + assert_not_nil(a.value) + assert_not_nil(p1.value) + assert_not_nil(p2.value) + assert_not_nil(p3.value) + + -- save reference to subnode + local subnode = a.value + + mime = nil + + -- in this case call `free` to root node. + -- there no way to get reference to this node from child + -- so there no way to use it. + gc_collect() + + -- libcurl still close all childs + -- so all reference are invalid + + assert_not_nil(a.value) + assert_not_nil(is_freed(a.value)) + assert_nil(p1.value) + assert_nil(p2.value) + assert_nil(p3.value) + + easy:close() +end + +function test_preserve_mime_by_easy() + + local mime do + mime = weak_ptr(easy:mime()) + easy:setopt_mimepost(mime.value) + end + + gc_collect() + + assert_not_nil(mime.value) + + easy:unsetopt_mimepost() + + gc_collect() + + assert_nil(mime.value) +end + +function test_mime_does_not_ref_to_easy() + -- exists of mime object does not prevent easy from GC + + local mime = easy:mime() + local peasy = weak_ptr(easy) + easy = nil + + gc_collect() + + assert_nil(peasy.value) +end + +function test_mimepost_does_not_ref_to_easy() + -- exists of mime object does not prevent easy from GC + + -- create mime and set it as mimepost option + local mime = easy:mime() + easy:setopt_mimepost(mime) + + local peasy = weak_ptr(easy) + easy = nil + + gc_collect() + + assert_nil(peasy.value) +end + +function test_cleanup_on_easy_reset() + + local mime do + mime = weak_ptr(easy:mime()) + easy:setopt_mimepost(mime.value) + end + + gc_collect() + + assert_not_nil(mime.value) + + easy:reset() + + gc_collect(10) + + assert_nil(mime.value) + + easy:setopt{url = GET_URL, writefunction = function() end} + easy:perform() +end + +end + +local _ENV = TEST_CASE'mime basic' if not curl.OPT_MIMEPOST then +function test() skip("MIMI API supports since cURL 7.56.0") end +else + +local easy, mime + +local function dump_mime(mime) + return dump_mime_(easy, mime, GET_URL) +end + +function setup() + easy = curl.easy() + mime = easy:mime() +end + +function teardown() + if easy then easy:close() end + if mime then mime:free() end + easy, mime = nil +end + +function test_data() + mime:addpart():data('hello') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) +end + +function test_data_type() + mime:addpart():data('hello', 'test/html') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) +end + +function test_data_type_name() + mime:addpart():data('hello', 'test/html', 'test') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-%sname="test"', info) +end + +function test_data_name() + mime:addpart():data('hello', nil, 'test') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Disposition:.-%sname="test"', info) +end + +function test_data_filename() + mime:addpart():data('hello', nil, nil, 'test.html') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Disposition:.-%sfilename="test%.html"', info) +end + +function test_data_should_not_unset_on_nil() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }) + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + + part:data('world', nil, 'test2') + info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nworld', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test2"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + + part:data('!!!!!', 'text/xml', nil) + info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\n!!!!!', info) + assert_match('Content%-Type:%s+text/xml', info) + assert_match('Content%-Disposition:.-name="test2"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + + part:data('!!!!!!!', 'text/xml', nil, nil) + info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\n!!!!!', info) + assert_match('Content%-Type:%s+text/xml', info) + assert_match('Content%-Disposition:.-name="test2"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_data_headers() + mime:addpart():data('hello', { + 'X-Custom-Header: hello' + }) + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_name() + mime:addpart():data('hello', 'test/html', 'test'):name(false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) +end + +function test_unset_name_by_null() + mime:addpart():data('hello', 'test/html', 'test'):name(null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) +end + +function test_unset_type() + mime:addpart():data('hello', 'test/html'):type(false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+test/html', info) +end + +function test_unset_type_by_null() + mime:addpart():data('hello', 'test/html'):type(null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+test/html', info) +end + +function test_unset_headers() + mime:addpart():data('hello', 'test/html',{ + 'X-Custom-Header: hello' + }):headers(false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_headers_by_null() + mime:addpart():data('hello', 'test/html',{ + 'X-Custom-Header: hello' + }):headers(null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_headers_by_empty_array() + mime:addpart():data('hello', 'test/html',{ + 'X-Custom-Header: hello' + }):headers({}) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data() + mime:addpart():data('hello', 'test/html', 'test'):data(false) + + local info = assert_string(dump_mime(mime)) + assert_not_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) +end + +function test_unset_data_by_null() + mime:addpart():data('hello', 'test/html', 'test'):data(null) + + local info = assert_string(dump_mime(mime)) + assert_not_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) +end + +function test_unset_data_type_1() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_type_1_by_null() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_type_2() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', false, nil, nil) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_name_1() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_name_1_by_null() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_name_2() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, false, nil) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_header() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, nil, nil, false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_header_by_null() + local part = mime:addpart():data('hello', 'test/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, nil, nil, null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_filename_1() + local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', { + 'X-Custom-Header: hello' + }):data('hello', nil, nil, false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-%sname="test"', info) + assert_not_match('Content%-Disposition:.-%sfilename="test%.html"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_filename_1_by_null() + local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', { + 'X-Custom-Header: hello' + }):data('hello', nil, nil, null) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-%sname="test"', info) + assert_not_match('Content%-Disposition:.-%sfilename="test%.html"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_fail_pass_nil_as_first_arg() + local part = mime:addpart() + assert_error(function() part:data() end) + assert_error(function() part:data(nil) end) + + assert_error(function() part:name() end) + assert_error(function() part:name(nil) end) + + assert_error(function() part:type() end) + assert_error(function() part:type(nil) end) + + assert_error(function() part:headers() end) + assert_error(function() part:headers(nil) end) +end + +end + +local _ENV = TEST_CASE'mime addpart' if not curl.OPT_MIMEPOST then +function test() skip("MIMI API supports since cURL 7.56.0") end +else + +local easy, mime + +local function dump_mime(mime) + return dump_mime_(easy, mime, GET_URL) +end + +function setup() + easy = curl.easy() + mime = easy:mime() +end + +function teardown() + if easy then easy:close() end + if mime then mime:free() end + easy, mime = nil +end + +function test_empty_table() + assert(mime:addpart{}) +end + +function test_pass_args() + assert(mime:addpart{ + data = 'hello'; + encoder = 'base64'; + name = 'test'; + filename = 'test.html'; + type = 'test/html'; + headers = { + 'X-Custom-Header: hello'; + } + }) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\naGVsbG8=', info) + assert_match('Content%-Type:%s+test/html', info) + assert_match('Content%-Disposition:.-%sname="test"', info) + assert_not_match('Content%-Disposition:.-%sname="test%.html"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + assert_match('Content%-Transfer%-Encoding:%s*base64', info) +end + +local function easy_dump_mime(easy, mime, url) + assert(mime:addpart{ + data = 'hello'; + encoder = 'base64'; + name = 'test'; + filename = 'test.html'; + type = 'test/html'; + headers = { + 'X-Custom-Header: hello'; + } + }) + + easy:setopt{ + mimepost = mime; + } + + assert(easy:setopt{ + url = GET_URL; + customrequest = "GET"; + + writefunction = function()end; + }) + + if not ok then return nil, err end + + ok, err = easy:perform() + + if not ok then return nil, err end + + return table.concat(buffer) +end + +end + +RUN() diff --git a/watchdog/third_party/lua-curl/test/test_multi_callback.lua b/watchdog/third_party/lua-curl/test/test_multi_callback.lua new file mode 100644 index 0000000..4240557 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_multi_callback.lua @@ -0,0 +1,116 @@ +local curl = require "lcurl" + +local called, active_coroutine = 0 + +-- for Lua 5.1 compat +local function co_running() + local co, main = coroutine.running() + if main == true then return nil end + return co +end + +function on_timer() + called = called + 1 + -- use `os.exit` because now Lua-cURL did not propogate error from callback + if co_running() ~= active_coroutine then os.exit(-1) end +end + +local function test_1() + io.write('Test #1 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = nil + m:remove_handle(e) + assert(called == 2) + + io.write('pass!\n') +end + +local function test_2() + io.write('Test #2 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = coroutine.create(function() + m:remove_handle(e) + end) + coroutine.resume(active_coroutine) + assert(called == 2) + + io.write('pass!\n') +end + +local function test_3() + io.write('Test #3 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = nil + e:close() + assert(called == 2) + + io.write('pass!\n') +end + +local function test_4() + io.write('Test #4 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = coroutine.create(function() + e:close() + end) + coroutine.resume(active_coroutine) + assert(called == 2) + + io.write('pass!\n') +end + +test_1() + +test_2() + +test_3() + +test_4() diff --git a/watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua b/watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua new file mode 100644 index 0000000..31d8fd9 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua @@ -0,0 +1,47 @@ +local curl = require "lcurl" + +-- for Lua 5.1 compat +local function co_running() + local co, main = coroutine.running() + if main == true then return nil end + return co +end + +local state, called = true, 0 +local thread, msg + +local function check_thread() + if thread ~= co_running() then + print(msg) + os.exit(-1) + end +end + +local m; m = curl.multi{ + timerfunction = function() + check_thread() + called = called + 1 + + if state then state = false + thread = coroutine.create(function() + local e = curl.easy() + m:add_handle(e) + end) + + msg = 'add from coroutine' + coroutine.resume(thread) + assert(called == 2) + + msg, thread = 'add from main' + local e = curl.easy() + m:add_handle(e) + assert(called == 3) + end + end +} + +e = curl.easy() + +m:add_handle(e) + +assert(called == 3) diff --git a/watchdog/third_party/lua-curl/test/test_pause02.c.lua b/watchdog/third_party/lua-curl/test/test_pause02.c.lua new file mode 100644 index 0000000..3a1fb4f --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_pause02.c.lua @@ -0,0 +1,89 @@ +local curl = require "lcurl" + +-- How many times curl_multi_perform should be called before hitting of CURLPAUSE_CONT. +-- (including curl_multi_perform that causes WriteFunction to pause writes, +-- i.e. 1 means that CURLPAUSE_CONT will be performed immediately after pause.) +local WAIT_COUNT = 15 + +local SIZE = 10 * 1024 +-- local RESOURCE_URL = "http://httpbin.org/bytes/" .. SIZE +local RESOURCE_URL = "http://127.0.0.1:7090/bytes/" .. SIZE + +local State = { + PAUSE = 0, -- write function should return CURL_WRITEFUNC_PAUSE + WAIT = 1, -- waiting for CURLPAUSE_CONT + WRITE = 2, -- write function should perform write + WRITTEN = 3, -- write function have performed write +} + +-- Current state +local state = State.PAUSE + +-- Countdown to continue writes +local waitCount = 0 + +-- Received data and data size +local data, datasize = {}, 0 + +local function WriteFunction(str) + if state == State.PAUSE then + state = State.WAIT + waitCount = WAIT_COUNT + return curl.WRITEFUNC_PAUSE + end + + if state == State.WAIT then + -- callback shouldn't be called in this state + print("WARNING: write-callback called in STATE_WAIT") + return curl.WRITEFUNC_PAUSE + end + + if state == State.WRITE then + state = State.WRITTEN + end + + datasize = datasize + #str + data[#data + 1] = str +end + +local function perform(multi, easy) + while true do + local handles = multi:perform() + + if state == State.WAIT then + waitCount = waitCount - 1 + if waitCount == 0 then + state = State.WRITE + easy:pause(curl.PAUSE_CONT) + end + end + + if state == State.WRITTEN then + state = State.PAUSE + end + + if 0 == handles then + local h, ok, err = multi:info_read() + return not not ok, err + end + + multi:wait() + end +end + +local easy = curl.easy{ + url = RESOURCE_URL, + accept_encoding = "gzip,deflate", + writefunction = WriteFunction, +} +local multi = curl.multi() +multi:add_handle(easy) + +local ok, err = perform(multi, easy) + +if ok then + print("OK: data retrieved successfully (" .. tostring(datasize) .. ")") +else + print("ERROR: data retrieve failed (" .. tostring(err) .. ")") + os.exit(1) +end diff --git a/watchdog/third_party/lua-curl/test/test_safe.lua b/watchdog/third_party/lua-curl/test/test_safe.lua new file mode 100644 index 0000000..eedfc25 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_safe.lua @@ -0,0 +1,287 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end + +local _ENV = TEST_CASE'error_mode' do + +local c + +function teardown() + if c then c:close() end + c = nil +end + +function test_safe() + local curl = require "lcurl.safe" + c = assert(curl.easy()) + assert_equal(c, c:setopt_url("aaaaa://123")) + assert_nil(c:perform()) +end + +function test_raise() + local curl = require "lcurl" + c = assert(curl.easy()) + assert_equal(c, c:setopt_url("aaaaa://123")) + assert_error(function() c:perform() end) +end + +end + +local _ENV = TEST_CASE'setopt' do + +local curl = require "lcurl.safe" +local c + +function setup() + c = assert(curl.easy()) +end + +function teardown() + if c then c:close() end + c = nil +end + +function test_number() + assert_equal(c, c:setopt_verbose(false)) + assert_equal(c, c:setopt_verbose(true)) + assert_equal(c, c:setopt_verbose(1)) + assert_equal(c, c:setopt_verbose(0)) + assert_error(function() c:setopt_verbose("1") end) + assert_error(function() c:setopt_verbose("true") end) +end + +function test_string() + assert_error(function() c:setopt_url(true) end) + assert_error(function() c:setopt_url(1) end) + assert_equal(c, c:setopt_url("1")) +end + +function test_array() + assert_error(function() c:setopt_httpheader(true) end) + assert_error(function() c:setopt_httpheader(1) end) + assert_error(function() c:setopt_httpheader("k:v")end) + assert_equal(c, c:setopt_httpheader{"k:v"}) +end + +function test_multiple_options() + assert_error(function() c:setopt{verbose = "false"} end) + assert_error(function() c:setopt{verbose = "1"} end) + assert_equal(c, c:setopt{verbose = false}) + assert_equal(c, c:setopt{[curl.OPT_VERBOSE] = false}) +end + +end + +local _ENV = TEST_CASE'error_object' do + +local curl = require "lcurl" + +function test() + local e1 = curl.error(curl.ERROR_EASY, 0) -- ok + assert_equal(curl.ERROR_EASY, e1:category()) + assert_equal(curl.E_OK, e1:no()) + assert_equal("OK", e1:name()) + + local e2 = curl.error(curl.ERROR_MULTI, 0) -- ok + local e3 = curl.error(curl.ERROR_MULTI, 0) -- ok + assert_equal(0, e1:no()) + assert_equal(0, e2:no()) + assert(e1 ~= e2) + assert(e3 == e2) +end + +end + +local _ENV = TEST_CASE'ctor' do + +local scurl = require "lcurl.safe" +local curl = require "lcurl" +local c + +function teardown() + if c then c:close() end + c = nil +end + +function test_easy_error() + c = assert(curl.easy()) + c:close() + c = assert(curl.easy{ + url = "http://example.com", + [curl.OPT_VERBOSE] = true, + }) + c:close() + + assert_error(function() + c = curl.easy{ + url_111 = "http://example.com", + } + end) + + assert_error(function() + c = curl.easy{ + url = 123, + } + end) +end + +function test_easy_safe() + c = assert(scurl.easy()) + c:close() + c = assert(scurl.easy{ + url = "http://example.com", + [curl.OPT_VERBOSE] = true, + }) + c:close() + + assert_pass(function() + c = scurl.easy{ + url_111 = "http://example.com", + } + end) + assert_nil(c) + + assert_error(function() + c = scurl.easy{ + url = 123, + } + end) +end + +function test_multi_error() + c = assert(curl.multi()) + c:close() + c = assert(curl.multi{ + maxconnects = 10; + [curl.OPT_MULTI_PIPELINING] = true, + }) + c:close() + + assert_error(function() + c = curl.multi{ + url_111 = "http://example.com", + } + end) + + assert_error(function() + c = curl.multi{ + maxconnects = "hello", + } + end) +end + +function test_multi_safe() + c = assert(scurl.multi()) + c:close() + c = assert(scurl.multi{ + maxconnects = 10; + [curl.OPT_MULTI_PIPELINING] = true, + }) + c:close() + + assert_pass(function() + c = scurl.multi{ + url_111 = "http://example.com", + } + end) + assert_nil(c) + + assert_error(function() + c = scurl.multi{ + maxconnects = "hello", + } + end) +end + +function test_share_error() + assert(curl.LOCK_DATA_COOKIE) + + c = assert(curl.share()) + c:close() + c = assert(curl.share{ + share = curl.LOCK_DATA_COOKIE; + }) + c:close() + + assert_error(function() + c = curl.share{ + url_111 = "http://example.com", + } + end) + + assert_error(function() + c = curl.share{ + share = "hello"; + } + end) +end + +function test_share_safe() + assert(curl.LOCK_DATA_COOKIE) + + c = assert(scurl.share()) + c:close() + c = assert(curl.share{ + share = scurl.LOCK_DATA_COOKIE; + }) + c:close() + + assert_pass(function() + c = scurl.share{ + url_111 = "http://example.com", + } + end) + + assert_error(function() + c = scurl.share{ + share = "hello"; + } + end) +end + +end + +local _ENV = TEST_CASE'objects_have_same_metatables' do + +local scurl = require "lcurl.safe" +local curl = require "lcurl" +local e1, e2, m + +function teardown() + if m then m:close() end + if e1 then e1:close() end + if e2 then e2:close() end + e1, e2, m = nil +end + +function test_1() + e1 = assert(scurl.easy()) + e2 = assert(curl.easy()) + m = assert(scurl.multi()) + + assert_equal(m, m:add_handle(e1)) + assert_equal(m, m:add_handle(e2)) +end + +function test_2() + e1 = assert(scurl.easy()) + e2 = assert(curl.easy()) + m = assert(curl.multi()) + + assert_equal(m, m:add_handle(e1)) + assert_equal(m, m:add_handle(e2)) +end + +end + +RUN() diff --git a/watchdog/third_party/lua-curl/test/test_urlapi.lua b/watchdog/third_party/lua-curl/test/test_urlapi.lua new file mode 100644 index 0000000..e0cf4f0 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/test_urlapi.lua @@ -0,0 +1,315 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local _, luacov = pcall(require, "luacov") +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end +local curl = require "cURL" +local scurl = require "cURL.safe" +local utils = require "utils" +local json = require "dkjson" +local table = table + +local weak_ptr, gc_collect, is_curl_eq = utils.import('weak_ptr', 'gc_collect', 'is_curl_eq') + +local GET_URL = "http://127.0.0.1:7090/get" + +local tostring, pcall = tostring, pcall + +local function skip_case(msg) return function() skip(msg) end end + +local ENABLE = true + +local _ENV = TEST_CASE'urlapi' if ENABLE then + +if not curl.E_URL_OK then test = skip_case('URL API avaliable since libcurl 7.62.0') else + +local it = setmetatable(_ENV or _M, {__call = function(self, describe, fn) + self["test " .. describe] = fn +end}) + +local url + +local function U(u) + url = assert_userdata(curl.url()) + assert_equal(url, url:set_url(u)) + return url +end + +function teardown() + if url then url:cleanup() end + url = nil +end + +it('should export falgs', function() + assert_number(curl.U_DEFAULT_PORT ) + assert_number(curl.U_NO_DEFAULT_PORT ) + assert_number(curl.U_DEFAULT_SCHEME ) + assert_number(curl.U_NON_SUPPORT_SCHEME ) + assert_number(curl.U_PATH_AS_IS ) + assert_number(curl.U_DISALLOW_USER ) + assert_number(curl.U_URLDECODE ) + assert_number(curl.U_URLENCODE ) + assert_number(curl.U_APPENDQUERY ) + assert_number(curl.U_GUESS_SCHEME ) +end) + +it('should export parts', function() + assert_number(curl.UPART_URL ) + assert_number(curl.UPART_SCHEME ) + assert_number(curl.UPART_USER ) + assert_number(curl.UPART_PASSWORD ) + assert_number(curl.UPART_OPTIONS ) + assert_number(curl.UPART_HOST ) + assert_number(curl.UPART_PORT ) + assert_number(curl.UPART_PATH ) + assert_number(curl.UPART_QUERY ) + assert_number(curl.UPART_FRAGMENT ) +end) + +it('should export methods', function() + url = curl.url() + assert_function(url.dup ) + assert_function(url.cleanup ) + + assert_function(url.set_url ) + assert_function(url.set_scheme ) + assert_function(url.set_user ) + assert_function(url.set_password ) + assert_function(url.set_options ) + assert_function(url.set_host ) + assert_function(url.set_port ) + assert_function(url.set_path ) + assert_function(url.set_query ) + assert_function(url.set_fragment ) + + assert_function(url.get_url ) + assert_function(url.get_scheme ) + assert_function(url.get_user ) + assert_function(url.get_password ) + assert_function(url.get_options ) + assert_function(url.get_host ) + assert_function(url.get_port ) + assert_function(url.get_path ) + assert_function(url.get_query ) + assert_function(url.get_fragment ) +end) + +it('create and cleanup', function() + url = assert_userdata(curl.url()) + assert_nil(url:cleanup()) +end) + +it('constructor with parameters', function() + url = assert_userdata(curl.url('http://example.com/')) + assert_equal('http://example.com/', url:get_url()) + + url = assert_userdata(curl.url('example.com', curl.U_GUESS_SCHEME)) + assert_equal('http://example.com/', url:get_url()) +end) + +it('dup url', function() + url = assert_userdata(curl.url('http://example.com/')) + local u2 = url:dup() + assert_not_equal(url, u2) + assert_equal('http://example.com/', u2:get_url()) + assert_equal('http://example.com/', url:get_url()) + url:cleanup() + url = u2 + assert_equal('http://example.com/', url:get_url()) +end) + +it('should convert to string', function() + assert_equal('http://example.com/', tostring(U"http://example.com/")) +end) + +it('should decode url', function() + url = U"http://example.com" + assert_equal('http', url:get_scheme()) + assert_equal('example.com', url:get_host()) + assert_equal('/', url:get_path()) +end) + +it('should cast scheme to lower case', function() + url = U"HTTP://Example.com" + assert_equal('http', url:get_scheme()) + assert_equal('Example.com', url:get_host()) + assert_equal('/', url:get_path()) + assert_equal("http://Example.com/", url:get_url()) +end) + +it('should append query', function() + url = U"http://example.com" + assert_equal(url, url:set_query("a=hello world", curl.U_APPENDQUERY + curl.U_URLENCODE)) + assert_equal(url, url:set_query("b=A&B", curl.U_APPENDQUERY + curl.U_URLENCODE)) + assert_equal("http://example.com/?a=hello+world&b=A%26B", url:get_url()) +end) + +it('should append only one parameter in query per call', function() + url = U"http://example.com" + assert_equal(url, url:set_query("a=hello world&b=A&B", curl.U_APPENDQUERY + curl.U_URLENCODE)) + if is_curl_eq(7, 62, 0) then + assert_equal("http://example.com/?a=hello+world%26b=A%26B", url:get_url()) + else + assert_equal("http://example.com/?a=hello+world%26b%3dA%26B", url:get_url()) + end +end) + +it('should set encoded query', function() + url = U("http://example.com/?a=hello world&b=d") + assert_equal('a=hello world&b=d', url:get_query()) +end) + +it('should returns NULL as empty value', function() + url = curl.url() + assert_equal(curl.null, url:get_query()) + assert_equal(curl.null, url:get_host()) + assert_equal(curl.null, url:get_port()) + assert_equal(curl.null, url:get_password()) + assert_equal(curl.null, url:get_scheme()) + assert_equal(curl.null, url:get_options()) + assert_equal(curl.null, url:get_fragment()) + + assert_equal('/', url:get_path()) +end) + +it('should returns nil and error for invalid url in safe mode', function() + url = scurl.url() + local _, err = assert_nil(url:get_url()) + assert_equal('CURL-URL', err:cat()) +end) + +it('should raise error for invalid url', function() + url = curl.url() + local _, err = assert_false(pcall(url.get_url, url)) + assert_match('CURL%-URL', tostring(err)) +end) + +it('should raise error for tostring', function() + url = curl.url() + local _, err = assert_false(pcall(tostring, url)) + assert_match('CURL%-URL', tostring(err)) +end) + +it('should raise error for tostring in safe mode', function() + url = scurl.url() + local _, err = assert_false(pcall(tostring, url)) + assert_match('CURL%-URL', tostring(err)) +end) + +-- it('should set encoded query', function() +-- url = U"http://example.com" +-- assert_equal(url, url:set_query("a=hello world", curl.U_URLENCODE)) +-- assert_equal("http://example.com/?a=hello+world", url:get_url()) +-- end) + +if curl.UPART_ZONEID then + +it('should returns zoneid', function() + url = assert_userdata(scurl.url('http://[::1%18]:3800')) + assert_equal('18', url:get_zoneid()) +end) + +it('should returns empty on missing zoneid', function() + url = assert_userdata(scurl.url('http://[::1]:3800')) + assert_equal(curl.null, url:get_zoneid()) +end) + +else + test_zoneid = skip_case('URL API supports zoneid since version 7.65.0') +end + +end end + +local _ENV = TEST_CASE'curlu parameter' if ENABLE then + +if not curl.OPT_CURLU then test = skip_case('CURLU option avaliable since libcurl 7.63.0') else + +local it = setmetatable(_ENV or _M, {__call = function(self, describe, fn) + self["test " .. describe] = fn +end}) + +local url, easy, buffer + +local function writer(chunk) + table.insert(buffer, chunk) +end + +local function json_data() + return json.decode(table.concat(buffer)) +end + +local function U(u) + url = assert_userdata(curl.url()) + assert_equal(url, url:set_url(u)) + return url +end + +function setup() + buffer = {} +end + +function teardown() + if url then url:cleanup() end + if easy then easy:close() end + url = nil +end + +it('easy should prevent url from gc', function() + local purl + do + easy = curl.easy() + local url = U(GET_URL) + assert_equal(easy, easy:setopt_curlu(url)) + purl = weak_ptr(url) + end + + gc_collect() + assert_not_nil(purl.value) + + assert_equal(easy, easy:unsetopt_curlu()) + + gc_collect() + assert_not_nil(purl.value) +end) + +it('should use url from curlu parameter', function() + url = U(GET_URL) + easy = curl.easy {curlu = url, writefunction = writer} + assert_equal(easy, easy:perform()) + local response = assert_table(json_data()) + assert_equal(GET_URL, response.url) +end) + +it('should be possible reset url', function() + url = U("http://example.com") + easy = curl.easy {curlu = url, writefunction = writer} + url:set_url(GET_URL) + + assert_equal(easy, easy:perform()) + local response = assert_table(json_data()) + assert_equal(GET_URL, response.url) +end) + +it('should be possible reuse url', function() + url = U(GET_URL) + for i = 1, 5 do + local easy = curl.easy {curlu = url, writefunction = writer} + assert_equal(easy, easy:perform()) + local response = assert_table(json_data()) + assert_equal(GET_URL, response.url) + gc_collect() + end +end) + +end end + +RUN() diff --git a/watchdog/third_party/lua-curl/test/utils.lua b/watchdog/third_party/lua-curl/test/utils.lua new file mode 100644 index 0000000..0f81751 --- /dev/null +++ b/watchdog/third_party/lua-curl/test/utils.lua @@ -0,0 +1,151 @@ +local curl = require "lcurl" + +local function weak_ptr(val) + return setmetatable({value = val},{__mode = 'v'}) +end + +local function gc_collect(n) + for i = 1, (n or 2) do + collectgarbage("collect") + end +end + +local function cver(min, maj, pat) + return min * 2^16 + maj * 2^8 + pat +end + +local function is_curl_ge(min, maj, pat) + assert(0x070908 == cver(7,9,8)) + return curl.version_info('version_num') >= cver(min, maj, pat) +end + +local function is_curl_eq(min, maj, pat) + assert(0x070908 == cver(7,9,8)) + return curl.version_info('version_num') == cver(min, maj, pat) +end + +local function read_file(n) + local f, e = io.open(n, "r") + if not f then return nil, e end + local d, e = f:read("*all") + f:close() + return d, e +end + +local function get_bin_by(str,n) + local pos = 1 - n + return function() + pos = pos + n + return (str:sub(pos,pos+n-1)) + end +end + +local function stream(ch, n, m) + return n, get_bin_by( (ch):rep(n), m) +end + +local function Stream(ch, n, m) + local size, reader + + local _stream = {} + + function _stream:read(...) + _stream.called_ctx = self + _stream.called_co = coroutine.running() + return reader(...) + end + + function _stream:size() + return size + end + + function _stream:reset() + size, reader = stream(ch, n, m) + return self + end + + return _stream:reset() +end + +local function easy_dump_mime(easy, mime, url) + local buffer = {} + + local function dump_mime(type, data) + if type == curl.INFO_DATA_OUT then + buffer[#buffer + 1] = data + end + end + + local ok, err = easy:setopt{ + url = url or "http://127.0.0.1:7090"; + customrequest = "GET"; + mimepost = mime; + verbose = true; + debugfunction = dump_mime; + writefunction = function()end; + } + + if not ok then return nil, err end + + ok, err = easy:perform() + + if not ok then return nil, err end + + return table.concat(buffer) +end + +local function easy_dump_request(easy, url) + local buffer = {} + local headers = {} + + local function dump_mime(type, data) + if type == curl.INFO_DATA_OUT then + buffer[#buffer + 1] = data + end + + if type == curl.INFO_HEADER_OUT then + headers[#headers + 1] = data + end + end + + local ok, err = easy:setopt{ + url = url or "http://127.0.0.1:7090"; + customrequest = "GET"; + mimepost = mime; + verbose = true; + debugfunction = dump_mime; + writefunction = function()end; + } + + if not ok then return nil, err end + + ok, err = easy:perform() + + if not ok then return nil, err end + + return table.concat(buffer), table.concat(headers) +end + +local utils = { + weak_ptr = weak_ptr; + gc_collect = gc_collect; + is_curl_ge = is_curl_ge; + is_curl_eq = is_curl_eq; + get_bin_by = get_bin_by; + read_file = read_file; + dump_mime = easy_dump_mime; + dump_request = easy_dump_request; + stream = stream; + Stream = Stream; +} + +utils.import = function(...) + local n, t = select('#', ...), {} + for i = 1, n do + local name = select(i, ...) + t[#t + 1] = assert(utils[name], 'unknown field: ' .. tostring(name)) + end + return (unpack or table.unpack)(t, 1, n) +end + +return utils \ No newline at end of file From 7eb425138373d44d9b1b86c92e5e612734016b59 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:47:25 +0800 Subject: [PATCH 11/13] feat(release): align artifact versions and ship watchdog image Centralize toolchain pins in release/versions.env; load them in CI and release workflows. Add release manifest generation, version verification, watchdog Docker image build, and docker-save tarballs per arch. --- .../actions/load-release-versions/action.yml | 27 ++++++ .github/workflows/ci.yml | 23 +++-- .github/workflows/release.yml | 86 +++++++++++++++-- release/README.md | 48 ++++++++++ release/versions.env | 18 ++++ rust-toolchain.toml | 2 +- scripts/generate-release-manifest.sh | 91 ++++++++++++++++++ scripts/verify-release-versions.sh | 46 ++++++++++ watchdog/Dockerfile | 92 +++++++++++++++++++ watchdog/docker-entrypoint.sh | 11 +++ 10 files changed, 423 insertions(+), 21 deletions(-) create mode 100644 .github/actions/load-release-versions/action.yml create mode 100644 release/README.md create mode 100644 release/versions.env create mode 100755 scripts/generate-release-manifest.sh create mode 100755 scripts/verify-release-versions.sh create mode 100644 watchdog/Dockerfile create mode 100755 watchdog/docker-entrypoint.sh diff --git a/.github/actions/load-release-versions/action.yml b/.github/actions/load-release-versions/action.yml new file mode 100644 index 0000000..bb79dec --- /dev/null +++ b/.github/actions/load-release-versions/action.yml @@ -0,0 +1,27 @@ +name: "Load release versions" +description: "Export release/versions.env into the job environment (single pin source)." + +runs: + using: composite + steps: + - name: Load release/versions.env + shell: bash + run: | + set -euo pipefail + file="release/versions.env" + if [[ ! -f "${file}" ]]; then + echo "missing ${file}" >&2 + exit 1 + fi + while IFS= read -r line || [[ -n "${line}" ]]; do + line="${line%%#*}" + line="$(echo "${line}" | xargs)" + if [[ -z "${line}" ]]; then + continue + fi + if [[ "${line}" != *"="* ]]; then + echo "invalid line in ${file}: ${line}" >&2 + exit 1 + fi + echo "${line}" >> "${GITHUB_ENV}" + done < "${file}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bf6e6a..361304b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,15 +4,6 @@ on: push: pull_request: -env: - RUST_TOOLCHAIN: "1.95.0" - XGENEXT2FS_VERSION: v1.5.6 - XGENEXT2FS_SHA256_AMD64: 996e4e68a638b5dc5967d3410f92ecb8d2f41e32218bbe0f8b4c4474d7eebc59 - XGENEXT2FS_SHA256_ARM64: e5aca81164b762bbe5447bacef41e4fa9e357fd9c8f44e519c5206227d43144d - CARTESI_MACHINE_VERSION: v0.20.0-test2 - CARTESI_MACHINE_SHA256_AMD64: 39bbfc96a6cc6606307294b719df65f4f2725e8d200d062bcbd8c22355b99b56 - CARTESI_MACHINE_SHA256_ARM64: 787d823756000cdecd72da8a3494b4c08613087379035959e561bbaef7a220ba - jobs: rust: runs-on: ubuntu-latest @@ -22,6 +13,12 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Load release versions + uses: ./.github/actions/load-release-versions + + - name: Verify release version alignment + run: bash scripts/verify-release-versions.sh + - name: Install system dependencies run: | sudo apt-get update @@ -33,7 +30,7 @@ jobs: libslirp-dev - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.91.1 + uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} components: rustfmt, clippy @@ -71,6 +68,9 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Load release versions + uses: ./.github/actions/load-release-versions + - name: Setup guest toolchain uses: ./.github/actions/setup-guest-toolchain with: @@ -97,6 +97,9 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Load release versions + uses: ./.github/actions/load-release-versions + - name: Setup guest toolchain uses: ./.github/actions/setup-guest-toolchain with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d23ac3..5183186 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,15 +19,6 @@ on: permissions: contents: write -env: - RUST_TOOLCHAIN: "1.95.0" - XGENEXT2FS_VERSION: v1.5.6 - XGENEXT2FS_SHA256_AMD64: 996e4e68a638b5dc5967d3410f92ecb8d2f41e32218bbe0f8b4c4474d7eebc59 - XGENEXT2FS_SHA256_ARM64: e5aca81164b762bbe5447bacef41e4fa9e357fd9c8f44e519c5206227d43144d - CARTESI_MACHINE_VERSION: v0.20.0-test2 - CARTESI_MACHINE_SHA256_AMD64: 39bbfc96a6cc6606307294b719df65f4f2725e8d200d062bcbd8c22355b99b56 - CARTESI_MACHINE_SHA256_ARM64: 787d823756000cdecd72da8a3494b4c08613087379035959e561bbaef7a220ba - jobs: build-sequencer: name: Build sequencer (${{ matrix.arch }}) @@ -45,6 +36,9 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Load release versions + uses: ./.github/actions/load-release-versions + - name: Install system dependencies run: | sudo apt-get update @@ -85,6 +79,10 @@ jobs: mkdir -p "package/sequencer-${TAG}-linux-${ARCH}" cp "target/${TARGET}/release/sequencer" "package/sequencer-${TAG}-linux-${ARCH}/sequencer" + bash scripts/generate-release-manifest.sh \ + --tag "${TAG}" \ + --git-sha "${GITHUB_SHA}" \ + --output "package/sequencer-${TAG}-linux-${ARCH}/RELEASE.json" cat > "package/sequencer-${TAG}-linux-${ARCH}/RUNNING.md" <<'EOF' ## Running @@ -122,6 +120,9 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Load release versions + uses: ./.github/actions/load-release-versions + - name: Setup guest toolchain uses: ./.github/actions/setup-guest-toolchain with: @@ -159,29 +160,93 @@ jobs: name: canonical-machine-images path: dist/canonical-machine-image-*.tar.gz + build-watchdog-image: + name: Build watchdog image (${{ matrix.arch }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + platform: linux/amd64 + deb_sha_env: CARTESI_MACHINE_SHA256_AMD64 + - arch: arm64 + platform: linux/arm64 + deb_sha_env: CARTESI_MACHINE_SHA256_ARM64 + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Load release versions + uses: ./.github/actions/load-release-versions + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build and export watchdog image + env: + TAG: ${{ inputs.tag || github.ref_name }} + DEB_SHA_ENV: ${{ matrix.deb_sha_env }} + run: | + set -euo pipefail + DEB_SHA="${!DEB_SHA_ENV}" + image="sequencer-watchdog:${TAG}" + docker build \ + --platform "${{ matrix.platform }}" \ + --build-arg "RELEASE_TAG=${TAG}" \ + --build-arg "GIT_COMMIT=${GITHUB_SHA}" \ + --build-arg "CARTESI_MACHINE_VERSION=${CARTESI_MACHINE_VERSION}" \ + --build-arg "CARTESI_MACHINE_DEB_SHA256=${DEB_SHA}" \ + --build-arg "LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA}" \ + -f watchdog/Dockerfile \ + -t "${image}" \ + . + mkdir -p dist + docker save "${image}" | gzip -9 > "dist/sequencer-watchdog-${TAG}-linux-${{ matrix.arch }}.tar.gz" + + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: watchdog-image-linux-${{ matrix.arch }} + path: dist/sequencer-watchdog-*.tar.gz + publish: name: Publish GitHub Release runs-on: ubuntu-latest needs: - build-sequencer - build-canonical-machine-image + - build-watchdog-image steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Load release versions + uses: ./.github/actions/load-release-versions + - name: Download build artifacts uses: actions/download-artifact@v6 with: path: dist - name: Flatten artifacts + env: + TAG: ${{ inputs.tag || github.ref_name }} run: | set -euo pipefail mkdir -p out find dist -type f -name '*.tar.gz' -exec cp -v '{}' out/ \; + bash scripts/generate-release-manifest.sh \ + --tag "${TAG}" \ + --git-sha "${GITHUB_SHA}" \ + --output "out/release-manifest-${TAG}.json" - name: Generate checksums working-directory: out run: | set -euo pipefail - sha256sum *.tar.gz > SHA256SUMS + sha256sum *.tar.gz *.json > SHA256SUMS - name: Create GitHub Release uses: softprops/action-gh-release@v2 @@ -191,4 +256,5 @@ jobs: fail_on_unmatched_files: true files: | out/*.tar.gz + out/*.json out/SHA256SUMS diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000..6f3af36 --- /dev/null +++ b/release/README.md @@ -0,0 +1,48 @@ +# Release bundle versioning + +All release artifacts for tag `vX.Y.Z` share one **bundle version** (`vX.Y.Z`) and one +**toolchain pin set** in [`versions.env`](versions.env). + +| Artifact | Version key | +|----------|-------------| +| `sequencer-vX-linux-{amd64,arm64}.tar.gz` | `vX` (git tag) | +| `canonical-machine-image-{devnet,sepolia}-vX.tar.gz` | `vX` + guest built with pins below | +| `sequencer-watchdog-vX-linux-{amd64,arm64}.tar.gz` | `vX` OCI image (`docker save`) | +| `release-manifest-vX.json` | Lists all artifacts + pins | + +## Single pin source + +Edit **`release/versions.env` only** — CI and release workflows load it via +[`.github/actions/load-release-versions`](../.github/actions/load-release-versions/action.yml). +After editing, run `bash scripts/verify-release-versions.sh` (also enforced in CI) and bump +`rust-toolchain.toml` / `watchdog/third_party/lua-curl/UPSTREAM` when those pins change. + +`CARTESI_MACHINE_VERSION` must match: + +- The `cartesi-machine` inside the watchdog image +- The emulator used to build `canonical-machine-image-*` tarballs + +Mismatch causes CM load/advance failures or false `state_mismatch` alarms. + +## Watchdog image + +```bash +docker load < sequencer-watchdog-vX-linux-amd64.tar.gz +docker run --rm \ + -e WATCHDOG_MODE=compare \ + -e WATCHDOG_ONCE=1 \ + -e WATCHDOG_SEQUENCER_URL=... \ + -e WATCHDOG_L1_RPC_URL=... \ + -v /var/lib/watchdog/checkpoints:/checkpoints \ + sequencer-watchdog:vX +``` + +Mount `canonical-machine-image-sepolia-vX.tar.gz` extract for bootstrap +(`WATCHDOG_CM_SNAPSHOT_DIR`) on first run. + +Inspect alignment: + +```bash +docker inspect --format '{{ index .Config.Labels "org.cartesi.sequencer.release-tag" }}' IMAGE +cat /opt/watchdog/RELEASE.json # inside container +``` diff --git a/release/versions.env b/release/versions.env new file mode 100644 index 0000000..407d335 --- /dev/null +++ b/release/versions.env @@ -0,0 +1,18 @@ +# Single source of truth for release + CI toolchain pins. +# Workflows load this via .github/actions/load-release-versions. +# Release tag (git v*) is the sequencer / watchdog / CM image bundle version. + +RUST_TOOLCHAIN=1.95.0 + +XGENEXT2FS_VERSION=v1.5.6 +XGENEXT2FS_SHA256_AMD64=996e4e68a638b5dc5967d3410f92ecb8d2f41e32218bbe0f8b4c4474d7eebc59 +XGENEXT2FS_SHA256_ARM64=e5aca81164b762bbe5447bacef41e4fa9e357fd9c8f44e519c5206227d43144d + +# Must match the cartesi-machine used to build canonical-machine-image-* tarballs +# and the watchdog OCI image (in-process cartesi Lua module + CLI). +CARTESI_MACHINE_VERSION=v0.20.0-test2 +CARTESI_MACHINE_SHA256_AMD64=39bbfc96a6cc6606307294b719df65f4f2725e8d200d062bcbd8c22355b99b56 +CARTESI_MACHINE_SHA256_ARM64=787d823756000cdecd72da8a3494b4c08613087379035959e561bbaef7a220ba + +# Lua-cURLv3 pin (watchdog-lua-deps + watchdog image build). +LUA_CURL_UPSTREAM_SHA=9f8b6dba8b5ef1b26309a571ae75cda4034279e5 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4f22047..6360c18 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.91.1" +channel = "1.95.0" components = ["rustfmt", "clippy"] diff --git a/scripts/generate-release-manifest.sh b/scripts/generate-release-manifest.sh new file mode 100755 index 0000000..45d289d --- /dev/null +++ b/scripts/generate-release-manifest.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Emit release-manifest.json: ties sequencer, watchdog, CM images, and toolchain pins. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +versions="${root}/release/versions.env" + +usage() { + echo "usage: $0 --tag TAG [--git-sha SHA] [--output PATH]" >&2 + exit 1 +} + +tag="" +git_sha="" +output="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --tag) + tag="${2:-}" + shift 2 + ;; + --git-sha) + git_sha="${2:-}" + shift 2 + ;; + --output) + output="${2:-}" + shift 2 + ;; + *) + usage + ;; + esac +done + +if [[ -z "${tag}" ]]; then + usage +fi + +if [[ -z "${git_sha}" ]]; then + git_sha="$(git -C "${root}" rev-parse HEAD 2>/dev/null || echo unknown)" +fi + +if [[ ! -f "${versions}" ]]; then + echo "missing ${versions}" >&2 + exit 1 +fi + +# shellcheck disable=SC1090 +set -a +source "${versions}" +set +a + +artifacts_json="$(cat < "${output:-/dev/stdout}" +import json, os, sys + +tag, git_sha, artifacts_json = sys.argv[1], sys.argv[2], sys.argv[3] +artifacts = json.loads(artifacts_json) + +manifest = { + "release_tag": tag, + "git_commit": git_sha, + "bundle_version": tag, + "toolchain": { + "rust": os.environ["RUST_TOOLCHAIN"], + "xgenext2fs": os.environ["XGENEXT2FS_VERSION"], + "cartesi_machine": os.environ["CARTESI_MACHINE_VERSION"], + "lua_curl_upstream_sha": os.environ["LUA_CURL_UPSTREAM_SHA"], + }, + "artifacts": artifacts, + "alignment": { + "rule": "All artifacts sharing release_tag were built from git_commit with the same toolchain pins.", + "watchdog_cm_bootstrap": f"Use canonical-machine-image--{tag}.tar.gz with WATCHDOG_CM_SNAPSHOT_DIR; cartesi-machine in the watchdog image matches CARTESI_MACHINE_VERSION.", + }, +} +json.dump(manifest, sys.stdout, indent=2) +sys.stdout.write("\n") +PY diff --git a/scripts/verify-release-versions.sh b/scripts/verify-release-versions.sh new file mode 100755 index 0000000..5aa37c2 --- /dev/null +++ b/scripts/verify-release-versions.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Fail if release/versions.env drifts from other pinned artifacts in-tree. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +versions="${root}/release/versions.env" + +if [[ ! -f "${versions}" ]]; then + echo "missing ${versions}" >&2 + exit 1 +fi + +# shellcheck disable=SC1090 +set -a +source "${versions}" +set +a + +errors=0 + +rust_channel="$( + grep -E '^\s*channel\s*=' "${root}/rust-toolchain.toml" \ + | head -1 \ + | sed -E 's/.*=\s*"([^"]+)".*/\1/' +)" +if [[ "${rust_channel}" != "${RUST_TOOLCHAIN}" ]]; then + echo "rust-toolchain.toml channel=${rust_channel} != RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" >&2 + errors=$((errors + 1)) +fi + +upstream_file="${root}/watchdog/third_party/lua-curl/UPSTREAM" +if [[ -f "${upstream_file}" ]]; then + upstream_sha="$(grep -E '^Commit:' "${upstream_file}" | awk '{print $2}')" + if [[ "${upstream_sha}" != "${LUA_CURL_UPSTREAM_SHA}" ]]; then + echo "lua-curl UPSTREAM commit=${upstream_sha} != LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA}" >&2 + errors=$((errors + 1)) + fi +else + echo "missing ${upstream_file}" >&2 + errors=$((errors + 1)) +fi + +if [[ "${errors}" -ne 0 ]]; then + exit 1 +fi + +echo "release version pins aligned" diff --git a/watchdog/Dockerfile b/watchdog/Dockerfile new file mode 100644 index 0000000..60710b3 --- /dev/null +++ b/watchdog/Dockerfile @@ -0,0 +1,92 @@ +# Watchdog compare-mode runtime image. +# Build args RELEASE_TAG, GIT_COMMIT, and cartesi-machine pins must match release/versions.env +# and the canonical-machine-image-* tarballs produced in the same release. + +ARG DEBIAN_VERSION=bookworm-slim +FROM debian:${DEBIAN_VERSION} AS build + +ARG CARTESI_MACHINE_VERSION +ARG CARTESI_MACHINE_DEB_SHA256 +ARG TARGETARCH +ARG LUA_CURL_UPSTREAM_SHA + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + wget \ + make \ + gcc \ + pkg-config \ + liblua5.4-dev \ + libcurl4-openssl-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +COPY scripts/watchdog-lua-deps.sh scripts/watchdog-lua-deps.sh +COPY watchdog/third_party/lua-curl watchdog/third_party/lua-curl +COPY watchdog/third_party/json.lua watchdog/third_party/json.lua + +ENV LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA} +RUN bash scripts/watchdog-lua-deps.sh + +RUN set -euo pipefail \ + && case "${TARGETARCH}" in \ + amd64) CM_ARCH=amd64 ;; \ + arm64) CM_ARCH=arm64 ;; \ + *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ + esac \ + && wget -O /tmp/machine-emulator.deb \ + "https://github.com/cartesi/machine-emulator/releases/download/${CARTESI_MACHINE_VERSION}/machine-emulator_${CM_ARCH}.deb" \ + && echo "${CARTESI_MACHINE_DEB_SHA256} /tmp/machine-emulator.deb" | sha256sum --check \ + && dpkg-deb -x /tmp/machine-emulator.deb /tmp/cm \ + && mkdir -p /out/bin /out/lib /out/share/cartesi-machine \ + && cp -a /tmp/cm/usr/bin/cartesi-machine /out/bin/ \ + && cp -a /tmp/cm/usr/share/cartesi-machine/. /out/share/cartesi-machine/ \ + && cp /build/.deps/lua/lcurl.so /out/lib/lcurl.so + +FROM debian:${DEBIAN_VERSION} + +ARG RELEASE_TAG +ARG GIT_COMMIT +ARG CARTESI_MACHINE_VERSION +ARG LUA_CURL_UPSTREAM_SHA + +LABEL org.opencontainers.image.title="sequencer-watchdog" \ + org.opencontainers.image.version="${RELEASE_TAG}" \ + org.opencontainers.image.revision="${GIT_COMMIT}" \ + org.cartesi.sequencer.release-tag="${RELEASE_TAG}" \ + org.cartesi.cartesi-machine.version="${CARTESI_MACHINE_VERSION}" \ + org.cartesi.lua-curl.upstream-sha="${LUA_CURL_UPSTREAM_SHA}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + lua5.4 \ + libcurl4 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /out/bin/cartesi-machine /usr/local/bin/cartesi-machine +COPY --from=build /out/share/cartesi-machine /usr/local/share/cartesi-machine +COPY --from=build /out/lib/lcurl.so /opt/watchdog/lib/lcurl.so + +COPY watchdog /opt/watchdog/lua/watchdog +COPY watchdog/third_party/json.lua /opt/watchdog/lua/watchdog/third_party/json.lua +COPY watchdog/docker-entrypoint.sh /usr/local/bin/watchdog + +RUN chmod +x /usr/local/bin/watchdog /usr/local/bin/cartesi-machine \ + && printf '%s\n' \ + "{" \ + " \"component\": \"sequencer-watchdog\"," \ + " \"release_tag\": \"${RELEASE_TAG}\"," \ + " \"git_commit\": \"${GIT_COMMIT}\"," \ + " \"cartesi_machine_version\": \"${CARTESI_MACHINE_VERSION}\"," \ + " \"lua_curl_upstream_sha\": \"${LUA_CURL_UPSTREAM_SHA}\"" \ + "}" > /opt/watchdog/RELEASE.json + +ENV WATCHDOG_LUA_DEPS=/opt/watchdog/lib \ + LUA_PATH="/opt/watchdog/lua/?.lua;/opt/watchdog/lua/?/init.lua;;" \ + PATH="/usr/local/share/cartesi-machine:${PATH}" + +WORKDIR /opt/watchdog +ENTRYPOINT ["/usr/local/bin/watchdog"] +CMD [] diff --git a/watchdog/docker-entrypoint.sh b/watchdog/docker-entrypoint.sh new file mode 100755 index 0000000..b7fe743 --- /dev/null +++ b/watchdog/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Wrapper: run compare/advance loop with image-bundled Lua paths. +set -euo pipefail + +if [[ "${WATCHDOG_PRINT_RELEASE_INFO:-0}" == "1" ]]; then + cat /opt/watchdog/RELEASE.json + cartesi-machine --version 2>&1 | sed 's/^/cartesi-machine: /' +fi + +cd /opt/watchdog/lua +exec lua watchdog/main.lua "$@" From 33f6a8825269f54058c2aa47699760c4ff04883c Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:30:42 +0800 Subject: [PATCH 12/13] chore(watchdog): fetch lua-cURLv3 at build time instead of vendoring sources --- .gitignore | 3 +- docs/watchdog/README.md | 4 +- scripts/watchdog-lua-deps.sh | 65 +- watchdog/Dockerfile | 2 +- .../lua-curl/.appveyor/install.bat | 216 -- .../lua-curl/.appveyor/install_curl.bat | 49 - .../lua-curl/.appveyor/pack_artifact.bat | 12 - watchdog/third_party/lua-curl/.config | 2 - watchdog/third_party/lua-curl/.gitignore | 32 - watchdog/third_party/lua-curl/.travis.yml | 84 - .../third_party/lua-curl/.travis/platform.sh | 15 - .../third_party/lua-curl/.travis/setup_uv.sh | 25 - watchdog/third_party/lua-curl/CMakeLists.txt | 8 - watchdog/third_party/lua-curl/LICENSE | 21 - watchdog/third_party/lua-curl/Makefile | 82 - watchdog/third_party/lua-curl/README.md | 147 - watchdog/third_party/lua-curl/UPSTREAM | 12 +- watchdog/third_party/lua-curl/appveyor.yml | 90 - watchdog/third_party/lua-curl/doc/config.ld | 41 - watchdog/third_party/lua-curl/doc/curl.ldoc | 175 -- watchdog/third_party/lua-curl/doc/lcurl.ldoc | 657 ----- .../lua-curl/examples/cURLv2/browser.lua | 266 -- .../lua-curl/examples/cURLv2/file.lua | 16 - .../lua-curl/examples/cURLv2/multi.lua | 22 - .../lua-curl/examples/cURLv2/post_stream.lua | 49 - .../lua-curl/examples/cURLv2/rss.lua | 54 - .../lua-curl/examples/cURLv3/file.lua | 16 - .../lua-curl/examples/cURLv3/multi.lua | 17 - .../lua-curl/examples/cURLv3/multi2.lua | 35 - .../lua-curl/examples/cURLv3/multi3.lua | 28 - .../lua-curl/examples/cURLv3/pop3.lua | 346 --- .../lua-curl/examples/cURLv3/post_form.lua | 87 - .../lua-curl/examples/cURLv3/post_json.lua | 12 - .../lua-curl/examples/cURLv3/post_mime.lua | 47 - .../lua-curl/examples/cURLv3/rss.lua | 47 - .../lua-curl/examples/cURLv3/uvwget.lua | 278 -- .../lua-curl/examples/lcurl/crul_info.lua | 36 - .../lua-curl/examples/lcurl/curl_debug.lua | 42 - .../lua-curl/examples/lcurl/curl_info.lua | 36 - .../lua-curl/examples/lcurl/easy.obj.lua | 147 - .../lua-curl/examples/lcurl/file.lua | 18 - .../lua-curl/examples/lcurl/fnmatch.lua | 47 - .../lua-curl/examples/lcurl/ftpupload.lua | 17 - .../lua-curl/examples/lcurl/multi.lua | 24 - .../examples/lcurl/multi_iterator.lua | 78 - .../lua-curl/examples/lcurl/pause.lua | 78 - .../lua-curl/examples/lcurl/post.lua | 69 - .../lua-curl/examples/lcurl/post_stream.lua | 26 - .../lua-curl/examples/lcurl/share.lua | 23 - .../lua-curl/examples/lcurl/smtp-mime.lua | 85 - watchdog/third_party/lua-curl/lakeconfig.lua | 260 -- watchdog/third_party/lua-curl/lakefile | 44 - .../rockspecs/lua-curl-0.3.0-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.1-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.10-1.rockspec | 75 - .../rockspecs/lua-curl-0.3.11-1.rockspec | 75 - .../rockspecs/lua-curl-0.3.12-1.rockspec | 75 - .../rockspecs/lua-curl-0.3.13-1.rockspec | 75 - .../rockspecs/lua-curl-0.3.2-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.3-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.4-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.5-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.6-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.7-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.8-1.rockspec | 74 - .../rockspecs/lua-curl-0.3.8-2.rockspec | 74 - .../rockspecs/lua-curl-0.3.9-1.rockspec | 75 - .../rockspecs/lua-curl-scm-0.rockspec | 75 - watchdog/third_party/lua-curl/src/l52util.c | 178 -- watchdog/third_party/lua-curl/src/l52util.h | 97 - watchdog/third_party/lua-curl/src/lceasy.c | 2469 ----------------- watchdog/third_party/lua-curl/src/lceasy.h | 127 - .../third_party/lua-curl/src/lcerr_easy.h | 146 - .../third_party/lua-curl/src/lcerr_form.h | 8 - .../third_party/lua-curl/src/lcerr_multi.h | 14 - .../third_party/lua-curl/src/lcerr_share.h | 8 - watchdog/third_party/lua-curl/src/lcerr_url.h | 20 - watchdog/third_party/lua-curl/src/lcerror.c | 342 --- watchdog/third_party/lua-curl/src/lcerror.h | 34 - watchdog/third_party/lua-curl/src/lcflags.h | 283 -- .../third_party/lua-curl/src/lchttppost.c | 595 ---- .../third_party/lua-curl/src/lchttppost.h | 47 - .../third_party/lua-curl/src/lcinfoeasy.h | 94 - watchdog/third_party/lua-curl/src/lcmime.c | 686 ----- watchdog/third_party/lua-curl/src/lcmime.h | 66 - watchdog/third_party/lua-curl/src/lcmulti.c | 670 ----- watchdog/third_party/lua-curl/src/lcmulti.h | 50 - watchdog/third_party/lua-curl/src/lcopteasy.h | 557 ---- .../third_party/lua-curl/src/lcoptmulti.h | 17 - .../third_party/lua-curl/src/lcoptshare.h | 27 - watchdog/third_party/lua-curl/src/lcopturl.h | 29 - watchdog/third_party/lua-curl/src/lcshare.c | 152 - watchdog/third_party/lua-curl/src/lcshare.h | 30 - watchdog/third_party/lua-curl/src/lcurl.c | 487 ---- watchdog/third_party/lua-curl/src/lcurl.h | 31 - watchdog/third_party/lua-curl/src/lcurlapi.c | 218 -- watchdog/third_party/lua-curl/src/lcurlapi.h | 34 - watchdog/third_party/lua-curl/src/lcutils.c | 408 --- watchdog/third_party/lua-curl/src/lcutils.h | 108 - .../third_party/lua-curl/src/lua/cURL.lua | 14 - .../lua-curl/src/lua/cURL/impl/cURL.lua | 739 ----- .../lua-curl/src/lua/cURL/safe.lua | 14 - .../lua-curl/src/lua/cURL/utils.lua | 79 - watchdog/third_party/lua-curl/test/.luacov | 38 - .../test/deps/lua-http-parser-2.7-1.rockspec | 28 - .../lua-curl/test/lunit/console.lua | 132 - watchdog/third_party/lua-curl/test/run.lua | 34 - watchdog/third_party/lua-curl/test/server.lua | 121 - .../third_party/lua-curl/test/test_curl.lua | 451 --- .../third_party/lua-curl/test/test_easy.lua | 1309 --------- .../third_party/lua-curl/test/test_form.lua | 490 ---- .../third_party/lua-curl/test/test_mime.lua | 631 ----- .../lua-curl/test/test_multi_callback.lua | 116 - .../test/test_multi_nested_callback.lua | 47 - .../lua-curl/test/test_pause02.c.lua | 89 - .../third_party/lua-curl/test/test_safe.lua | 287 -- .../third_party/lua-curl/test/test_urlapi.lua | 315 --- watchdog/third_party/lua-curl/test/utils.lua | 151 - 118 files changed, 61 insertions(+), 18338 deletions(-) delete mode 100644 watchdog/third_party/lua-curl/.appveyor/install.bat delete mode 100644 watchdog/third_party/lua-curl/.appveyor/install_curl.bat delete mode 100644 watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat delete mode 100644 watchdog/third_party/lua-curl/.config delete mode 100644 watchdog/third_party/lua-curl/.gitignore delete mode 100644 watchdog/third_party/lua-curl/.travis.yml delete mode 100644 watchdog/third_party/lua-curl/.travis/platform.sh delete mode 100644 watchdog/third_party/lua-curl/.travis/setup_uv.sh delete mode 100644 watchdog/third_party/lua-curl/CMakeLists.txt delete mode 100644 watchdog/third_party/lua-curl/LICENSE delete mode 100644 watchdog/third_party/lua-curl/Makefile delete mode 100644 watchdog/third_party/lua-curl/README.md delete mode 100644 watchdog/third_party/lua-curl/appveyor.yml delete mode 100644 watchdog/third_party/lua-curl/doc/config.ld delete mode 100644 watchdog/third_party/lua-curl/doc/curl.ldoc delete mode 100644 watchdog/third_party/lua-curl/doc/lcurl.ldoc delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/browser.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/file.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/multi.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv2/rss.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/file.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/multi.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/rss.lua delete mode 100644 watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/file.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/multi.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/pause.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/post.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/share.lua delete mode 100644 watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua delete mode 100644 watchdog/third_party/lua-curl/lakeconfig.lua delete mode 100644 watchdog/third_party/lua-curl/lakefile delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec delete mode 100644 watchdog/third_party/lua-curl/src/l52util.c delete mode 100644 watchdog/third_party/lua-curl/src/l52util.h delete mode 100644 watchdog/third_party/lua-curl/src/lceasy.c delete mode 100644 watchdog/third_party/lua-curl/src/lceasy.h delete mode 100644 watchdog/third_party/lua-curl/src/lcerr_easy.h delete mode 100644 watchdog/third_party/lua-curl/src/lcerr_form.h delete mode 100644 watchdog/third_party/lua-curl/src/lcerr_multi.h delete mode 100644 watchdog/third_party/lua-curl/src/lcerr_share.h delete mode 100644 watchdog/third_party/lua-curl/src/lcerr_url.h delete mode 100644 watchdog/third_party/lua-curl/src/lcerror.c delete mode 100644 watchdog/third_party/lua-curl/src/lcerror.h delete mode 100644 watchdog/third_party/lua-curl/src/lcflags.h delete mode 100644 watchdog/third_party/lua-curl/src/lchttppost.c delete mode 100644 watchdog/third_party/lua-curl/src/lchttppost.h delete mode 100644 watchdog/third_party/lua-curl/src/lcinfoeasy.h delete mode 100644 watchdog/third_party/lua-curl/src/lcmime.c delete mode 100644 watchdog/third_party/lua-curl/src/lcmime.h delete mode 100644 watchdog/third_party/lua-curl/src/lcmulti.c delete mode 100644 watchdog/third_party/lua-curl/src/lcmulti.h delete mode 100644 watchdog/third_party/lua-curl/src/lcopteasy.h delete mode 100644 watchdog/third_party/lua-curl/src/lcoptmulti.h delete mode 100644 watchdog/third_party/lua-curl/src/lcoptshare.h delete mode 100644 watchdog/third_party/lua-curl/src/lcopturl.h delete mode 100644 watchdog/third_party/lua-curl/src/lcshare.c delete mode 100644 watchdog/third_party/lua-curl/src/lcshare.h delete mode 100644 watchdog/third_party/lua-curl/src/lcurl.c delete mode 100644 watchdog/third_party/lua-curl/src/lcurl.h delete mode 100644 watchdog/third_party/lua-curl/src/lcurlapi.c delete mode 100644 watchdog/third_party/lua-curl/src/lcurlapi.h delete mode 100644 watchdog/third_party/lua-curl/src/lcutils.c delete mode 100644 watchdog/third_party/lua-curl/src/lcutils.h delete mode 100644 watchdog/third_party/lua-curl/src/lua/cURL.lua delete mode 100644 watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua delete mode 100644 watchdog/third_party/lua-curl/src/lua/cURL/safe.lua delete mode 100644 watchdog/third_party/lua-curl/src/lua/cURL/utils.lua delete mode 100644 watchdog/third_party/lua-curl/test/.luacov delete mode 100644 watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec delete mode 100644 watchdog/third_party/lua-curl/test/lunit/console.lua delete mode 100644 watchdog/third_party/lua-curl/test/run.lua delete mode 100644 watchdog/third_party/lua-curl/test/server.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_curl.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_easy.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_form.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_mime.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_multi_callback.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_pause02.c.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_safe.lua delete mode 100644 watchdog/third_party/lua-curl/test/test_urlapi.lua delete mode 100644 watchdog/third_party/lua-curl/test/utils.lua diff --git a/.gitignore b/.gitignore index 921651b..46d6cb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target .deps/ -# lua-curl sources are vendored under watchdog/third_party/lua-curl/ (see UPSTREAM). +watchdog/third_party/lua-curl/* +!watchdog/third_party/lua-curl/UPSTREAM watchdog-e2e-*/ .env .env.fish diff --git a/docs/watchdog/README.md b/docs/watchdog/README.md index 12cfb1d..ced0dc3 100644 --- a/docs/watchdog/README.md +++ b/docs/watchdog/README.md @@ -62,7 +62,7 @@ pkg-config --exists libcurl && echo "libcurl ok" test -f /usr/include/lua5.4/lua.h && echo "lua headers ok" ``` -On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/include/`. The repo script passes `LUA_INC` accordingly when invoking the vendored lua-cURL Makefile (`scripts/watchdog-lua-deps.sh`). +On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/include/`. The repo script fetches pinned lua-cURLv3 at build time and passes `LUA_INC` accordingly (`scripts/watchdog-lua-deps.sh`). ### Troubleshooting `just watchdog-lua-deps` @@ -72,7 +72,7 @@ On Debian/Ubuntu, Lua headers live under **`/usr/include/lua5.4/`**, not `/usr/i | `install Lua headers` | `sudo apt-get install -y liblua5.4-dev` | | `fatal error: lua.h: No such file or directory` | Install `liblua5.4-dev`. If headers are present but build still fails, ensure you are on a tree where `scripts/watchdog-lua-deps.sh` passes **`LUA_INC`** (not `LUA_INCLUDE_DIR`) to make — see script in repo | | `built lcurl.so but lua cannot load it` | Lua version mismatch: build with the same `lua` you run (`lua -v` vs headers under `lua5.4`) | -| `need curl or wget` | Fetch tool to download pinned lua-cURL sources into `watchdog/third_party/lua-curl/` | +| `need curl or wget` | Install `curl` or `wget` to download pinned lua-cURLv3 into `.deps/lua-curl-src/` | CI runs **`just test-watchdog`** (mocked HTTP) and the Rust watchdog compare harness (`watchdog_genesis_compare_test`) in the rollups-e2e job. Full local smoke remains available via `just test-watchdog-compare-harness`. diff --git a/scripts/watchdog-lua-deps.sh b/scripts/watchdog-lua-deps.sh index 2a72bfa..5738460 100755 --- a/scripts/watchdog-lua-deps.sh +++ b/scripts/watchdog-lua-deps.sh @@ -1,14 +1,49 @@ #!/usr/bin/env bash # Build watchdog Lua native deps: lcurl (lua-cURLv3) into .deps/lua. +# Sources are fetched at build time (pinned); only UPSTREAM is tracked in git. # JSON is pure Lua under watchdog/third_party/json.lua (no compile step). set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" out_dir="${root}/.deps/lua" out_so="${out_dir}/lcurl.so" -vendor_dir="${root}/watchdog/third_party/lua-curl" -upstream_sha="${LUA_CURL_UPSTREAM_SHA:-9f8b6dba8b5ef1b26309a571ae75cda4034279e5}" +upstream_file="${root}/watchdog/third_party/lua-curl/UPSTREAM" +versions_file="${root}/release/versions.env" + +resolve_upstream_sha() { + if [[ -n "${LUA_CURL_UPSTREAM_SHA:-}" ]]; then + echo "${LUA_CURL_UPSTREAM_SHA}" + return + fi + if [[ -f "${versions_file}" ]]; then + # shellcheck disable=SC1090 + local from_versions + from_versions="$( + set -a + source "${versions_file}" + set +a + echo "${LUA_CURL_UPSTREAM_SHA:-}" + )" + if [[ -n "${from_versions}" ]]; then + echo "${from_versions}" + return + fi + fi + if [[ -f "${upstream_file}" ]]; then + grep -E '^Commit:' "${upstream_file}" | awk '{print $2}' + return + fi + echo "9f8b6dba8b5ef1b26309a571ae75cda4034279e5" +} + +upstream_sha="$(resolve_upstream_sha)" +if [[ -z "${upstream_sha}" ]]; then + echo "watchdog-lua-deps: could not resolve Lua-cURLv3 upstream pin" >&2 + exit 1 +fi + upstream_tar="https://github.com/Lua-cURL/Lua-cURLv3/archive/${upstream_sha}.tar.gz" +src_cache="${root}/.deps/lua-curl-src/${upstream_sha}" mkdir -p "${out_dir}" @@ -20,10 +55,11 @@ if [[ -f "${out_so}" ]] && lcurl_loadable; then exit 0 fi -if [[ ! -f "${vendor_dir}/Makefile" ]]; then - echo "watchdog-lua-deps: populating ${vendor_dir} from pinned Lua-cURLv3 (${upstream_sha})" >&2 +fetch_sources() { + echo "watchdog-lua-deps: fetching Lua-cURLv3 ${upstream_sha}" >&2 + local tmp tmp="$(mktemp -d)" - trap 'rm -rf "${tmp}"' EXIT + trap 'rm -rf "${tmp}"' RETURN if command -v curl >/dev/null 2>&1; then curl -fsSL "${upstream_tar}" | tar -xz -C "${tmp}" elif command -v wget >/dev/null 2>&1; then @@ -33,14 +69,18 @@ if [[ ! -f "${vendor_dir}/Makefile" ]]; then exit 1 fi shopt -s nullglob - dirs=("${tmp}"/Lua-cURLv3-*) + local dirs=("${tmp}"/Lua-cURLv3-*) if [[ ${#dirs[@]} -ne 1 ]]; then echo "watchdog-lua-deps: unexpected Lua-cURLv3 extract layout" >&2 exit 1 fi - rm -rf "${vendor_dir}" - mkdir -p "$(dirname "${vendor_dir}")" - cp -a "${dirs[0]}" "${vendor_dir}" + rm -rf "${src_cache}" + mkdir -p "$(dirname "${src_cache}")" + cp -a "${dirs[0]}" "${src_cache}" +} + +if [[ ! -f "${src_cache}/Makefile" ]]; then + fetch_sources fi lua_inc="" @@ -66,9 +106,6 @@ if ! pkg-config --exists libcurl 2>/dev/null; then exit 1 fi -build_dir="$(mktemp -d)" -trap 'rm -rf "${build_dir}"' EXIT -cp -a "${vendor_dir}/." "${build_dir}/" # Lua-cURL Makefile uses LUA_INC (not LUA_INCLUDE_DIR). On Debian/Ubuntu headers # live under /usr/include/lua5.4/, not /usr/include/. lua_impl="" @@ -86,9 +123,9 @@ if [[ -n "${lua_impl}" ]]; then make_args+=("LUA_IMPL=${lua_impl}") fi -make -C "${build_dir}" "${make_args[@]}" >/dev/null +make -C "${src_cache}" "${make_args[@]}" >/dev/null -built_so="$(find "${build_dir}" -name 'lcurl.so' -o -name 'cURL.so' | head -1)" +built_so="$(find "${src_cache}" -name 'lcurl.so' -o -name 'cURL.so' | head -1)" if [[ -z "${built_so}" ]]; then echo "watchdog-lua-deps: make succeeded but lcurl.so not found" >&2 exit 1 diff --git a/watchdog/Dockerfile b/watchdog/Dockerfile index 60710b3..55dc823 100644 --- a/watchdog/Dockerfile +++ b/watchdog/Dockerfile @@ -24,7 +24,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /build COPY scripts/watchdog-lua-deps.sh scripts/watchdog-lua-deps.sh -COPY watchdog/third_party/lua-curl watchdog/third_party/lua-curl +COPY watchdog/third_party/lua-curl/UPSTREAM watchdog/third_party/lua-curl/UPSTREAM COPY watchdog/third_party/json.lua watchdog/third_party/json.lua ENV LUA_CURL_UPSTREAM_SHA=${LUA_CURL_UPSTREAM_SHA} diff --git a/watchdog/third_party/lua-curl/.appveyor/install.bat b/watchdog/third_party/lua-curl/.appveyor/install.bat deleted file mode 100644 index 4cef264..0000000 --- a/watchdog/third_party/lua-curl/.appveyor/install.bat +++ /dev/null @@ -1,216 +0,0 @@ -@echo off - -cd %APPVEYOR_BUILD_FOLDER% - -:: ========================================================= -:: Set some defaults. Infer some variables. -:: -:: These are set globally -if not "%LUA_VER%"=="" ( - set LUA=lua - set LUA_SHORTV=%LUA_VER:~0,3% -) else if not "%LJ_VER%"=="" ( - set LUA=luajit - set LJ_SHORTV=%LJ_VER:~0,3% - set LUA_SHORTV=5.1 -) else ( - echo Can not recognize needed Lua version - echo Please set LUA_VER or LJ_VER - exit /B 3 -) - -:: Now we declare a scope -Setlocal EnableDelayedExpansion EnableExtensions - -set LUAROCKS_SHORTV=%LUAROCKS_VER:~0,3% - -if not defined LUAROCKS_URL set LUAROCKS_URL=http://keplerproject.github.io/luarocks/releases -if not defined LUAROCKS_REPO set LUAROCKS_REPO=http://rocks.moonscript.org -if not defined LUA_URL set LUA_URL=http://www.lua.org/ftp -if not defined LUAJIT_GIT_REPO set LUAJIT_GIT_REPO=http://luajit.org/git/luajit-2.0.git -if not defined LUAJIT_URL set LUAJIT_URL=http://luajit.org/download - -if not defined LR_EXTERNAL set LR_EXTERNAL=c:\external -if not defined LUAROCKS_INSTALL set LUAROCKS_INSTALL=%ProgramFiles(x86)%\LuaRocks -if not defined LR_ROOT set LR_ROOT=%LUAROCKS_INSTALL%\%LUAROCKS_SHORTV% -if not defined LR_SYSTREE set LR_SYSTREE=%LUAROCKS_INSTALL%\systree -if /I "%platform%"=="x64" set LR_SYSTREE=%ProgramFiles%\LuaRocks\systree - -if not defined SEVENZIP set SEVENZIP=7z - -:: -:: ========================================================= - -:: first create some necessary directories: -mkdir downloads 2>NUL - -:: defines LUA_DIR so Cmake can find this Lua install -if "%LUA%"=="luajit" ( - set LUA_DIR=c:\lua\lj%LJ_SHORTV% -) else ( - set LUA_DIR=c:\lua\%LUA_VER% -) - -:: Download and compile Lua (or LuaJIT) -if "%LUA%"=="luajit" ( - if not exist %LUA_DIR% ( - if "%LJ_SHORTV%"=="2.1" ( - :: Clone repository and checkout 2.1 branch - set lj_source_folder=%APPVEYOR_BUILD_FOLDER%\downloads\luajit-%LJ_VER% - if not exist !lj_source_folder! ( - echo Cloning git repo %LUAJIT_GIT_REPO% !lj_source_folder! - git clone %LUAJIT_GIT_REPO% !lj_source_folder! || call :die "Failed to clone repository" - ) - cd !lj_source_folder!\src - git checkout v2.1 || call :die - ) else ( - set lj_source_folder=%APPVEYOR_BUILD_FOLDER%\downloads\luajit-%LJ_VER% - if not exist !lj_source_folder! ( - echo Downloading... %LUAJIT_URL%/LuaJIT-%LJ_VER%.tar.gz - curl --silent --fail --max-time 120 --connect-timeout 30 %LUAJIT_URL%/LuaJIT-%LJ_VER%.tar.gz | %SEVENZIP% x -si -so -tgzip | %SEVENZIP% x -si -ttar -aoa -odownloads - ) - cd !lj_source_folder!\src - ) - :: Compiles LuaJIT - call msvcbuild.bat - - mkdir %LUA_DIR% 2> NUL - for %%a in (bin include lib) do ( mkdir "%LUA_DIR%\%%a" ) - - for %%a in (luajit.exe lua51.dll) do ( move "!lj_source_folder!\src\%%a" "%LUA_DIR%\bin" ) - - move "!lj_source_folder!\src\lua51.lib" "%LUA_DIR%\lib" - for %%a in (lauxlib.h lua.h lua.hpp luaconf.h lualib.h luajit.h) do ( - copy "!lj_source_folder!\src\%%a" "%LUA_DIR%\include" - ) - - ) else ( - echo LuaJIT %LJ_VER% already installed at %LUA_DIR% - ) -) else ( - if not exist %LUA_DIR% ( - :: Download and compile Lua - if not exist downloads\lua-%LUA_VER% ( - curl --silent --fail --max-time 120 --connect-timeout 30 %LUA_URL%/lua-%LUA_VER%.tar.gz | %SEVENZIP% x -si -so -tgzip | %SEVENZIP% x -si -ttar -aoa -odownloads - ) - - mkdir downloads\lua-%LUA_VER%\etc 2> NUL - if not exist downloads\lua-%LUA_VER%\etc\winmake.bat ( - curl --silent --location --insecure --fail --max-time 120 --connect-timeout 30 https://github.com/Tieske/luawinmake/archive/master.tar.gz | %SEVENZIP% x -si -so -tgzip | %SEVENZIP% e -si -ttar -aoa -odownloads\lua-%LUA_VER%\etc luawinmake-master\etc\winmake.bat - ) - - cd downloads\lua-%LUA_VER% - call etc\winmake - call etc\winmake install %LUA_DIR% - ) else ( - echo Lua %LUA_VER% already installed at %LUA_DIR% - ) -) - -if not exist %LUA_DIR%\bin\%LUA%.exe ( - echo Missing Lua interpreter - exit /B 1 -) - -set PATH=%LUA_DIR%\bin;%PATH% -call %LUA% -v - -:: ========================================================= -:: LuaRocks -:: ========================================================= - -if not exist "%LR_ROOT%" ( - :: Downloads and installs LuaRocks - cd %APPVEYOR_BUILD_FOLDER% - - if not exist downloads\luarocks-%LUAROCKS_VER%-win32.zip ( - echo Downloading LuaRocks... - curl --silent --fail --max-time 120 --connect-timeout 30 --output downloads\luarocks-%LUAROCKS_VER%-win32.zip %LUAROCKS_URL%/luarocks-%LUAROCKS_VER%-win32.zip - %SEVENZIP% x -aoa -odownloads downloads\luarocks-%LUAROCKS_VER%-win32.zip - ) - - cd downloads\luarocks-%LUAROCKS_VER%-win32 - call install.bat /LUA %LUA_DIR% /Q /LV %LUA_SHORTV% /P "%LUAROCKS_INSTALL%" -) - -if not exist "%LR_ROOT%" ( - echo LuaRocks installation failed. - exit /B 2 -) - -set PATH=%LR_ROOT%;%LR_SYSTREE%\bin;%PATH% - -:: Lua will use just the system rocks -set LUA_PATH=%LR_ROOT%\lua\?.lua;%LR_ROOT%\lua\?\init.lua -set LUA_PATH=%LUA_PATH%;.\?.lua; -set LUA_PATH=%LUA_PATH%;%LR_SYSTREE%\share\lua\%LUA_SHORTV%\?.lua -set LUA_PATH=%LUA_PATH%;%LR_SYSTREE%\share\lua\%LUA_SHORTV%\?\init.lua -set LUA_CPATH=%LR_SYSTREE%\lib\lua\%LUA_SHORTV%\?.dll - -call luarocks --version || call :die "Error with LuaRocks installation" -call luarocks list - -if not exist "%LR_EXTERNAL%" ( - mkdir "%LR_EXTERNAL%" - mkdir "%LR_EXTERNAL%\lib" - mkdir "%LR_EXTERNAL%\include" -) - -set PATH=%LR_EXTERNAL%;%PATH% - -:: Exports the following variables: -:: (beware of whitespace between & and ^ below) -endlocal & set PATH=%PATH%&^ -set LUA_DIR=%LUA_DIR%&^ -set LR_SYSTREE=%LR_SYSTREE%&^ -set LUA_PATH=%LUA_PATH%&^ -set LUA_CPATH=%LUA_CPATH%&^ -set LR_EXTERNAL=%LR_EXTERNAL% - -echo ====================================================== -if "%LUA%"=="luajit" ( - echo Installation of LuaJIT %LJ_VER% and LuaRocks %LUAROCKS_VER% done. -) else ( - echo Installation of Lua %LUA_VER% and LuaRocks %LUAROCKS_VER% done. -) -echo Platform - %platform% -echo LUA - %LUA% -echo LUA_SHORTV - %LUA_SHORTV% -echo LJ_SHORTV - %LJ_SHORTV% -echo LUA_PATH - %LUA_PATH% -echo LUA_CPATH - %LUA_CPATH% -echo -echo LR_EXTERNAL - %LR_EXTERNAL% -echo ====================================================== - -goto :eof - - - - - - - - - - - - - - - - - - -:: This blank space is intentional. If you see errors like "The system cannot find the batch label specified 'foo'" -:: then try adding or removing blank lines lines above. -:: Yes, really. -:: http://stackoverflow.com/questions/232651/why-the-system-cannot-find-the-batch-label-specified-is-thrown-even-if-label-e - -:: helper functions: - -:: for bailing out when an error occurred -:die %1 -echo %1 -exit 1 -goto :eof diff --git a/watchdog/third_party/lua-curl/.appveyor/install_curl.bat b/watchdog/third_party/lua-curl/.appveyor/install_curl.bat deleted file mode 100644 index c996d71..0000000 --- a/watchdog/third_party/lua-curl/.appveyor/install_curl.bat +++ /dev/null @@ -1,49 +0,0 @@ -SETLOCAL - -set PLAT_NAME=Win32 -set CURL_PLAT=Win32 -set CURL_URL=http://curl.haxx.se/download -set CURL_CFG=DLL Release - DLL Windows SSPI - -if /I "%platform%" == "x64" ( - set PLAT_NAME=Win64 - set CURL_PLAT=x64 -) - -cd %APPVEYOR_BUILD_FOLDER% - -echo ========================================= -echo External library path: %LR_EXTERNAL% -echo ========================================= - -if not exist %LR_EXTERNAL%\libcurl.dll ( - @echo Download %CURL_URL%/curl-%CURL_VER%.zip ... - - appveyor DownloadFile %CURL_URL%/curl-%CURL_VER%.zip - 7z x curl-%CURL_VER%.zip - cd curl-%CURL_VER% - - @echo Build curl %CURL_CFG% / %PLAT_NAME% ... - - if exist projects\Windows\VC12\curl.sln ( - msbuild projects\Windows\VC12\curl.sln /p:Configuration="%CURL_CFG%" /p:Platform=%CURL_PLAT% - ) else ( - msbuild projects\Windows\VC12\curl-all.sln /p:Configuration="%CURL_CFG%" /p:Platform=%CURL_PLAT% - ) - - @echo Build curl done - - if not exist %LR_EXTERNAL%\include\curl mkdir %LR_EXTERNAL%\include\curl - copy "include\curl\*.h" %LR_EXTERNAL%\include\curl - copy "build\%PLAT_NAME%\VC12\%CURL_CFG%\libcurl.lib" %LR_EXTERNAL%\lib\libcurl.lib - copy "build\%PLAT_NAME%\VC12\%CURL_CFG%\libcurl.lib" %LR_EXTERNAL%\libcurl.lib - copy "build\%PLAT_NAME%\VC12\%CURL_CFG%\libcurl.dll" %LR_EXTERNAL%\libcurl.dll -) - -if not exist %LR_EXTERNAL%\libcurl.dll ( - exit /B 1 -) - -appveyor PushArtifact %LR_EXTERNAL%\libcurl.dll -DeploymentName ext-deps - -cd %APPVEYOR_BUILD_FOLDER% diff --git a/watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat b/watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat deleted file mode 100644 index bd52934..0000000 --- a/watchdog/third_party/lua-curl/.appveyor/pack_artifact.bat +++ /dev/null @@ -1,12 +0,0 @@ -@setlocal - -@for /f "delims=" %%A in ('luarocks pack %1') do @set "rock_name=%%A" - -@set rock_name=%rock_name:Packed: =% - -@echo make rock file as artifact: %rock_name% - -@appveyor PushArtifact %rock_name% -DeploymentName %2 - -@endlocal - diff --git a/watchdog/third_party/lua-curl/.config b/watchdog/third_party/lua-curl/.config deleted file mode 100644 index 30c625a..0000000 --- a/watchdog/third_party/lua-curl/.config +++ /dev/null @@ -1,2 +0,0 @@ -# You can freely define some Makefile variables here (if you don't want to call: -# $ make VAR=val diff --git a/watchdog/third_party/lua-curl/.gitignore b/watchdog/third_party/lua-curl/.gitignore deleted file mode 100644 index 82062cf..0000000 --- a/watchdog/third_party/lua-curl/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# Object files -*.o -*.ko -*.obj -*.elf - -# Libraries -*.lib -*.a - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -#MSVC -msvc/ - -#ldoc -doc/doc/ - -#lake deps -*.d diff --git a/watchdog/third_party/lua-curl/.travis.yml b/watchdog/third_party/lua-curl/.travis.yml deleted file mode 100644 index 194ba7c..0000000 --- a/watchdog/third_party/lua-curl/.travis.yml +++ /dev/null @@ -1,84 +0,0 @@ -language: c - -sudo: false - -env: - global: - - LCURL_CC_FLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" - - LCURL_LD_FLAGS="-shared --coverage" - -matrix: - include: - - env: LUA="lua 5.1" - os: osx - - env: LUA="lua 5.1" - os: linux - - env: LUA="lua 5.2" - os: linux - - env: LUA="lua 5.3" - os: linux - - env: LUA="lua 5.4" - os: linux - - env: LUA="luajit 2.0" - os: linux - - env: LUA="luajit 2.1" - os: linux - -cache: - directories: - - here - - $HOME/.cache/pip - -branches: - only: - - master - - curl_mime - -before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=$PATH:~/Library/Python/2.7/bin/; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export LCURL_LD_FLAGS="-bundle -undefined dynamic_lookup -all_load --coverage"; fi - - pip2 install --user cpp-coveralls - - pip2 install --user hererocks - - hererocks here -r^ --$LUA - - source here/bin/activate - - luarocks show lluv > /dev/null 2>&1 || bash .travis/setup_uv.sh - -install: - - luarocks make rockspecs/lua-curl-scm-0.rockspec CFLAGS="$LCURL_CC_FLAGS" LIBFLAG="$LCURL_LD_FLAGS" - -before_script: - - luarocks show luacov-coveralls > /dev/null 2>&1 || luarocks install luacov-coveralls - - luarocks show lunitx > /dev/null 2>&1 || luarocks install lunitx - - luarocks show luafilesystem > /dev/null 2>&1 || luarocks install luafilesystem - - luarocks show dkjson > /dev/null 2>&1 || luarocks install dkjson --deps-mode=none - - luarocks show luarocks-fetch-gitrec > /dev/null 2>&1 || luarocks install luarocks-fetch-gitrec - - luarocks show lua-http-parser > /dev/null 2>&1 || luarocks install lua-http-parser || luarocks install test/deps/lua-http-parser-2.7-1.rockspec - - luarocks show pegasus > /dev/null 2>&1 || luarocks install pegasus http.parser - --server=http://luarocks.org/manifests/moteus - - luarocks show pegasus-router > /dev/null 2>&1 || luarocks install pegasus-router - --server=http://luarocks.org/dev - - luarocks show lluv > /dev/null 2>&1 || luarocks install lluv UV_DIR=$TRAVIS_BUILD_DIR/libuv - --server=http://luarocks.org/dev - - luarocks show lluv-pegasus > /dev/null 2>&1 || luarocks install lluv-pegasus - --server=http://luarocks.org/dev --deps-mode=none - - lua test/server.lua & - - curl -s http://127.0.0.1:7090/get - -script: - - cd test - - lua -e "print(require 'cURL.utils'.find_ca_bundle())" - - lunit.sh run.lua - - lua test_pause02.c.lua - - lua test_multi_callback.lua - - lua test_multi_nested_callback.lua - -before_cache: - - coveralls -b .. -r .. --dump c.report.json - - luacov-coveralls -j c.report.json -v - - luarocks remove lua-curl - - rm -f /home/travis/.cache/pip/log/debug.log - -notifications: - email: - on_success: change - on_failure: always diff --git a/watchdog/third_party/lua-curl/.travis/platform.sh b/watchdog/third_party/lua-curl/.travis/platform.sh deleted file mode 100644 index 7259a7d..0000000 --- a/watchdog/third_party/lua-curl/.travis/platform.sh +++ /dev/null @@ -1,15 +0,0 @@ -if [ -z "${PLATFORM:-}" ]; then - PLATFORM=$TRAVIS_OS_NAME; -fi - -if [ "$PLATFORM" == "osx" ]; then - PLATFORM="macosx"; -fi - -if [ -z "$PLATFORM" ]; then - if [ "$(uname)" == "Linux" ]; then - PLATFORM="linux"; - else - PLATFORM="macosx"; - fi; -fi diff --git a/watchdog/third_party/lua-curl/.travis/setup_uv.sh b/watchdog/third_party/lua-curl/.travis/setup_uv.sh deleted file mode 100644 index 34eb88e..0000000 --- a/watchdog/third_party/lua-curl/.travis/setup_uv.sh +++ /dev/null @@ -1,25 +0,0 @@ -#! /bin/bash - -source .travis/platform.sh - -cd $TRAVIS_BUILD_DIR - -git clone https://github.com/libuv/libuv.git -b v1.x - -cd libuv - -git checkout v1.19.0 - -mkdir -p lib -mkdir -p build -git clone https://chromium.googlesource.com/external/gyp build/gyp - -if [ "$PLATFORM" == "macosx" ]; then - ./gyp_uv.py -f xcode && xcodebuild -ARCHS="x86_64" -project uv.xcodeproj -configuration Release -target All - cp ./build/Release/libuv.a ./lib; -else - ./gyp_uv.py -f make && BUILDTYPE=Release CFLAGS=-fPIC make -C out - cp ./out/Release/libuv.a ./lib; -fi - -cd $TRAVIS_BUILD_DIR diff --git a/watchdog/third_party/lua-curl/CMakeLists.txt b/watchdog/third_party/lua-curl/CMakeLists.txt deleted file mode 100644 index fcd5b73..0000000 --- a/watchdog/third_party/lua-curl/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -cmake_minimum_required (VERSION 2.6) -project (lcurl) - -file(GLOB lcurl_sources ./src/**.c) -SET(lcurl ${lcurl_sources}) - -ADD_LIBRARY (lcurl ${lcurl}) -TARGET_LINK_LIBRARIES (lcurl) \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/LICENSE b/watchdog/third_party/lua-curl/LICENSE deleted file mode 100644 index 3850127..0000000 --- a/watchdog/third_party/lua-curl/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014-2021 Alexey Melnichuk - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/watchdog/third_party/lua-curl/Makefile b/watchdog/third_party/lua-curl/Makefile deleted file mode 100644 index 3696549..0000000 --- a/watchdog/third_party/lua-curl/Makefile +++ /dev/null @@ -1,82 +0,0 @@ -include .config - -T=lcurl - -UNAME ?= $(shell uname) -DESTDIR ?= / -PKG_CONFIG ?= pkg-config -INSTALL ?= install -RM ?= rm -LUA_IMPL ?= lua -CC ?= $(MAC_ENV) gcc - -LUA_VERSION = $(shell $(PKG_CONFIG) --print-provides --silence-errors $(LUA_IMPL)) -ifeq ($(LUA_VERSION),) -LUA_CMOD ?= /usr/lib/lua/5.1 -LUA_LMOD ?= /usr/share/lua/5.1 -LIBDIR ?= /usr/lib -LUA_INC ?= /usr/include -CURL_LIBS = -L/usr/lib -lcurl -else -LUA_CMOD ?= $(shell $(PKG_CONFIG) --variable INSTALL_CMOD $(LUA_IMPL)) -LUA_LMOD ?= $(shell $(PKG_CONFIG) --variable INSTALL_LMOD $(LUA_IMPL)) -LIBDIR ?= $(shell $(PKG_CONFIG) --variable libdir $(LUA_IMPL)) -LUA_INC ?= $(shell $(PKG_CONFIG) --variable includedir $(LUA_IMPL)) -LUA_LIBS = $(shell $(PKG_CONFIG) --libs $(LUA_IMPL)) -CURL_LIBS = $(shell $(PKG_CONFIG) --libs libcurl) -endif - -ifeq ($(UNAME), Linux) -OS_FLAGS ?= -shared -endif -ifeq ($(UNAME), Darwin) -OS_FLAGS ?= -bundle -undefined dynamic_lookup -MAC_ENV ?= env MACOSX_DEPLOYMENT_TARGET='10.3' -endif - -ifneq ($(DEBUG),) -DBG = -ggdb -endif - -ifeq ($(DEV),) -WARN = -Wall -Wno-unused-value -else -WARN = -Wall -W -Waggregate-return -Wcast-align -Wmissing-prototypes -Wnested-externs -Wshadow -Wwrite-strings -pedantic -endif - -INCLUDES = -I$(LUA_INC) -DEFINES = -LIBS = $(CURL_LIBS) - -COMMONFLAGS = -O2 -g -pipe -fPIC $(OS_FLAGS) $(DBG) -LF = $(LIBS) $(LDFLAGS) -CF = $(INCLUDES) $(DEFINES) $(COMMONFLAGS) $(WARN) -DPTHREADS $(CFLAGS) - -SCR = src/lua/*.lua src/lua/cURL/*.lua src/lua/cURL/impl/*.lua -SRCS = src/*.c -OBJS = $(subst src/,,$(subst .c,.o,$(SRCS))) - -BIN = $(T).so -STATIC_LIB = $(T).a - -all: $(BIN) - -$(BIN): $(SRCS) - $(CC) $(CF) -o $@ $^ $(LF) - -$(OBJS): $(SRCS) - $(CC) $(CF) -c $^ $(LF) - -$(STATIC_LIB): $(OBJS) - ar rcs $@ $^ - -install: all - $(INSTALL) -d $(DESTDIR)$(LUA_CMOD) $(DESTDIR)$(LUA_LMOD)/cURL/impl - $(INSTALL) $(BIN) $(DESTDIR)$(LUA_CMOD) - $(INSTALL) src/lua/cURL.lua $(DESTDIR)$(LUA_LMOD) - $(INSTALL) src/lua/cURL/safe.lua $(DESTDIR)$(LUA_LMOD)/cURL - $(INSTALL) src/lua/cURL/utils.lua $(DESTDIR)$(LUA_LMOD)/cURL - $(INSTALL) src/lua/cURL/impl/cURL.lua $(DESTDIR)$(LUA_LMOD)/cURL/impl - -clean: - $(RM) -f $(BIN) $(OBJS) $(STATIC_LIB) diff --git a/watchdog/third_party/lua-curl/README.md b/watchdog/third_party/lua-curl/README.md deleted file mode 100644 index e64d70e..0000000 --- a/watchdog/third_party/lua-curl/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# Lua binding to [libcurl](http://curl.haxx.se/libcurl) -[![Build Status](https://travis-ci.org/Lua-cURL/Lua-cURLv3.svg?branch=master)](https://travis-ci.org/Lua-cURL/Lua-cURLv3) -[![Build status](https://ci.appveyor.com/api/projects/status/oxiuv2u04ob391k0?svg=true)](https://ci.appveyor.com/project/moteus/lua-curlv3) -[![Coverage Status](https://coveralls.io/repos/Lua-cURL/Lua-cURLv3/badge.png?branch=master)](https://coveralls.io/r/Lua-cURL/Lua-cURLv3?branch=master) -[![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) - -## Status - -This module include three layer - -1. `lcurl` module provide low level pure C binding to libcurl.
-Almost ready and needs tests. I have no plans to change this API. - -2. `cURL` module provide compatibility for Lua-cURLv2 API.
-Almost ready and needs tests. - -3. `cURL` module provide new high level API.
-In fact for now it provide `lcurl` API directly and needed to redesign.
- - -## Documentation -[lcurl API](http://lua-curl.github.io/lcurl/modules/lcurl.html)
-[Lua-cURLv2 API](http://lua-curl.github.io)
-[Lua-cURLv3 API](http://lua-curl.github.io/lcurl/modules/cURL.html) - -## - -Original Lua-cURLv2 binding has several problems: - -* it can not return error codes but just raise Lua errors (Fixed. use `cURL.safe` module) -* it raise Lua error from callback that may result resource leak in libcurl (Fixed.) -* it does not provide building multipart/formdata explicitly (Fixed.) -* it has memory leak when send multipart/formdata (Fixed.) -* it does not save string for curl options that may result crush in libcurl (Fixed.) -* there no way to get result for operations in multi interface (e.g. if one of easy operation fail you can not get result code/error message) (Fixed. But it does not very handy interface.) -* you can not use your own callback function to perform operation with multi interface (Could not be fixed without changing API.) -* you can not pass your context to callback functions (Could not be fixed without changing API.) - -## Installation - -Using LuaRocks: -``` -luarocks install Lua-cURL -``` - -Install current master: -``` -luarocks install Lua-cURL --server=https://luarocks.org/dev -``` - -## List of incompatibility with original [Lua-cURLv2](https://github.com/Lua-cURL/Lua-cURLv2) - -* objects are tables -* multi:perform() also returns ("done",code), ("error",error) and ("response",code) records -* writer callback does not recv string len (just string itself) -* on Lua > 5.2 errors are objects but not strings - -## Usage - -```Lua --- HTTP Get -curl.easy{ - url = 'http://httpbin.org/get', - httpheader = { - "X-Test-Header1: Header-Data1", - "X-Test-Header2: Header-Data2", - }, - writefunction = io.stderr -- use io.stderr:write() - } - :perform() -:close() -``` - -```Lua --- HTTP Post -curl.easy() - :setopt_url('http://posttestserver.com/post.php') - :setopt_writefunction(io.write) - :setopt_httppost(curl.form() -- Lua-cURL guarantee that form will be alive - :add_content("test_content", "some data", { - "MyHeader: SomeValue" - }) - :add_buffer("test_file", "filename", "text data", "text/plain", { - "Description: my file description" - }) - :add_file("test_file2", "BuildLog.htm", "application/octet-stream", { - "Description: my file description" - }) - ) - :perform() -:close() -``` - -```Lua --- FTP Upload -local function get_bin_by(str,n) - local pos = 1 - n - return function() - pos = pos + n - return (str:sub(pos,pos+n-1)) - end -end - -curl.easy() - :setopt_url("ftp://moteus:123456@127.0.0.1/test.dat") - :setopt_upload(true) - :setopt_readfunction( - get_bin_by(("0123456789"):rep(4), 9) - ) - :perform() -:close() -``` - -```Lua --- Multi FTP Upload - --- We get error E_LOGIN_DENIED for this operation -e1 = curl.easy{url = "ftp://moteus:999999@127.0.0.1/test1.dat", upload = true} - :setopt_readfunction( - function(t) return table.remove(t) end, {"1111", "2222"} - ) - -e2 = curl.easy{url = "ftp://moteus:123456@127.0.0.1/test2.dat", upload = true} - :setopt_readfunction(get_bin_by(("e"):rep(1000), 5)) - -m = curl.multi() -m:add_handle(e1) -m:add_handle(e2) - -while m:perform() > 0 do m:wait() end - -while true do - h, ok, err = m:info_read() - if h == 0 then break end - - if h == e1 then - assert(ok == nil) - assert(err:name() == "LOGIN_DENIED") - assert(err:no() == curl.E_LOGIN_DENIED) - end - - if h == e2 then - assert(ok == true) - end -end -``` - diff --git a/watchdog/third_party/lua-curl/UPSTREAM b/watchdog/third_party/lua-curl/UPSTREAM index ee9ee5a..84782fe 100644 --- a/watchdog/third_party/lua-curl/UPSTREAM +++ b/watchdog/third_party/lua-curl/UPSTREAM @@ -4,11 +4,9 @@ URL: https://github.com/Lua-cURL/Lua-cURLv3 Commit: 9f8b6dba8b5ef1b26309a571ae75cda4034279e5 License: MIT (see upstream LICENSE) -Sources are extracted here by `scripts/watchdog-lua-deps.sh` (pinned tarball). -We build only the lcurl C module into `.deps/lua/lcurl.so` — not the optional -`cURL/*.lua` helper layer. +Sources are not vendored in git. `scripts/watchdog-lua-deps.sh` downloads this +pinned tarball at build time into `.deps/lua-curl-src//` and compiles +`lcurl.so` into `.deps/lua/`. Pin is also recorded in `release/versions.env` +as `LUA_CURL_UPSTREAM_SHA`. -To populate manually: - - curl -fsSL https://github.com/Lua-cURL/Lua-cURLv3/archive/9f8b6dba8b5ef1b26309a571ae75cda4034279e5.tar.gz \ - | tar -xz -C watchdog/third_party/lua-curl --strip-components=1 +We build only the lcurl C module — not the optional `cURL/*.lua` helper layer. diff --git a/watchdog/third_party/lua-curl/appveyor.yml b/watchdog/third_party/lua-curl/appveyor.yml deleted file mode 100644 index 33e885c..0000000 --- a/watchdog/third_party/lua-curl/appveyor.yml +++ /dev/null @@ -1,90 +0,0 @@ -version: 0.3.2.{build} - -os: -- Windows Server 2012 R2 - -shallow_clone: true - -environment: - LR_EXTERNAL: c:\external - CURL_VER: 7.74.0 - - matrix: - - LUA: "lua 5.1" - - LUA: "lua 5.2" - - LUA: "lua 5.3" - - LUA: "lua 5.4" - -platform: - - x64 - - x86 - # - mingw - -cache: - - c:\hererocks -> appveyor.yml - - c:\external -> appveyor.yml - -install: - - set PATH=C:\Python27\Scripts;%LR_EXTERNAL%;%PATH% - - if /I "%platform%"=="x86" set HR_TARGET=vs_32 - - if /I "%platform%"=="x64" set HR_TARGET=vs_64 - - if /I "%platform%"=="mingw" set HR_TARGET=mingw - - if /I "%platform%"=="mingw" set PATH=C:\MinGW\bin;%PATH% - - if not exist "%LR_EXTERNAL%" ( - mkdir "%LR_EXTERNAL%" && - mkdir "%LR_EXTERNAL%\lib" && - mkdir "%LR_EXTERNAL%\include" - ) - - if not exist c:\hererocks ( - pip install hererocks && - hererocks c:\hererocks --%LUA% --target %HR_TARGET% -rlatest - ) - - call c:\hererocks\bin\activate - - luarocks show luarocks-fetch-gitrec >nul 2>&1 || luarocks install luarocks-fetch-gitrec - -before_build: - # external deps - - call .appveyor\install_curl.bat - -build_script: - - set LUA_CURL_VER=scm-0 - - if "%APPVEYOR_REPO_TAG%" == "true" set LUA_CURL_VER=%APPVEYOR_REPO_TAG_NAME:~1%-1 - - echo "Making lua-curl-%LUA_CURL_VER% ..." - - luarocks make rockspecs/lua-curl-%LUA_CURL_VER%.rockspec - -before_test: - # test deps - - if "%LUA%"=="lua 5.1" luarocks show bit32 >nul 2>&1 || luarocks install bit32 - - luarocks show lunitx >nul 2>&1 || luarocks install lunitx - - luarocks show dkjson >nul 2>&1 || luarocks install dkjson - - luarocks show luafilesystem >nul 2>&1 || luarocks install luafilesystem - - luarocks show lua-path >nul 2>&1 || luarocks install lua-path - - luarocks show luarocks-fetch-gitrec > /dev/null 2>&1 || luarocks install luarocks-fetch-gitrec - - luarocks show lua-http-parser > /dev/null 2>&1 || luarocks install lua-http-parser || luarocks install test/deps/lua-http-parser-2.7-1.rockspec - - luarocks show pegasus >nul 2>&1 || luarocks install pegasus http.parser - --server=http://luarocks.org/manifests/moteus - - luarocks show pegasus-router >nul 2>&1 || luarocks install pegasus-router - --server=http://luarocks.org/dev - - ps: $TestServer = Start-Process lua -ArgumentList test/server.lua -RedirectStandardOutput "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt" -RedirectStandardError "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt" -PassThru - - curl -s http://127.0.0.1:7090/get - -test_script: - - echo "Testing..." - - cd %APPVEYOR_BUILD_FOLDER%\test - - lua run.lua - - lua test_pause02.c.lua - - lua test_multi_callback.lua - - lua test_multi_nested_callback.lua - -after_test: - - cd %APPVEYOR_BUILD_FOLDER% - - .appveyor\pack_artifact.bat lua-curl bin-rock - -on_failure: - - ps: Stop-Process -Id $TestServer.Id - - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } - - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } - -on_finish: - - curl -s http://127.0.0.1:7090/get - - ps: Stop-Process -Id $TestServer.Id diff --git a/watchdog/third_party/lua-curl/doc/config.ld b/watchdog/third_party/lua-curl/doc/config.ld deleted file mode 100644 index 17a292b..0000000 --- a/watchdog/third_party/lua-curl/doc/config.ld +++ /dev/null @@ -1,41 +0,0 @@ -project='Lua-cURLv3' -title='Lua-cURLv3 Docmentation' -description='Lua binding to libcurl' -format='discount' -backtick_references=false -file={ - './curl.ldoc', - './lcurl.ldoc', -} -dir='html' -readme='../README.md' -style='!pale' -kind_names={topic='Manual',script='Programs'} -examples = { - '../examples/lcurl', - '../examples/lcurl/file.lua', - '../examples/lcurl/pause.lua', - '../examples/lcurl/ftpupload.lua', - '../examples/lcurl/easy.obj.lua', - '../examples/lcurl/multi_iterator.lua', - '../examples/lcurl/multi.lua', - '../examples/lcurl/post.lua', - '../examples/lcurl/post_stream.lua', - '../examples/lcurl/share.lua', - - '../examples/cURLv2/file.lua', - '../examples/cURLv2/browser.lua', - '../examples/cURLv2/rss.lua', - '../examples/cURLv2/multi.lua', - '../examples/cURLv2/post_stream.lua', - '../examples/cURLv3/post_json.lua', - '../examples/cURLv3/post_form.lua', - - '../examples/cURLv3/pop3.lua', - '../examples/cURLv3/file.lua', - '../examples/cURLv3/rss.lua', - '../examples/cURLv3/multi3.lua', - '../examples/cURLv3/multi.lua', - '../examples/cURLv3/post_form.lua', - '../examples/cURLv3/multi2.lua', -} diff --git a/watchdog/third_party/lua-curl/doc/curl.ldoc b/watchdog/third_party/lua-curl/doc/curl.ldoc deleted file mode 100644 index a915d4c..0000000 --- a/watchdog/third_party/lua-curl/doc/curl.ldoc +++ /dev/null @@ -1,175 +0,0 @@ ---- --- Provide same functions as `lcurl` module with some additions. --- Here are the only functions that have been added or changed. --- --- @module cURL -do - ---- Create HTTP multipart/formdata object. --- --- @tparam FORM_DESCRIPTION form description --- @treturn[1] Form new curl HTTP Post object context --- --- @see FORM_DESCRIPTION -function form() end - -end - -do - ---- Form description table. --- --- @table FORM_DESCRIPTION --- @tfield string|form_stream|form_file|form_buffer|form_content content_name --- --- @usage --- post_form = cURL.form{ --- --- -- file form filesystem --- name01 = { --- file = "post_form.lua", --- type = "text/plain", --- name = "post.lua", --- }, --- --- -- file form string --- name02 = { --- data = "bold", --- name = "dummy.html", --- type = "text/html", --- }, --- --- -- file form stream object --- name03 = { --- stream = Stream:new('8', 25), --- name = "stream1.txt", --- type = "text/plain", --- headers = { --- "X-Test-Char : 8", --- "X-Test-length : 25", --- } --- }, --- --- -- file form stream function --- name04 = { --- stream = stream, --- length = length, --- name = "stream2.txt", --- type = "text/plain", --- }, --- --- -- content form string --- name05 = 'value05', --- --- -- content form string --- name06 = { 'value06', -- or content = 'value05' --- type = "text/plain", --- }, --- --- } - ---- Table describe stream part in form. --- --- @table form_stream --- @tfield function|table|userdata stream stream function is same as in readfunction. --- Also stream object may provide `length` method. --- @tfield[opt=stream:length()] number length --- @tfield[opt] string name file name --- @tfield[opt] string type mime type (e.g. 'text/plain') --- @tfield[opt] table headers array of headers --- - ---- Table describe file part in form. --- --- @table form_file --- @tfield string file path to file in local FS (e.g. 'path/to/some/file.txt') --- @tfield[opt=basename(file)] string name file name --- @tfield[opt] string type mime type (e.g. 'text/plain') --- @tfield[opt] table headers array of headers --- - ---- Table describe buffer part in form. --- --- @table form_buffer --- @tfield string data file content --- @tfield string name file name --- @tfield[opt] string type mime type (e.g. 'text/plain') --- @tfield[opt] table headers array of headers --- - ---- Table describe content part in form. --- --- @table form_content --- @tfield string content value (or first field in table) --- @tfield[opt] string type mime type (e.g. 'text/plain') --- @tfield[opt] table headers array of headers --- - -end - ---- HTTP multipart/formdata object --- @type httpform --- -do - ---- Add new part to form. --- --- @tparam FORM_DESCRIPTION form description --- @treturn[1] httpform self --- --- @see FORM_DESCRIPTION -function add() end - -end - ---- Easy curl object --- @type easy --- -do - ---- Perform a file transfer --- --- @tparam[opt] table options --- @treturn[1] easy self --- @usage --- e:perfom{writefunction = assert(io.open("fname.txt", "w+b"))} -function perfom() end - ---- User data. --- --- Please use this field to associate any data with curl handle. --- --- @field data --- --- @usage --- f = io.open("lua.org.download", "w+") --- e = curl.easy{url = "http://lua.org", writefunction = f} --- e.data = f - -end - ---- Muli curl object --- @type multi --- -do - ---- Iterator that returns the next data, type and corresponding easy handle. --- ---
Returned data types: `response`, `header`, `data`, `error`, `done`. ---
Note. response data may appeare several times (e.g. if you proceed http request --- with digest auth you should get 401 and 200 responses). ---
Easy handle is removed at the end of the operation (when get `done` or `error` row). --- --- @treturn Iterator iterator for generic for --- --- @usage --- for data, type, easy in m:iperform() do ... end -function iperform() end - ---- User data. --- --- Please use this field to associate any data with curl handle. --- --- @field data - -end diff --git a/watchdog/third_party/lua-curl/doc/lcurl.ldoc b/watchdog/third_party/lua-curl/doc/lcurl.ldoc deleted file mode 100644 index b997847..0000000 --- a/watchdog/third_party/lua-curl/doc/lcurl.ldoc +++ /dev/null @@ -1,657 +0,0 @@ ---- --- @module lcurl -do - ---- Create HTTP multipart/formdata object. --- --- @treturn[1] httpform new curl HTTP Post object context -function form() end - ---- Create Easy object --- --- @tparam[opt] table options --- @treturn[1] easy new curl easy object --- --- @usage --- c = curl.easy{ --- url = 'http://example.com', --- [curl.OPT_VERBOSE] = true, --- } --- -function easy() end - ---- Create Multi object --- --- @tparam[opt] table options --- @treturn[1] multi new curl multi object --- --- @usage --- m = curl.multi{ --- socketfunction = handle_socket; --- timerfunction = start_timeout; --- } --- -function multi() end - ---- Create Share object --- --- @tparam[opt] table options --- @treturn[1] share new curl share object -function share() end - ---- Returns libcurl version as human readable string --- -function version() end - ---- Returns libcurl version as table --- --- @tparam[opt] string param specific version info --- @treturn[1] table full version into if `param` was not specify --- @return[2] specific info parameter --- --- @usage --- proto = curl.version_info('protocols') --- if proto.HTTP then ... -- libcurl support http protocol -function version_info() end - ---- File description table. --- --- Representation for `struct curl_fileinfo` --- --- @table curl_fileinfo --- --- @tfield string filename --- @tfield integer filetype --- @tfield integer time --- @tfield integer perm --- @tfield integer uid --- @tfield integer gid --- @tfield integer size --- @tfield integer hardlinks --- @tfield integer flags --- @tfield table strings can have this string fields: `time`, `perm` , `user` , `group`, `target` - -end - ---- HTTP multipart/formdata object --- @type httpform --- -do - ---- Add new part to form. --- --- @tparam string name provide the name of this part --- @tparam string content actual data to send --- @tparam[opt] string type provides the content-type for this part --- @tparam[opt] table headers specifies extra headers for the form POST section --- @return[1] self -function add_content() end - ---- Add new part to form. --- --- @tparam string name provide the name of this part --- @tparam string filename provides the filename field in the content header --- @tparam string content actual data to send --- @tparam[opt] string type provides the content-type for this part --- @tparam[opt] table headers specifies extra headers for the form POST section --- @return[1] self -function add_buffer() end - ---- Add new part to form. --- --- @tparam string name provide the name of this part --- @tparam string path path to file that contain actual data to send --- @tparam[opt] string type provides the content-type for this part --- @tparam[opt] string filename provides the filename field in the content header. --- By default it is basename of path. --- @tparam[opt] table headers specifies extra headers for the form POST section --- @return[1] self -function add_file() end - ---- Add new part to form. --- --- @tparam string name provide the name of this part --- @tparam[opt] string filename provides the filename field in the content header. --- @tparam[opt] string type provides the content-type for this part --- @tparam[opt] table headers specifies extra headers for the form POST section --- @tparam number size stream size in bytes --- @tparam function/object reader --- @param[opt] context reader context --- @return[1] self --- --- @see easy:setopt_readfunction -function add_stream() end - ---- Serialize multipart/formdata HTTP POST chain. --- --- @return[1] string serialized data --- --- @usage print(post:get()) --- -function get() end - ---- Serialize multipart/formdata HTTP POST chain. --- --- Writer function can return true or number of written bytes. --- Also if function does not return anything is considered as success. --- --- @tparam function writer --- @param[opt] context writer context --- @return[1] self --- --- @usage --- t = {} --- post:get(table.insert, t) --- print(table.concat(t)) --- -function get() end - ---- Serialize multipart/formdata HTTP POST chain. --- --- This call same as httpform:get(writer.write, writer) --- --- @tparam object writer --- @return[1] self --- --- @usage --- f = io.open(...) --- post:get(f) --- -function get() end - ---- Free multipart/formdata. --- -function free() end - -end - ---- Curl error object --- @type error --- -do - ---- Get the error category. --- --- @treturn string string of error category (curl.ERROR_XXX constants) --- --- @usage --- if err:category() == curl.ERROR_EASY then --- -- proceed easy error --- end -function category ()end - ---- Get the number value of error. --- --- @treturn number number of error (curl.E_XXX constants) -function no ()end - ---- Get the error name. --- --- @treturn string error name (e.g. "UNSUPPORTED_PROTOCOL", "BAD_OPTION") -function name ()end - ---- Get the error description. --- --- @treturn string error description (e.g. "Login denied") -function msg ()end - ---- Get the full error description. --- --- @treturn string string that contain name, message and number of error -function __tostring ()end - -end - ---- Easy curl object --- @type easy --- -do - ---- Perform a file transfer --- --- @return[1] self -function perfom() end - ---- URL encodes the given string --- --- @tparam string url --- @return[1] encoded url -function escape() end - ---- URL decodes the given string --- --- @tparam string url --- @return[1] decoded url -function unescape() end - ---- Re-initializes all options previously set. --- --- @treturn easy self -function reset() end - ---- Pause and unpause a connection. --- --- @tparam number mask set of bits that sets the new state of the connection (one of PAUSE_XXX constant) --- @treturn easy self -function pause() end - ---- End easy session --- -function close() end - ---- Set options. --- --- @tparam number|table opt one of `curl.OPT_XXX` constant or options table --- @param ... value --- @treturn easy self --- --- @usage --- c:setopt(curl.OPT_URL, "http://example.com") --- c:setopt(curl.OPT_READFUNCTION, --- function(t, n) return table.remove(t) end, --- {"1111", "2222"} --- ) ---c:setopt{ --- url = 'http://example.com', --- [curl.OPT_VERBOSE] = true, --- } -function setopt() end - ---- Reset option to default value. --- --- @tparam number opt one of `curl.OPT_XXX` constant or options table --- @treturn easy self --- --- @usage --- c:unsetopt(curl.OPT_URL) --- c:unsetopt(curl.OPT_READFUNCTION) -function unsetopt() end - ---- Get information. --- --- @tparam number info one of `curl.INFO_XXX` constant --- @return value --- --- @usage --- print(c:getinfo(curl.INFO_EFFECTIVE_URL)) --- print(c:getinfo(curl.INFO_TOTAL_TIME)) --- print(c:getinfo(curl.INFO_RESPONSE_CODE)) -function getinfo() end - ---- Set writer function. --- --- A callback accepting one or two parameters. --- The first is the writer context if any, and the second is a string with the data to be written. --- Function must return `true` (any non number true value) or full data length or nothing to continue operation. --- Otherwise the transfer will be aborted with an error. --- --- @tparam function writer --- @param[opt] context writer context --- @return[1] self --- -function setopt_writefunction() end - ---- Set writer function. --- --- This call same as easy:setopt_writefunction(writer.write, writer) --- --- @tparam object writer --- @return[1] self --- -function setopt_writefunction() end - ---- Set header function. --- --- A callback accepting one or two parameters. --- The first is the writer context if any, and the second is a string with the data to be written. --- Function must return `true` (any non number true value) or full data length or nothing to continue operation. --- Otherwise the transfer will be aborted with an error. --- --- @tparam function writer --- @param[opt] context writer context --- @return[1] self --- -function setopt_headerfunction() end - ---- Set header function. --- --- This call same as easy:setopt_headerfunction(writer.header, writer) --- --- @tparam object writer --- @return[1] self --- -function setopt_headerfunction() end - ---- Set reader function. --- --- A callback accepting one or two parameters. --- The first is the reader context if any, and the second is the maximum amount of data to be read. --- You can ignore second argument and pass as mach data as you need. lcurl can split data. --- Function must return data to continue operation. To stop operation it must return empty string or nil or nothing. --- Otherwise the transfer will be aborted with an error. --- --- --- @tparam function reader --- @param[opt] context reader context --- @return[1] self --- --- @usage --- local counter = 10 --- c:setopt_readfunction(function() --- if counter > 0 then --- counter = counter - 1 --- return 'a' --- end --- end) -function setopt_readfunction() end - ---- Set reader function. --- --- This call same as easy:setopt_readfunction(reader.read, reader) --- --- @tparam object reader --- @return[1] self --- -function setopt_readfunction() end - ---- Set progress function. --- --- A callback accepting four or five parameters. --- The first is the reader context if any, the second is the total number --- of bytes expected to be downloaded in this transfer, --- the third is the number of bytes downloaded so far, --- the fourth is the total number of bytes expected to be uploaded --- in this transfer, and the fifth is the number of bytes uploaded so far. --- Function must return `true` or `1` or nothing to continue operation. --- Otherwise the transfer will be aborted with an error `ABORTED_BY_CALLBACK`.
--- --- !!! NOTE !!! This is differents form libcurl API. In libcurl returning a non-zero --- value from this callback will cause libcurl to abort the transfer and return. --- This done to be consisted with writefunction that should return `true` to continue. --- --- @tparam function progress --- @param[opt] context progress context --- @return[1] self --- -function setopt_progressfunction() end - ---- Set progress function. --- --- This call same as easy:setopt_progressfunction(progress.progress, progress) --- --- @tparam object progress --- @return[1] self --- -function setopt_progressfunction() end - ---- Set seek callback function. --- --- A callback accepting two or three parameters. --- The first is the seek context if any, the second is the `origin` value --- same as in file.seek function from io library. Can be (`cur`, `set` and `end`). --- The third parameter is offset or position in input stream. --- If stream can not be seeking and need stop current transfer operation --- function have to raise Lua error or returns `nil` and error message. --- To indicate success function have to return any truthy value --- In other case Function must return `true` or `1` or nothing to continue operation. --- False value indicate that while the seek failed, libcurl is free to work --- around the problem if possible. --- --- !!! NOTE !!! This is differents form libcurl API by swapping parameters in callback. --- It done to be compatiable with Lua io library. --- --- @tparam function seek --- @param[opt] context seek context --- @return[1] self --- -function setopt_seekfunction() end - ---- Set seek callback function. --- --- This call same as easy:setopt_seekfunction(stream.seek, seek) --- --- @tparam object stream --- @return[1] self --- -function setopt_seekfunction() end - ---- Set debug callback function. --- --- A callback accepting two or three parameters. --- The first is the debug context if any, the second is the `data_type` value --- which represent what kinde of data is passed. This value can be one of --- constant `INFO_TEXT`, `INFO_HEADER_IN`, `INFO_HEADER_OUT`, `INFO_DATA_IN`, --- `INFO_DATA_OUT`, `INFO_SSL_DATA_IN`, `INFO_SSL_DATA_OUT`. --- The third parameter is data itself. --- Any errors and returning values from this callback are ignored. --- --- @tparam function debug --- @param[opt] context debug context --- @return[1] self --- -function setopt_debugfunction() end - ---- Set debug callback function. --- --- This call same as easy:setopt_debugfunction(debug.debug, debug) --- --- @tparam object debug --- @return[1] self --- -function setopt_debugfunction() end - ---- Set match callback function. --- --- A callback accepting two or three parameters. --- The first is the match context if any, the second is the pattern string, --- the third is the string to match to pattern. --- Function must return truthy value to indicate that string match to pattern. --- If function raise error or return nil and error message then it indicate --- that curl should interrupt current transfer operation. --- False value indicate that strind does not match to pattern --- --- @tparam function match --- @param[opt] context match context --- @return[1] self --- -function setopt_fnmatch_function() end - ---- Set match callback function. --- --- This call same as easy:setopt_fnmatch_function(match.match, match) --- --- @tparam object match --- @return[1] self --- -function setopt_fnmatch_function() end - - ---- Set chunk begin function. --- --- A callback accepting two or three parameters. --- The first is the chunk context if any, the second is the @{curl_fileinfo} table. --- The third parameter contains number of chunks remaining per the transfer. --- Function must return truthy value or nothing to indicate that transfer can begin. --- If function raise error or return nil and error message then it indicate --- that curl should interrupt current transfer operation. --- False value indicate that curl should skip this chunk. --- --- @tparam function chunk_bgn --- @param[opt] context chunk context --- @return[1] self --- -function setopt_chunk_bgn_function() end - ---- Set chunk begin function. --- --- This call same as easy:setopt_chunk_bgn_function(context.chunk_bgn, context) --- --- @tparam object context --- @return[1] self --- -function setopt_chunk_bgn_function() end - ---- Set chunk end function. --- --- A callback accepting zero or one parameter. --- The only parameter is the chunk context if any. --- Function must return truthy value or nothing to indicate that transfer can continue --- otherwise it indicate that curl should interrupt current transfer operation. --- --- @tparam function chunk_end --- @param[opt] context chunk context --- @return[1] self --- -function setopt_chunk_end_function() end - ---- Set chunk begin function. --- --- This call same as easy:setopt_chunk_end_function(context.chunk_end, context) --- --- @tparam object context --- @return[1] self --- -function setopt_chunk_end_function() end - ---- Set HTTP multipart/formdata. --- --- Caller does not have to save data. --- --- @tparam httpform data --- @return[1] self -function setopt_httppost() end - ---- Set HTTP multipart/formdata. --- --- @tparam string data --- @tparam[opt=#data] number length --- @return[1] self -function setopt_postfields() end - ---- Set curl share object. --- --- Caller does not have to save data. --- --- @tparam share data --- @return[1] self -function setopt_share() end - -end - ---- Muli curl object --- @type multi --- -do - ---- Add Easy object. --- --- Caller must ensure that easy object is alive until end of operation. --- --- @treturn multi self --- @tparam easy handle -function add_handle() end - ---- Remove Easy object. --- --- @tparam easy handle --- @treturn multi self -function remove_handle() end - ---- reads/writes available data from each easy handle. --- --- @treturn number handles number of active easy handles -function perfom() end - ---- Read multi stack informationals. --- --- Note. If curl_multi_remove_handle fail then there no error occure but handle jast stay in multi handle. --- --- @tparam[opt] remove remove easy handle if it done. --- @treturn[1] number 0 there no informationals --- @treturn[2] easy handle --- @treturn[2] boolean true --- @treturn[3] easy handle --- @treturn[3] nil --- @treturn[3] error error code -function info_read() end - ---- Set options. --- --- @tparam number|table opt one of `curl.OPT_MULTI_XXX` constant --- @param ... value --- @treturn multi self --- --- @usage --- c:setopt(curl.OPT_MULTI_MAXCONNECTS, 10) --- c:setopt{maxconnects = 10} -function setopt() end - ---- Perform socket action. --- --- @tparam[opt=curl.SOCKET_TIMEOUT] number socket --- @tparam[opt=0] number mask --- @treturn multi self --- --- @usage --- c:socket_action() --- c:socket_action(sock_fd, curl.CSELECT_IN) --- c:socket_action(sock_fd, curl.CSELECT_OUT) -function socket_action() end - ---- Set timer callback. --- --- @tparam function timer timer callback --- @param[opt] context timer context --- @treturn multi self --- -function setopt_timerfunction() end - ---- Set timer callback. --- --- This call same as easy:setopt_writefunction(timer.timer, timer) --- --- @tparam userdata|table timer timer object --- @treturn multi self --- -function setopt_timerfunction() end - ---- Polls on all easy objects in a multi object. --- --- @tparam[opt] number timeout milliseconds timeout. By default it is `multi:timeout()`. --- @treturn number number of affected objects --- -function wait() end - ---- How long to wait for action before proceeding. --- --- @treturn number timeout milliseconds timeout --- -function timeout() end - ---- End multi session. --- -function close() end - -end - ---- Share curl object --- @type share --- -do - ---- Set options. --- --- @tparam number|table opt one of `curl.OPT_SHARE_XXX` constant --- @param ... value --- @treturn share self --- --- @usage --- c:setopt(curl.OPT_SHARE_SHARE, curl.LOCK_DATA_COOKIE) --- c:setopt{share = curl.LOCK_DATA_COOKIE} -function setopt() end - ---- End share session. --- -function close() end - -end diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/browser.lua b/watchdog/third_party/lua-curl/examples/cURLv2/browser.lua deleted file mode 100644 index a73e151..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv2/browser.lua +++ /dev/null @@ -1,266 +0,0 @@ ------------------------------------------------------------------------------ --- A Browser Class for easy Web Automation with Lua-cURL --- Author: Kai Uwe Jesussek --- RCS ID: $Id: browser.lua,v 0.1 2011/03/11 23:55:20 kai Exp $ ------------------------------------------------------------------------------ - -local cURL = require("cURL") -local string = require("string") -local table = require("table") -local base = _G - -USERAGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" --windows xp internet explorer 6.0 - ---this function joins 2 urls (absolute or relative) -function url_join(_base, _url) - assert(type(_url) == "string") - - if _base == nil or _base == "" then - return _url - end - - assert(type(_base) == "string") - local base = url_split(_base) - local url = url_split(_url) - - - local protocol = base.protocol - local host = base.host - - local path = "" - local port = "" - - if url.protocol ~= nil then - protocol = url.protocol - if url.path ~= nil then - path = url.path - end - if url.port ~= nil and url.port ~= "" then - port = url.port - end - if url.host ~= nil then - host = url.host - end - else - if _url:sub(1,2) == "//" then - --set host and path - host, port, path = _url:match("^//([^;/%?]+)(:?%d*)(/?.*)") - if path == nil then - path = "" - end - elseif _url:sub(1,1) == "/" then - port = base.port - --replace path - path = _url - else - --combine paths :( - path = base.path:match("^(.*)/[^/]*") - port = base.port - if path ~= nil then - path = path .. "/" .. _url - else - path = _url - end - end - - end - local ret = protocol .. "://" .. host .. port .. path - return ret -end - ---this function splits an url into its parts -function url_split(_url) - --print(_url) - local ret = {} - --test ipv6 - ret.protocol, ret.host, ret.port, ret.path = _url:match("^(https?)://(%[[0-9a-fA-F:]+%])(:?%d*)(.*)$") - - if ret.host == nil then - --fall back to ipv4 - ret.protocol, ret.host, ret.port, ret.path = _url:match("^(https?)://([^:/]+)(:?%d*)(.*)$") - end - return ret -end - - ------------------------------------------------------------------------------ --- Encodes a string into its escaped hexadecimal representation --- Input --- s: binary string to be encoded --- Returns --- escaped representation of string binary --- taken from Lua Socket and added underscore to ignore (MIT-License) ------------------------------------------------------------------------------ -function escape(s) - return string.gsub(s, "([^A-Za-z0-9_])", function(c) - return string.format("%%%02x", string.byte(c)) - end) -end - ------------------------------------------------------------------------------ --- Encodes a string into its escaped hexadecimal representation --- Input --- s: binary string to be encoded --- Returns --- escaped representation of string binary --- taken from Lua Socket ------------------------------------------------------------------------------ -function unescape(s) - return string.gsub(s, "%%(%x%x)", function(hex) - return string.char(base.tonumber(hex, 16)) - end) -end - --- from encodes a key, value dictionary table -function tblencode (_arguments) - local ret = "" - if _arguments == nil or next(_arguments) == nil then -- no _arguments or empty _arguments? - return ret - end - - --iterate over each key -> value pairs and urlencode them - for key, vals in pairs(_arguments) do - if type(vals) ~= "table" then - vals = {vals} - end - for i,val in ipairs(vals) do - ret = ret .. "&"..key.. "=" ..escape(val) - end - end - - --cut off leadin '&' - return string.sub(ret,2) -end - - ---function helper for result ---taken from luasocket page (MIT-License) -local function build_w_cb(t) - return function(s) - table.insert(t, s) - return #s,nil - end -end - ---function helper for headers ---taken from luasocket page (MIT-License) -local function h_build_w_cb(t) - return function(s) - --stores the received data in the table t - --prepare header data - name, value = s:match("(.-): (.+)") - if name and value then - t.headers[name] = value:gsub("[\n\r]", "") - else - code, codemessage = string.match(s, "^HTTP/.* (%d+) (.+)$") - if code and codemessage then - t.code = tonumber(code) - t.codemessage = codemessage:gsub("[\n\r]", "") - end - end - return #s,nil - end -end - - ---the browser easy to use interface -browser = {} -function browser:new(_share) - if _share == nil then - _share = cURL.share_init() - _share:setopt_share("COOKIE") - _share:setopt_share("DNS") - end - local object = { url = nil, share = _share} - setmetatable(object, {__index = browser}) - return object -end - ---this function sets the proxy variables for the prepare function -function browser:setProxy(_proxy, _proxytype) - self.proxy = _proxy - self.proxytype = _proxytype - print("setting proxy", self.proxy, self.proxytype) -end - ---this function prepares a request -function browser:prepare(post_data, urlencoded) - local req = cURL.easy_init() - req:setopt_share(self.share) - req:setopt_url(self.url) - req:setopt_useragent(USERAGENT) - if self.proxy ~= nil and self.proxytype ~= nil then - req:setopt_proxy(self.proxy) - req:setopt_proxytype(self.proxytype) - end - if self.caInfoPath ~= nil then - req:setopt_cainfo(self.caInfoPath) - end - if post_data ~= nil then - if urlencoded and type(post_data) == "table" then - post_data = tblencode(post_data) - end - - if type(post_data) == "string" then - req:setopt_post(1) - req:setopt_postfields(post_data) - req:setopt_postfieldsize(#post_data) - else - req:post(post_data) - end - end - return req -end - ---this function sets the url -function browser:setUrl(url) - --appends a leading / to url if needed - if self.url and self.url:match("^(https?://[^/]+)$") then - self.url = self.url .. "/" - end - self.url = url_join(self.url or "", url) -end - ---opens a webpage :) only the first parameter is required -function browser:open(url, post_data, redirect, urlencoded) - local redirect = redirect or true - local urlencoded = urlencoded == nil - local ret = {} - response_body = {} - ret.headers = {} - - self:setUrl(url) - local req = self:prepare(post_data, urlencoded) - - req:perform({headerfunction=h_build_w_cb(ret), writefunction=build_w_cb(response_body)}) - - self:setUrl(url) - ret.body = table.concat(response_body) - - if redirect and ret.headers and (ret.headers.Location or ret.headers.location) and (ret.code == 301 or ret.code == 302) then - return self:open(url_join(self.url, ret.headers.Location or ret.headers.location), nil, extra_headers, redirect) - end - return ret - -end ---opens a webpage :) only the first and second parameters are required -function browser:save(url, filename, post_data) - local ret = {} - ret.headers = {} - self:setUrl(url) - local req = self:prepare(post_data, false) - file = io.open(filename) - req:perform({headerfunction=h_build_w_cb(ret), writefunction=function(str) file:write(str) end }) - file:close() -end - -function browser:setCaInfo(path) - self.caInfoPath = path -end - ---[[ usage examples: --- b = browser:new() --- resp = b:open("http://www.html-kit.com/tools/cookietester/") --- print(resp.body) --- table.foreach(resp.headers, print) ---]] diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/file.lua b/watchdog/third_party/lua-curl/examples/cURLv2/file.lua deleted file mode 100644 index 64c23a8..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv2/file.lua +++ /dev/null @@ -1,16 +0,0 @@ -local cURL = require("cURL") - --- open output file -f = io.open("example_homepage", "w") - -c = cURL.easy_init() --- setup url -c:setopt_url("http://www.example.com/") --- perform, invokes callbacks -c:perform{writefunction = function(str) - f:write(str) -end} - --- close output file -f:close() -print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/multi.lua b/watchdog/third_party/lua-curl/examples/cURLv2/multi.lua deleted file mode 100644 index 8052e93..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv2/multi.lua +++ /dev/null @@ -1,22 +0,0 @@ -local cURL = require("cURL") - --- setup easy -c1 = cURL.easy_init() -c2 = cURL.easy_init() - --- setup url -c1:setopt_url("http://www.lua.org/") -c2:setopt_url("http://luajit.org/") - -m = cURL.multi_init() -m:add_handle(c1) -m:add_handle(c2) - -local f1 = io.open("lua.html","a+") -local f2 = io.open("luajit.html","a+") - -for data, type, easy in m:perform() do --- if (type == "header") then print(data) end - if (type == "data" and c1 == easy) then f1:write(data) end - if (type == "data" and c2 == easy) then f2:write(data) end -end \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua b/watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua deleted file mode 100644 index 94bbe05..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv2/post_stream.lua +++ /dev/null @@ -1,49 +0,0 @@ -local cURL = require("cURL") - --- returns size and reader -local function make_stream(ch, n, m) - local size = n * m - local i = -1 - return size, function() - i = i + 1 - if i < m then - return (tostring(ch)):rep(n - 2) .. '\r\n' - end - end -end - -c = cURL.easy_init() - -c:setopt_url("http://posttestserver.com/post.php") - -c:setopt_post(true) - -local length, reader = make_stream("a", 10, 4) - -c:post{-- post file from private read function - -- Lua-cURL compatiable - -- allows only one stream - name = { - file="stream.txt", - stream_length=length, - type="text/plain", - } -} - -c:perform{readfunction = reader} - - -local length, reader = make_stream("b", 10, 4) -c:post{-- post file from private read function - -- define stream callback - name = { - file = "stream.txt", - type = "text/plain", - stream_length = length, - stream = reader, - } -} - -c:perform{} - -print("Done") diff --git a/watchdog/third_party/lua-curl/examples/cURLv2/rss.lua b/watchdog/third_party/lua-curl/examples/cURLv2/rss.lua deleted file mode 100644 index e21d864..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv2/rss.lua +++ /dev/null @@ -1,54 +0,0 @@ --- use LuaExpat and Lua-CuRL together for On-The-Fly XML parsing -local lxp = require("lxp") -local cURL = require("cURL") - -tags = {} -items = {} - -callback = {} - -function callback.StartElement(parser, tagname) - tags[#tags + 1] = tagname - if (tagname == "item") then - items[#items + 1] = {} - end -end - -function callback.CharacterData(parser, str) - if (tags[#tags -1] == "item") then - --we are parsing a item, get rid of trailing whitespace - items[#items][tags[#tags]] = string.gsub(str, "%s*$", "") - end -end - -function callback.EndElement(parser, tagname) - --assuming well formed xml - tags[#tags] = nil -end - -p = lxp.new(callback) - --- create and setup easy handle -c = cURL.easy_init() -c:setopt_url("http://www.lua.org/news.rss") - -m = cURL.multi_init() -m:add_handle(c) - -for data,type in m:perform() do - -- ign "header" - if (type == "data") then - assert(p:parse(data)) - end -end - ---finish document -assert(p:parse()) -p:close() - -for i, item in ipairs(items) do - for k, v in pairs(item) do - print(k,v) - end - print() -end \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/file.lua b/watchdog/third_party/lua-curl/examples/cURLv3/file.lua deleted file mode 100644 index 4ddf256..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/file.lua +++ /dev/null @@ -1,16 +0,0 @@ -local cURL = require "cURL" - --- open output file -f = io.open("example_homepage", "w") - -cURL.easy{ - url = "http://www.example.com/", - writefunction = f -} -:perform() -:close() - --- close output file -f:close() - -print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/multi.lua b/watchdog/third_party/lua-curl/examples/cURLv3/multi.lua deleted file mode 100644 index 395a297..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/multi.lua +++ /dev/null @@ -1,17 +0,0 @@ -local cURL = require("cURL") - --- setup easy and url -c1 = cURL.easy{url = "http://www.lua.org/"} -c2 = cURL.easy{url = "http://luajit.org/"} - -m = cURL.multi() - :add_handle(c1) - :add_handle(c2) - -local f1 = io.open("lua.html", "w+b") -local f2 = io.open("luajit.html", "w+b") - -for data, type, easy in m:iperform() do - if type == "data" and c1 == easy then f1:write(data) end - if type == "data" and c2 == easy then f2:write(data) end -end diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua b/watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua deleted file mode 100644 index 49850c2..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/multi2.lua +++ /dev/null @@ -1,35 +0,0 @@ -local cURL = require("cURL") - -local f1 = io.open("lua.html", "w+b") -local f2 = io.open("luajit.html", "w+b") - --- setup easy and url -c1 = cURL.easy{url = "http://www.lua.org/", writefunction = f1} -c2 = cURL.easy{url = "http://luajit.org/", writefunction = f2} -c3 = cURL.easy{url = "****://luajit.org/"} -- UNSUPPORTED_PROTOCOL - -m = cURL.multi() - :add_handle(c1) - :add_handle(c2) - :add_handle(c3) - -local remain = 3 -while remain > 0 do - local last = m:perform() -- do some work - if last < remain then -- we have done some tasks - while true do -- proceed results/errors - local e, ok, err = m:info_read(true) -- get result and remove handle - if e == 0 then break end -- no more finished tasks - if ok then -- succeed - print(e:getinfo_effective_url(), '-', e:getinfo_response_code()) - else -- failure - print(e:getinfo_effective_url(), '-', err) - end - e:close() - end - end - remain = last - - -- wait while libcurl do io select - m:wait() -end diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua b/watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua deleted file mode 100644 index fff826c..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/multi3.lua +++ /dev/null @@ -1,28 +0,0 @@ -local cURL = require("cURL") - -local urls = { - "http://httpbin.org/get?key=1", - "http://httpbin.org/get?key=2", - "http://httpbin.org/get?key=3", - "http://httpbin.org/get?key=4", -} - -local function next_easy() - local url = table.remove(urls, 1) - if url then return cURL.easy{url = url} end -end - -m = cURL.multi():add_handle(next_easy()) -for data, type, easy in m:iperform() do - - if type == "done" or type == "error" then - print("Done", easy:getinfo_effective_url(), ":", data) - easy:close() - easy = next_easy() - if easy then m:add_handle(easy) end - end - - if type == "data" then print(data) end - -end - diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua b/watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua deleted file mode 100644 index 3ecc401..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/pop3.lua +++ /dev/null @@ -1,346 +0,0 @@ --- Simple pop3 wrapper --- --- @usage --- local mbox = pop3.new('pop3s://pop3.yandex.ru') --- --- -- Yandex works only with tls --- print('Open: ', mbox:open_tls('***', '***')) --- print('NOOP: ', mbox:noop()) --- print('RETR: ', mbox:retr(1)) --- --- -- use message class from lua-pop3 --- -- https://github.com/moteus/lua-pop3 --- for k, msg in mbox:messages() do --- print"----------------------------------------------" --- print("subject: ", msg:subject()) --- print("from: ", msg:from()) --- print("to: ", msg:to()) --- for i,v in ipairs(msg:full_content()) do --- if v.text then print(" ", i , "TEXT: ", v.type, #v.text) --- else print(" ", i , "FILE: ", v.type, v.file_name or v.name, #v.data) end --- end --- end --- --- mbox:close() --- - -local cURL = require "cURL.safe" -local find_ca_bundle = require "cURL.utils".find_ca_bundle -local message do local ok - ok, message = pcall(require, "pop3.message") - if not ok then message = nil end -end - -local function split(str, sep, plain) - local b, res = 1, {} - while b <= #str do - local e, e2 = string.find(str, sep, b, plain) - if e then - table.insert(res, (string.sub(str, b, e-1))) - b = e2 + 1 - else - table.insert(res, (string.sub(str, b))) - break - end - end - return res -end - -local function pars_response(resp) - local code, info = string.match(resp,"%s*(%S*)(.*)%s*") - -- SASL GET ONLY "+"/"-" - if code == '+OK' or code == '+' then - return true, info - elseif code == '-ERR' or code == '-' then - return false, info - end - return nil, resp -end - -local function split_2_numbers(data) - local n1, n2 = string.match(data, "%s*(%S+)%s+(%S+)") - return tonumber(n1), tonumber(n2) -end - -local function split_1_number(data) - local n1,s= string.match(data, "%s*(%S+)%s*(%S*)") - return tonumber(n1),s -end - -local crln = '\r\n' -local function writer(cb, ctx) - local tail - return function(str) - -- @check Is libcurl guarantee thant data will be arraived line by line? - -- if so we can just call `cb` - if str then - local t = split(tail and (tail .. str) or str, crln, true) - if str:sub(-2) == crln then tail = nil - else tail = table.remove(t) end - for _, s in ipairs(t) do cb(s, ctx) end - elseif tail then cb(tail, ctx) end - end -end - -local pop3 = {} do -pop3.__index = pop3 - -function pop3:new(host) - return setmetatable({ - _url = assert(host) - },self) -end - -local function open(self, user, password, ssl) - self._easy, err = cURL.easy{ - url = self._url, - username = user, - password = password, - customrequest = 'NOOP', - nobody = true, - headerfunction = function(h) self._response = h end - } - if not self._easy then return nil, err end - - if ssl then - -- For AVAST - -- http://www.avast.com/en-eu/faq.php?article=AVKB91#artTitle - local cainfo, capath = find_ca_bundle('MailShield.crt') - if not cainfo then - -- On Windows try find ca_bundle - cainfo, capath = find_ca_bundle() - end - local ok, err = self._easy:setopt{ - use_ssl = cURL.USESSL_ALL, - cainfo = cainfo, - capath = capath, - } - if not ok then - self:close() - return nil, err - end - end - - local ok, err = self._easy:perform() - if not ok then - self:close() - return nil, err - end - - return self -end - -local function exec(self) - self._response = nil - local ok, err = self._easy:perform() - if not ok then return nil, err end - assert(self._response) - self._response = self._response:gsub("%s+$", "") - return self:response() -end - -local function exec_no_body(self, cmd, url) - local ok, err = self._easy:setopt{ - url = self._url .. (url and ("/" .. url) or ""), - customrequest = cmd or '', - nobody = true, - } - if not ok then return nil, err end - return exec(self) -end - -local function exec_body_cb(self, cb, cmd, url) - local ok, err = self._easy:setopt{ - url = self._url .. (url and ("/" .. url) or ""), - customrequest = cmd or '', - nobody = false, - writefunction = writer(assert(cb)) - } - if not ok then return nil, err end - ok, err = exec(self) - if not ok then return nil, err end - return true -end - -local function exec_body(self, cmd, url) - local t = {} - local ok, err = exec_body_cb(self, - function(s) t[#t+1] = s end, - cmd, url - ) - if not ok then return nil, err end - return t -end - -local function exec_no_body_2_numbers(...) - local ok, data = exec_no_body(...) - if not ok then return ok, data end - return split_2_numbers(data) -end - -function pop3:open(user, password) - return open(self, user, password, false) -end - -function pop3:response() - if self._response then - return pars_response(self._response) - end -end - -function pop3:verbose(...) - local ok, err - if select("#", ...) == 0 then - ok, err = self._easy:setopt_verbose(true) - else - ok, err = self._easy:setopt_verbose(not not ...) - end - if not ok then return nil, ok end - return self -end - -function pop3:open_tls(user, password) - return open(self, user, password, true) -end - -function pop3:noop() - return exec_no_body(self, 'NOOP') -end - -function pop3:stat() - return exec_no_body_2_numbers(self, 'STAT') -end - -function pop3:dele(id) - return exec_no_body(self, 'DELE ' .. id) -end - -function pop3:rset() - return exec_no_body_2_numbers(self, 'RSET') -end - -function pop3:list(id) - if id then - return exec_no_body_2_numbers(self, 'LIST ' .. id) - end - - local t,i = {},0 - local fn = function(data) - local no, size = split_2_numbers(data) - if not (no and size) then - return nil, "Wrong Response: `" .. data .. "`" - end - t[no] = size - i = i + 1 - end - - local ok, err = exec_body_cb(self, fn, '') - if not ok then return nil, err end - if not ok then return nil, err end - return t, i -end - -function pop3:uidl(id) - if id then - local ok, data = exec_no_body(self, 'UIDL ' .. id) - if not ok then return ok, data end - local no, id = split_1_number(data) - if not (no and id) then - return nil, "Wrong Response:" .. data - end - return no,id - end - - local t,i = {},0 - local fn = function(data) - local no, id = split_1_number(data) - if not (no and id) then - return nil, "Wrong Response:" .. data - end - t[no]=id - i = i + 1 - return true - end - - local ok, err = exec_body_cb(self, fn, 'UIDL') - if not ok then return nil, err end - if not ok then return nil, err end - return t, i -end - -function pop3:retr(id) - assert(id) - return exec_body(self, '', tostring(id)) -end - -function pop3:top(id, n) - assert(id) - assert(type(n) == "number") - return exec_body(self, 'TOP ' .. id .. ' ' .. n) -end - -function pop3:make_iter(fn) - local lst, err = self:list() - if not lst then error(err) end - local k = nil - - local iter - iter = function () - k = next(lst, k) - if not k then return nil end - - -- skip deleted messages ? - local no, size = self:list(k) - if no == false then return iter() end -- next message - if not no then return error(size) end - assert(no == k) - - local data, err = fn(self, k, size) - if not data then error(err) end - - return k, data - end - - return iter -end - -if message then - -function pop3:message(msgid) - local msg, err = self:retr(msgid) - if not msg then return nil, err end - return message(msg) -end - -end - -function pop3:retrs() - return self:make_iter(self.retr) -end - -function pop3:tops(n) - return self:make_iter(function(self, msgid) - return self:top(msgid, n) - end) -end - -function pop3:messages() - return self:make_iter(self.message) -end - -function pop3:closed() - return not not self._easy -end - -function pop3:close() - if self._easy then - self._easy:close() - self._easy = false - end -end - -end - -return { - new = function(...) return pop3:new() end; -} diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua b/watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua deleted file mode 100644 index ce3a8f8..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/post_form.lua +++ /dev/null @@ -1,87 +0,0 @@ -local cURL = require "cURL" - --- Stream class -local Stream = {} do -Stream.__index = Stream - -function Stream:new(ch, length) - local o = setmetatable({}, self) - o._len = length - o._ch = ch - o._pos = 0 - o._step = 7 - return o -end - -function Stream:length() - return self._len -end - -function Stream:read() - local n = self._len - self._pos - if n <= 0 then return '' end -- eof - if n > self._step then n = self._step end - self._pos = self._pos + n - return self._ch:rep(n) -end - -end - --- returns size and reader -local function make_stream(ch, n, m) - local size = n * m - local i = -1 - return size, function() - i = i + 1 - if i < m then - return (tostring(ch)):rep(n - 2) .. '\r\n' - end - return nil - end -end - -local length, stream = make_stream("a", 10, 4) - -c = cURL.easy{ - url = "http://posttestserver.com/post.php", - -- url = "http://httpbin.org/post", - post = true, - httppost = cURL.form{ - - -- file form filesystem - name01 = { - file = "post_form.lua", - type = "text/plain", - name = "post.lua", - }, - - -- file form string - name02 = { - data = "bold", - name = "dummy.html", - type = "text/html", - }, - - -- file form stream object - name03 = { - stream = Stream:new('8', 25), - name = "stream1.txt", - type = "text/plain", - headers = { - "X-Test-Char : 8", - "X-Test-length : 25", - } - }, - - -- file form stream function - name04 = { - stream = stream, - length = length, - name = "stream2.txt", - type = "text/plain", - }, - - }, -} - -c:perform() diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua b/watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua deleted file mode 100644 index f41f8f0..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/post_json.lua +++ /dev/null @@ -1,12 +0,0 @@ -local cURL = require "cURL" - -c = cURL.easy{ - url = "http://posttestserver.com/post.php", - post = true, - httpheader = { - "Content-Type: application/json"; - }; - postfields = '{"hello": "world"}'; -} - -c:perform() \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua b/watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua deleted file mode 100644 index fd041db..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/post_mime.lua +++ /dev/null @@ -1,47 +0,0 @@ -local curl = require "cURL" - --- Documentation does not restrict using the same mime with different easy handle. --- In fact it does not mention this case at all. But in my experiments it works. -local easy = curl.easy() - --- Create new mime object. -local mime = easy:mime() - --- Add part. Source of data is file on disk. --- If file not exists then perform returns `[CURL-EASY][READ_ERROR]` error --- There no way to remove existed part from mime. -local part = mime:addpart{ - filedata = './post_mime.lua', - type = 'application/lua', - filename = 'post_mime.lua', - encoder = 'base64', -} - -local buffer = {} - --- Use mime object as request body -easy:setopt{ - url = 'http://127.0.0.1:7090/post', - mimepost = mime, -} - -easy:setopt_writefunction(table.insert, buffer) - -local ok, err = easy:perform() -if ok then - local code, url, content = easy:getinfo_effective_url(), - easy:getinfo_response_code(), table.concat(buffer) - print(code, url) - print(content) -else - print(err) -end - -easy:close() - --- Lua-cURL does not free mime when close `parent` easy. - --- Explicitly free mime object and all its parts --- There no way to reuse only some parts or submimes. --- but it possible reuse entire mime again. -mime:free() diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/rss.lua b/watchdog/third_party/lua-curl/examples/cURLv3/rss.lua deleted file mode 100644 index f29d914..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/rss.lua +++ /dev/null @@ -1,47 +0,0 @@ --- use LuaExpat and Lua-CuRL together for On-The-Fly XML parsing -local lxp = require "lxp" -local cURL = require "cURL" - --- create XML parser -items, tags = {}, {} -p = lxp.new{ - StartElement = function (parser, tagname) - tags[#tags + 1] = tagname - if (tagname == "item") then - items[#items + 1] = {} - end - end; - - CharacterData = function (parser, str) - if (tags[#tags -1] == "item") then - --we are parsing a item, get rid of trailing whitespace - items[#items][tags[#tags]] = string.gsub(str, "%s*$", "") - end - end; - - EndElement = function (parser, tagname) - --assuming well formed xml - tags[#tags] = nil - end; -} - --- create and setup easy handle -c = cURL.easy{url = "http://www.lua.org/news.rss"} - --- setup writer function with context -c:setopt_writefunction(p.parse, p) - --- perform request and close easy handle --- perform raise error if parser fail -c:perform():close() - ---finish document -assert(p:parse()) -p:close() - -for i, item in ipairs(items) do - for k, v in pairs(item) do - print(k,v) - end - print() -end \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua b/watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua deleted file mode 100644 index 2d6f1ac..0000000 --- a/watchdog/third_party/lua-curl/examples/cURLv3/uvwget.lua +++ /dev/null @@ -1,278 +0,0 @@ --- --- implementation of uvwget example from --- http://nikhilm.github.io/uvbook/index.html --- - -local curl = require "cURL" -local uv = require "lluv" -local ut = require "lluv.utils" - -local fprintf = function(f, ...) f:write((string.format(...))) end - -local stderr = io.stderr - -local trace = false do - -trace = trace and print or function() end - -end - -local ACTION_NAMES = { - [curl.POLL_IN ] = "POLL_IN"; - [curl.POLL_INOUT ] = "POLL_INOUT"; - [curl.POLL_OUT ] = "POLL_OUT"; - [curl.POLL_NONE ] = "POLL_NONE"; - [curl.POLL_REMOVE ] = "POLL_REMOVE"; -} - -local POLL_IO_FLAGS = { - [ curl.POLL_IN ] = uv.READABLE; - [ curl.POLL_OUT ] = uv.WRITABLE; - [ curl.POLL_INOUT ] = uv.READABLE + uv.WRITABLE; -} - -local EVENT_NAMES = { - [ uv.READABLE ] = "READABLE"; - [ uv.WRITABLE ] = "WRITABLE"; - [ uv.READABLE + uv.WRITABLE ] = "READABLE + WRITABLE"; -} - -local FLAGS = { - [ uv.READABLE ] = curl.CSELECT_IN; - [ uv.WRITABLE ] = curl.CSELECT_OUT; - [ uv.READABLE + uv.WRITABLE ] = curl.CSELECT_IN + curl.CSELECT_OUT; -} - -local Context = ut.class() do - -function Context:__init(fd) - self._fd = assert(fd) - self._poll = uv.poll_socket(fd) - self._poll.data = {context = self} - - assert(self._poll:fileno() == fd) - - return self -end - -function Context:close() - if not self._poll then return end - self._poll.data = nil - self._poll:close() - self._poll, self._fd = nil -end - -function Context:poll(...) - self._poll:start(...) -end - -function Context:fileno() - return self._fd -end - -end - -local MAX_REQUESTS = 64 -- Number of parallel request -local timer, multi -local qtask = ut.Queue.new() -- wait tasks -local qfree = ut.Queue.new() -- avaliable easy handles -local qeasy = {} -- all easy handles - -local function on_begin(handle, url, num) - local filename = tostring(num) .. ".download" - local file = io.open(filename, "w") - if not file then - fprintf(stderr, "Error opening %s\n", filename) - return - end - handle.data.file = file - handle:setopt_writefunction(file) - - fprintf(stderr, "Added download %s -> %s\n", url, filename); - return true -end - -local function on_end(handle, err, url) - handle.data.file:close() - handle.data.file = nil - - if err then - fprintf(stderr, "%s ERROR - %s\n", url, tostring(err)); - else - fprintf(stderr, "%s DONE\n", url); - end -end - -local function cleanup() - timer:close() - - for i, easy in ipairs(qeasy) do - multi:remove_handle(easy) - if easy.data then - local context = easy.data.context - if context then context:close() end - - local file = easy.data.file - if file then on_end(easy, 'closed', easy:getinfo_effective_url()) end - end - easy:close() - end - - multi:close() -end - -local proceed_queue, add_download do - -proceed_queue = function() - while true do - if qtask:empty() then return end - - if qfree:empty() then - if #qeasy < MAX_REQUESTS then - local easy = assert(curl.easy()) - qeasy[#qeasy + 1] = easy - qfree:push(easy) - else - return - end - end - - local task = assert(qtask:pop()) - local url, num = task[1], task[2] - - local handle = assert(qfree:pop()) - - handle:setopt{ - url = url; - fresh_connect = true; - forbid_reuse = true; - } - - handle.data = {} - - if on_begin(handle, url, num) then - multi:add_handle(handle) - else - handle:reset().data = nil - qfree:push(handle) - end - end -end - -add_download = function(url, num) - qtask:push{url, num} - - proceed_queue() -end - -end - -local on_libuv_poll, on_libuv_timeout - -local on_curl_timeout, on_curl_action do - -on_curl_timeout = function(ms) - -- calls by curl -- - trace("CURL::TIMEOUT", ms) - - if not timer:active() then - if ms <= 0 then ms = 1 end - - timer:start(ms, 0, on_libuv_timeout) - end -end - -on_curl_action = function(easy, fd, action) - local ok, err = pcall(function() - trace("CURL::SOCKET", easy, fd, ACTION_NAMES[action] or action) - - local context = easy.data.context - - local flag = POLL_IO_FLAGS[action] - if flag then - if not context then - context = Context.new(fd) - easy.data.context = context - end - context:poll(flag, on_libuv_poll) - elseif action == curl.POLL_REMOVE then - if context then - easy.data.context = nil - context:close() - end - end - end) - - if not ok then uv.defer(function() error(err) end) end -end - -end - --- on_libuv_poll, on_libuv_timeout -do - -local curl_check_multi_info = function() - while true do - local easy, ok, err = multi:info_read(true) - - if not easy then - multi:close() - error(err) - end - - if easy == 0 then break end - - local done_url = easy:getinfo_effective_url() - - local context = easy.data.context - if context then context:close() end - easy.data.context = nil - - if ok then on_end(easy, nil, done_url) else on_end(easy, err, done_url) end - - easy:reset() - easy.data = nil - qfree:push(easy) - end - - proceed_queue() -end - -on_libuv_poll = function(poller, err, events) - trace("UV::POLL", poller, err, EVENT_NAMES[events] or events) - - local flags = assert(FLAGS[events], ("unknown event:" .. events)) - - local context = poller.data.context - - multi:socket_action(context:fileno(), flags) - - curl_check_multi_info() -end - -on_libuv_timeout = function(timer) - trace("UV::TIMEOUT", timer) - - multi:socket_action() - - curl_check_multi_info() -end - -end - -timer = uv.timer() - -multi = curl.multi{ - timerfunction = on_curl_timeout; - socketfunction = on_curl_action; -} - -for i = 1, math.huge do - local url = arg[i] - if not url then break end - add_download(url, i) -end - -uv.run() - -cleanup() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua b/watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua deleted file mode 100644 index e49419a..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/crul_info.lua +++ /dev/null @@ -1,36 +0,0 @@ -local curl = require "lcurl" - -local function keys(t) - local s = {} - for k in pairs(t) do - s[#s + 1] = k - end - table.sort(s) - return s -end - -local function printf(...) - return print(string.format(...)) -end - -local exclude = {protocols = 1, features = 1, version = 1, version_num = 1} - -local info = curl.version_info() - -print(curl.version()) - -for _, key in ipairs(keys(info)) do if not exclude[key] then - printf("%15s: %s", key, info[key]) -end end - -print('Protocols:') -for _, protocol in ipairs(keys(info.protocols))do - local on = info.protocols[protocol] - printf(' [%s] %s', on and '+' or '-', protocol) -end - -print('Features:') -for _, feature in ipairs(keys(info.features))do - local on = info.features[feature] - printf(' [%s] %s', on and '+' or '-', feature) -end diff --git a/watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua b/watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua deleted file mode 100644 index ddf57f4..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/curl_debug.lua +++ /dev/null @@ -1,42 +0,0 @@ --- --- convert `debug.c` example from libcurl examples --- - -local curl = require "lcurl" - -local function printf(...) - io.stderr:write(string.format(...)) -end - -local function dump(title, data, n) - n = n or 16 - printf("%s, %10.10d bytes (0x%8.8x)\n", title, #data, #data) - for i = 1, #data do - if (i - 1) % n == 0 then printf("%4.4x: ", i-1) end - printf("%02x ", string.byte(data, i, i)) - if i % n == 0 then printf("\n") end - end - if #data % n ~= 0 then printf("\n") end -end - -local function my_trace(type, data) - local text - if type == curl.INFO_TEXT then printf("== Info: %s", data) end - if type == curl.INFO_HEADER_OUT then text = "=> Send header" end - if type == curl.INFO_DATA_OUT then text = "=> Send data" end - if type == curl.INFO_SSL_DATA_OUT then text = "=> Send SSL data" end - if type == curl.INFO_HEADER_IN then text = "<= Recv header" end - if type == curl.INFO_DATA_IN then text = "<= Recv data" end - if type == curl.INFO_SSL_DATA_IN then text = "<= Recv SSL data" end - if text then dump(text, data) end -end - -local easy = curl.easy{ - url = "http://google.com", - verbose = true, - debugfunction = my_trace, - followlocation = true, - writefunction = function()end, -} - -easy:perform() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua b/watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua deleted file mode 100644 index e49419a..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/curl_info.lua +++ /dev/null @@ -1,36 +0,0 @@ -local curl = require "lcurl" - -local function keys(t) - local s = {} - for k in pairs(t) do - s[#s + 1] = k - end - table.sort(s) - return s -end - -local function printf(...) - return print(string.format(...)) -end - -local exclude = {protocols = 1, features = 1, version = 1, version_num = 1} - -local info = curl.version_info() - -print(curl.version()) - -for _, key in ipairs(keys(info)) do if not exclude[key] then - printf("%15s: %s", key, info[key]) -end end - -print('Protocols:') -for _, protocol in ipairs(keys(info.protocols))do - local on = info.protocols[protocol] - printf(' [%s] %s', on and '+' or '-', protocol) -end - -print('Features:') -for _, feature in ipairs(keys(info.features))do - local on = info.features[feature] - printf(' [%s] %s', on and '+' or '-', feature) -end diff --git a/watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua b/watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua deleted file mode 100644 index abee1f5..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/easy.obj.lua +++ /dev/null @@ -1,147 +0,0 @@ ---- High level wrapper around lcurl.easy --- --- Usage: --- --- local e = cURL.easy{ --- url = 'http://example.com', --- writefunction = io.stdout; --- headerfunction = function(msg) io.stderr:write(msg) end; --- } --- e:perform() --- - -local curl = require "lcurl.safe" - -local easy = {} do - -local LCURL_ERROR_EASY = curl.ERROR_EASY -local CURLE_UNKNOWN_OPTION = curl.E_UNKNOWN_OPTION - -easy.__index = function(self, k) - if k:sub(1, 4) == 'set_' then - local h = self:handle() - local name = k:sub(5) - if h['setopt_' .. name] then - easy[k] = function(self, value) return self:set(name, value) end - end - elseif k:sub(1, 4) == 'get_' then - local h = self:handle() - local name = k:sub(5) - if h['getinfo_' .. name] then - easy[k] = function(self) return self:get(name) end - end - end - return easy[k] -end - -local function setopt(handle, k, v, ...) - local fn = handle["setopt_" .. k] - if not fn then - return nil, curl.error(LCURL_ERROR_EASY, CURLE_UNKNOWN_OPTION) - end - local ok, err = fn(handle, v, ...) - if not ok then return nil, err end - return true -end - -function easy:new(opt) - local handle, err = curl.easy() - if not handle then return nil, err end - - if opt then for k, v in pairs(opt) do - local ok, err = setopt(handle, k, v) - if not ok then - handle:close() - return nil, err - end - end end - - local o = setmetatable({ - _handle = handle; - }, self) - - return o -end - -function easy:handle() - return self._handle -end - -function easy:set(k, v, ...) - local handle = self:handle() - - if type(k) == "table" then - assert(v == nil) - for k, v in pairs(k) do - local ok, err = setopt(handle, k, v) - if not ok then return nil, err, k end - end - return self - end - - assert(type(k) == 'string') - local ok, err = setopt(handle, k, v, ...) - if not ok then return nil, err end - return self -end - -function easy:perform(opt) - if opt then - local ok, err = self:set(opt) - if not ok then return nil, err end - end - - local e = self:handle() - - local ok, err = e:perform() - if ok then return nil, err end - - return self -end - -function easy:post(data) - local post = curl.form() - local ok, err - for k, v in pairs(data) do - if type(v) == "string" then - ok, err = post:add_content(k, v) - else - assert(type(v) == "table") - if v.data then - ok, err = post:add_buffer(k, v.file, v.data, v.type, v.headers) - else - ok, err = post:add_file(k, v.file, v.data, v.type, v.filename, v.headers) - end - end - if not ok then break end - end - if not ok then - post:free() - return nil, err - end - - local e = self:handle() - ok, err = e:setopt_httppost(post) - if not ok then - post:free() - return nil, err - end - - return self -end - -function easy:get(k, ...) - assert(type(k) == "string") - local handle = self:handle() - local fn = handle["getinfo_" .. k] - if not fn then return nil, "Unknown info: " .. k end - return fn(handle, ...) -end - -end - -local cURL = {} - -cURL.easy = function(...) return easy:new(...) end - -return cURL diff --git a/watchdog/third_party/lua-curl/examples/lcurl/file.lua b/watchdog/third_party/lua-curl/examples/lcurl/file.lua deleted file mode 100644 index f5267bd..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/file.lua +++ /dev/null @@ -1,18 +0,0 @@ -local cURL = require("lcurl") - --- open output file -f = assert(io.open("example_homepage.html", "w")) - -cURL.easy() - -- setup url - :setopt_url("http://www.example.com/") - -- setup file object as writer - :setopt_writefunction(f) - -- perform, invokes callbacks - :perform() --- close easy -:close() - --- close output file -f:close() -print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua b/watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua deleted file mode 100644 index 5e32fed..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/fnmatch.lua +++ /dev/null @@ -1,47 +0,0 @@ --- --- Example of how to download multiple files in single perform --- - -local curl = require "lcurl" - -local function printf(...) - io.stderr:write(string.format(...)) -end - -local function pat2pat(s) - return "^" .. string.gsub(s, ".", { - ["*"] = '[^%.]*'; - ["."] = '%.'; - }) .. "$" -end - -local c = curl.easy{ - url = "ftp://moteus:123456@127.0.0.1/test.*"; - wildcardmatch = true; -} - -local data, n = 0, 0 -c:setopt_writefunction(function(chunk) data = #chunk + data end) - --- called before each new file -c:setopt_chunk_bgn_function(function(info, remains) - data, n = 0, n + 1 - printf('\n======================================================\n') - printf('new file `%s` #%d, remains - %d\n', info.filename, n, remains or -1) -end) - --- called after file download complite -c:setopt_chunk_end_function(function() - printf('total size %d[B]\n', data) - printf('------------------------------------------------------\n') -end) - --- custom pattern matching function -c:setopt_fnmatch_function(function(pattern, name) - local p = pat2pat(pattern) - local r = not not string.match(name, p) - printf("%s %s %s\n", r and '+' or '-', pattern, name) - return r -end) - -c:perform() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua b/watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua deleted file mode 100644 index d3c82c1..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/ftpupload.lua +++ /dev/null @@ -1,17 +0,0 @@ --- simple "On the fly" fileupload -local cURL = require("lcurl") - -count=0 -cURL.easy() - :setopt_url("ftp://ftptest:secret0815@targethost/file.dat") - :setopt_upload(true) - :setopt_readfunction(function() - count = count + 1 - if count < 10 then - return "Line " .. count .. "\n" - end - end) - :perform() -:close() - -print("Fileupload done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/multi.lua b/watchdog/third_party/lua-curl/examples/lcurl/multi.lua deleted file mode 100644 index 666cc72..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/multi.lua +++ /dev/null @@ -1,24 +0,0 @@ -local cURL = require("lcurl") - -local f1 = assert(io.open("lua.html","a+")) - -local f2 = assert(io.open("luajit.html","a+")) - -c = cURL.easy() - :setopt_url("http://www.lua.org/") - :setopt_writefunction(f1) - -c2 = cURL.easy() - :setopt_url("http://luajit.org/") - :setopt_writefunction(f2) - -m = cURL.multi() - :add_handle(c) - :add_handle(c2) - -while m:perform() > 0 do - m:wait() -end - -print(m:info_read()) -print(m:info_read()) diff --git a/watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua b/watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua deleted file mode 100644 index 7513200..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/multi_iterator.lua +++ /dev/null @@ -1,78 +0,0 @@ --- -local function multi_iterator(...) - local curl = require "lcurl.safe" - - local buffers = {_ = {}} do - - function buffers:append(e, ...) - local b = self._[e] or {} - self._[e] = b - b[#b + 1] = {...} - end - - function buffers:next() - for e, t in pairs(self._) do - local m = table.remove(t, 1) - if m then return e, m end - end - end - - end - - local function multi_init(...) - local remain = 0 - local m = curl.multi() - for _, e in ipairs{...} do - e:setopt_writefunction(function(str) buffers:append(e, "data", str) end) - e:setopt_headerfunction(function(str) buffers:append(e, "header", str) end) - m:add_handle(e) - remain = remain + 1 - end - return m, remain - end - - local m, remain = multi_init(...) - m:perform() - return function() - while true do - local e, t = buffers:next() - if t then return e, unpack(t) end - if remain == 0 then break end - - m:wait() - - local n, err = m:perform() - if not n then m:close() error(err) end - - if n <= remain then - while true do - local e, ok, err = m:info_read() - if not e then m:close() error(err) end - if e == 0 then break end - if ok then buffers:append(e, "done", ok) - else buffers:append(e, "error", err) end - end - remain = n - end - - end - m:close() - end -end - --- -local curl = require "lcurl" - -c1 = curl.easy() - :setopt_url("http://www.lua.org/") - -c2 = curl.easy() - :setopt_url("http://luajit.org/") - -for easy, type, data in multi_iterator(c1, c2) do - if type == 'header' then print(easy, type, (data:gsub("%s*$", ""))) - elseif type == 'data' then print(easy, type, #data) - elseif type == 'error' then print(easy, type, data) - elseif type == 'done' then print(easy, type, data) - end -end diff --git a/watchdog/third_party/lua-curl/examples/lcurl/pause.lua b/watchdog/third_party/lua-curl/examples/lcurl/pause.lua deleted file mode 100644 index f293d2a..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/pause.lua +++ /dev/null @@ -1,78 +0,0 @@ -local curl = require "lcurl" - -local WAIT_COUNT = 1 - -local RESOURCE_URL = "http://www.rfc-editor.org/rfc/rfc2543.txt" - -local State = { - PAUSE = 0, -- write function should return CURL_WRITEFUNC_PAUSE - WAIT = 1, -- waiting for CURLPAUSE_CONT - WRITE = 2, -- write function should perform write - WRITTEN = 3, -- write function have performed write -} - --- Current state -local state = State.PAUSE - --- Countdown to continue writes -local waitCount = 0 - --- Received data and data size -local data, datasize = {}, 0 - -local function WriteFunction(str) - if state == State.PAUSE then - state = State.WAIT - waitCount = WAIT_COUNT - print('Pause') - return curl.WRITEFUNC_PAUSE - end - - if state == State.WAIT then - -- callback shouldn't be called in this state - print("WARNING: write-callback called in STATE_WAIT") - return curl.WRITEFUNC_PAUSE - end - - if state == State.WRITE then - state = State.WRITTEN - end - - datasize = datasize + #str - data[#data + 1] = str -end - -local progress = 0 -local function ProgressFunction() - - progress = progress + 1 - - if state == State.WAIT then - -- Wait until unpause - waitCount = waitCount - 1 - if waitCount == 0 then - state = State.WRITE - print('Unpause :', progress) - easy:pause(curl.PAUSE_CONT) - end - end - - -- WriteFunction written some data - if state == State.WRITTEN then - -- send pause to WriteFunction - state = State.PAUSE - progress = 0 - end - -end - -easy = curl.easy{ - url = RESOURCE_URL, - accept_encoding = "gzip,deflate", - writefunction = WriteFunction, - progressfunction = ProgressFunction, - noprogress = false, - followlocation = true, -} - -easy:perform() \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/post.lua b/watchdog/third_party/lua-curl/examples/lcurl/post.lua deleted file mode 100644 index 6f7aa87..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/post.lua +++ /dev/null @@ -1,69 +0,0 @@ -local cURL = require("lcurl") - -local post = cURL.form() - -- post file from filesystem - :add_file ("name", "post.lua", "text/plain") - -- post file from data variable - :add_buffer("name2", "dummy.html", "bold", "text/html") - -cURL.easy() - :setopt_url("http://localhost") - :setopt_httppost(post) - :perform() -:close() - --- Lua-cURL compatiable function -local function post(e, data) - local form = cURL.form() - local ok, err = true - - for k, v in pairs(data) do - if type(v) == "string" then - ok, err = form:add_content(k, v) - else - assert(type(v) == "table") - if v.stream_length then - form:free() - error("Stream does not support") - end - if v.data then - ok, err = form:add_buffer(k, v.file, v.data, v.type, v.headers) - else - ok, err = form:add_file(k, v.file, v.data, v.type, v.filename, v.headers) - end - end - if not ok then break end - end - - if not ok then - form:free() - return nil, err - end - - ok, err = e:setopt_httppost(form) - if not ok then - form:free() - return nil, err - end - - return e -end - -local e = cURL.easy() - :setopt_url("http://localhost") - -postdata = { - -- post file from filesystem - name = {file="post.lua", - type="text/plain" - }, - -- post file from data variable - name2 = {file="dummy.html", - data="bold", - type="text/html" - }, -} - -post(e, postdata):perform() - -print("Done") \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua b/watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua deleted file mode 100644 index b0f50c8..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/post_stream.lua +++ /dev/null @@ -1,26 +0,0 @@ -local curl = require "lcurl" - --- returns size and reader -local function make_stream(ch, n, m) - local size = n * m - local i = -1 - return size, function() - i = i + 1 - if i < m then - return (tostring(ch)):rep(n - 2) .. '\r\n' - end - end -end - -local form = curl.form() - :add_stream("test1", make_stream("a", 10, 4)) - :add_stream("test2", "test2.txt", make_stream("b", 10, 4)) - :add_stream("test3", "test3.txt", "text/plain", make_stream("c", 10, 4)) - -curl.easy{ - url = 'http://posttestserver.com/post.php', - writefunction = io.write, - httppost = form, - post = true, -}:perform() - diff --git a/watchdog/third_party/lua-curl/examples/lcurl/share.lua b/watchdog/third_party/lua-curl/examples/lcurl/share.lua deleted file mode 100644 index 7f49102..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/share.lua +++ /dev/null @@ -1,23 +0,0 @@ --- Cookie data will be shared across the easy handles to do an authorized download -local cURL = require("lcurl") - --- create share handle (share COOKIE and DNS Cache) -s = cURL.share() - :setopt_share(cURL.LOCK_DATA_COOKIE ) - :setopt_share(cURL.LOCK_DATA_DNS ) - --- create first easy handle to do the login -c = cURL.easy() - :setopt_share(s) - :setopt_url("http://targethost/login.php?username=foo&password=bar") - --- create second easy handle to do the download -c2 = cURL.easy() - :setopt_share(s) - :setopt_url("http://targethost/download.php?id=test") - --- login -c:perform() - --- download -c2:perform() diff --git a/watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua b/watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua deleted file mode 100644 index f829726..0000000 --- a/watchdog/third_party/lua-curl/examples/lcurl/smtp-mime.lua +++ /dev/null @@ -1,85 +0,0 @@ -local curl = require "lcurl" - -local SMTP = { - url = "smtp://mail.example.com"; -} - -local FROM = "" -local TO = "" -local CC = "" -local FILE = "smtp-mime.lua" -- if you send this file do not forget it may have mail password -local CT_FILE = "application/lua" - -local DUMP_MIME = false - -local headers = { - "Date: Tue, 22 Aug 2017 14:08:43 +0100", - "To: " .. TO, - "From: " .. FROM .. " (Example User)", - "Cc: " .. CC .. " (Another example User)", - "Message-ID: ", - "Subject: example sending a MIME-formatted message", -} - -local inline_text = "" - .. "This is the inline text message of the e-mail.\r\n" - .. "\r\n" - .. " It could be a lot of lines that would be displayed in an e-mail\r\n" - .. "viewer that is not able to handle HTML.\r\n" - -local inline_html = "" - .. "\r\n" - .. "

This is the inline HTML message of the e-mail.

" - .. "
\r\n" - .. "

It could be a lot of HTML data that would be displayed by " - .. "e-mail viewers able to handle HTML.

" - .. "\r\n" - -local function dump_mime(type, data) - if type == curl.INFO_DATA_OUT then io.write(data) end -end - -local easy = curl.easy() - -local mime = easy:mime() do - local alt = easy:mime() - alt - :addpart() - :data(inline_html, "text/html") - alt - :addpart() - :data(inline_text) - mime:addpart() - :subparts(alt, "multipart/alternative", { - "Content-Disposition: inline" - }) - mime - :addpart() - :filedata(FILE, CT_FILE) -end - -easy:setopt{ - url = SMTP.url, - mail_from = FROM, - mail_rcpt = {TO, CC}, - httpheader = headers; - mimepost = mime; - ssl_verifyhost = false; - ssl_verifypeer = false; - username = SMTP.user; - password = SMTP.password; - upload = true; -} - -if DUMP_MIME then - easy:setopt{ - verbose = true; - debugfunction = dump_mime; - } -end - -easy:perform() - -easy:close() - -mime:free() diff --git a/watchdog/third_party/lua-curl/lakeconfig.lua b/watchdog/third_party/lua-curl/lakeconfig.lua deleted file mode 100644 index ccbbb11..0000000 --- a/watchdog/third_party/lua-curl/lakeconfig.lua +++ /dev/null @@ -1,260 +0,0 @@ -local io = require "io" -io.stdout:setvbuf"no" -io.stderr:setvbuf"no" - -function vc_version() - local VER = lake.compiler_version() - MSVC_VER = ({ - [15] = '9'; - [16] = '10'; - })[VER.MAJOR] or '' - return MSVC_VER -end - -if not L then - -local function arkey(t) - assert(type(t) == 'table') - local keys = {} - for k in pairs(t) do - assert(type(k) == 'number') - table.insert(keys, k) - end - table.sort(keys) - return keys -end - -local function ikeys(t) - local keys = arkey(t) - local i = 0 - return function() - i = i + 1 - local k = keys[i] - if k == nil then return end - return k, t[k] - end -end - -local function expand(arr, t) - if t == nil then return arr end - - if type(t) ~= 'table' then - table.insert(arr, t) - return arr - end - - for _, v in ikeys(t) do - expand(arr, v) - end - - return arr -end - -function L(...) - return expand({}, {...}) -end - -end - -J = J or path.join - -IF = IF or lake.choose or choose - -DIR_SEP = package.config:sub(1,1) - -function prequire(...) - local ok, mod = pcall(require, ...) - if ok then return mod end -end - -function clone(t, o) - o = o or {} - for k, v in pairs(t) do - if o[k] == nil then o[k] = v end - end - return o -end - -function each_join(dir, list) - for i, v in ipairs(list) do - list[i] = path.join(dir, v) - end - return list -end - -function run(file, cwd) - print() - print("run " .. file) - if not TESTING then - if cwd then lake.chdir(cwd) end - local status, code = utils.execute( LUA_RUNNER .. ' ' .. file ) - if cwd then lake.chdir("<") end - print() - return status, code - end - return true, 0 -end - -function exec(file, cwd) - print() - print("exec " .. file) - if not TESTING then - if cwd then lake.chdir(cwd) end - local status, code = utils.execute( file ) - if cwd then lake.chdir("<") end - print() - return status, code - end - return true, 0 -end - -local TESTS = {} - -function run_test(name, params) - local test_dir = TESTDIR or J(ROOT, 'test') - local cmd = J(test_dir, name) - if params then cmd = cmd .. ' ' .. params end - local ok = run(cmd, test_dir) - - table.insert(TESTS, {cmd = cmd, result = ok}) - - print("TEST " .. name .. (ok and ' - pass!' or ' - fail!')) -end - -function exec_test(name, params) - local test_dir = TESTDIR or J(ROOT, 'test') - local cmd = J(test_dir, name) - if params then cmd = cmd .. ' ' .. params end - local ok = exec(cmd, test_dir) - - table.insert(TESTS, {cmd = cmd, result = ok}) - - print("TEST " .. name .. (ok and ' - pass!' or ' - fail!')) -end - -function test_summary() - local ok = true - print("") - print("------------------------------------") - print("Number of tests:", #TESTS) - for _, t in ipairs(TESTS) do - ok = ok and t.result - print((t.result and ' Pass' or ' Fail') .. " - TEST " .. t.cmd) - end - print("------------------------------------") - print("") - return ok -end - ---[[spawn]] if WINDOWS then - function spawn(file, cwd) - local winapi = prequire "winapi" - if not winapi then - quit('needs winapi for spawn!') - return false - end - - print("spawn " .. file) - if not TESTING then - if cwd then lake.chdir(cwd) end - assert(winapi.shell_exec(nil, LUA_RUNNER, file, cwd)) - if cwd then lake.chdir("<") end - print() - end - return true - end -else - function spawn(file, cwd) - print("spawn " .. file) - if not TESTING then - assert(run(file .. ' &', cwd)) - end - return true - end -end - -function as_bool(v,d) - if v == nil then return not not d end - local n = tonumber(v) - if n == 0 then return false end - if n then return true end - return false -end - ---- set global variables --- LUA_NEED --- LUA_DIR --- LUA_RUNNER --- ROOT --- LUADIR --- LIBDIR --- TESTDIR --- DOCDIR --- DYNAMIC -function INITLAKEFILE() - if LUA_VER == '5.3' then - LUA_NEED = 'lua53' - LUA_DIR = ENV.LUA_DIR_5_3 or ENV.LUA_DIR - LUA_RUNNER = LUA_RUNNER or 'lua53' - elseif LUA_VER == '5.2' then - LUA_NEED = 'lua52' - LUA_DIR = ENV.LUA_DIR_5_2 or ENV.LUA_DIR - LUA_RUNNER = LUA_RUNNER or 'lua52' - elseif LUA_VER == '5.1' then - LUA_NEED = 'lua51' - LUA_DIR = ENV.LUA_DIR - LUA_RUNNER = LUA_RUNNER or 'lua' - else - LUA_NEED = 'lua' - LUA_DIR = ENV.LUA_DIR - LUA_RUNNER = LUA_RUNNER or 'lua' - end - ROOT = ROOT or J( LUA_DIR, 'libs', PROJECT ) - LUADIR = LUADIR or J( ROOT, 'share' ) - LIBDIR = LIBDIR or J( ROOT, 'share' ) - TESTDIR = TESTDIR or J( ROOT, 'test' ) - DOCDIR = DOCDIR or J( ROOT, 'doc' ) - DYNAMIC = as_bool(DYNAMIC, false) -end - ------------------------ --- needs -- ------------------------ - -lake.define_need('lua53', function() - return { - incdir = J(ENV.LUA_DIR_5_3, 'include'); - libdir = J(ENV.LUA_DIR_5_3, 'lib'); - libs = {'lua53'}; - } -end) - -lake.define_need('lua52', function() - return { - incdir = J(ENV.LUA_DIR_5_2, 'include'); - libdir = J(ENV.LUA_DIR_5_2, 'lib'); - libs = {'lua52'}; - } -end) - -lake.define_need('lua51', function() - return { - incdir = J(ENV.LUA_DIR, 'include'); - libdir = J(ENV.LUA_DIR, 'lib'); - libs = {'lua5.1'}; - } -end) - -local CURL_DIR = CURL_DIR or ENV.CURL_DIR or J(ENV.CPPLIB_DIR, 'curl', '7.37.1') - -lake.define_need('libcurl', function() - return { - incdir = J(CURL_DIR, 'include'); - libdir = J(CURL_DIR, 'lib'); - libs = {'libcurl'}; - } -end) - -lake.define_need('winsock2', function() - return {libs = {"ws2_32"}} -end) diff --git a/watchdog/third_party/lua-curl/lakefile b/watchdog/third_party/lua-curl/lakefile deleted file mode 100644 index aa83c99..0000000 --- a/watchdog/third_party/lua-curl/lakefile +++ /dev/null @@ -1,44 +0,0 @@ -PROJECT = 'cURL' - -INITLAKEFILE() - -DEFINES = L{DEFINES, - IF(WINDOWS, 'DLL_EXPORT', ''); - IF(DEBUG, 'LCURL_RESET_NULL_LUA', ''); -} - -cURL = c.shared{'lcurl', - base = 'src', - src = '*.c', - needs = {LUA_NEED, 'libcurl', IF(WINDOWS, 'winsock2', 'sockets')}, - defines = DEFINES, - dynamic = DYNAMIC, - strip = true, - cflags = IF(not MSVC, {'-Wno-unused-local-typedefs'}) -} - -target('build', cURL) - -install = target('install', { - file.group{odir=LIBDIR; src = cURL }; - file.group{odir=LIBDIR; src = J("src", "lua") ; recurse = true }; - file.group{odir=J(ROOT, 'examples'); src = 'examples'; recurse = true }; - file.group{odir=TESTDIR; src = 'test'; recurse = true }; -}) - -target('test', install, function() - run_test('test_easy.lua') - run_test('test_safe.lua') - run_test('test_form.lua') - run_test('test_pause02.c.lua') - run_test('test_curl.lua') - run_test('test_mime.lua') - run_test('test_multi_callback.lua') - run_test('test_multi_nested_callback.lua') - run_test('test_urlapi.lua') - - if not test_summary() then - quit("test fail") - end -end) - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec deleted file mode 100644 index 2dcf3d2..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.0-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.0-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.0.zip", - dir = "Lua-cURLv3-0.3.0", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.3" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec deleted file mode 100644 index dbac658..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.1-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.1-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.1.zip", - dir = "Lua-cURLv3-0.3.1", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec deleted file mode 100644 index 6c39358..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.10-1.rockspec +++ /dev/null @@ -1,75 +0,0 @@ -package = "Lua-cURL" -version = "0.3.10-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.10.zip", - dir = "Lua-cURLv3-0.3.10", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - "src/lcurlapi.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec deleted file mode 100644 index 9aaa241..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.11-1.rockspec +++ /dev/null @@ -1,75 +0,0 @@ -package = "Lua-cURL" -version = "0.3.11-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.11.zip", - dir = "Lua-cURLv3-0.3.11", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - "src/lcurlapi.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec deleted file mode 100644 index d828cb9..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.12-1.rockspec +++ /dev/null @@ -1,75 +0,0 @@ -package = "Lua-cURL" -version = "0.3.12-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.12.zip", - dir = "Lua-cURLv3-0.3.12", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.5" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - "src/lcurlapi.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec deleted file mode 100644 index f5bed7a..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.13-1.rockspec +++ /dev/null @@ -1,75 +0,0 @@ -package = "Lua-cURL" -version = "0.3.13-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.13.zip", - dir = "Lua-cURLv3-0.3.13", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.5" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - "src/lcurlapi.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec deleted file mode 100644 index 28b69b9..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.2-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.2-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.2.zip", - dir = "Lua-cURLv3-0.3.2", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec deleted file mode 100644 index bc9e124..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.3-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.3-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.3.zip", - dir = "Lua-cURLv3-0.3.3", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec deleted file mode 100644 index d4d40b8..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.4-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.4-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.4.zip", - dir = "Lua-cURLv3-0.3.4", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec deleted file mode 100644 index 169f0ab..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.5-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.5-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.5.zip", - dir = "Lua-cURLv3-0.3.5", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec deleted file mode 100644 index 76aa110..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.6-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.6-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.6.zip", - dir = "Lua-cURLv3-0.3.6", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec deleted file mode 100644 index 6d55ef4..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.7-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.7-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.7.zip", - dir = "Lua-cURLv3-0.3.7", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec deleted file mode 100644 index 2fd2ca8..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-1.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.8-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.8.zip", - dir = "Lua-cURLv3-0.3.8", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec deleted file mode 100644 index 5e75398..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.8-2.rockspec +++ /dev/null @@ -1,74 +0,0 @@ -package = "Lua-cURL" -version = "0.3.8-2" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.8.zip", - dir = "Lua-cURLv3-0.3.8", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec deleted file mode 100644 index f95e1e3..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-0.3.9-1.rockspec +++ /dev/null @@ -1,75 +0,0 @@ -package = "Lua-cURL" -version = "0.3.9-1" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/v0.3.9.zip", - dir = "Lua-cURLv3-0.3.9", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.4" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - "src/lcurlapi.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec b/watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec deleted file mode 100644 index 5aff1c6..0000000 --- a/watchdog/third_party/lua-curl/rockspecs/lua-curl-scm-0.rockspec +++ /dev/null @@ -1,75 +0,0 @@ -package = "Lua-cURL" -version = "scm-0" - -source = { - url = "https://github.com/Lua-cURL/Lua-cURLv3/archive/master.zip", - dir = "Lua-cURLv3-master", -} - -description = { - summary = "Lua binding to libcurl", - detailed = [[ - ]], - homepage = "https://github.com/Lua-cURL", - license = "MIT/X11" -} - -dependencies = { - "lua >= 5.1, < 5.5" -} - -external_dependencies = { - platforms = { - windows = { - CURL = { - header = "curl/curl.h", - library = "libcurl", - } - }; - unix = { - CURL = { - header = "curl/curl.h", - -- library = "curl", - } - }; - } -} - -build = { - copy_directories = {'doc', 'examples', 'test'}, - - type = "builtin", - - platforms = { - windows = { modules = { - lcurl = { - libraries = {"libcurl", "ws2_32"}, - } - }}, - unix = { modules = { - lcurl = { - libraries = {"curl"}, - } - }} - }, - - modules = { - ["cURL" ] = "src/lua/cURL.lua", - ["cURL.safe" ] = "src/lua/cURL/safe.lua", - ["cURL.utils" ] = "src/lua/cURL/utils.lua", - ["cURL.impl.cURL" ] = "src/lua/cURL/impl/cURL.lua", - - lcurl = { - sources = { - "src/l52util.c", "src/lceasy.c", "src/lcerror.c", - "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", "src/lcmime.c", - "src/lcurlapi.c", - }, - incdirs = { "$(CURL_INCDIR)" }, - libdirs = { "$(CURL_LIBDIR)" } - }, - } -} - - diff --git a/watchdog/third_party/lua-curl/src/l52util.c b/watchdog/third_party/lua-curl/src/l52util.c deleted file mode 100644 index 6373687..0000000 --- a/watchdog/third_party/lua-curl/src/l52util.c +++ /dev/null @@ -1,178 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "l52util.h" - -#include -#include /* for memset */ -#include - -#if LUA_VERSION_NUM >= 502 - -int luaL_typerror (lua_State *L, int narg, const char *tname) { - const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, - luaL_typename(L, narg)); - return luaL_argerror(L, narg, msg); -} - -#ifndef luaL_register - -void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l){ - if(libname) lua_newtable(L); - luaL_setfuncs(L, l, 0); -} - -#endif - -#else - -void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup){ - luaL_checkstack(L, nup, "too many upvalues"); - for (; l->name != NULL; l++) { /* fill the table with given functions */ - int i; - for (i = 0; i < nup; i++) /* copy upvalues to the top */ - lua_pushvalue(L, -nup); - lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ - lua_setfield(L, -(nup + 2), l->name); - } - lua_pop(L, nup); /* remove upvalues */ -} - -void lua_rawgetp(lua_State *L, int index, const void *p){ - index = lua_absindex(L, index); - lua_pushlightuserdata(L, (void *)p); - lua_rawget(L, index); -} - -void lua_rawsetp (lua_State *L, int index, const void *p){ - index = lua_absindex(L, index); - lua_pushlightuserdata(L, (void *)p); - lua_insert(L, -2); - lua_rawset(L, index); -} - -#endif - -int lutil_newmetatablep (lua_State *L, const void *p) { - lua_rawgetp(L, LUA_REGISTRYINDEX, p); - if (!lua_isnil(L, -1)) /* name already in use? */ - return 0; /* leave previous value on top, but return 0 */ - lua_pop(L, 1); - - lua_newtable(L); /* create metatable */ - lua_pushvalue(L, -1); /* duplicate metatable to set*/ - - lua_pushliteral (L, "__type"); - lua_pushstring(L, p); // push meta name - lua_settable (L, -3); // set meta name - - lua_rawsetp(L, LUA_REGISTRYINDEX, p); - - return 1; -} - -void lutil_getmetatablep (lua_State *L, const void *p) { - lua_rawgetp(L, LUA_REGISTRYINDEX, p); -} - -void lutil_setmetatablep (lua_State *L, const void *p) { - lutil_getmetatablep(L, p); - assert(lua_istable(L,-1)); - lua_setmetatable (L, -2); -} - -int lutil_isudatap (lua_State *L, int ud, const void *p) { - if (lua_isuserdata(L, ud)){ - if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ - int res; - lutil_getmetatablep(L,p); /* get correct metatable */ - res = lua_rawequal(L, -1, -2); /* does it have the correct mt? */ - lua_pop(L, 2); /* remove both metatables */ - return res; - } - } - return 0; -} - -void *lutil_checkudatap (lua_State *L, int ud, const void *p) { - void *up = lua_touserdata(L, ud); - if (up != NULL) { /* value is a userdata? */ - if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ - lutil_getmetatablep(L,p); /* get correct metatable */ - if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ - lua_pop(L, 2); /* remove both metatables */ - return up; - } - } - } - luaL_typerror(L, ud, p); /* else error */ - return NULL; /* to avoid warnings */ -} - -int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup) { - if (!lutil_newmetatablep(L, p)){ - lua_insert(L, -1 - nup); /* move mt prior upvalues */ - return 0; - } - - lua_insert(L, -1 - nup); /* move mt prior upvalues */ - luaL_setfuncs (L, methods, nup); /* define methods */ - lua_pushliteral (L, "__index"); /* define metamethods */ - lua_pushvalue (L, -2); - lua_settable (L, -3); - return 1; -} - -void *lutil_newudatap_impl(lua_State *L, size_t size, const void *p){ - void *obj = lua_newuserdata (L, size); - memset(obj, 0, size); - lutil_setmetatablep(L, p); - return obj; -} - -void lutil_pushint64(lua_State *L, int64_t v){ - if(sizeof(lua_Integer) >= sizeof(int64_t)){ - lua_pushinteger(L, (lua_Integer)v); - return; - } - lua_pushnumber(L, (lua_Number)v); -} - -void lutil_pushuint(lua_State *L, unsigned int v){ -#if LUA_VERSION_NUM >= 503 - lua_pushinteger(L, (lua_Integer)v); -#else - lua_pushnumber(L, (lua_Number)v); -#endif -} - -int64_t lutil_checkint64(lua_State *L, int idx){ - if(sizeof(lua_Integer) >= sizeof(int64_t)) - return luaL_checkinteger(L, idx); - return (int64_t)luaL_checknumber(L, idx); -} - -int64_t lutil_optint64(lua_State *L, int idx, int64_t v){ - if(sizeof(lua_Integer) >= sizeof(int64_t)) - return luaL_optinteger(L, idx, v); - return (int64_t)luaL_optnumber(L, idx, v); -} - -void lutil_pushnvalues(lua_State *L, int n){ - for(;n;--n) lua_pushvalue(L, -n); -} - -int lutil_is_null(lua_State *L, int i){ - return lua_islightuserdata(L, i) && 0 == lua_touserdata(L, i); -} - -void lutil_push_null(lua_State *L){ - lua_pushlightuserdata(L, (void*)0); -} diff --git a/watchdog/third_party/lua-curl/src/l52util.h b/watchdog/third_party/lua-curl/src/l52util.h deleted file mode 100644 index 97348a0..0000000 --- a/watchdog/third_party/lua-curl/src/l52util.h +++ /dev/null @@ -1,97 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _L52UTIL_H_ -#define _L52UTIL_H_ - -#include "lua.h" -#include "lauxlib.h" -#include - -#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ - -#ifndef luaL_checkint -#define luaL_checkint luaL_checkinteger -#endif - -#ifndef luaL_checklong -#define luaL_checklong luaL_checkinteger -#endif - -#ifndef luaL_optint -#define luaL_optint luaL_optinteger -#endif - -#ifndef luaL_optlong -#define luaL_optlong luaL_optinteger -#endif - -#endif - -#if LUA_VERSION_NUM >= 502 /* Lua 5.2 */ - -/* lua_rawgetp */ -/* lua_rawsetp */ -/* luaL_setfuncs */ -/* lua_absindex */ - -#ifndef lua_objlen -#define lua_objlen lua_rawlen -#endif - -int luaL_typerror (lua_State *L, int narg, const char *tname); - -#ifndef luaL_register -void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l); -#endif - -#ifndef lua_equal -#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) -#endif - -#else /* Lua 5.1 */ - -/* functions from lua 5.2 */ - -# define lua_absindex(L, i) (((i)>0)?(i):((i)<=LUA_REGISTRYINDEX?(i):(lua_gettop(L)+(i)+1))) -# define lua_rawlen lua_objlen - -void lua_rawgetp (lua_State *L, int index, const void *p); -void lua_rawsetp (lua_State *L, int index, const void *p); -void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); - -#endif - -int lutil_newmetatablep (lua_State *L, const void *p); -void lutil_getmetatablep (lua_State *L, const void *p); -void lutil_setmetatablep (lua_State *L, const void *p); - -#define lutil_newudatap(L, TTYPE, TNAME) (TTYPE *)lutil_newudatap_impl(L, sizeof(TTYPE), TNAME) -int lutil_isudatap (lua_State *L, int ud, const void *p); -void *lutil_checkudatap (lua_State *L, int ud, const void *p); -int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup); - -void *lutil_newudatap_impl (lua_State *L, size_t size, const void *p); - -void lutil_pushuint(lua_State *L, unsigned int v); - -void lutil_pushint64(lua_State *L, int64_t v); - -int64_t lutil_checkint64(lua_State *L, int idx); - -int64_t lutil_optint64(lua_State *L, int idx, int64_t v); - -void lutil_pushnvalues(lua_State *L, int n); - -int lutil_is_null(lua_State *L, int i); - -void lutil_push_null(lua_State *L); - -#endif diff --git a/watchdog/third_party/lua-curl/src/lceasy.c b/watchdog/third_party/lua-curl/src/lceasy.c deleted file mode 100644 index ad48022..0000000 --- a/watchdog/third_party/lua-curl/src/lceasy.c +++ /dev/null @@ -1,2469 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lceasy.h" -#include "lcerror.h" -#include "lcutils.h" -#include "lchttppost.h" -#include "lcshare.h" -#include "lcmulti.h" -#include "lcmime.h" -#include "lcurlapi.h" -#include - -static const char *LCURL_ERROR_TAG = "LCURL_ERROR_TAG"; - -#define LCURL_EASY_NAME LCURL_PREFIX" Easy" -static const char *LCURL_EASY = LCURL_EASY_NAME; - -#if LCURL_CURL_VER_GE(7,21,5) -# define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_OPTION -#else -# define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_TELNET_OPTION -#endif - -/* Before call curl_XXX function which can call any callback - * need set Current Lua thread pointer in easy/multi contexts. - * But it also possible that we already in callback call. - * E.g. `curl_easy_pause` function may be called from write callback. - * and it even may be called in different thread. - * ```Lua - * multi:add_handle(easy) - * easy:setopt_writefunction(function(...) - * coroutine.wrap(function() multi:add_handle(easy2) end)() - * end) - * ``` - * So we have to restore previews Lua state in callback contexts. - * But if previews Lua state is NULL then we can just do not set it back. - * But set it to NULL make easier to debug code. - */ -void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi){ - if(p->multi && assign_multi){ - lcurl__multi_assign_lua(L, p->multi, value, 1); - } - else{ - p->L = value; - if(p->post){ - p->post->L = value; - } -#if LCURL_CURL_VER_GE(7,56,0) - if(p->mime){ - lcurl_mime_set_lua(L, p->mime, value); - } -#endif - } -} - -//{ - -int lcurl_easy_create(lua_State *L, int error_mode){ - lcurl_easy_t *p; - int i; - - lua_settop(L, 1); /* options */ - - p = lutil_newudatap(L, lcurl_easy_t, LCURL_EASY); - - p->curl = curl_easy_init(); - - p->err_mode = error_mode; - if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); - - p->magic = LCURL_EASY_MAGIC; - p->L = NULL; - p->post = NULL; - p->multi = NULL; -#if LCURL_CURL_VER_GE(7,56,0) - p->mime = NULL; -#endif - p->storage = lcurl_storage_init(L); - p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; - p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; - p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; - p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; - p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; - p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; - p->match.cb_ref = p->match.ud_ref = LUA_NOREF; - p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; - p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; -#if LCURL_CURL_VER_GE(7,19,6) - p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; -#endif -#if LCURL_CURL_VER_GE(7,64,0) - p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; - p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; -#endif - p->rbuffer.ref = LUA_NOREF; - for(i = 0; i < LCURL_LIST_COUNT; ++i){ - p->lists[i] = LUA_NOREF; - } - - if(lua_type(L, 1) == LUA_TTABLE){ - int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); - if(ret) return ret; - assert(lua_gettop(L) == 2); - } - - return 1; -} - -lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i){ - lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, i, LCURL_EASY); - luaL_argcheck (L, p != NULL, 1, LCURL_EASY_NAME" object expected"); - return p; -} - -static int lcurl_easy_to_s(lua_State *L){ - lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, 1, LCURL_EASY); - lua_pushfstring(L, LCURL_EASY_NAME" (%p)", (void*)p); - return 1; -} - -static int lcurl_easy_cleanup_storage(lua_State *L, lcurl_easy_t *p){ - int i; - - if(p->storage != LUA_NOREF){ - p->storage = lcurl_storage_free(L, p->storage); - } - - p->post = NULL; -#if LCURL_CURL_VER_GE(7,56,0) - p->mime = NULL; -#endif - - luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->match.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->match.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.ud_ref); -#if LCURL_CURL_VER_GE(7,19,6) - luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.ud_ref); -#endif -#if LCURL_CURL_VER_GE(7,64,0) - luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.ud_ref); -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.ud_ref); -#endif - luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); - - p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; - p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; - p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; - p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; - p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; - p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; - p->match.cb_ref = p->match.ud_ref = LUA_NOREF; - p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; - p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; -#if LCURL_CURL_VER_GE(7,19,6) - p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; -#endif -#if LCURL_CURL_VER_GE(7,64,0) - p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; - p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; -#endif - p->rbuffer.ref = LUA_NOREF; - - for(i = 0; i < LCURL_LIST_COUNT; ++i){ - p->lists[i] = LUA_NOREF; - } -} - -static int lcurl_easy_cleanup(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - lua_settop(L, 1); - - if(p->multi){ - LCURL_UNUSED_VAR CURLMcode code = lcurl__multi_remove_handle(L, p->multi, p); - - //! @todo what I can do if I can not remove it??? - } - - if(p->curl){ - lua_State *curL; - - // In my tests when I cleanup some easy handle. - // timerfunction called only for single multi handle. - // Also may be this function may call `close` callback - // for `curl_mimepart` structure. - curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); - curl_easy_cleanup(p->curl); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__easy_assign_lua(L, p, curL, 1); - - p->curl = NULL; - } - - lcurl_easy_cleanup_storage(L, p); - - lua_pushnil(L); - lua_rawset(L, LCURL_USERVALUES); - - return 0; -} - -static int lcurl_easy_perform(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; - lua_State *curL; - int top = 1; - lua_settop(L, top); - - assert(p->rbuffer.ref == LUA_NOREF); - - // store reference to current coroutine to callbacks - // User should not call `perform` if handle assign to multi - curL = p->L; lcurl__easy_assign_lua(L, p, L, 0); - code = curl_easy_perform(p->curl); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__easy_assign_lua(L, p, curL, 0); - - if(p->rbuffer.ref != LUA_NOREF){ - luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); - p->rbuffer.ref = LUA_NOREF; - } - - if(code == CURLE_OK){ - lua_settop(L, 1); - return 1; - } - - if((lua_gettop(L) > top)&&(lua_touserdata(L, top + 1) == LCURL_ERROR_TAG)){ - return lua_error(L); - } - - if(code == CURLE_WRITE_ERROR){ - if(lua_gettop(L) > top){ - return lua_gettop(L) - top; - } - } - - if(code == CURLE_ABORTED_BY_CALLBACK){ - if(lua_gettop(L) > top){ - return lua_gettop(L) - top; - } - } - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); -} - -static int lcurl_easy_escape(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - size_t data_size; const char *data = luaL_checklstring(L, 2, &data_size); - const char *ret = curl_easy_escape(p->curl, data, (int)data_size); - if(!ret){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_OUT_OF_MEMORY); - } - lua_pushstring(L, ret); - curl_free((char*)ret); - return 1; -} - -static int lcurl_easy_unescape(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - size_t data_size; const char *data = luaL_checklstring(L, 2, &data_size); - int ret_size; const char *ret = curl_easy_unescape(p->curl, data, (int)data_size, &ret_size); - if(!ret){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_OUT_OF_MEMORY); - } - lua_pushlstring(L, ret, ret_size); - curl_free((char*)ret); - return 1; -} - -static int lcurl_easy_reset(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - curl_easy_reset(p->curl); - lua_settop(L, 1); - - lcurl_easy_cleanup_storage(L, p); - p->storage = lcurl_storage_init(L); - - return 1; -} - -#if LCURL_CURL_VER_GE(7,56,0) - -static int lcurl_easy_mime(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_mime_create(L, p->err_mode); -} - -#endif - -#if LCURL_CURL_VER_GE(7,62,0) - -static int lcurl_easy_upkeep(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code = curl_easy_upkeep(p->curl); - if(code == CURLE_OK){ - lua_settop(L, 1); - return 1; - } - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); -} - -#endif - -//{ OPTIONS - -//{ set - -static int lcurl_opt_set_long_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - long val; CURLcode code; - - if(lua_isboolean(L, 2)){ - val = lua_toboolean(L, 2); - if( val - && ( - (opt == CURLOPT_SSL_VERIFYHOST) -#if LCURL_CURL_VER_GE(7,52,0) - || (opt == CURLOPT_PROXY_SSL_VERIFYHOST) -#endif - ) - ){ - val = 2; - } - } - else{ - luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); - val = luaL_checklong(L, 2); - } - - code = curl_easy_setopt(p->curl, opt, val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lua_settop(L, 1); - return 1; -} - -#if LCURL_CURL_VER_GE(7,59,0) - -static int lcurl_opt_set_off_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - curl_off_t val; CURLcode code; - - luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number expected"); - val = lutil_checkint64(L, 2); - - code = curl_easy_setopt(p->curl, opt, val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lua_settop(L, 1); - return 1; -} - -#endif - -static int lcurl_opt_set_string_(lua_State *L, int opt, int store){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; const char *value; - - luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); - - value = lua_tostring(L, 2); - code = curl_easy_setopt(p->curl, opt, value); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - if(store){ - if(value) - lcurl_storage_preserve_iv(L, p->storage, opt, 2); - else - lcurl_storage_remove_i(L, p->storage, opt); - } - - lua_settop(L, 1); - return 1; -} - -static int lcurl_opt_set_slist_(lua_State *L, int opt, int list_no){ - lcurl_easy_t *p = lcurl_geteasy(L); - struct curl_slist *list = lcurl_util_to_slist(L, 2); - CURLcode code; - int ref = p->lists[list_no]; - - luaL_argcheck(L, list || lua_istable(L, 2) || lutil_is_null(L, 2), 2, "array expected"); - - if(ref != LUA_NOREF){ - struct curl_slist *tmp = lcurl_storage_remove_slist(L, p->storage, ref); - curl_slist_free_all(tmp); - p->lists[list_no] = LUA_NOREF; - } - - code = curl_easy_setopt(p->curl, opt, list); - - if(code != CURLE_OK){ - curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - if (list) { - p->lists[list_no] = lcurl_storage_preserve_slist(L, p->storage, list); - } - - lua_settop(L, 1); - return 1; -} - -#if LCURL_CURL_VER_GE(7,73,0) - -static int lcurl_opt_set_blob_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; const char *value; size_t len; - struct curl_blob blob; - - luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); - - value = lua_tolstring(L, 2, &len); - - blob.data = (void*)value; - blob.len = len; - blob.flags = CURL_BLOB_COPY; - - code = curl_easy_setopt(p->curl, opt, value); - - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lua_settop(L, 1); - return 1; -} - -#endif - -#define LCURL_STR_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ - return lcurl_opt_set_string_(L, CURLOPT_##N, (S)); \ -} - -#define LCURL_LST_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ - return lcurl_opt_set_slist_(L, CURLOPT_##N, LCURL_##N##_LIST);\ -} - -#define LCURL_LNG_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ - return lcurl_opt_set_long_(L, CURLOPT_##N);\ -} - -#define LCURL_OFF_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ - return lcurl_opt_set_off_(L, CURLOPT_##N);\ -} - -#define LCURL_BLB_OPT(N, S) static int lcurl_easy_set_##N(lua_State *L){\ - return lcurl_opt_set_blob_(L, CURLOPT_##N); \ -} - -#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_OPT(N, S) - -#include "lcopteasy.h" - -#undef OPT_ENTRY - -static int lcurl_easy_set_POSTFIELDS(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - size_t len; const char *val = luaL_checklstring(L, 2, &len); - CURLcode code; - if(lua_isnumber(L, 3)){ - size_t n = (size_t)lua_tonumber(L, 3); - luaL_argcheck(L, len <= n, 3, "data length too big"); - len = n; - } - code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDS, val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lcurl_storage_preserve_iv(L, p->storage, CURLOPT_POSTFIELDS, 2); - code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDSIZE, (long)len); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lua_settop(L, 1); - return 1; -} - -#undef LCURL_STR_OPT -#undef LCURL_LST_OPT -#undef LCURL_LNG_OPT -#undef LCURL_OFF_OPT -#undef LCURL_BLB_OPT - -static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg); - -static int lcurl_easy_set_HTTPPOST(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - lcurl_hpost_t *post = lcurl_gethpost_at(L, 2); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HTTPPOST, post->post); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_preserve_iv(L, p->storage, CURLOPT_HTTPPOST, 2); - - if(post->stream){ - curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, lcurl_hpost_read_callback); - } - - p->post = post; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_set_SHARE(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - lcurl_share_t *sh = lcurl_getshare_at(L, 2); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SHARE, sh->curl); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_preserve_iv(L, p->storage, CURLOPT_SHARE, 2); - - lua_settop(L, 1); - return 1; -} - -#if LCURL_CURL_VER_GE(7,46,0) - -static int lcurl_easy_set_STREAM_DEPENDS_impl(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - lcurl_easy_t *e = lcurl_geteasy_at(L, 2); - CURLcode code = curl_easy_setopt(p->curl, opt, e->curl); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_preserve_iv(L, p->storage, opt, 2); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_set_STREAM_DEPENDS(lua_State *L){ - return lcurl_easy_set_STREAM_DEPENDS_impl(L, CURLOPT_STREAM_DEPENDS); -} - -static int lcurl_easy_set_STREAM_DEPENDS_E(lua_State *L){ - return lcurl_easy_set_STREAM_DEPENDS_impl(L, CURLOPT_STREAM_DEPENDS_E); -} - -#endif - -#if LCURL_CURL_VER_GE(7,56,0) - -static int lcurl_easy_set_MIMEPOST(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - lcurl_mime_t *mime = lcurl_getmime_at(L, 2); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, mime->mime); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_preserve_iv(L, p->storage, CURLOPT_MIMEPOST, 2); - - p->mime = mime; - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,63,0) - -static int lcurl_easy_set_CURLU(lua_State *L) { - lcurl_easy_t *p = lcurl_geteasy(L); - lcurl_url_t *url = lcurl_geturl_at(L, 2); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CURLU, url->url); - if (code != CURLE_OK) { - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_preserve_iv(L, p->storage, CURLOPT_CURLU, 2); - - lua_settop(L, 1); - return 1; -} - -#endif - -//} - -//{ unset - -static int lcurl_opt_unset_long_(lua_State *L, int opt, long val){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; - - code = curl_easy_setopt(p->curl, opt, val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lua_settop(L, 1); - return 1; -} - -#if LCURL_CURL_VER_GE(7,59,0) - -static int lcurl_opt_unset_off_(lua_State *L, int opt, curl_off_t val){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; - - code = curl_easy_setopt(p->curl, opt, val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lua_settop(L, 1); - return 1; -} - -#endif - -static int lcurl_opt_unset_string_(lua_State *L, int opt, const char *val){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; - - code = curl_easy_setopt(p->curl, opt, val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_remove_i(L, p->storage, opt); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_opt_unset_slist_(lua_State *L, int opt, int list_no){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; - int ref = p->lists[list_no]; - - code = curl_easy_setopt(p->curl, opt, NULL); - - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - if(ref != LUA_NOREF){ - struct curl_slist *list = lcurl_storage_remove_slist(L, p->storage, ref); - curl_slist_free_all(list); - p->lists[list_no] = LUA_NOREF; - } - - lua_settop(L, 1); - return 1; -} - -#if LCURL_CURL_VER_GE(7,73,0) - -static int lcurl_opt_unset_blob_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code; - - code = curl_easy_setopt(p->curl, opt, 0); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lua_settop(L, 1); - return 1; -} - -#endif - -#define LCURL_STR_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ - return lcurl_opt_unset_string_(L, CURLOPT_##N, (D)); \ -} - -#define LCURL_LST_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ - return lcurl_opt_unset_slist_(L, CURLOPT_##N, LCURL_##N##_LIST);\ -} - -#define LCURL_LNG_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ - return lcurl_opt_unset_long_(L, CURLOPT_##N, (D));\ -} - -#define LCURL_OFF_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ - return lcurl_opt_unset_off_(L, CURLOPT_##N, (D));\ -} - -#define LCURL_BLB_OPT(N, S, D) static int lcurl_easy_unset_##N(lua_State *L){\ - return lcurl_opt_unset_blob_(L, CURLOPT_##N);\ -} - -#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_OPT(N, S, D) - -#include "lcopteasy.h" - -#undef OPT_ENTRY - -#undef LCURL_STR_OPT -#undef LCURL_LST_OPT -#undef LCURL_LNG_OPT -#undef LCURL_OFF_OPT -#undef LCURL_BLB_OPT - -static int lcurl_easy_unset_HTTPPOST(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HTTPPOST, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_get_i(L, p->storage, CURLOPT_HTTPPOST); - if(!lua_isnil(L, -1)){ - lcurl_hpost_t *form = lcurl_gethpost_at(L, -1); - if(form->stream){ - /* with stream we do not set CURLOPT_READDATA but - we also unset it to be sure that there no way to - call default curl reader with our READDATA - */ - curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, NULL); - curl_easy_setopt(p->curl, CURLOPT_READDATA, NULL); - } - lcurl_storage_remove_i(L, p->storage, CURLOPT_HTTPPOST); - } - - p->post = NULL; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_SHARE(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SHARE, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_remove_i(L, p->storage, CURLOPT_SHARE); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_WRITEFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_WRITEFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_WRITEDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->wr.ud_ref); - p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_READFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_READFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_READDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); - p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_HEADERFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HEADERFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_HEADERDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hd.ud_ref); - p->hd.cb_ref = p->hd.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_PROGRESSFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_PROGRESSFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_PROGRESSDATA, NULL); - -#if LCURL_CURL_VER_GE(7,32,0) - curl_easy_setopt(p->curl, CURLOPT_XFERINFOFUNCTION, NULL); - curl_easy_setopt(p->curl, CURLOPT_XFERINFODATA, NULL); -#endif - - luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->pr.ud_ref); - p->pr.cb_ref = p->pr.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_POSTFIELDS(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_POSTFIELDS, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - curl_easy_setopt(p->curl, CURLOPT_POSTFIELDSIZE, -1); - lcurl_storage_remove_i(L, p->storage, CURLOPT_POSTFIELDS); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_SEEKFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SEEKFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_SEEKDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->seek.ud_ref); - p->seek.cb_ref = p->seek.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_DEBUGFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_DEBUGFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_DEBUGDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->debug.ud_ref); - p->debug.cb_ref = p->debug.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -#if LCURL_CURL_VER_GE(7,19,6) - -static int lcurl_easy_unset_SSH_KEYFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_SSH_KEYFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_SSH_KEYDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->ssh_key.ud_ref); - p->ssh_key.cb_ref = p->ssh_key.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,21,0) - -static int lcurl_easy_unset_FNMATCH_FUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_FNMATCH_FUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_FNMATCH_DATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->match.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->match.ud_ref); - p->match.cb_ref = p->match.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_CHUNK_BGN_FUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CHUNK_BGN_FUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - if(p->chunk_end.cb_ref == LUA_NOREF){ - // if other callback not set - curl_easy_setopt(p->curl, CURLOPT_CHUNK_DATA, NULL); - } - - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_bgn.ud_ref); - p->chunk_bgn.cb_ref = p->chunk_bgn.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_CHUNK_END_FUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CHUNK_END_FUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - if(p->chunk_bgn.cb_ref == LUA_NOREF){ - // if other callback not set - curl_easy_setopt(p->curl, CURLOPT_CHUNK_DATA, NULL); - } - - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->chunk_end.ud_ref); - p->chunk_end.cb_ref = p->chunk_end.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,46,0) - -static int lcurl_easy_unset_STREAM_DEPENDS(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_STREAM_DEPENDS, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_remove_i(L, p->storage, CURLOPT_STREAM_DEPENDS); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_STREAM_DEPENDS_E(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_STREAM_DEPENDS_E, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_remove_i(L, p->storage, CURLOPT_STREAM_DEPENDS_E); - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,56,0) - -static int lcurl_easy_unset_MIMEPOST(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_remove_i(L, p->storage, CURLOPT_MIMEPOST); - - p->mime = NULL; - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,63,0) - -static int lcurl_easy_unset_CURLU(lua_State *L) { - lcurl_easy_t *p = lcurl_geteasy(L); - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_CURLU, NULL); - if (code != CURLE_OK) { - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_storage_remove_i(L, p->storage, CURLOPT_CURLU); - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,64,0) - -static int lcurl_easy_unset_TRAILERFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_TRAILERFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_TRAILERDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->trailer.ud_ref); - p->trailer.cb_ref = p->trailer.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -#endif - -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - -static int lcurl_easy_unset_HSTSREADFUNCTION (lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HSTSREADFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_HSTSREADDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstsread.ud_ref); - p->hstsread.cb_ref = p->hstsread.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_unset_HSTSWRITEFUNCTION (lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - - CURLcode code = curl_easy_setopt(p->curl, CURLOPT_HSTSWRITEFUNCTION, NULL); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, CURLOPT_HSTSWRITEDATA, NULL); - - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->hstswrite.ud_ref); - p->hstswrite.cb_ref = p->hstswrite.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - return 1; -} - -#endif - -//} - -//} - -//{ info - -static int lcurl_info_get_long_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - long val; CURLcode code; - - code = curl_easy_getinfo(p->curl, opt, &val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - -#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ - if(sizeof(lua_Integer) >= sizeof(val)) - lua_pushinteger(L, (lua_Integer)val); - else -#endif - lua_pushnumber(L, val); - - return 1; -} - -static int lcurl_info_get_double_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - double val; CURLcode code; - - code = curl_easy_getinfo(p->curl, opt, &val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lua_pushnumber(L, val); - return 1; -} - -#if LCURL_CURL_VER_GE(7,55,0) - -static int lcurl_info_get_offset_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - curl_off_t val; CURLcode code; - - code = curl_easy_getinfo(p->curl, opt, &val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lutil_pushint64(L, val); - return 1; -} - -#endif - -static int lcurl_info_get_string_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - char *val; CURLcode code; - - code = curl_easy_getinfo(p->curl, opt, &val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lua_pushstring(L, val); - return 1; -} - -static int lcurl_info_get_slist_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - struct curl_slist *val; CURLcode code; - - code = curl_easy_getinfo(p->curl, opt, &val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lcurl_util_slist_to_table(L, val); - curl_slist_free_all(val); - - return 1; -} - -static int lcurl_info_get_certinfo_(lua_State *L, int opt){ - lcurl_easy_t *p = lcurl_geteasy(L); - int decode = lua_toboolean(L, 2); - struct curl_certinfo * val; CURLcode code; - - code = curl_easy_getinfo(p->curl, opt, &val); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - - lua_newtable(L); - { int i = 0; for(;inum_of_certs; ++i){ - struct curl_slist *slist = val->certinfo[i]; - if (decode) { - lua_newtable(L); - for(;slist; slist = slist->next){ - const char *ptr = strchr(slist->data, ':'); - if(ptr){ - lua_pushlstring(L, slist->data, ptr - slist->data); - lua_pushstring(L, ptr + 1); - lua_rawset(L, -3); - } - } - } - else{ - lcurl_util_slist_to_table(L, slist); - } - lua_rawseti(L, -2, i + 1); - }} - - return 1; -} - -#define LCURL_STR_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ - return lcurl_info_get_string_(L, CURLINFO_##N); \ -} - -#define LCURL_LST_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ - return lcurl_info_get_slist_(L, CURLINFO_##N);\ -} - -#define LCURL_LNG_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ - return lcurl_info_get_long_(L, CURLINFO_##N);\ -} - -#define LCURL_DBL_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ - return lcurl_info_get_double_(L, CURLINFO_##N);\ -} - -#define LCURL_OFF_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ - return lcurl_info_get_offset_(L, CURLINFO_##N);\ -} - -#define LCURL_CERTINFO_INFO(N, S) static int lcurl_easy_get_##N(lua_State *L){\ - return lcurl_info_get_certinfo_(L, CURLINFO_##N);\ -} - -#define OPT_ENTRY(L, N, T, S) LCURL_##T##_INFO(N, S) - -#include "lcinfoeasy.h" - -#undef OPT_ENTRY - -#undef LCURL_STR_INFO -#undef LCURL_LST_INFO -#undef LCURL_LNG_INFO -#undef LCURL_DBL_INFO - -//} - -//{ CallBack - -static int lcurl_easy_set_callback(lua_State *L, - lcurl_easy_t *p, lcurl_callback_t *c, - int OPT_CB, int OPT_UD, - const char *method, void *func -) -{ - CURLcode code; - lcurl_set_callback(L, c, 2, method); - - code = curl_easy_setopt(p->curl, OPT_CB, (c->cb_ref == LUA_NOREF)?0:func); - if((code != CURLE_OK)&&(c->cb_ref != LUA_NOREF)){ - luaL_unref(L, LCURL_LUA_REGISTRY, c->cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, c->ud_ref); - c->cb_ref = c->ud_ref = LUA_NOREF; - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - curl_easy_setopt(p->curl, OPT_UD, (c->cb_ref == LUA_NOREF)?0:p); - - return 1; -} - -static size_t lcurl_write_callback_(lua_State*L, - lcurl_easy_t *p, lcurl_callback_t *c, - char *ptr, size_t size, size_t nmemb -){ - size_t ret = size * nmemb; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, c); - - lua_pushlstring(L, ptr, ret); - if(lua_pcall(L, n, LUA_MULTRET, 0)){ - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top+1); - return 0; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1)){ - if(lua_gettop(L) == (top+1)) lua_settop(L, top); - return 0; - } - if(lua_isnumber(L, top + 1)){ - ret = (size_t)lua_tonumber(L, top + 1); - } - else{ - if(!lua_toboolean(L, top + 1)) ret = 0; - } - } - - lua_settop(L, top); - return ret; -} - -//{ Writer - -static size_t lcurl_write_callback(char *ptr, size_t size, size_t nmemb, void *arg){ - lcurl_easy_t *p = arg; - assert(NULL != p->L); - return lcurl_write_callback_(p->L, p, &p->wr, ptr, size, nmemb); -} - -static int lcurl_easy_set_WRITEFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->wr, - CURLOPT_WRITEFUNCTION, CURLOPT_WRITEDATA, - "write", lcurl_write_callback - ); -} - -//} - -//{ Reader - -size_t lcurl_read_callback(lua_State *L, - lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, - char *buffer, size_t size, size_t nitems -){ - const char *data; size_t data_size; - - size_t ret = size * nitems; - int n, top = lua_gettop(L); - - if(rbuffer->ref != LUA_NOREF){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, rbuffer->ref); - data = luaL_checklstring(L, -1, &data_size); - lua_pop(L, 1); - - data = data + rbuffer->off; - data_size -= rbuffer->off; - - if(data_size > ret){ - data_size = ret; - memcpy(buffer, data, data_size); - rbuffer->off += data_size; - } - else{ - memcpy(buffer, data, data_size); - luaL_unref(L, LCURL_LUA_REGISTRY, rbuffer->ref); - rbuffer->ref = LUA_NOREF; - } - - lua_settop(L, top); - return data_size; - } - - // buffer is clean - assert(rbuffer->ref == LUA_NOREF); - - n = lcurl_util_push_cb(L, rd); - lua_pushinteger(L, ret); - if(lua_pcall(L, n, LUA_MULTRET, 0)){ - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top+1); - return CURL_READFUNC_ABORT; - } - - if(lua_gettop(L) == top){ - return 0; - } - - assert(lua_gettop(L) >= top); - - if(lua_type(L, top + 1) != LUA_TSTRING){ - if(lua_isnil(L, top + 1)){ - if(lua_gettop(L) == (top+1)){// only nil -> EOF - lua_settop(L, top); - return 0; - } - } - else{ - if(lua_type(L, top + 1) == LUA_TNUMBER){ - size_t ret = lua_tointeger(L, top + 1); - if(ret == (size_t)CURL_READFUNC_PAUSE){ - lua_settop(L, top); - return CURL_READFUNC_PAUSE; - } - } - lua_settop(L, top); - } - return CURL_READFUNC_ABORT; - } - - data = lua_tolstring(L, top + 1, &data_size); - assert(data); - if(data_size > ret){ - data_size = ret; - rbuffer->ref = luaL_ref(L, LCURL_LUA_REGISTRY); - rbuffer->off = data_size; - } - memcpy(buffer, data, data_size); - - lua_settop(L, top); - return data_size; -} - -static size_t lcurl_easy_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ - lcurl_easy_t *p = arg; - if(p->magic == LCURL_HPOST_STREAM_MAGIC){ - return lcurl_hpost_read_callback(buffer, size, nitems, arg); - } - assert(NULL != p->L); - return lcurl_read_callback(p->L, &p->rd, &p->rbuffer, buffer, size, nitems); -} - -static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ - lcurl_hpost_stream_t *p = arg; - assert(NULL != p->L); - return lcurl_read_callback(*p->L, &p->rd, &p->rbuffer, buffer, size, nitems); -} - -static int lcurl_easy_set_READFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->rd, - CURLOPT_READFUNCTION, CURLOPT_READDATA, - "read", lcurl_easy_read_callback - ); -} - -//} - -//{ Header - -static size_t lcurl_header_callback(char *ptr, size_t size, size_t nmemb, void *arg){ - lcurl_easy_t *p = arg; - assert(NULL != p->L); - return lcurl_write_callback_(p->L, p, &p->hd, ptr, size, nmemb); -} - -static int lcurl_easy_set_HEADERFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->hd, - CURLOPT_HEADERFUNCTION, CURLOPT_HEADERDATA, - "header", lcurl_header_callback - ); -} - -//} - -//{ Progress - -static int lcurl_xferinfo_callback(void *arg, curl_off_t dltotal, curl_off_t dlnow, - curl_off_t ultotal, curl_off_t ulnow) -{ - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int n, top, ret = 0; - - assert(NULL != p->L); - - top = lua_gettop(L); - n = lcurl_util_push_cb(L, &p->pr); - - lua_pushnumber( L, (lua_Number)dltotal ); - lua_pushnumber( L, (lua_Number)dlnow ); - lua_pushnumber( L, (lua_Number)ultotal ); - lua_pushnumber( L, (lua_Number)ulnow ); - - if(lua_pcall(L, n+3, LUA_MULTRET, 0)){ - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top+1); - return 1; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1)){ - if(lua_gettop(L) == (top+1)) lua_settop(L, top); - return 1; - } - if(lua_isboolean(L, top + 1)) - ret = lua_toboolean(L, top + 1)?0:1; - else{ - ret = lua_tonumber(L, top + 1); - #if LCURL_CURL_VER_GE(7,68,0) - if(ret != (size_t)CURL_PROGRESSFUNC_CONTINUE) - #endif - if(ret == 0) ret = 1; else ret = 0; - } - } - - lua_settop(L, top); - return ret; -} - -static int lcurl_progress_callback(void *arg, double dltotal, double dlnow, - double ultotal, double ulnow) -{ - return lcurl_xferinfo_callback(arg, - (curl_off_t)dltotal, - (curl_off_t)dlnow, - (curl_off_t)ultotal, - (curl_off_t)ulnow - ); -} - -static int lcurl_easy_set_PROGRESSFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - int n = lcurl_easy_set_callback(L, p, &p->pr, - CURLOPT_PROGRESSFUNCTION, CURLOPT_PROGRESSDATA, - "progress", lcurl_progress_callback - ); - -#if LCURL_CURL_VER_GE(7,32,0) - if(p->pr.cb_ref != LUA_NOREF){ - curl_easy_setopt(p->curl, CURLOPT_XFERINFOFUNCTION, lcurl_xferinfo_callback); - curl_easy_setopt(p->curl, CURLOPT_XFERINFODATA, p); - } -#endif - - return n; -} - -//} - -//{ Seek - -static int lcurl_seek_callback(void *arg, curl_off_t offset, int origin){ - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int ret = CURL_SEEKFUNC_OK; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->seek); - - assert(NULL != p->L); - - if (SEEK_SET == origin) lua_pushliteral(L, "set"); - else if (SEEK_CUR == origin) lua_pushliteral(L, "cur"); - else if (SEEK_END == origin) lua_pushliteral(L, "end"); - else lua_pushinteger(L, origin); - lutil_pushint64(L, offset); - - if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_SEEKFUNC_FAIL; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ - lua_settop(L, top + 2); - lua_remove(L, top + 1); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_SEEKFUNC_FAIL; - } - ret = lua_toboolean(L, top + 1) ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; - } - - lua_settop(L, top); - return ret; -} - -static int lcurl_easy_set_SEEKFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->seek, - CURLOPT_SEEKFUNCTION, CURLOPT_SEEKDATA, - "seek", lcurl_seek_callback - ); -} - -//} - -//{ Debug - -static int lcurl_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *arg){ - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->debug); - - assert(NULL != p->L); - assert(handle == p->curl); - - lua_pushinteger(L, type); - lua_pushlstring(L, data, size); - - // just ignore all errors from Lua callback - lua_pcall(L, n + 1, LUA_MULTRET, 0); - lua_settop(L, top); - - return 0; -} - -static int lcurl_easy_set_DEBUGFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->debug, - CURLOPT_DEBUGFUNCTION, CURLOPT_DEBUGDATA, - "debug", lcurl_debug_callback - ); -} - -//} - -//{ Match - -#if LCURL_CURL_VER_GE(7,21,0) - -static int lcurl_match_callback(void *arg, const char *pattern, const char *string) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int ret = CURL_FNMATCHFUNC_NOMATCH; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->match); - - assert(NULL != p->L); - - lua_pushstring(L, pattern); - lua_pushstring(L, string); - - if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_FNMATCHFUNC_FAIL; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ - lua_settop(L, top + 2); - lua_remove(L, top + 1); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_FNMATCHFUNC_FAIL; - } - ret = lua_toboolean(L, top + 1) ? CURL_FNMATCHFUNC_MATCH : CURL_FNMATCHFUNC_NOMATCH; - } - - lua_settop(L, top); - return ret; -} - -static int lcurl_easy_set_FNMATCH_FUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->match, - CURLOPT_FNMATCH_FUNCTION, CURLOPT_FNMATCH_DATA, - "match", lcurl_match_callback - ); -} - -#endif - -//} - -//{ Chunk begin/end - -#if LCURL_CURL_VER_GE(7,21,0) - -static int lcurl_chunk_bgn_callback(struct curl_fileinfo *info, void *arg, int remains) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int ret = CURL_CHUNK_BGN_FUNC_OK; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->chunk_bgn); - - assert(NULL != p->L); - - lua_newtable(L); - lua_pushstring (L, info->filename ); lua_setfield(L, -2, "filename" ); - lua_pushinteger(L, info->filetype ); lua_setfield(L, -2, "filetype" ); - lutil_pushint64(L, info->time ); lua_setfield(L, -2, "time" ); - lutil_pushint64(L, info->perm ); lua_setfield(L, -2, "perm" ); - lua_pushinteger(L, info->uid ); lua_setfield(L, -2, "uid" ); - lua_pushinteger(L, info->gid ); lua_setfield(L, -2, "gid" ); - lutil_pushint64(L, info->size ); lua_setfield(L, -2, "size" ); - lutil_pushint64(L, info->hardlinks ); lua_setfield(L, -2, "hardlinks" ); - lutil_pushint64(L, info->flags ); lua_setfield(L, -2, "flags" ); - - lua_newtable(L); - if(info->strings.time) { lua_pushstring (L, info->strings.time ); lua_setfield(L, -2, "time" ); } - if(info->strings.perm) { lua_pushstring (L, info->strings.perm ); lua_setfield(L, -2, "perm" ); } - if(info->strings.user) { lua_pushstring (L, info->strings.user ); lua_setfield(L, -2, "user" ); } - if(info->strings.group) { lua_pushstring (L, info->strings.group ); lua_setfield(L, -2, "group" ); } - if(info->strings.target){ lua_pushstring (L, info->strings.target); lua_setfield(L, -2, "target"); } - lua_setfield(L, -2, "strings"); - - lua_pushinteger(L, remains); - - if (lua_pcall(L, n+1, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_CHUNK_BGN_FUNC_FAIL; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ - lua_settop(L, top + 2); - lua_remove(L, top + 1); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_CHUNK_BGN_FUNC_FAIL; - } - ret = lua_toboolean(L, top + 1) ? CURL_CHUNK_BGN_FUNC_OK : CURL_CHUNK_BGN_FUNC_SKIP; - } - - lua_settop(L, top); - return ret; -} - -static int lcurl_chunk_end_callback(void *arg) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int ret = CURL_CHUNK_END_FUNC_OK; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->chunk_end); - - assert(NULL != p->L); - - if (lua_pcall(L, n-1, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_CHUNK_END_FUNC_FAIL; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1) && (!lua_isnoneornil(L, top + 2))){ - lua_settop(L, top + 2); - lua_remove(L, top + 1); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_CHUNK_END_FUNC_FAIL; - } - ret = lua_toboolean(L, top + 1) ? CURL_CHUNK_END_FUNC_OK : CURL_CHUNK_END_FUNC_FAIL; - } - - lua_settop(L, top); - return ret; -} - -static int lcurl_easy_set_CHUNK_BGN_FUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->chunk_bgn, - CURLOPT_CHUNK_BGN_FUNCTION, CURLOPT_CHUNK_DATA, - "chunk_bgn", lcurl_chunk_bgn_callback - ); -} - -static int lcurl_easy_set_CHUNK_END_FUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->chunk_end, - CURLOPT_CHUNK_END_FUNCTION, CURLOPT_CHUNK_DATA, - "chunk_end", lcurl_chunk_end_callback - ); -} - -#endif - -//} - -//{ Trailer - -#if LCURL_CURL_VER_GE(7,64,0) - -static int lcurl_trailer_callback(struct curl_slist **list, void *arg) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->trailer); - - if (lua_pcall(L, n - 1, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURL_TRAILERFUNC_ABORT; - } - - n = lua_gettop(L); - - if (n == top) { - return CURL_TRAILERFUNC_OK; - } - - /* libcurl will free the list */ - *list = lcurl_util_to_slist(L, top + 1); - if (*list) { - lua_settop(L, top); - return CURL_TRAILERFUNC_OK; - } - - // empty array or NULL - if (lua_istable(L, top + 1) || lutil_is_null(L, top + 1)) { - lua_settop(L, top); - return CURL_TRAILERFUNC_OK; - } - - // true - if((lua_type(L, top + 1) == LUA_TBOOLEAN) && (lua_toboolean(L, top + 1))){ - lua_settop(L, top); - return CURL_TRAILERFUNC_OK; - } - - // single nil - if((n == (top + 1)) && lua_isnil(L, top + 1)){ - lua_settop(L, top); - return CURL_TRAILERFUNC_OK; - } - - lua_settop(L, top); - return CURL_TRAILERFUNC_ABORT; -} - -static int lcurl_easy_set_TRAILERFUNCTION (lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->trailer, - CURLOPT_TRAILERFUNCTION, CURLOPT_TRAILERDATA, - "trailer", lcurl_trailer_callback - ); -} - -#endif - -//} - -//{ HSTS Reader - -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - -#define LCURL_HSTS_EXPIRE_LEN 18 - -static int lcurl_hstsread_callback(CURL *easy, struct curl_hstsentry *sts, void *arg) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->hstsread); - const char *name; size_t namelen; - const char *expire; size_t expirelen; - int type; - - assert(NULL != p->L); - - lua_pushinteger(L, sts->namelen); - if (lua_pcall(L, n, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURLSTS_FAIL; - } - - if (lua_gettop(L) == top) { - return CURLSTS_DONE; - } - - assert(lua_gettop(L) >= top); - - type = lua_type(L, top + 1); - if (type == LUA_TNIL) { - lua_settop(L, top); - return CURLSTS_DONE; - } - - if (type != LUA_TSTRING) { - lua_settop(L, top); - return CURLSTS_FAIL; - } - - name = lua_tolstring(L, top + 1, &namelen); - - if(namelen > sts->namelen) { - lua_settop(L, top); - return CURLSTS_FAIL; - } - - memcpy(sts->name, name, namelen + 1); - - type = lua_type(L, top + 2); - if (type == LUA_TNONE) { - lua_settop(L, top); - return CURLSTS_OK; - } - - if ((type != LUA_TBOOLEAN) && (type != LUA_TNIL)) { - lua_settop(L, top); - return CURLSTS_FAIL; - } - - if (type == LUA_TBOOLEAN) { - sts->includeSubDomains = lua_toboolean(L, top + 2) ? 0 : 1; - } - else if (type != LUA_TNIL) { - lua_settop(L, top); - return CURLSTS_FAIL; - } - - type = lua_type(L, top + 3); - if ((type == LUA_TNONE) || (type == LUA_TNIL)) { - lua_settop(L, top); - return CURLSTS_OK; - } - - if(type != LUA_TSTRING) { - lua_settop(L, top); - return CURLSTS_FAIL; - } - - expire = lua_tolstring(L, top + 3, &expirelen); - - if (expirelen != LCURL_HSTS_EXPIRE_LEN - 1) { - lua_settop(L, top); - return CURLSTS_FAIL; - } - - memcpy(sts->expire, expire, expirelen + 1); - - lua_settop(L, top); - return CURLSTS_OK; -} - -static int lcurl_easy_set_HSTSREADFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->hstsread, - CURLOPT_HSTSREADFUNCTION, CURLOPT_HSTSREADDATA, - "hstsread", lcurl_hstsread_callback - ); -} - -#endif - -//} - -//{ HSTS Writer - -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - -static int lcurl_hstswrite_callback(CURL *easy, struct curl_hstsentry *sts, struct curl_index *count, void *arg) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->hstswrite); - int type; - - assert(NULL != p->L); - - lua_pushstring(L, sts->name); - lua_pushboolean(L, sts->includeSubDomains ? 1 : 0); - if (sts->expire[0]) { - lua_pushstring(L, sts->expire); - } else { - lua_pushnil(L); - } - lua_pushinteger(L, count->index); - lua_pushinteger(L, count->total); - - if (lua_pcall(L, n + 4, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURLSTS_FAIL; - } - - if (lua_gettop(L) == top) { - return CURLSTS_OK; - } - - assert(lua_gettop(L) >= top); - - type = lua_type(L, top + 1); - if (type == LUA_TNIL) { - type = lua_type(L, top + 2); - lua_settop(L, top); - if(type == LUA_TNONE){ - return CURLSTS_OK; - } - return CURLSTS_FAIL; - } - - if (type == LUA_TNUMBER) { - int ret = lua_tointeger(L, top + 1); - lua_settop(L, top); - return ret; - } - - if (type == LUA_TBOOLEAN) { - int ret = lua_toboolean(L, top + 1); - lua_settop(L, top); - return ret ? CURLSTS_OK : CURLSTS_DONE; - } - - lua_settop(L, top); - return CURLSTS_FAIL; -} - -static int lcurl_easy_set_HSTSWRITEFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->hstswrite, - CURLOPT_HSTSWRITEFUNCTION, CURLOPT_HSTSWRITEDATA, - "hstswrite", lcurl_hstswrite_callback - ); -} - -#endif - -//} - -//{ SSH key - -#if LCURL_CURL_VER_GE(7,19,6) - -static void lcurl_ssh_key_push(lua_State *L, const struct curl_khkey *key){ - if (!key) { - lua_pushnil(L); - return; - } - - lua_newtable(L); - - if(key->len){ - lua_pushliteral(L, "raw"); - lua_pushlstring(L, key->key, key->len); - } else { - lua_pushliteral(L, "base64"); - lua_pushstring(L, key->key); - } - lua_rawset(L, -3); - - lua_pushliteral(L, "type"); - lutil_pushuint(L, key->keytype); - lua_rawset(L, -3); -} - -static int lcurl_ssh_key_callback( - CURL *easy, - const struct curl_khkey *knownkey, - const struct curl_khkey *foundkey, - enum curl_khmatch khmatch, - void *arg -) { - lcurl_easy_t *p = arg; - lua_State *L = p->L; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->ssh_key); - - assert(NULL != p->L); - - lcurl_ssh_key_push(L, knownkey); - lcurl_ssh_key_push(L, foundkey); - lutil_pushuint(L, khmatch); - - if (lua_pcall(L, n + 2, LUA_MULTRET, 0)) { - assert(lua_gettop(L) >= top); - lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - lua_insert(L, top + 1); - return CURLKHSTAT_REJECT; - } - - if (lua_gettop(L) > top) { - int ret = lua_tointeger(L, top + 1); - lua_settop(L, top); - - switch (ret) -#if LCURL_CURL_VER_GE(7,73,0) - case CURLKHSTAT_FINE_REPLACE: -#endif - case CURLKHSTAT_FINE_ADD_TO_FILE: - case CURLKHSTAT_FINE: - case CURLKHSTAT_REJECT: - case CURLKHSTAT_DEFER: - return ret; - } - - return CURLKHSTAT_REJECT; -} - -static int lcurl_easy_set_SSH_KEYFUNCTION(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - return lcurl_easy_set_callback(L, p, &p->ssh_key, - CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA, - "ssh_key", lcurl_ssh_key_callback - ); -} - -#endif - -//} - -static int lcurl_easy_setopt(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - long opt; - - luaL_checkany(L, 2); - if(lua_type(L, 2) == LUA_TTABLE){ - int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); - if(ret) return ret; - lua_settop(L, 1); - return 1; - } - - opt = luaL_checklong(L, 2); - lua_remove(L, 2); - -#define OPT_ENTRY(l, N, T, S, D) case CURLOPT_##N: return lcurl_easy_set_##N(L); - switch(opt){ - #include "lcopteasy.h" - OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) - OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) - OPT_ENTRY(share, SHARE, TTT, 0, 0) - OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) - OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) - OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) - OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) - OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) - OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) -#if LCURL_CURL_VER_GE(7,19,6) - OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,46,0) - OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) - OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,56,0) - OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,63,0) - OPT_ENTRY(curlu, CURLU, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,64,0) - OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) - OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) -#endif - } -#undef OPT_ENTRY - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); -} - -static int lcurl_easy_unsetopt(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - long opt; - - opt = luaL_checklong(L, 2); - lua_remove(L, 2); - -#define OPT_ENTRY(l, N, T, S, D) case CURLOPT_##N: return lcurl_easy_unset_##N(L); - switch(opt){ - #include "lcopteasy.h" - OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) - OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) - OPT_ENTRY(share, SHARE, TTT, 0, 0) - OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) - OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) - OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) - OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) - OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) - OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) -#if LCURL_CURL_VER_GE(7,19,6) - OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,46,0) - OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) - OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,56,0) - OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,63,0) - OPT_ENTRY(curlu, CURLU, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,64,0) - OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) - OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) -#endif - } -#undef OPT_ENTRY - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); -} - -static int lcurl_easy_getinfo(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - long opt = luaL_checklong(L, 2); - lua_remove(L, 2); - -#define OPT_ENTRY(l, N, T, S) case CURLINFO_##N: return lcurl_easy_get_##N(L); - switch(opt){ - #include "lcinfoeasy.h" - } -#undef OPT_ENTRY - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, LCURL_E_UNKNOWN_OPTION); -} - -static int lcurl_easy_pause(lua_State *L){ - lcurl_easy_t *p = lcurl_geteasy(L); - lua_State *curL; - int mask = luaL_checkint(L, 2); - CURLcode code; - - curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); - code = curl_easy_pause(p->curl, mask); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__easy_assign_lua(L, p, curL, 1); - - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); - } - lua_settop(L, 1); - return 1; -} - -static int lcurl_easy_setdata(lua_State *L){ - lua_settop(L, 2); - lua_pushvalue(L, 1); - lua_insert(L, 2); - lua_rawset(L, LCURL_USERVALUES); - return 1; -} - -static int lcurl_easy_getdata(lua_State *L){ - lua_settop(L, 1); - lua_rawget(L, LCURL_USERVALUES); - return 1; -} - -//} - -static const struct luaL_Reg lcurl_easy_methods[] = { - -#define OPT_ENTRY(L, N, T, S, D) { "setopt_"#L, lcurl_easy_set_##N }, - #include "lcopteasy.h" - OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) - OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) - OPT_ENTRY(share, SHARE, TTT, 0, 0) - OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) - OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) - OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) - OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) - OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) - OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) -#if LCURL_CURL_VER_GE(7,19,6) - OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,46,0) - OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) - OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,56,0) - OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,63,0) - OPT_ENTRY(curlu, CURLU, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,64,0) - OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) - OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) -#endif -#undef OPT_ENTRY - -#define OPT_ENTRY(L, N, T, S, D) { "unsetopt_"#L, lcurl_easy_unset_##N }, - #include "lcopteasy.h" - OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) - OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) - OPT_ENTRY(share, SHARE, TTT, 0, 0) - OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) - OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) - OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) - OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) - OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) - OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) -#if LCURL_CURL_VER_GE(7,19,6) - OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,46,0) - OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) - OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,56,0) - OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,63,0) - OPT_ENTRY(curlu, CURLU, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,64,0) - OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) - OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) -#endif -#undef OPT_ENTRY - -#define OPT_ENTRY(L, N, T, S) { "getinfo_"#L, lcurl_easy_get_##N }, - #include "lcinfoeasy.h" -#undef OPT_ENTRY - -#if LCURL_CURL_VER_GE(7,56,0) - { "mime", lcurl_easy_mime }, -#endif - - { "pause", lcurl_easy_pause }, - { "reset", lcurl_easy_reset }, - { "setopt", lcurl_easy_setopt }, - { "getinfo", lcurl_easy_getinfo }, - { "unsetopt", lcurl_easy_unsetopt }, - { "escape", lcurl_easy_escape }, - { "unescape", lcurl_easy_unescape }, - { "perform", lcurl_easy_perform }, -#if LCURL_CURL_VER_GE(7,62,0) - { "upkeep", lcurl_easy_upkeep }, -#endif - { "close", lcurl_easy_cleanup }, - { "__gc", lcurl_easy_cleanup }, - { "__tostring", lcurl_easy_to_s }, - - { "setdata", lcurl_easy_setdata }, - { "getdata", lcurl_easy_getdata }, - - {NULL,NULL} -}; - -static const lcurl_const_t lcurl_easy_opt[] = { - -#define OPT_ENTRY(L, N, T, S, D) { "OPT_"#N, CURLOPT_##N }, -#define FLG_ENTRY(N) { #N, CURL_##N }, -#include "lcopteasy.h" - OPT_ENTRY(postfields, POSTFIELDS, TTT, 0, 0) - OPT_ENTRY(httppost, HTTPPOST, TTT, 0, 0) - OPT_ENTRY(share, SHARE, TTT, 0, 0) - OPT_ENTRY(writefunction, WRITEFUNCTION, TTT, 0, 0) - OPT_ENTRY(readfunction, READFUNCTION, TTT, 0, 0) - OPT_ENTRY(headerfunction, HEADERFUNCTION, TTT, 0, 0) - OPT_ENTRY(progressfunction, PROGRESSFUNCTION, TTT, 0, 0) - OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) - OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) -#if LCURL_CURL_VER_GE(7,19,6) - OPT_ENTRY(ssh_keyfunction, SSH_KEYFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) - OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,46,0) - OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) - OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,56,0) - OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,63,0) - OPT_ENTRY(curlu, CURLU, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,64,0) - OPT_ENTRY(trailerfunction, TRAILERFUNCTION, TTT, 0, 0) -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - OPT_ENTRY(hstsreadfunction, HSTSREADFUNCTION, TTT, 0, 0) - OPT_ENTRY(hstswritefunction, HSTSWRITEFUNCTION,TTT, 0, 0) -#endif -#undef OPT_ENTRY -#undef FLG_ENTRY - -#define OPT_ENTRY(L, N, T, S) { "INFO_"#N, CURLINFO_##N }, -#include "lcinfoeasy.h" -#undef OPT_ENTRY - -#define OPT_ENTRY(N) { #N, CURL##N }, - // Debug message types not easy info - OPT_ENTRY(INFO_TEXT ) - OPT_ENTRY(INFO_HEADER_IN ) - OPT_ENTRY(INFO_HEADER_OUT ) - OPT_ENTRY(INFO_DATA_IN ) - OPT_ENTRY(INFO_DATA_OUT ) - OPT_ENTRY(INFO_SSL_DATA_OUT ) - OPT_ENTRY(INFO_SSL_DATA_IN ) - - // File types for CURL_CHUNK_BGN_FUNCTION -#if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(FILETYPE_DEVICE_BLOCK ) - OPT_ENTRY(FILETYPE_DEVICE_CHAR ) - OPT_ENTRY(FILETYPE_DIRECTORY ) - OPT_ENTRY(FILETYPE_DOOR ) - OPT_ENTRY(FILETYPE_FILE ) - OPT_ENTRY(FILETYPE_NAMEDPIPE ) - OPT_ENTRY(FILETYPE_SOCKET ) - OPT_ENTRY(FILETYPE_SYMLINK ) - OPT_ENTRY(FILETYPE_UNKNOWN ) -#endif - -#undef OPT_ENTRY - - {NULL, 0} -}; - -void lcurl_easy_initlib(lua_State *L, int nup){ - - /* Hack. We ensure that lcurl_easy_t and lcurl_hpost_stream_t - compatiable for readfunction - */ - LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, magic, lcurl_hpost_stream_t, magic); - LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, L, lcurl_hpost_stream_t, L); - LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, rd, lcurl_hpost_stream_t, rd); - LCURL_ASSERT_SAME_OFFSET(lcurl_easy_t, rbuffer, lcurl_hpost_stream_t, rbuffer); - LCURL_ASSERT_SAME_FIELD_SIZE(lcurl_easy_t, rbuffer, lcurl_hpost_stream_t, rbuffer); - - if(!lutil_createmetap(L, LCURL_EASY, lcurl_easy_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - - lcurl_util_set_const(L, lcurl_easy_opt); -} - -//} diff --git a/watchdog/third_party/lua-curl/src/lceasy.h b/watchdog/third_party/lua-curl/src/lceasy.h deleted file mode 100644 index c03617c..0000000 --- a/watchdog/third_party/lua-curl/src/lceasy.h +++ /dev/null @@ -1,127 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCEASY_H_ -#define _LCEASY_H_ - -#include "lcurl.h" -#include "lcutils.h" -#include "lchttppost.h" - -#define LCURL_LST_INDEX(N) LCURL_##N##_LIST, -#define LCURL_STR_INDEX(N) -#define LCURL_LNG_INDEX(N) -#define LCURL_OFF_INDEX(N) -#define LCURL_BLB_INDEX(N) -#define OPT_ENTRY(L, N, T, S, D) LCURL_##T##_INDEX(N) - -enum { - LCURL_LIST_DUMMY = -1, - -#include"lcopteasy.h" - - LCURL_LIST_COUNT, -}; - -#undef LCURL_BLB_INDEX -#undef LCURL_OFF_INDEX -#undef LCURL_LST_INDEX -#undef LCURL_STR_INDEX -#undef LCURL_LNG_INDEX -#undef OPT_ENTRY - -#define LCURL_EASY_MAGIC 0xEA - -#if LCURL_CC_SUPPORT_FORWARD_TYPEDEF - typedef struct lcurl_multi_tag lcurl_multi_t; - #if LCURL_CURL_VER_GE(7,56,0) - typedef struct lcurl_mime_tag lcurl_mime_t; - #endif - #if LCURL_CURL_VER_GE(7,63,0) - typedef struct lcurl_url_tag lcurl_url_t; - #endif -#else - struct lcurl_multi_tag; - #define lcurl_multi_t struct lcurl_multi_tag - #if LCURL_CURL_VER_GE(7,56,0) - struct lcurl_mime_tag; - #define lcurl_mime_t struct lcurl_mime_tag - #endif - #if LCURL_CURL_VER_GE(7,63,0) - struct lcurl_url_tag; - #define lcurl_url_t struct lcurl_url_tag - #endif -#endif - -typedef struct lcurl_easy_tag{ - unsigned char magic; - - lua_State *L; - lcurl_callback_t rd; - lcurl_read_buffer_t rbuffer; - - lcurl_hpost_t *post; - - lcurl_multi_t *multi; - -#if LCURL_CURL_VER_GE(7,56,0) - lcurl_mime_t *mime; -#endif - - CURL *curl; - int storage; - int lists[LCURL_LIST_COUNT]; - int err_mode; - lcurl_callback_t wr; - lcurl_callback_t hd; - lcurl_callback_t pr; - lcurl_callback_t seek; - lcurl_callback_t debug; - lcurl_callback_t match; - lcurl_callback_t chunk_bgn; - lcurl_callback_t chunk_end; -#if LCURL_CURL_VER_GE(7,19,6) - lcurl_callback_t ssh_key; -#endif -#if LCURL_CURL_VER_GE(7,64,0) - lcurl_callback_t trailer; -#endif -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS - lcurl_callback_t hstsread; - lcurl_callback_t hstswrite; -#endif -}lcurl_easy_t; - -int lcurl_easy_create(lua_State *L, int error_mode); - -lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i); - -#define lcurl_geteasy(L) lcurl_geteasy_at((L),1) - -void lcurl_easy_initlib(lua_State *L, int nup); - -void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi); - -size_t lcurl_read_callback(lua_State *L, - lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, - char *buffer, size_t size, size_t nitems -); - -#if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF -#undef lcurl_multi_t -#ifdef lcurl_mime_t -#undef lcurl_mime_t -#endif -#ifdef lcurl_url_t -#undef lcurl_url_t -#endif -#endif - -#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_easy.h b/watchdog/third_party/lua-curl/src/lcerr_easy.h deleted file mode 100644 index 986026a..0000000 --- a/watchdog/third_party/lua-curl/src/lcerr_easy.h +++ /dev/null @@ -1,146 +0,0 @@ -ERR_ENTRY ( OK ) -ERR_ENTRY ( UNSUPPORTED_PROTOCOL ) -ERR_ENTRY ( FAILED_INIT ) -ERR_ENTRY ( URL_MALFORMAT ) -#if LCURL_CURL_VER_GE(7,21,5) -ERR_ENTRY ( NOT_BUILT_IN ) -#endif -ERR_ENTRY ( COULDNT_RESOLVE_PROXY ) -ERR_ENTRY ( COULDNT_RESOLVE_HOST ) -ERR_ENTRY ( COULDNT_CONNECT ) -#if LCURL_CURL_VER_GE(7,51,0) -ERR_ENTRY ( WEIRD_SERVER_REPLY ) -#else -ERR_ENTRY ( FTP_WEIRD_SERVER_REPLY ) -#endif -ERR_ENTRY ( REMOTE_ACCESS_DENIED ) -#if LCURL_CURL_VER_GE(7,31,0) -ERR_ENTRY ( FTP_ACCEPT_FAILED ) -#endif -ERR_ENTRY ( FTP_WEIRD_PASS_REPLY ) -#if LCURL_CURL_VER_GE(7,24,0) -ERR_ENTRY ( FTP_ACCEPT_TIMEOUT ) -#endif -ERR_ENTRY ( FTP_WEIRD_PASV_REPLY ) -ERR_ENTRY ( FTP_WEIRD_227_FORMAT ) -ERR_ENTRY ( FTP_CANT_GET_HOST ) -ERR_ENTRY ( FTP_COULDNT_SET_TYPE ) -ERR_ENTRY ( PARTIAL_FILE ) -ERR_ENTRY ( FTP_COULDNT_RETR_FILE ) -ERR_ENTRY ( OBSOLETE20 ) -ERR_ENTRY ( QUOTE_ERROR ) -ERR_ENTRY ( HTTP_RETURNED_ERROR ) -ERR_ENTRY ( WRITE_ERROR ) -ERR_ENTRY ( OBSOLETE24 ) -ERR_ENTRY ( UPLOAD_FAILED ) -ERR_ENTRY ( READ_ERROR ) -ERR_ENTRY ( OUT_OF_MEMORY ) -ERR_ENTRY ( OPERATION_TIMEDOUT ) -ERR_ENTRY ( OBSOLETE29 ) -ERR_ENTRY ( FTP_PORT_FAILED ) -ERR_ENTRY ( FTP_COULDNT_USE_REST ) -ERR_ENTRY ( OBSOLETE32 ) -ERR_ENTRY ( RANGE_ERROR ) -ERR_ENTRY ( HTTP_POST_ERROR ) -ERR_ENTRY ( SSL_CONNECT_ERROR ) -ERR_ENTRY ( BAD_DOWNLOAD_RESUME ) -ERR_ENTRY ( FILE_COULDNT_READ_FILE ) -ERR_ENTRY ( LDAP_CANNOT_BIND ) -ERR_ENTRY ( LDAP_SEARCH_FAILED ) -ERR_ENTRY ( OBSOLETE40 ) -ERR_ENTRY ( FUNCTION_NOT_FOUND ) -ERR_ENTRY ( ABORTED_BY_CALLBACK ) -ERR_ENTRY ( BAD_FUNCTION_ARGUMENT ) -ERR_ENTRY ( OBSOLETE44 ) -ERR_ENTRY ( INTERFACE_FAILED ) -ERR_ENTRY ( OBSOLETE46 ) -ERR_ENTRY ( TOO_MANY_REDIRECTS ) -#if LCURL_CURL_VER_GE(7,21,5) -ERR_ENTRY ( UNKNOWN_OPTION ) -#else -ERR_ENTRY ( UNKNOWN_TELNET_OPTION ) /* User specified an unknown option */ -#endif -ERR_ENTRY ( TELNET_OPTION_SYNTAX ) -ERR_ENTRY ( OBSOLETE50 ) -ERR_ENTRY ( PEER_FAILED_VERIFICATION ) -ERR_ENTRY ( GOT_NOTHING ) -ERR_ENTRY ( SSL_ENGINE_NOTFOUND ) -ERR_ENTRY ( SSL_ENGINE_SETFAILED ) -ERR_ENTRY ( SEND_ERROR ) -ERR_ENTRY ( RECV_ERROR ) -ERR_ENTRY ( OBSOLETE57 ) -ERR_ENTRY ( SSL_CERTPROBLEM ) -ERR_ENTRY ( SSL_CIPHER ) -#if LCURL_CURL_VER_GE(7,62,0) -ERR_ENTRY ( OBSOLETE51 ) -#else -ERR_ENTRY ( SSL_CACERT ) -#endif -ERR_ENTRY ( BAD_CONTENT_ENCODING ) -ERR_ENTRY ( LDAP_INVALID_URL ) -ERR_ENTRY ( FILESIZE_EXCEEDED ) -ERR_ENTRY ( USE_SSL_FAILED ) -ERR_ENTRY ( SEND_FAIL_REWIND ) -ERR_ENTRY ( SSL_ENGINE_INITFAILED ) -ERR_ENTRY ( LOGIN_DENIED ) -ERR_ENTRY ( TFTP_NOTFOUND ) -ERR_ENTRY ( TFTP_PERM ) -ERR_ENTRY ( REMOTE_DISK_FULL ) -ERR_ENTRY ( TFTP_ILLEGAL ) -ERR_ENTRY ( TFTP_UNKNOWNID ) -ERR_ENTRY ( REMOTE_FILE_EXISTS ) -ERR_ENTRY ( TFTP_NOSUCHUSER ) -ERR_ENTRY ( CONV_FAILED ) -ERR_ENTRY ( CONV_REQD ) -ERR_ENTRY ( SSL_CACERT_BADFILE ) -ERR_ENTRY ( REMOTE_FILE_NOT_FOUND ) -ERR_ENTRY ( SSH ) -ERR_ENTRY ( SSL_SHUTDOWN_FAILED ) -ERR_ENTRY ( AGAIN ) -ERR_ENTRY ( SSL_CRL_BADFILE ) -ERR_ENTRY ( SSL_ISSUER_ERROR ) -#if LCURL_CURL_VER_GE(7,20,0) -ERR_ENTRY ( FTP_PRET_FAILED ) -#endif -#if LCURL_CURL_VER_GE(7,21,0) -ERR_ENTRY ( FTP_BAD_FILE_LIST ) -#endif -#if LCURL_CURL_VER_GE(7,20,0) -ERR_ENTRY ( RTSP_CSEQ_ERROR ) -ERR_ENTRY ( RTSP_SESSION_ERROR ) -#endif -#if LCURL_CURL_VER_GE(7,21,0) -ERR_ENTRY ( CHUNK_FAILED ) -#endif -#if LCURL_CURL_VER_GE(7,30,0) -ERR_ENTRY ( NO_CONNECTION_AVAILABLE ) -#endif -#if LCURL_CURL_VER_GE(7,38,0) -ERR_ENTRY ( HTTP2 ) -#else -ERR_ENTRY ( OBSOLETE16 ) -#endif -#if LCURL_CURL_VER_GE(7,39,0) -ERR_ENTRY ( SSL_PINNEDPUBKEYNOTMATCH ) -#endif -#if LCURL_CURL_VER_GE(7,41,0) -ERR_ENTRY ( SSL_INVALIDCERTSTATUS ) -#endif -#if LCURL_CURL_VER_GE(7,49,0) -ERR_ENTRY ( HTTP2_STREAM ) -#endif -#if LCURL_CURL_VER_GE(7,59,0) -ERR_ENTRY ( RECURSIVE_API_CALL ) -#endif -#if LCURL_CURL_VER_GE(7,66,0) -ERR_ENTRY ( AUTH_ERROR ) -#endif -#if LCURL_CURL_VER_GE(7,68,0) -ERR_ENTRY ( HTTP3 ) -#endif -#if LCURL_CURL_VER_GE(7,69,0) -ERR_ENTRY ( QUIC_CONNECT_ERROR ) -#endif -#if LCURL_CURL_VER_GE(7,73,0) -ERR_ENTRY ( PROXY ) -#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_form.h b/watchdog/third_party/lua-curl/src/lcerr_form.h deleted file mode 100644 index 60ca8ce..0000000 --- a/watchdog/third_party/lua-curl/src/lcerr_form.h +++ /dev/null @@ -1,8 +0,0 @@ -ERR_ENTRY ( OK ) -ERR_ENTRY ( MEMORY ) -ERR_ENTRY ( OPTION_TWICE ) -ERR_ENTRY ( NULL ) -ERR_ENTRY ( UNKNOWN_OPTION ) -ERR_ENTRY ( INCOMPLETE ) -ERR_ENTRY ( ILLEGAL_ARRAY ) -ERR_ENTRY ( DISABLED ) diff --git a/watchdog/third_party/lua-curl/src/lcerr_multi.h b/watchdog/third_party/lua-curl/src/lcerr_multi.h deleted file mode 100644 index 45322b7..0000000 --- a/watchdog/third_party/lua-curl/src/lcerr_multi.h +++ /dev/null @@ -1,14 +0,0 @@ -ERR_ENTRY ( OK ) -ERR_ENTRY ( CALL_MULTI_PERFORM ) -ERR_ENTRY ( BAD_HANDLE ) -ERR_ENTRY ( BAD_EASY_HANDLE ) -ERR_ENTRY ( OUT_OF_MEMORY ) -ERR_ENTRY ( INTERNAL_ERROR ) -ERR_ENTRY ( BAD_SOCKET ) -ERR_ENTRY ( UNKNOWN_OPTION ) -#if LCURL_CURL_VER_GE(7,32,1) -ERR_ENTRY ( ADDED_ALREADY ) -#endif -#if LCURL_CURL_VER_GE(7,59,0) -ERR_ENTRY ( RECURSIVE_API_CALL ) -#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_share.h b/watchdog/third_party/lua-curl/src/lcerr_share.h deleted file mode 100644 index 7027c2a..0000000 --- a/watchdog/third_party/lua-curl/src/lcerr_share.h +++ /dev/null @@ -1,8 +0,0 @@ -ERR_ENTRY ( OK ) -ERR_ENTRY ( BAD_OPTION ) -ERR_ENTRY ( IN_USE ) -ERR_ENTRY ( INVALID ) -ERR_ENTRY ( NOMEM ) -#if LCURL_CURL_VER_GE(7,23,0) -ERR_ENTRY ( NOT_BUILT_IN ) -#endif diff --git a/watchdog/third_party/lua-curl/src/lcerr_url.h b/watchdog/third_party/lua-curl/src/lcerr_url.h deleted file mode 100644 index f1002a1..0000000 --- a/watchdog/third_party/lua-curl/src/lcerr_url.h +++ /dev/null @@ -1,20 +0,0 @@ -#if LCURL_CURL_VER_GE(7,61,0) -ERR_ENTRY ( BAD_HANDLE ) -ERR_ENTRY ( BAD_PARTPOINTER ) -ERR_ENTRY ( BAD_PORT_NUMBER ) -ERR_ENTRY ( MALFORMED_INPUT ) -ERR_ENTRY ( NO_FRAGMENT ) -ERR_ENTRY ( NO_HOST ) -ERR_ENTRY ( NO_OPTIONS ) -ERR_ENTRY ( NO_PASSWORD ) -ERR_ENTRY ( NO_PORT ) -ERR_ENTRY ( NO_QUERY ) -ERR_ENTRY ( NO_SCHEME ) -ERR_ENTRY ( NO_USER ) -ERR_ENTRY ( OK ) -ERR_ENTRY ( OUT_OF_MEMORY ) -ERR_ENTRY ( UNKNOWN_PART ) -ERR_ENTRY ( UNSUPPORTED_SCHEME) -ERR_ENTRY ( URLDECODE ) -ERR_ENTRY ( USER_NOT_ALLOWED ) -#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcerror.c b/watchdog/third_party/lua-curl/src/lcerror.c deleted file mode 100644 index 11eea55..0000000 --- a/watchdog/third_party/lua-curl/src/lcerror.c +++ /dev/null @@ -1,342 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lcerror.h" -#include -#include "lcutils.h" - -#define LCURL_ERROR_NAME LCURL_PREFIX" Error" -static const char *LCURL_ERROR = LCURL_ERROR_NAME; - -#define LCURL_ERROR_EASY_NAME "CURL-EASY" -#define LCURL_ERROR_MULTI_NAME "CURL-MULTI" -#define LCURL_ERROR_SHARE_NAME "CURL-SHARE" -#define LCURL_ERROR_FORM_NAME "CURL-FORM" -#define LCURL_ERROR_URL_NAME "CURL-URL" - -typedef struct lcurl_error_tag{ - int tp; - int no; -}lcurl_error_t; - -//{ - -static const char* lcurl_err_easy_mnemo(int err){ -#define ERR_ENTRY(E) case CURLE_##E: return #E; - - switch (err){ - #include "lcerr_easy.h" - } - return "UNKNOWN"; - -#undef ERR_ENTRY -} - -static const char* lcurl_err_multi_mnemo(int err){ -#define ERR_ENTRY(E) case CURLM_##E: return #E; - - switch (err){ - #include "lcerr_multi.h" - } - return "UNKNOWN"; - -#undef ERR_ENTRY -} - -static const char* lcurl_err_share_mnemo(int err){ -#define ERR_ENTRY(E) case CURLSHE_##E: return #E; - - switch (err){ - #include "lcerr_share.h" - } - return "UNKNOWN"; - -#undef ERR_ENTRY -} - -static const char* lcurl_err_form_mnemo(int err){ -#define ERR_ENTRY(E) case CURL_FORMADD_##E: return #E; - - switch (err){ - #include "lcerr_form.h" - } - return "UNKNOWN"; - -#undef ERR_ENTRY -} - -static const char* lcurl_err_url_mnemo(int err){ -#define ERR_ENTRY(E) case CURLUE_##E: return #E; - - switch (err){ - #include "lcerr_url.h" - } - return "UNKNOWN"; - -#undef ERR_ENTRY -} - -static const char* _lcurl_err_mnemo(int tp, int err){ - switch(tp){ - case LCURL_ERROR_EASY : return lcurl_err_easy_mnemo (err); - case LCURL_ERROR_MULTI: return lcurl_err_multi_mnemo(err); - case LCURL_ERROR_SHARE: return lcurl_err_share_mnemo(err); - case LCURL_ERROR_FORM : return lcurl_err_form_mnemo (err); - case LCURL_ERROR_URL : return lcurl_err_url_mnemo (err); - } - assert(0); - return ""; -} - -static const char* _lcurl_err_msg(int tp, int err){ - switch(tp){ - case LCURL_ERROR_EASY : return curl_easy_strerror (err); - case LCURL_ERROR_MULTI: return curl_multi_strerror(err); - case LCURL_ERROR_SHARE: return curl_share_strerror(err); - case LCURL_ERROR_FORM : return lcurl_err_form_mnemo(err); - case LCURL_ERROR_URL : return lcurl_err_url_mnemo(err); - } - assert(0); - return ""; -} - -static const char* _lcurl_err_category_name(int tp){ - assert( - (tp == LCURL_ERROR_EASY ) || - (tp == LCURL_ERROR_MULTI) || - (tp == LCURL_ERROR_SHARE) || - (tp == LCURL_ERROR_FORM ) || - (tp == LCURL_ERROR_URL ) || - 0 - ); - - switch(tp){ - case LCURL_ERROR_EASY: { - static const char *name = LCURL_ERROR_EASY_NAME; - return name; - } - case LCURL_ERROR_MULTI: { - static const char *name = LCURL_ERROR_MULTI_NAME; - return name; - } - case LCURL_ERROR_SHARE: { - static const char *name = LCURL_ERROR_SHARE_NAME; - return name; - } - case LCURL_ERROR_FORM: { - static const char *name = LCURL_ERROR_FORM_NAME; - return name; - } - case LCURL_ERROR_URL: { - static const char *name = LCURL_ERROR_URL_NAME; - return name; - } - } - - assert(0); - return NULL; -} - -static void _lcurl_err_pushstring(lua_State *L, int tp, int err){ - lua_pushfstring(L, "[%s][%s] %s (%d)", - _lcurl_err_category_name(tp), - _lcurl_err_mnemo(tp, err), - _lcurl_err_msg(tp, err), - err - ); -} - -//} - -//{ - -int lcurl_error_create(lua_State *L, int error_type, int no){ - lcurl_error_t *err = lutil_newudatap(L, lcurl_error_t, LCURL_ERROR); - - assert( - (error_type == LCURL_ERROR_EASY ) || - (error_type == LCURL_ERROR_MULTI) || - (error_type == LCURL_ERROR_SHARE) || - (error_type == LCURL_ERROR_FORM ) || - (error_type == LCURL_ERROR_URL ) || - 0 - ); - - err->tp = error_type; - err->no = no; - return 1; -} - -static lcurl_error_t *lcurl_geterror_at(lua_State *L, int i){ - lcurl_error_t *err = (lcurl_error_t *)lutil_checkudatap (L, i, LCURL_ERROR); - luaL_argcheck (L, err != NULL, 1, LCURL_PREFIX"error object expected"); - return err; -} - -#define lcurl_geterror(L) lcurl_geterror_at((L),1) - -static int lcurl_err_no(lua_State *L){ - lcurl_error_t *err = lcurl_geterror(L); - lua_pushinteger(L, err->no); - return 1; -} - -static int lcurl_err_msg(lua_State *L){ - lcurl_error_t *err = lcurl_geterror(L); - lua_pushstring(L, _lcurl_err_msg(err->tp, err->no)); - return 1; -} - -static int lcurl_err_mnemo(lua_State *L){ - lcurl_error_t *err = lcurl_geterror(L); - lua_pushstring(L, _lcurl_err_mnemo(err->tp, err->no)); - return 1; -} - -static int lcurl_err_tostring(lua_State *L){ - lcurl_error_t *err = lcurl_geterror(L); - _lcurl_err_pushstring(L, err->tp, err->no); - return 1; -} - -static int lcurl_err_equal(lua_State *L){ - lcurl_error_t *lhs = lcurl_geterror_at(L, 1); - lcurl_error_t *rhs = lcurl_geterror_at(L, 2); - lua_pushboolean(L, ((lhs->no == rhs->no)&&(lhs->tp == rhs->tp))?1:0); - return 1; -} - -static int lcurl_err_category(lua_State *L){ - lcurl_error_t *err = lcurl_geterror(L); - lua_pushstring(L, _lcurl_err_category_name(err->tp)); - return 1; -} - -//} - -//{ - -int lcurl_fail_ex(lua_State *L, int mode, int error_type, int code){ - if(mode == LCURL_ERROR_RETURN){ - lua_pushnil(L); - lcurl_error_create(L, error_type, code); - return 2; - } - -#if LUA_VERSION_NUM >= 502 // lua 5.2 - lcurl_error_create(L, error_type, code); -#else - _lcurl_err_pushstring(L, error_type, code); -#endif - - assert(LCURL_ERROR_RAISE == mode); - - return lua_error(L); -} - -int lcurl_fail(lua_State *L, int error_type, int code){ - return lcurl_fail_ex(L, LCURL_ERROR_RETURN, error_type, code); -} - -//} - -static const int ERROR_CATEGORIES[] = { - LCURL_ERROR_EASY, - LCURL_ERROR_MULTI, - LCURL_ERROR_SHARE, - LCURL_ERROR_FORM, - LCURL_ERROR_URL, -}; - -static const char* ERROR_CATEGORIES_NAME[] = { - LCURL_ERROR_EASY_NAME, - LCURL_ERROR_MULTI_NAME, - LCURL_ERROR_SHARE_NAME, - LCURL_ERROR_FORM_NAME, - LCURL_ERROR_URL_NAME, - NULL -}; - -int lcurl_error_new(lua_State *L){ - int tp, no = luaL_checkint(L, 2); - if (lua_isnumber(L, 1)){ - tp = luaL_checkint(L, 2); - } - else{ - tp = luaL_checkoption(L, 1, NULL, ERROR_CATEGORIES_NAME); - tp = ERROR_CATEGORIES[tp]; - } - - //! @todo checks error type value - - lcurl_error_create(L, tp, no); - return 1; -} - -static const struct luaL_Reg lcurl_err_methods[] = { - {"no", lcurl_err_no }, - {"msg", lcurl_err_msg }, - {"name", lcurl_err_mnemo }, - {"mnemo", lcurl_err_mnemo }, - {"cat", lcurl_err_category }, - {"category", lcurl_err_category }, - {"__tostring", lcurl_err_tostring }, - {"__eq", lcurl_err_equal }, - - {NULL,NULL} -}; - -static const lcurl_const_t lcurl_error_codes[] = { - -#define ERR_ENTRY(N) { "E_"#N, CURLE_##N }, -#include "lcerr_easy.h" -#undef ERR_ENTRY - -/* libcurl rename CURLE_FTP_WEIRD_SERVER_REPLY to CURLE_WEIRD_SERVER_REPLY in version 7.51.0*/ -/* we can not have both codes in general because we have to be able convern error number to error name*/ -/* so we use newest name but add error code as alias.*/ -#if LCURL_CURL_VER_GE(7,51,0) - { "E_FTP_WEIRD_SERVER_REPLY", CURLE_FTP_WEIRD_SERVER_REPLY }, -#else - { "E_WEIRD_SERVER_REPLY", CURLE_FTP_WEIRD_SERVER_REPLY }, -#endif - -#define ERR_ENTRY(N) { "E_MULTI_"#N, CURLM_##N }, -#include "lcerr_multi.h" -#undef ERR_ENTRY - -#define ERR_ENTRY(N) { "E_SHARE_"#N, CURLSHE_##N }, -#include "lcerr_share.h" -#undef ERR_ENTRY - -#define ERR_ENTRY(N) { "E_FORM_"#N, CURL_FORMADD_##N }, -#include "lcerr_form.h" -#undef ERR_ENTRY - -#define ERR_ENTRY(N) { "E_URL_"#N, CURLUE_##N }, -#include "lcerr_url.h" -#undef ERR_ENTRY - - {NULL, 0} -}; - -void lcurl_error_initlib(lua_State *L, int nup){ - if(!lutil_createmetap(L, LCURL_ERROR, lcurl_err_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - - lcurl_util_set_const(L, lcurl_error_codes); - - lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_EASY ));lua_setfield(L, -2, "ERROR_EASY" ); - lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_MULTI ));lua_setfield(L, -2, "ERROR_MULTI"); - lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_SHARE ));lua_setfield(L, -2, "ERROR_SHARE"); - lua_pushstring(L, _lcurl_err_category_name(LCURL_ERROR_FORM ));lua_setfield(L, -2, "ERROR_FORM" ); -} diff --git a/watchdog/third_party/lua-curl/src/lcerror.h b/watchdog/third_party/lua-curl/src/lcerror.h deleted file mode 100644 index da4e1c5..0000000 --- a/watchdog/third_party/lua-curl/src/lcerror.h +++ /dev/null @@ -1,34 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCERROR_H_ -#define _LCERROR_H_ - -#include "lcurl.h" - -#define LCURL_ERROR_CURL 1 -#define LCURL_ERROR_EASY 1 -#define LCURL_ERROR_MULTI 2 -#define LCURL_ERROR_SHARE 3 -#define LCURL_ERROR_FORM 4 -#define LCURL_ERROR_URL 5 - -#define LCURL_ERROR_RETURN 1 -#define LCURL_ERROR_RAISE 2 - -int lcurl_fail(lua_State *L, int error_type, int code); - -int lcurl_fail_ex(lua_State *L, int mode, int error_type, int code); - -int lcurl_error_new(lua_State *L); - -void lcurl_error_initlib(lua_State *L, int nup); - -#endif diff --git a/watchdog/third_party/lua-curl/src/lcflags.h b/watchdog/third_party/lua-curl/src/lcflags.h deleted file mode 100644 index 0fbeff2..0000000 --- a/watchdog/third_party/lua-curl/src/lcflags.h +++ /dev/null @@ -1,283 +0,0 @@ -/* Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options */ -FLG_ENTRY(AUTH_NONE ) -FLG_ENTRY(AUTH_BASIC ) -FLG_ENTRY(AUTH_DIGEST ) -FLG_ENTRY(AUTH_GSSNEGOTIATE ) -#if LCURL_CURL_VER_GE(7,38,0) -FLG_ENTRY(AUTH_NEGOTIATE ) -#endif -FLG_ENTRY(AUTH_NTLM ) -#if LCURL_CURL_VER_GE(7,19,3) -FLG_ENTRY(AUTH_DIGEST_IE ) -#endif -#if LCURL_CURL_VER_GE(7,19,6) -FLG_ENTRY(KHSTAT_FINE_ADD_TO_FILE ) -FLG_ENTRY(KHSTAT_FINE ) -FLG_ENTRY(KHSTAT_REJECT ) -FLG_ENTRY(KHSTAT_DEFER ) -FLG_ENTRY(KHMATCH_OK ) -FLG_ENTRY(KHMATCH_MISMATCH ) -FLG_ENTRY(KHMATCH_MISSING ) -FLG_ENTRY(KHTYPE_RSA1 ) -FLG_ENTRY(KHTYPE_RSA ) -FLG_ENTRY(KHTYPE_DSS ) -#endif -#if LCURL_CURL_VER_GE(7,58,0) -FLG_ENTRY(KHTYPE_ECDSA ) -FLG_ENTRY(KHTYPE_ED25519 ) -#endif -#if LCURL_CURL_VER_GE(7,73,0) -FLG_ENTRY(KHSTAT_FINE_REPLACE ) -#endif - -#if LCURL_CURL_VER_GE(7,22,0) -FLG_ENTRY(AUTH_NTLM_WB ) -#endif -#if LCURL_CURL_VER_GE(7,21,3) -FLG_ENTRY(AUTH_ONLY ) -#endif -FLG_ENTRY(AUTH_ANY ) -FLG_ENTRY(AUTH_ANYSAFE ) -#if LCURL_CURL_VER_GE(7,55,0) -FLG_ENTRY(AUTH_GSSAPI ) -#endif -#if LCURL_CURL_VER_GE(7,61,0) -FLG_ENTRY(AUTH_BEARER ) -#endif - -#ifdef CURLSSH_AUTH_ANY -FLG_ENTRY(SSH_AUTH_ANY ) -#endif -#ifdef CURLSSH_AUTH_NONE -FLG_ENTRY(SSH_AUTH_NONE ) -#endif -#ifdef CURLSSH_AUTH_PUBLICKEY -FLG_ENTRY(SSH_AUTH_PUBLICKEY ) -#endif -#ifdef CURLSSH_AUTH_PASSWORD -FLG_ENTRY(SSH_AUTH_PASSWORD ) -#endif -#ifdef CURLSSH_AUTH_HOST -FLG_ENTRY(SSH_AUTH_HOST ) -#endif -#ifdef CURLSSH_AUTH_GSSAPI -FLG_ENTRY(SSH_AUTH_GSSAPI ) -#endif -#ifdef CURLSSH_AUTH_KEYBOARD -FLG_ENTRY(SSH_AUTH_KEYBOARD ) -#endif -#ifdef CURLSSH_AUTH_AGENT -FLG_ENTRY(SSH_AUTH_AGENT ) -#endif -#ifdef CURLSSH_AUTH_DEFAULT -FLG_ENTRY(SSH_AUTH_DEFAULT ) -#endif - -#ifdef CURLGSSAPI_DELEGATION_NONE -FLG_ENTRY(GSSAPI_DELEGATION_NONE ) -#endif -#ifdef CURLGSSAPI_DELEGATION_POLICY_FLAG -FLG_ENTRY(GSSAPI_DELEGATION_POLICY_FLAG ) -#endif -#ifdef CURLGSSAPI_DELEGATION_FLAG -FLG_ENTRY(GSSAPI_DELEGATION_FLAG ) -#endif - -/* Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options */ -FLG_ENTRY(USESSL_NONE ) -FLG_ENTRY(USESSL_TRY ) -FLG_ENTRY(USESSL_CONTROL ) -FLG_ENTRY(USESSL_ALL ) - -/* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */ -#ifdef CURLSSLOPT_ALLOW_BEAST -FLG_ENTRY(SSLOPT_ALLOW_BEAST ) -#endif -#ifdef CURLSSLOPT_NO_REVOKE -FLG_ENTRY(SSLOPT_NO_REVOKE ) -#endif -#ifdef CURLSSLOPT_NO_PARTIALCHAIN -FLG_ENTRY(SSLOPT_NO_PARTIALCHAIN ) -#endif -#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT -FLG_ENTRY(SSLOPT_REVOKE_BEST_EFFORT ) -#endif -#ifdef CURLSSLOPT_NATIVE_CA -FLG_ENTRY(SSLOPT_NATIVE_CA ) -#endif - -/* parameter for the CURLOPT_FTP_SSL_CCC option */ -FLG_ENTRY(FTPSSL_CCC_NONE ) -FLG_ENTRY(FTPSSL_CCC_PASSIVE ) -FLG_ENTRY(FTPSSL_CCC_ACTIVE ) - -/* parameter for the CURLOPT_FTPSSLAUTH option */ -FLG_ENTRY(FTPAUTH_DEFAULT ) -FLG_ENTRY(FTPAUTH_SSL ) -FLG_ENTRY(FTPAUTH_TLS ) - -/* parameter for the CURLOPT_FTP_CREATE_MISSING_DIRS option */ -FLG_ENTRY(FTP_CREATE_DIR_NONE ) -FLG_ENTRY(FTP_CREATE_DIR ) -FLG_ENTRY(FTP_CREATE_DIR_RETRY ) -FLG_ENTRY(FTP_CREATE_DIR_LAST ) - -/* parameter for the CURLOPT_FTP_FILEMETHOD option */ -FLG_ENTRY(FTPMETHOD_DEFAULT ) -FLG_ENTRY(FTPMETHOD_MULTICWD ) -FLG_ENTRY(FTPMETHOD_NOCWD ) -FLG_ENTRY(FTPMETHOD_SINGLECWD ) - -/* bitmask defines for CURLOPT_HEADEROPT */ -#if LCURL_CURL_VER_GE(7,37,0) -FLG_ENTRY(HEADER_UNIFIED ) -FLG_ENTRY(HEADER_SEPARATE ) -#endif - -/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */ -FLG_ENTRY(PROTO_HTTP ) -FLG_ENTRY(PROTO_HTTPS ) -FLG_ENTRY(PROTO_FTP ) -FLG_ENTRY(PROTO_FTPS ) -FLG_ENTRY(PROTO_SCP ) -FLG_ENTRY(PROTO_SFTP ) -FLG_ENTRY(PROTO_TELNET ) -FLG_ENTRY(PROTO_LDAP ) -FLG_ENTRY(PROTO_LDAPS ) -FLG_ENTRY(PROTO_DICT ) -FLG_ENTRY(PROTO_FILE ) -FLG_ENTRY(PROTO_TFTP ) -#ifdef CURLPROTO_IMAP -FLG_ENTRY(PROTO_IMAP ) -#endif -#ifdef CURLPROTO_IMAPS -FLG_ENTRY(PROTO_IMAPS ) -#endif -#ifdef CURLPROTO_POP3 -FLG_ENTRY(PROTO_POP3 ) -#endif -#ifdef CURLPROTO_POP3S -FLG_ENTRY(PROTO_POP3S ) -#endif -#ifdef CURLPROTO_SMTP -FLG_ENTRY(PROTO_SMTP ) -#endif -#ifdef CURLPROTO_SMTPS -FLG_ENTRY(PROTO_SMTPS ) -#endif -#ifdef CURLPROTO_RTSP -FLG_ENTRY(PROTO_RTSP ) -#endif -#ifdef CURLPROTO_RTMP -FLG_ENTRY(PROTO_RTMP ) -#endif -#ifdef CURLPROTO_RTMPT -FLG_ENTRY(PROTO_RTMPT ) -#endif -#ifdef CURLPROTO_RTMPE -FLG_ENTRY(PROTO_RTMPE ) -#endif -#ifdef CURLPROTO_RTMPTE -FLG_ENTRY(PROTO_RTMPTE ) -#endif -#ifdef CURLPROTO_RTMPS -FLG_ENTRY(PROTO_RTMPS ) -#endif -#ifdef CURLPROTO_RTMPTS -FLG_ENTRY(PROTO_RTMPTS ) -#endif -#ifdef CURLPROTO_GOPHER -FLG_ENTRY(PROTO_GOPHER ) -#endif -#ifdef CURLPROTO_SMB -FLG_ENTRY(PROTO_SMB ) -#endif -#ifdef CURLPROTO_SMBS -FLG_ENTRY(PROTO_SMBS ) -#endif -#ifdef CURLPROTO_MQTT -FLG_ENTRY(PROTO_MQTT ) -#endif -FLG_ENTRY(PROTO_ALL ) - -FLG_ENTRY(PROXY_HTTP ) /* added in 7.10.0 */ -FLG_ENTRY(PROXY_HTTP_1_0 ) /* added in 7.19.4 */ -FLG_ENTRY(PROXY_SOCKS4 ) /* added in 7.15.2 */ -FLG_ENTRY(PROXY_SOCKS5 ) /* added in 7.10.0 */ -FLG_ENTRY(PROXY_SOCKS4A ) /* added in 7.18.0 */ -FLG_ENTRY(PROXY_SOCKS5_HOSTNAME ) /* added in 7.18.0 */ -#if LCURL_CURL_VER_GE(7,52,0) -FLG_ENTRY(PROXY_HTTPS ) -#endif - -FLG_ENTRY(PAUSE_ALL ) /* added in 7.18.0 */ -FLG_ENTRY(PAUSE_CONT ) /* added in 7.18.0 */ -FLG_ENTRY(PAUSE_RECV ) /* added in 7.18.0 */ -FLG_ENTRY(PAUSE_RECV_CONT ) /* added in 7.18.0 */ -FLG_ENTRY(PAUSE_SEND ) /* added in 7.18.0 */ -FLG_ENTRY(PAUSE_SEND_CONT ) /* added in 7.18.0 */ - -#if LCURL_CURL_VER_GE(7,64,1) -FLG_ENTRY(ALTSVC_H1) -FLG_ENTRY(ALTSVC_H2) -FLG_ENTRY(ALTSVC_H3) -FLG_ENTRY(ALTSVC_READONLYFILE) -#endif - -#if LCURL_CURL_VER_GE(7,73,0) -FLG_ENTRY(PX_OK) -FLG_ENTRY(PX_BAD_ADDRESS_TYPE) -FLG_ENTRY(PX_BAD_VERSION) -FLG_ENTRY(PX_CLOSED) -FLG_ENTRY(PX_GSSAPI) -FLG_ENTRY(PX_GSSAPI_PERMSG) -FLG_ENTRY(PX_GSSAPI_PROTECTION) -FLG_ENTRY(PX_IDENTD) -FLG_ENTRY(PX_IDENTD_DIFFER) -FLG_ENTRY(PX_LONG_HOSTNAME) -FLG_ENTRY(PX_LONG_PASSWD) -FLG_ENTRY(PX_LONG_USER) -FLG_ENTRY(PX_NO_AUTH) -FLG_ENTRY(PX_RECV_ADDRESS) -FLG_ENTRY(PX_RECV_AUTH) -FLG_ENTRY(PX_RECV_CONNECT) -FLG_ENTRY(PX_RECV_REQACK) -FLG_ENTRY(PX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED) -FLG_ENTRY(PX_REPLY_COMMAND_NOT_SUPPORTED) -FLG_ENTRY(PX_REPLY_CONNECTION_REFUSED) -FLG_ENTRY(PX_REPLY_GENERAL_SERVER_FAILURE) -FLG_ENTRY(PX_REPLY_HOST_UNREACHABLE) -FLG_ENTRY(PX_REPLY_NETWORK_UNREACHABLE) -FLG_ENTRY(PX_REPLY_NOT_ALLOWED) -FLG_ENTRY(PX_REPLY_TTL_EXPIRED) -FLG_ENTRY(PX_REPLY_UNASSIGNED) -FLG_ENTRY(PX_REQUEST_FAILED) -FLG_ENTRY(PX_RESOLVE_HOST) -FLG_ENTRY(PX_SEND_AUTH) -FLG_ENTRY(PX_SEND_CONNECT) -FLG_ENTRY(PX_SEND_REQUEST) -FLG_ENTRY(PX_UNKNOWN_FAIL) -FLG_ENTRY(PX_UNKNOWN_MODE) -FLG_ENTRY(PX_USER_REJECTED) -#endif - -#if LCURL_CURL_VER_GE(7,73,0) -FLG_ENTRY(OT_LONG) -FLG_ENTRY(OT_VALUES) -FLG_ENTRY(OT_OFF_T) -FLG_ENTRY(OT_OBJECT) -FLG_ENTRY(OT_STRING) -FLG_ENTRY(OT_SLIST) -FLG_ENTRY(OT_CBPTR) -FLG_ENTRY(OT_BLOB) -FLG_ENTRY(OT_FUNCTION) -FLG_ENTRY(OT_FLAG_ALIAS) -#endif - -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS -FLG_ENTRY(HSTS_ENABLE) -FLG_ENTRY(HSTS_READONLYFILE) -FLG_ENTRY(STS_OK) -FLG_ENTRY(STS_DONE) -FLG_ENTRY(STS_FAIL) -#endif diff --git a/watchdog/third_party/lua-curl/src/lchttppost.c b/watchdog/third_party/lua-curl/src/lchttppost.c deleted file mode 100644 index 2d49323..0000000 --- a/watchdog/third_party/lua-curl/src/lchttppost.c +++ /dev/null @@ -1,595 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lchttppost.h" -#include "lcerror.h" -#include "lcutils.h" - -#define LCURL_HTTPPOST_NAME LCURL_PREFIX" HTTPPost" -static const char *LCURL_HTTPPOST = LCURL_HTTPPOST_NAME; - - -#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ - -/*! @fixme detect real types (e.g. float/int32_t) */ - -# define LCURL_USE_INTEGER - -#endif - -#ifdef LCURL_USE_INTEGER -# ifdef LUA_32BITS -# define LCURL_INT_SIZE_16 -# define LCURL_INT_SIZE_32 -# else -# define LCURL_INT_SIZE_16 -# define LCURL_INT_SIZE_32 -# define LCURL_INT_SIZE_64 -# endif -#endif - -#if LCURL_CURL_VER_GE(7,46,0) -# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTLEN -# define LCURL_LEN_TYPE curl_off_t -#else -# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTSLENGTH -# define LCURL_LEN_TYPE long -#endif - -/* 7.56.0 changed code for `curl_formget` if callback abort write. - * - * https://github.com/curl/curl/issues/1987#issuecomment-336139060 - * ... not sure its worth the effort to document its return codes to - * any further extent then it currently is. This function is very - * rarely used, and the new mime API doesn't even have a version of it. - **/ -#if LCURL_CURL_VER_GE(7,56,0) -# define LCURL_GET_CB_ERROR CURLE_READ_ERROR -#else -# define LCURL_GET_CB_ERROR (CURLcode)-1 -#endif - -//{ stream - -static lcurl_hpost_stream_t *lcurl_hpost_stream_add(lua_State *L, lcurl_hpost_t *p){ - lcurl_hpost_stream_t *ptr = p->stream; - lcurl_hpost_stream_t *stream = malloc(sizeof(lcurl_hpost_stream_t)); - if(!stream) return NULL; - - stream->magic = LCURL_HPOST_STREAM_MAGIC; - stream->L = &p->L; - stream->rbuffer.ref = LUA_NOREF; - stream->rd.cb_ref = stream->rd.ud_ref = LUA_NOREF; - stream->next = NULL; - if(!p->stream) p->stream = stream; - else{ - while(ptr->next) ptr = ptr->next; - ptr->next = stream; - } - return stream; -} - -static void lcurl_hpost_stream_free(lua_State *L, lcurl_hpost_stream_t *ptr){ - if(ptr){ - luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rbuffer.ref); - luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.ud_ref); - free(ptr); - } -} - -static void lcurl_hpost_stream_free_last(lua_State *L, lcurl_hpost_t *p){ - lcurl_hpost_stream_t *ptr = p->stream; - if(!ptr) return; - if(!ptr->next){ - lcurl_hpost_stream_free(L, ptr); - p->stream = 0; - } - - while(ptr->next->next) ptr = ptr->next; - lcurl_hpost_stream_free(L, ptr->next); - ptr->next = NULL; -} - -static void lcurl_hpost_stream_free_all(lua_State *L, lcurl_hpost_t *p){ - lcurl_hpost_stream_t *ptr = p->stream; - while(ptr){ - lcurl_hpost_stream_t *next = ptr->next; - lcurl_hpost_stream_free(L, ptr); - ptr = next; - } - p->stream = 0; -} - -//} - -//{ HTTPPost - -int lcurl_hpost_create(lua_State *L, int error_mode){ - lcurl_hpost_t *p = lutil_newudatap(L, lcurl_hpost_t, LCURL_HTTPPOST); - p->post = p->last = 0; - p->storage = lcurl_storage_init(L); - p->err_mode = error_mode; - p->stream = 0; - - return 1; -} - -lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i){ - lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, i, LCURL_HTTPPOST); - luaL_argcheck (L, p != NULL, 1, LCURL_HTTPPOST_NAME" object expected"); - return p; -} - -static int lcurl_hpost_to_s(lua_State *L){ - lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, 1, LCURL_HTTPPOST); - lua_pushfstring(L, LCURL_HTTPPOST_NAME" (%p)", (void*)p); - return 1; -} - -static int lcurl_hpost_add_content(lua_State *L){ - // add_buffer(name, data, [type,] [headers]) - lcurl_hpost_t *p = lcurl_gethpost(L); - size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); - size_t cont_len; const char *cont = luaL_checklstring(L, 3, &cont_len); - const char *type = lua_tostring(L, 4); - struct curl_slist *list = lcurl_util_to_slist(L, type?5:4); - struct curl_forms forms[3]; - CURLFORMcode code; - - int i = 0; - if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } - if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } - forms[i].option = CURLFORM_END; - - code = curl_formadd(&p->post, &p->last, - CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, - CURLFORM_PTRCONTENTS, cont, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)cont_len, - CURLFORM_ARRAY, forms, - CURLFORM_END); - - if(code != CURL_FORMADD_OK){ - if(list) curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); - } - - lcurl_storage_preserve_value(L, p->storage, 2); - lcurl_storage_preserve_value(L, p->storage, 3); - if(list) lcurl_storage_preserve_slist (L, p->storage, list); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_hpost_add_buffer(lua_State *L){ - // add_buffer(name, filename, data, [type,] [headers]) - lcurl_hpost_t *p = lcurl_gethpost(L); - size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); - const char *buff = luaL_checkstring(L, 3); - size_t cont_len; const char *cont = luaL_checklstring(L, 4, &cont_len); - const char *type = lua_tostring(L, 5); - struct curl_slist *list = lcurl_util_to_slist(L, ((!type)&&(lua_isnone(L,6)))?5:6); - struct curl_forms forms[3]; - CURLFORMcode code; - - int i = 0; - if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } - if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } - forms[i].option = CURLFORM_END; - - code = curl_formadd(&p->post, &p->last, - CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, - CURLFORM_BUFFER, buff, - CURLFORM_BUFFERPTR, cont, CURLFORM_BUFFERLENGTH, cont_len, - CURLFORM_ARRAY, forms, - CURLFORM_END); - - if(code != CURL_FORMADD_OK){ - if(list) curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); - } - - lcurl_storage_preserve_value(L, p->storage, 2); - lcurl_storage_preserve_value(L, p->storage, 4); - if(list) lcurl_storage_preserve_slist (L, p->storage, list); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_hpost_add_file(lua_State *L){ - // add_file(name, path, [type, [fname,]] [headers]) - // add_file("Picture", "c:\\image.jpg") - // add_file("Picture", "c:\\image.jpg", "image/jpeg") - // add_file("Picture", "c:\\image.jpg", "image/jpeg", {"XDescript: my image"}) - // add_file("Picture", "c:\\image.jpg", "image/jpeg", "avatar.jpeg", {"XDescript: my image"}) - // add_file("Picture", "c:\\image.jpg", nil, "avatar.jpeg", {"XDescript: my image"}) - - int top = lua_gettop(L); - lcurl_hpost_t *p = lcurl_gethpost(L); - size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); - const char *path = luaL_checkstring(L, 3); - const char *type = 0, *fname = 0; - struct curl_slist *list = NULL; - struct curl_forms forms[4]; - CURLFORMcode code; - int i = 0; - - if(top == 4){ /* name, path, type | headers */ - if(lua_istable(L, 4)) - list = lcurl_util_to_slist(L, 4); - else - type = lua_tostring(L, 4); - } - else if(top > 4){ /* name, path, type, fname | [fname, headers] */ - type = lua_tostring(L, 4); - if(top == 5){ /* name, path, type, fname | headers */ - if(lua_istable(L, 5)) - list = lcurl_util_to_slist(L, 5); - else - fname = lua_tostring(L, 5); - } - else{ /* name, path, type, fname, headers */ - fname = lua_tostring(L, 5); - list = lcurl_util_to_slist(L, 6); - } - } - - if(fname){ forms[i].option = CURLFORM_FILENAME; forms[i++].value = fname; } - if(type) { forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; } - if(list) { forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; } - forms[i].option = CURLFORM_END; - - code = curl_formadd(&p->post, &p->last, - CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, - CURLFORM_FILE, path, - CURLFORM_ARRAY, forms, - CURLFORM_END); - - if(code != CURL_FORMADD_OK){ - if(list) curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); - } - - lcurl_storage_preserve_value(L, p->storage, 2); - if(list) lcurl_storage_preserve_slist (L, p->storage, list); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_hpost_add_stream(lua_State *L){ - static const char *EMPTY = ""; - - // add_stream(name, [filename, [type,]] [headers,] size, reader [,context]) - lcurl_hpost_t *p = lcurl_gethpost(L); - size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); - struct curl_slist *list = NULL; int ilist = 0; - const char *type = 0, *fname = 0; - size_t len; - CURLFORMcode code; - lcurl_callback_t rd = {LUA_NOREF, LUA_NOREF}; - lcurl_hpost_stream_t *stream; - int n = 0, i = 3; - struct curl_forms forms[4]; - - while(1){ // [filename, [type,]] [headers,] - if(lua_isnone(L, i)){ - lua_pushliteral(L, "stream size required"); - lua_error(L); - } - if(lua_type(L, i) == LUA_TNUMBER){ - break; - } - if(lua_type(L, i) == LUA_TTABLE){ - ilist = i++; - break; - } - else if(!fname){ - if(lua_isnil(L, i)) fname = EMPTY; - else fname = luaL_checkstring(L, i); - } - else if(!type){ - if(lua_isnil(L, i)) type = EMPTY; - else type = luaL_checkstring(L, i); - } - else{ - if(lua_isnil(L, i) && (!ilist)){ - ++i; // empty headers - break; - } - lua_pushliteral(L, "stream size required"); - lua_error(L); - } - ++i; - } - -#if defined(LCURL_INT_SIZE_64) && LCURL_CURL_VER_GE(7,46,0) - len = luaL_checkinteger(L, i); -#else - len = luaL_checklong(L, i); -#endif - - lcurl_set_callback(L, &rd, i + 1, "read"); - - luaL_argcheck(L, rd.cb_ref != LUA_NOREF, i + 1, "function expected"); - - if(ilist) list = lcurl_util_to_slist(L, ilist); - if(fname == EMPTY) fname = NULL; - if(type == EMPTY) type = NULL; - - n = 0; - if(fname){ forms[n].option = CURLFORM_FILENAME; forms[n++].value = fname; } - if(type) { forms[n].option = CURLFORM_CONTENTTYPE; forms[n++].value = type; } - if(list) { forms[n].option = CURLFORM_CONTENTHEADER; forms[n++].value = (char*)list; } - forms[n].option = CURLFORM_END; - - stream = lcurl_hpost_stream_add(L, p); - if(!stream){ - if(list) curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY); - } - - stream->rd = rd; - - code = curl_formadd(&p->post, &p->last, - CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, - CURLFORM_STREAM, stream, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)len, - CURLFORM_ARRAY, forms, - CURLFORM_END - ); - - if(code != CURL_FORMADD_OK){ - lcurl_hpost_stream_free_last(L, p); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); - } - - lcurl_storage_preserve_value(L, p->storage, 2); - if(list) lcurl_storage_preserve_slist (L, p->storage, list); - - lua_settop(L, 1); - return 1; -} - -static int lcurl_hpost_add_files(lua_State *L){ - lcurl_hpost_t *p = lcurl_gethpost(L); - size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len); - int i; int opt_count = 0; - int arr_count = lua_rawlen(L, 3); - struct curl_forms *forms; - CURLFORMcode code; - - lua_settop(L, 3); - if(lua_type(L, -1) != LUA_TTABLE){ - //! @fixme use library specific error codes - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); - } - - for(i = 1; i <= arr_count; ++i){ - int n; - lua_rawgeti(L, 3, i); - - if((lua_type(L, -1) != LUA_TTABLE) && (lua_type(L, -1) != LUA_TSTRING)){ - //! @fixme use library specific error codes - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); - } - - n = (lua_type(L, -1) == LUA_TSTRING) ? 1: lua_rawlen(L, -1); - if(n == 1) opt_count += 1; // name - else if(n == 2) opt_count += 2; // name and type - else if(n == 3) opt_count += 3; // name, type and filename - else{ - //! @fixme use library specific error codes - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY); - } - - lua_pop(L, 1); - } - - if(opt_count == 0){ - lua_settop(L, 1); - return 1; - } - - forms = calloc(opt_count + 1, sizeof(struct curl_forms)); - if(!forms){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY); - } - forms[opt_count].option = CURLFORM_END; - - opt_count = 0; - for(i = 1; i <= arr_count; ++i){ - int n; - - lua_rawgeti(L, 3, i); - if (lua_type(L, -1) == LUA_TSTRING){ - forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1); - } - else{ - n = lua_rawlen(L, -1); - lua_rawgeti(L, -1, 1); - forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1); - lua_pop(L, 1); - if(n > 1){ - lua_rawgeti(L, -1, 2); - forms[opt_count].option = CURLFORM_CONTENTTYPE; forms[opt_count++].value = luaL_checkstring(L, -1); - lua_pop(L, 1); - } - if(n > 2){ - lua_rawgeti(L, -1, 3); - forms[opt_count].option = CURLFORM_FILENAME; forms[opt_count++].value = luaL_checkstring(L, -1); - lua_pop(L, 1); - } - } - - lua_pop(L, 1); - } - - code = curl_formadd(&p->post, &p->last, - CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len, - CURLFORM_ARRAY, forms, - CURLFORM_END); - - free(forms); - - if(code != CURL_FORMADD_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code); - } - - lua_settop(L, 1); - return 1; -} - -static size_t lcurl_hpost_getter_by_buffer(void *arg, const char *buf, size_t len){ - luaL_Buffer *b = arg; - luaL_addlstring(b, buf, len); - return len; -} - -static size_t call_writer(lua_State *L, int fn, int ctx, const char *buf, size_t len){ - int top = lua_gettop(L); - int n = 1; // number of args - lua_Number ret = (lua_Number)len; - - lua_pushvalue(L, fn); - if(ctx){ - lua_pushvalue(L, ctx); - n += 1; - } - lua_pushlstring(L, buf, len); - - if(lua_pcall(L, n, LUA_MULTRET, 0)) return 0; - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1)) return 0; - if(lua_isboolean(L, top + 1)){ - if(!lua_toboolean(L, top + 1)) ret = 0; - } - else ret = lua_tonumber(L, top + 1); - } - lua_settop(L, top); - - return (size_t)ret; -} - -static size_t lcurl_hpost_getter_by_callback1(void *arg, const char *buf, size_t len){ - lua_State *L = arg; - assert(2 == lua_gettop(L)); - return call_writer(L, 2, 0, buf, len); -} - -static size_t lcurl_hpost_getter_by_callback2(void *arg, const char *buf, size_t len){ - lua_State *L = arg; - assert(3 == lua_gettop(L)); - return call_writer(L, 2, 3, buf, len); -} - -static int lcurl_hpost_get(lua_State *L){ - // get() - // get(fn [, ctx]) - // get(object) - lcurl_hpost_t *p = lcurl_gethpost(L); - CURLcode code; - int top; - - if(lua_isnoneornil(L, 2)){ - luaL_Buffer b; - luaL_buffinit(L, &b); - - code = curl_formget(p->post, &b, lcurl_hpost_getter_by_buffer); - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code); - } - - luaL_pushresult(&b); - return 1; - } - - if(lua_isfunction(L, 2)){ - if(lua_gettop(L) == 2){ - top = 2; - code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback1); - } - else{ - top = 3; - lua_settop(L, 3); - code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback2); - } - } - else if(lua_isuserdata(L, 2) || lua_istable(L, 2)){ - lua_settop(L, 2); - lua_getfield(L, 2, "write"); - luaL_argcheck(L, lua_isfunction(L, -1), 2, "write method not found in object"); - assert(3 == lua_gettop(L)); - lua_insert(L, -2); - top = 3; - code = curl_formget(p->post, L, lcurl_hpost_getter_by_callback2); - } - else{ - lua_pushliteral(L, "invalid writer type"); - return lua_error(L); - } - - if(LCURL_GET_CB_ERROR == code){ - if(((lua_gettop(L) == top+1))&&(lua_isstring(L, -1))){ - return lua_error(L); - } - return lua_gettop(L) - top; - } - - if(code != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code); - } - - lua_settop(L, 1); - return 1; -} - -static int lcurl_hpost_free(lua_State *L){ - lcurl_hpost_t *p = lcurl_gethpost(L); - if(p->post){ - curl_formfree(p->post); - p->post = p->last = 0; - } - - if(p->storage != LUA_NOREF){ - p->storage = lcurl_storage_free(L, p->storage); - } - - lcurl_hpost_stream_free_all(L, p); - - return 0; -} - -//} - -static const struct luaL_Reg lcurl_hpost_methods[] = { - {"add_content", lcurl_hpost_add_content }, - {"add_buffer", lcurl_hpost_add_buffer }, - {"add_file", lcurl_hpost_add_file }, - {"add_stream", lcurl_hpost_add_stream }, - - {"add_files", lcurl_hpost_add_files }, - - {"get", lcurl_hpost_get }, - {"free", lcurl_hpost_free }, - {"__gc", lcurl_hpost_free }, - {"__tostring", lcurl_hpost_to_s }, - - {NULL,NULL} -}; - -void lcurl_hpost_initlib(lua_State *L, int nup){ - if(!lutil_createmetap(L, LCURL_HTTPPOST, lcurl_hpost_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); -} - diff --git a/watchdog/third_party/lua-curl/src/lchttppost.h b/watchdog/third_party/lua-curl/src/lchttppost.h deleted file mode 100644 index 3299069..0000000 --- a/watchdog/third_party/lua-curl/src/lchttppost.h +++ /dev/null @@ -1,47 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCHTTPPOST_H_ -#define _LCHTTPPOST_H_ - -#include "lcurl.h" -#include "lcutils.h" -#include - -#define LCURL_HPOST_STREAM_MAGIC 0xAA - -typedef struct lcurl_hpost_stream_tag{ - unsigned char magic; - - lua_State **L; - lcurl_callback_t rd; - lcurl_read_buffer_t rbuffer; - struct lcurl_hpost_stream_tag *next; -}lcurl_hpost_stream_t; - -typedef struct lcurl_hpost_tag{ - lua_State *L; - struct curl_httppost *post; - struct curl_httppost *last; - int storage; - int err_mode; - lcurl_hpost_stream_t *stream; -}lcurl_hpost_t; - -int lcurl_hpost_create(lua_State *L, int error_mode); - -void lcurl_hpost_initlib(lua_State *L, int nup); - -lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i); - -#define lcurl_gethpost(L) lcurl_gethpost_at((L),1) - - -#endif diff --git a/watchdog/third_party/lua-curl/src/lcinfoeasy.h b/watchdog/third_party/lua-curl/src/lcinfoeasy.h deleted file mode 100644 index 6cbf8a9..0000000 --- a/watchdog/third_party/lua-curl/src/lcinfoeasy.h +++ /dev/null @@ -1,94 +0,0 @@ -OPT_ENTRY( effective_url, EFFECTIVE_URL, STR, 0) -OPT_ENTRY( response_code, RESPONSE_CODE, LNG, 0) -OPT_ENTRY( http_connectcode, HTTP_CONNECTCODE, LNG, 0) -OPT_ENTRY( filetime, FILETIME, LNG, 0) -OPT_ENTRY( total_time, TOTAL_TIME, DBL, 0) -OPT_ENTRY( namelookup_time, NAMELOOKUP_TIME, DBL, 0) -OPT_ENTRY( connect_time, CONNECT_TIME, DBL, 0) -OPT_ENTRY( appconnect_time, APPCONNECT_TIME, DBL, 0) -OPT_ENTRY( pretransfer_time, PRETRANSFER_TIME, DBL, 0) -OPT_ENTRY( starttransfer_time, STARTTRANSFER_TIME, DBL, 0) -OPT_ENTRY( redirect_time, REDIRECT_TIME, DBL, 0) -OPT_ENTRY( redirect_count, REDIRECT_COUNT, LNG, 0) -OPT_ENTRY( redirect_url, REDIRECT_URL, STR, 0) -OPT_ENTRY( size_upload, SIZE_UPLOAD, DBL, 0) -OPT_ENTRY( size_download, SIZE_DOWNLOAD, DBL, 0) -OPT_ENTRY( speed_download, SPEED_DOWNLOAD, DBL, 0) -OPT_ENTRY( speed_upload, SPEED_UPLOAD, DBL, 0) -OPT_ENTRY( header_size, HEADER_SIZE, LNG, 0) -OPT_ENTRY( request_size, REQUEST_SIZE, LNG, 0) -OPT_ENTRY( ssl_verifyresult, SSL_VERIFYRESULT, LNG, 0) -OPT_ENTRY( ssl_engines, SSL_ENGINES, LST, 0) -OPT_ENTRY( content_length_download, CONTENT_LENGTH_DOWNLOAD, DBL, 0) -OPT_ENTRY( content_length_upload, CONTENT_LENGTH_UPLOAD, DBL, 0) -OPT_ENTRY( content_type, CONTENT_TYPE, STR, 0) -OPT_ENTRY( httpauth_avail, HTTPAUTH_AVAIL, LNG, 0) -OPT_ENTRY( proxyauth_avail, PROXYAUTH_AVAIL, LNG, 0) -OPT_ENTRY( os_errno, OS_ERRNO, LNG, 0) -OPT_ENTRY( num_connects, NUM_CONNECTS, LNG, 0) -OPT_ENTRY( primary_ip, PRIMARY_IP, STR, 0) -OPT_ENTRY( certinfo, CERTINFO, CERTINFO, 0) -#if LCURL_CURL_VER_GE(7,21,0) -OPT_ENTRY( primary_port, PRIMARY_PORT, LNG, 0) -OPT_ENTRY( local_ip, LOCAL_IP, STR, 0) -OPT_ENTRY( local_port, LOCAL_PORT, LNG, 0) -#endif -OPT_ENTRY( cookielist, COOKIELIST, LST, 0) -OPT_ENTRY( lastsocket, LASTSOCKET, LNG, 0) -OPT_ENTRY( ftp_entry_path, FTP_ENTRY_PATH, STR, 0) -OPT_ENTRY( condition_unmet, CONDITION_UNMET, LNG, 0) -#if LCURL_CURL_VER_GE(7,20,0) -OPT_ENTRY( rtsp_session_id, RTSP_SESSION_ID, STR, 0) -OPT_ENTRY( rtsp_client_cseq, RTSP_CLIENT_CSEQ, LNG, 0) -OPT_ENTRY( rtsp_server_cseq, RTSP_SERVER_CSEQ, LNG, 0) -OPT_ENTRY( rtsp_cseq_recv, RTSP_CSEQ_RECV, LNG, 0) -#endif - -#if LCURL_CURL_VER_GE(7,50,1) -OPT_ENTRY( http_version, HTTP_VERSION, LNG, 0) -#endif - -#if LCURL_CURL_VER_GE(7,52,0) -OPT_ENTRY( proxy_ssl_verifyresult, PROXY_SSL_VERIFYRESULT, LNG, 0) -OPT_ENTRY( protocol, PROTOCOL, LNG, 0) -OPT_ENTRY( scheme, SCHEME, STR, 0) -#endif - -#if LCURL_CURL_VER_GE(7,55,0) -OPT_ENTRY( content_length_download_t, CONTENT_LENGTH_DOWNLOAD_T, OFF, 0) -OPT_ENTRY( content_length_upload_t, CONTENT_LENGTH_UPLOAD_T, OFF, 0) -OPT_ENTRY( size_download_t, SIZE_DOWNLOAD_T, OFF, 0) -OPT_ENTRY( size_upload_t, SIZE_UPLOAD_T, OFF, 0) -OPT_ENTRY( speed_download_t, SPEED_DOWNLOAD_T, OFF, 0) -OPT_ENTRY( speed_upload_t, SPEED_UPLOAD_T, OFF, 0) -#endif - -#if LCURL_CURL_VER_GE(7,59,0) -OPT_ENTRY( filetime_t, FILETIME_T, OFF, 0) -#endif - -#if LCURL_CURL_VER_GE(7,61,0) -OPT_ENTRY(appconnect_time_t, APPCONNECT_TIME_T, OFF, 0) -OPT_ENTRY(connect_time_t, CONNECT_TIME_T, OFF, 0) -OPT_ENTRY(namelookup_time_t, NAMELOOKUP_TIME_T, OFF, 0) -OPT_ENTRY(pretransfer_time_t, PRETRANSFER_TIME_T, OFF, 0) -OPT_ENTRY(redirect_time_t, REDIRECT_TIME_T, OFF, 0) -OPT_ENTRY(starttransfer_time_t, STARTTRANSFER_TIME_T, OFF, 0) -OPT_ENTRY(total_time_t, TOTAL_TIME_T, OFF, 0) -#endif - -#if LCURL_CURL_VER_GE(7,66,0) -OPT_ENTRY(retry_after, RETRY_AFTER, OFF, 0) -#endif - -#if LCURL_CURL_VER_GE(7,72,0) -OPT_ENTRY(effective_method, EFFECTIVE_METHOD, STR, 0) -#endif - -#if LCURL_CURL_VER_GE(7,73,0) -OPT_ENTRY(proxy_error, PROXY_ERROR, LNG, 0) -#endif - -// OPT_ENTRY( PRIVATE, void ) -// OPT_ENTRY( TLS_SSL_PTR, struct curl_tlssessioninfo ** -// OPT_ENTRY( TLS_SESSION, struct curl_tlssessioninfo * diff --git a/watchdog/third_party/lua-curl/src/lcmime.c b/watchdog/third_party/lua-curl/src/lcmime.c deleted file mode 100644 index 90fd6b4..0000000 --- a/watchdog/third_party/lua-curl/src/lcmime.c +++ /dev/null @@ -1,686 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2017-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lcmime.h" -#include "lceasy.h" -#include "lcerror.h" -#include "lcutils.h" - -/* API Notes. - * 1. Each mime can be root or child. If mime is a child (subpart) then curl free it - * when parent mime is freed or when remove this part from parent. There no way reuse same mime. - * Its not clear is it possible use mime created by one easy handle when do preform in another. - * `m=e1:mime() e2:setopt_httpmime(m) e1:close() e2:perform()` - * - * // Attach child to root (root also can have parent) - * curl_mime_subparts(root, child); - * - * // curl free `child` and all its childs - * curl_mime_subparts(root, other_child_or_null); - * - * // forbidden - * curl_mime_free(child); - */ - -#if LCURL_CURL_VER_GE(7,56,0) - -#define LCURL_MIME_NAME LCURL_PREFIX" MIME" -static const char *LCURL_MIME = LCURL_MIME_NAME; - -#define LCURL_MIME_PART_NAME LCURL_PREFIX" MIME Part" -static const char *LCURL_MIME_PART = LCURL_MIME_PART_NAME; - -//{ Free mime and subparts - -static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it); - -static lcurl_mime_t* lcurl_mime_part_get_subparts(lua_State *L, lcurl_mime_part_t *part){ - lcurl_mime_t *sub = NULL; - - if(LUA_NOREF != part->subpart_ref){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, part->subpart_ref); - sub = lcurl_getmime_at(L, -1); - lua_pop(L, 1); - } - - return sub; -} - -static int lcurl_mime_part_reset(lua_State *L, lcurl_mime_part_t *p){ - p->part = NULL; - - luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); - - p->headers_ref = p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; - - /*free only if we have no parents*/ - lcurl_mime_part_remove_subparts(L, p, 0); - - return 0; -} - -static int lcurl_mime_reset(lua_State *L, lcurl_mime_t *p){ - lcurl_mime_part_t *ptr; - - /* reset all parts*/ - for(ptr = p->parts; ptr; ptr=ptr->next){ - lcurl_mime_part_reset(L, ptr); - } - - if(LUA_NOREF != p->storage){ - p->storage = lcurl_storage_free(L, p->storage); - } - - p->parts = p->parent = NULL; - p->mime = NULL; - - /* remove weak reference to easy */ - lua_pushnil(L); - lua_rawsetp(L, LCURL_MIME_EASY, p); - - return 0; -} - -static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it){ - lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, p); - if(sub){ - assert(LUA_NOREF != p->subpart_ref); - /* detach `subpart` mime from current mime part */ - /* if set `sub->parent = NULL` then gc for mime will try free curl_mime_free. */ - - luaL_unref(L, LCURL_LUA_REGISTRY, p->subpart_ref); - p->subpart_ref = LUA_NOREF; - - if(p->part && free_it){ - curl_mime_subparts(p->part, NULL); - } - - /* seems curl_mime_subparts(h, NULL) free asubparts. - so we have to invalidate all reference to all nested objects (part/mime). - NOTE. All resources already feed. So just need set all pointers to NULL - and free all Lua resources (like references and storages) - */ - { - lcurl_mime_part_t *ptr; - /* reset all parts*/ - for(ptr = sub->parts; ptr; ptr=ptr->next){ - lcurl_mime_part_remove_subparts(L, p, 0); - } - lcurl_mime_reset(L, sub); - } - } -} - -//} - -int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ - lcurl_mime_part_t *part; - for(part = p->parts; part; part=part->next){ - lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, part); - if(sub) lcurl_mime_set_lua(L, sub, v); - part->L = v; - } - return 0; -} - -#define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) -#define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE -#define IS_FALSE(L, i) ((lua_type(L, i) == LUA_TBOOLEAN) && (!lua_toboolean(L, i))) || lutil_is_null(L,i) -#define IS_OPTSTR(L, i) (IS_FALSE(L, i)) || (IS_NILORSTR(L, i)) - -static int lutil_isarray(lua_State *L, int i){ - int ret = 0; - i = lua_absindex(L, i); - lua_pushnil(L); - if(lua_next(L, i)){ - ret = lua_isnumber(L, -2); - lua_pop(L, 2); - } - return ret; -} - -static int lcurl_mime_part_assign(lua_State *L, int part, const char *method){ - int top = lua_gettop(L); - - lua_pushvalue(L, part); - lua_insert(L, -2); - lua_getfield(L, -2, method); - lua_insert(L, -3); - lua_call(L, 2, LUA_MULTRET); - - return lua_gettop(L) - top + 1; -} - -static const char *lcurl_mime_part_fields[] = { - "data", "filedata", "name", "filename", "headers", "encoder", "type", NULL -}; - -static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ - int top = lua_gettop(L); - const char *method; int i; - - part = lua_absindex(L, part); - t = lua_absindex(L, t); - - if(lutil_isarray(L, t)){ - int ret; - lua_pushvalue(L, t); - ret = lcurl_mime_part_assign(L, part, "headers"); - if(ret != 1) return ret; - - lua_pop(L, 1); - - assert(top == lua_gettop(L)); - } - else{ - for(i=0;method = lcurl_mime_part_fields[i]; ++i){ - lua_getfield(L, t, method); - if(!lua_isnil(L, -1)){ - int ret = lcurl_mime_part_assign(L, part, method); - if(ret != 1) return ret; - } - lua_pop(L, 1); - - assert(top == lua_gettop(L)); - } - - lua_getfield(L, t, "subparts"); - if(!lua_isnil(L, -1)){ - if(IS_FALSE(L, -1) || lcurl_getmime_at(L, -1)){ - int ret = lcurl_mime_part_assign(L, part, "subparts"); - if(ret != 1) return ret; - } - } - lua_pop(L, 1); - assert(top == lua_gettop(L)); - } - - return 0; -} - -//{ MIME - -static lcurl_mime_part_t* lcurl_mime_parts_append(lcurl_mime_t *m, lcurl_mime_part_t *p){ - if(!m->parts) m->parts = p; - else{ - lcurl_mime_part_t *ptr = m->parts; - while(ptr->next)ptr = ptr->next; - ptr->next = p; - } - return p; -} - -static lcurl_mime_part_t* lcurl_mime_parts_find(lcurl_mime_t *m, lcurl_mime_part_t *p){ - lcurl_mime_part_t *ptr; - - for(ptr = m->parts; ptr; ptr = ptr->next){ - if(ptr == p) return p; - } - - return NULL; -} - -int lcurl_mime_create(lua_State *L, int error_mode){ - //! @todo make this function as method of easy handle - lcurl_easy_t *e = lcurl_geteasy(L); - - lcurl_mime_t *p = lutil_newudatap(L, lcurl_mime_t, LCURL_MIME); - - p->mime = curl_mime_init(e->curl); - - //! @todo return more accurate error category/code - if(!p->mime) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); - - p->storage = lcurl_storage_init(L); - p->err_mode = error_mode; - p->parts = p->parent = NULL; - - /* weak reference from mime to easy handle */ - lua_pushvalue(L, 1); - lua_rawsetp(L, LCURL_MIME_EASY, (void*)p); - - return 1; -} - -lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i){ - lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, i, LCURL_MIME); - luaL_argcheck (L, p != NULL, i, LCURL_MIME_NAME" object expected"); - luaL_argcheck (L, p->mime != NULL, i, LCURL_MIME_NAME" object freed"); - return p; -} - -static int lcurl_mime_to_s(lua_State *L){ - lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); - luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); - - lua_pushfstring(L, LCURL_MIME_NAME" (%p)%s", (void*)p, - p->mime ? (p->parent ? " (subpart)" : "") : " (freed)" - ); - return 1; -} - -static int lcurl_mime_free(lua_State *L){ - lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); - luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); - - if((p->mime) && (NULL == p->parent)){ - curl_mime_free(p->mime); - } - - return lcurl_mime_reset(L, p); -} - -static int lcurl_mime_addpart(lua_State *L){ - lcurl_mime_t *p = lcurl_getmime(L); - int ret; - - lua_settop(L, 2); - - ret = lcurl_mime_part_create(L, p->err_mode); - if(ret != 1) return ret; - - /* store mime part in storage */ - lcurl_storage_preserve_value(L, p->storage, lua_absindex(L, -1)); - lcurl_mime_parts_append(p, lcurl_getmimepart_at(L, -1)); - - if(lua_istable(L, 2)){ - ret = lcurl_mime_part_assing_table(L, 3, 2); - if(ret) return ret; - } - - return 1; -} - -static int lcurl_mime_easy(lua_State *L){ - lcurl_mime_t *p = lcurl_getmime(L); - lua_rawgetp(L, LCURL_MIME_EASY, p); - return 1; -} - -//} - -//{ MIME Part - -int lcurl_mime_part_create(lua_State *L, int error_mode){ - //! @todo make this function as method of mime handle - lcurl_mime_t *m = lcurl_getmime(L); - - lcurl_mime_part_t *p = lutil_newudatap(L, lcurl_mime_part_t, LCURL_MIME_PART); - - p->part = curl_mime_addpart(m->mime); - - //! @todo return more accurate error category/code - if(!p->part) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); - - p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; - p->err_mode = error_mode; - p->subpart_ref = p->headers_ref = LUA_NOREF; - p->parent = m; - - return 1; -} - -lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i){ - lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, i, LCURL_MIME_PART); - luaL_argcheck (L, p != NULL, i, LCURL_MIME_PART_NAME" object expected"); - luaL_argcheck (L, p->part != NULL, i, LCURL_MIME_PART_NAME" object freed"); - return p; -} - -static int lcurl_mime_part_to_s(lua_State *L){ - lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); - luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); - - lua_pushfstring(L, LCURL_MIME_PART_NAME" (%p)%s", (void*)p, p->part ? "" : " (freed)"); - return 1; -} - -static int lcurl_mime_part_free(lua_State *L){ - lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); - luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); - - lcurl_mime_part_reset(L, p); - - return 0; -} - -static int lcurl_mime_part_assing_ext(lua_State *L, int part, int i){ -#define UNSET_VALUE (const char*)-1 - - const char *mime_type = NULL, *mime_name = NULL, *mime_fname = NULL; - int headers = 0; - CURLcode ret; - lcurl_mime_part_t *p = lcurl_getmimepart_at(L, part); - - if(IS_TABLE(L, i)) headers = i; - else if (IS_OPTSTR(L, i)) { - mime_type = IS_FALSE(L, i) ? UNSET_VALUE : lua_tostring(L, i); - if(IS_TABLE(L, i+1)) headers = i+1; - else if(IS_OPTSTR(L, i+1)){ - mime_name = IS_FALSE(L, i+1) ? UNSET_VALUE : lua_tostring(L, i+1); - if(IS_TABLE(L, i+2)) headers = i+2; - else if(IS_OPTSTR(L, i+2)){ - mime_fname = IS_FALSE(L, i+2) ? UNSET_VALUE : lua_tostring(L, i+2); - if(IS_TABLE(L, i+3)) headers = i+3; - else if(IS_FALSE(L, i+3)){ - headers = -1; - } - } - } - } - - if(mime_type){ - ret = curl_mime_type(p->part, mime_type == UNSET_VALUE ? NULL : mime_type); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - } - - if(mime_name){ - ret = curl_mime_name(p->part, mime_name == UNSET_VALUE ? NULL : mime_name); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - } - - if(mime_fname){ - ret = curl_mime_filename(p->part, mime_fname == UNSET_VALUE ? NULL : mime_fname); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - } - - if(headers){ - if(-1 == headers){ - ret = curl_mime_headers(p->part, NULL, 0); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - } - else - return lcurl_mime_part_assing_table(L, part, headers); - } - - return 0; - -#undef UNSET_VALUE -} - -// part:data(str[, type[, name[, filename]]][, headers]) -static int lcurl_mime_part_data(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - size_t len; const char *data; - CURLcode ret; - - if(IS_FALSE(L, 2)){ - data = NULL; - len = 0; - } - else{ - data = luaL_checklstring(L, 2, &len); - /*string too long*/ - if(len == CURL_ZERO_TERMINATED){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); - } - } - - /* curl_mime_data copies data */ - ret = curl_mime_data(p->part, data, len); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - if (lua_gettop(L) > 2){ - int res = lcurl_mime_part_assing_ext(L, 1, 3); - if (res) return res; - } - - lua_settop(L, 1); - return 1; -} - -// part:subparts(mime[, type[, name]][, headers]) -static int lcurl_mime_part_subparts(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - lcurl_mime_t *mime = lcurl_getmime_at(L, 2); - CURLcode ret; - - /* we can attach mime to only one part */ - if(mime->parent){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); - } - - /* if we already have one subpart then libcurl free it so we can not use any references to it */ - lcurl_mime_part_remove_subparts(L, p, 1); - - ret = curl_mime_subparts(p->part, mime->mime); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - lua_pushvalue(L, 2); - p->subpart_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - mime->parent = p; - - if (lua_gettop(L) > 2){ - int res = lcurl_mime_part_assing_ext(L, 1, 3); - if (res) return res; - } - - lua_settop(L, 1); - return 1; -} - -// part:filedata(path[, type[, name[, filename]]][, headers]) -static int lcurl_mime_part_filedata(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *data = luaL_checkstring(L, 2); - CURLcode ret; - - ret = curl_mime_filedata(p->part, data); - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - if (lua_gettop(L) > 2){ - int res = lcurl_mime_part_assing_ext(L, 1, 3); - if (res) return res; - } - - lua_settop(L, 1); - return 1; -} - -// part:headers(t) -static int lcurl_mime_part_headers(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - struct curl_slist *list; - CURLcode ret; - - if(IS_FALSE(L, 2)){ - list = NULL; - } - else{ - list = lcurl_util_to_slist(L, 2); - luaL_argcheck(L, list || IS_TABLE(L, 2), 2, "array or null expected"); - } - - ret = curl_mime_headers(p->part, list, 1); - - if(ret != CURLE_OK){ - if(list) curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - lua_settop(L, 1); - return 1; -} - -// part:type(t) -static int lcurl_mime_part_type(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *mime_type; - CURLcode ret; - - if(IS_FALSE(L, 2)){ - mime_type = NULL; - } - else{ - mime_type = luaL_checkstring(L, 2); - } - - ret = curl_mime_type(p->part, mime_type); - - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - lua_settop(L, 1); - return 1; -} - -// part:name(t) -static int lcurl_mime_part_name(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *mime_name; - CURLcode ret; - - if(IS_FALSE(L, 2)){ - mime_name = NULL; - } - else{ - mime_name = luaL_checkstring(L, 2); - } - ret = curl_mime_name(p->part, mime_name); - - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - lua_settop(L, 1); - return 1; -} - -// part:filename(t) -static int lcurl_mime_part_filename(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *mime_name; - CURLcode ret; - - if(IS_FALSE(L, 2)){ - mime_name = NULL; - } - else{ - mime_name = luaL_checkstring(L, 2); - } - ret = curl_mime_filename(p->part, mime_name); - - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - lua_settop(L, 1); - return 1; -} - -// part:encoder(t) -static int lcurl_mime_part_encoder(lua_State *L){ - lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *mime_encode; - CURLcode ret; - - if(IS_FALSE(L, 2)){ - mime_encode = NULL; - } - else{ - mime_encode = luaL_checkstring(L, 2); - } - ret = curl_mime_encoder(p->part, mime_encode); - - if(ret != CURLE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - - lua_settop(L, 1); - return 1; -} - -//} - -static const struct luaL_Reg lcurl_mime_methods[] = { - - {"addpart", lcurl_mime_addpart }, - {"easy", lcurl_mime_easy }, - - {"free", lcurl_mime_free }, - {"__gc", lcurl_mime_free }, - {"__tostring", lcurl_mime_to_s }, - - {NULL,NULL} -}; - -static const struct luaL_Reg lcurl_mime_part_methods[] = { - - {"subparts", lcurl_mime_part_subparts }, - {"data", lcurl_mime_part_data }, - {"filedata", lcurl_mime_part_filedata }, - {"headers", lcurl_mime_part_headers }, - {"name", lcurl_mime_part_name }, - {"filename", lcurl_mime_part_filename }, - {"type", lcurl_mime_part_type }, - {"encoder", lcurl_mime_part_encoder }, - - - {"free", lcurl_mime_part_free }, - {"__gc", lcurl_mime_part_free }, - {"__tostring", lcurl_mime_part_to_s }, - - {NULL,NULL} -}; - -static int lcurl_pushvalues(lua_State *L, int nup) { - assert(lua_gettop(L) >= nup); - - if (nup > 0) { - int b = lua_absindex(L, -nup); - int e = lua_absindex(L, -1); - int i; - - lua_checkstack(L, nup); - - for(i = b; i <= e; ++i) - lua_pushvalue(L, i); - } - - return nup; -} - -#endif - -void lcurl_mime_initlib(lua_State *L, int nup){ -#if LCURL_CURL_VER_GE(7,56,0) - lcurl_pushvalues(L, nup); - - if(!lutil_createmetap(L, LCURL_MIME, lcurl_mime_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - - if(!lutil_createmetap(L, LCURL_MIME_PART, lcurl_mime_part_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - -#else - lua_pop(L, nup); -#endif -} - diff --git a/watchdog/third_party/lua-curl/src/lcmime.h b/watchdog/third_party/lua-curl/src/lcmime.h deleted file mode 100644 index 6dbe9c5..0000000 --- a/watchdog/third_party/lua-curl/src/lcmime.h +++ /dev/null @@ -1,66 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2017-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCMIME_H_ -#define _LCMIME_H_ - -#include "lcurl.h" -#include "lcutils.h" -#include - -void lcurl_mime_initlib(lua_State *L, int nup); - -#if LCURL_CURL_VER_GE(7,56,0) - -typedef struct lcurl_mime_part_tag{ - lua_State *L; - - lcurl_callback_t rd; - lcurl_read_buffer_t rbuffer; - - curl_mimepart *part; - - struct lcurl_mime_tag *parent; /*always set and can not be changed*/ - - int subpart_ref; - int headers_ref; - - int err_mode; - - struct lcurl_mime_part_tag *next; -}lcurl_mime_part_t; - -typedef struct lcurl_mime_tag{ - curl_mime *mime; - - int storage; - int err_mode; - - lcurl_mime_part_t *parts; - lcurl_mime_part_t *parent; /*after set there no way change it*/ -}lcurl_mime_t; - -int lcurl_mime_create(lua_State *L, int error_mode); - -lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i); - -#define lcurl_getmime(L) lcurl_getmime_at((L), 1) - -int lcurl_mime_part_create(lua_State *L, int error_mode); - -lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i); - -#define lcurl_getmimepart(L) lcurl_getmimepart_at((L), 1) - -int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v); - -#endif - -#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcmulti.c b/watchdog/third_party/lua-curl/src/lcmulti.c deleted file mode 100644 index b040083..0000000 --- a/watchdog/third_party/lua-curl/src/lcmulti.c +++ /dev/null @@ -1,670 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#if defined(_WINDOWS) || defined(_WIN32) -# define LCURL_WINDOWS -#endif - -#ifdef LCURL_WINDOWS -# include -#else -# include -#endif - -#include "lcurl.h" -#include "lceasy.h" -#include "lcmulti.h" -#include "lcerror.h" -#include "lcutils.h" -#include "lchttppost.h" - -#define LCURL_MULTI_NAME LCURL_PREFIX" Multi" -static const char *LCURL_MULTI = LCURL_MULTI_NAME; - -#if defined(DEBUG) || defined(_DEBUG) -static void lcurl__multi_validate_sate(lua_State *L, lcurl_multi_t *p){ - int top = lua_gettop(L); - - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - assert(lua_istable(L, -1)); - - lua_pushnil(L); - while(lua_next(L, -2)){ - lcurl_easy_t *e = lcurl_geteasy_at(L, -1); - void *ptr = lua_touserdata(L, -2); - - assert(e->curl == ptr); - assert(e->multi == p); - assert(e->L == p->L); - - lua_pop(L, 1); - } - - lua_pop(L, 1); - assert(lua_gettop(L) == top); -} -#else -# define lcurl__multi_validate_sate(L, p) (void*)(0) -#endif - -void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy){ - lcurl__multi_validate_sate(L, p); - - if((assign_easy)&&(p->L != value)){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushnil(L); - while(lua_next(L, -2)){ - lcurl_easy_t *e = lcurl_geteasy_at(L, -1); - lcurl__easy_assign_lua(L, e, value, 0); - lua_pop(L, 1); - } - lua_pop(L, 1); - } - - p->L = value; -} - -//{ - -int lcurl_multi_create(lua_State *L, int error_mode){ - lcurl_multi_t *p; - - lua_settop(L, 1); - - p = lutil_newudatap(L, lcurl_multi_t, LCURL_MULTI); - p->curl = curl_multi_init(); - p->err_mode = error_mode; - if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_INTERNAL_ERROR); - p->L = NULL; - lcurl_util_new_weak_table(L, "v"); - p->h_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; - p->sc.cb_ref = p->sc.ud_ref = LUA_NOREF; - - if(lua_type(L, 1) == LUA_TTABLE){ - int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); - if(ret) return ret; - assert(lua_gettop(L) == 2); - } - - return 1; -} - -lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i){ - lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, i, LCURL_MULTI); - luaL_argcheck (L, p != NULL, 1, LCURL_MULTI_NAME" object expected"); - return p; -} - -static int lcurl_multi_to_s(lua_State *L){ - lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, 1, LCURL_MULTI); - lua_pushfstring(L, LCURL_MULTI_NAME" (%p)", (void*)p); - return 1; -} - -static int lcurl_multi_cleanup(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - if(p->curl){ - curl_multi_cleanup(p->curl); - p->curl = NULL; - } - - if(p->h_ref != LUA_NOREF){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushnil(L); - while(lua_next(L, -2)){ - lcurl_easy_t *e = lcurl_geteasy_at(L, -1); - e->multi = NULL; - lua_pop(L, 1); - } - lua_pop(L, 1); - luaL_unref(L, LCURL_LUA_REGISTRY, p->h_ref); - p->h_ref = LUA_NOREF; - } - - luaL_unref(L, LCURL_LUA_REGISTRY, p->tm.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->tm.ud_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->sc.cb_ref); - luaL_unref(L, LCURL_LUA_REGISTRY, p->sc.ud_ref); - p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; - p->sc.cb_ref = p->sc.ud_ref = LUA_NOREF; - - lua_settop(L, 1); - lua_pushnil(L); - lua_rawset(L, LCURL_USERVALUES); - - return 0; -} - -static int lcurl_multi_add_handle(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - lcurl_easy_t *e = lcurl_geteasy_at(L, 2); - CURLMcode code; - lua_State *curL; - - if(e->multi){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, -#if LCURL_CURL_VER_GE(7,32,1) - CURLM_ADDED_ALREADY -#else - CURLM_BAD_EASY_HANDLE -#endif - ); - } - - // From doc: - // If you have CURLMOPT_TIMERFUNCTION set in the multi handle, - // that callback will be called from within this function to ask - // for an updated timer so that your main event loop will get - // the activity on this handle to get started. - // - // So we should add easy before this call - // call chain may be like => timerfunction->socket_action->socketfunction - lua_settop(L, 2); - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushvalue(L, 2); - lua_rawsetp(L, -2, e->curl); - lua_settop(L, 1); - - // all `esay` handles have to have same L - lcurl__easy_assign_lua(L, e, p->L, 0); - - e->multi = p; - - curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); - code = curl_multi_add_handle(p->curl, e->curl); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__multi_assign_lua(L, p, curL, 1); - - if(code != CURLM_OK){ - // remove - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushnil(L); - lua_rawsetp(L, -2, e->curl); - e->multi = NULL; - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - return 1; -} - -static int lcurl_multi_remove_handle(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - lcurl_easy_t *e = lcurl_geteasy_at(L, 2); - CURLMcode code = lcurl__multi_remove_handle(L, p, e); - - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - - lua_settop(L, 1); - return 1; -} - -CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e){ - CURLMcode code; - lua_State *curL; - - if(e->multi != p){ - // cURL returns CURLM_OK for such call so we do the same. - // tested on 7.37.1 - return CURLM_OK; - } - - curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); - code = curl_multi_remove_handle(p->curl, e->curl); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__multi_assign_lua(L, p, curL, 1); - - if(code == CURLM_OK){ - e->multi = NULL; - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushnil(L); - lua_rawsetp(L, -2, e->curl); - lua_pop(L, 1); - } - - return code; -} - -static int lcurl_multi_perform(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - int running_handles = 0; - CURLMcode code; - lua_State *curL; - - curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); - while((code = curl_multi_perform(p->curl, &running_handles)) == CURLM_CALL_MULTI_PERFORM); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__multi_assign_lua(L, p, curL, 1); - - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - lua_pushnumber(L, running_handles); - return 1; -} - -static int lcurl_multi_info_read(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - int msgs_in_queue = 0; - CURLMsg *msg = curl_multi_info_read(p->curl, &msgs_in_queue); - int remove = lua_toboolean(L, 2); - - lcurl_easy_t *e; - if(!msg){ - lua_pushnumber(L, msgs_in_queue); - return 1; - } - - if(msg->msg == CURLMSG_DONE){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_rawgetp(L, -1, msg->easy_handle); - e = lcurl_geteasy_at(L, -1); - if(remove){ - //! @fixme We ignore any errors - CURLMcode code; - lua_State *curL; - - curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); - code = curl_multi_remove_handle(p->curl, e->curl); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__multi_assign_lua(L, p, curL, 1); - - if(CURLM_OK == code){ - e->multi = NULL; - lua_pushnil(L); - lua_rawsetp(L, -3, e->curl); - } - } - if(msg->data.result == CURLE_OK){ - lua_pushboolean(L, 1); - return 2; - } - return 1 + lcurl_fail_ex(L, LCURL_ERROR_RETURN, LCURL_ERROR_EASY, msg->data.result); - } - - // @todo handle unknown message - lua_pushboolean(L, 0); - return 1; -} - -static int lcurl_multi_wait(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - CURLMcode code; - int maxfd; long ms; - - if(lua_isnoneornil(L, 2)){ - code = curl_multi_timeout(p->curl, &ms); - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - } - else{ - ms = luaL_checklong(L, 2); - } - - if(ms < 0){ - /* if libcurl returns a -1 timeout here, it just means that libcurl - currently has no stored timeout value. You must not wait too long - (more than a few seconds perhaps) before you call - curl_multi_perform() again. - */ - ms = 1000; - } - -#if LCURL_CURL_VER_GE(7,28,0) - //! @todo supports extra_fds - code = curl_multi_wait(p->curl, 0, 0, ms, &maxfd); - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - lua_pushnumber(L, maxfd); - return 1; -#else - { - fd_set fdread, fdwrite, fdexcep; - - FD_ZERO(&fdread); - FD_ZERO(&fdwrite); - FD_ZERO(&fdexcep); - - code = curl_multi_fdset(p->curl, &fdread, &fdwrite, &fdexcep, &maxfd); - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - - //if(maxfd > 0) - { - struct timeval tv; - tv.tv_sec = ms / 1000; - tv.tv_usec = (ms % 1000) * 1000; - - maxfd = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv); - if(maxfd < 0){ - //! @fixme return error - } - } - - lua_pushnumber(L, maxfd); - return 1; - } -#endif -} - -static int lcurl_multi_timeout(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - long n; - CURLMcode code = curl_multi_timeout(p->curl, &n); - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - lua_pushnumber(L, n); - return 1; -} - -static int lcurl_multi_socket_action(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - curl_socket_t s = lcurl_opt_os_socket(L, 2, CURL_SOCKET_TIMEOUT); - CURLMcode code; int n, mask; - lua_State *curL; - - if(s == CURL_SOCKET_TIMEOUT) mask = lutil_optint64(L, 3, 0); - else mask = lutil_checkint64(L, 3); - - curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); - code = curl_multi_socket_action(p->curl, s, mask, &n); -#ifndef LCURL_RESET_NULL_LUA - if(curL != NULL) -#endif - lcurl__multi_assign_lua(L, p, curL, 1); - - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - lua_pushinteger(L, n); - return 1; -} - -//{ OPTIONS -static int lcurl_opt_set_long_(lua_State *L, int opt){ - lcurl_multi_t *p = lcurl_getmulti(L); - long val; CURLMcode code; - - if(lua_isboolean(L, 2)) val = lua_toboolean(L, 2); - else{ - luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); - val = luaL_checklong(L, 2); - } - - code = curl_multi_setopt(p->curl, opt, val); - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - lua_settop(L, 1); - return 1; -} - -static int lcurl_opt_set_string_array_(lua_State *L, int opt){ - lcurl_multi_t *p = lcurl_getmulti(L); - CURLMcode code; - int n; - - if (lutil_is_null(L, 2)) { - n = 0; - } - else { - luaL_argcheck(L, lua_type(L, 2) == LUA_TTABLE, 2, "array expected"); - n = lua_rawlen(L, 2); - } - - if(n == 0){ - code = curl_multi_setopt(p->curl, opt, 0); - } - else{ - int i; - char const**val = malloc(sizeof(char*) * (n + 1)); - if(!val){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_OUT_OF_MEMORY); - } - for(i = 1; i <= n; ++i){ - lua_rawgeti(L, 2, i); - val[i-1] = lua_tostring(L, -1); - lua_pop(L, 1); - } - val[n] = NULL; - code = curl_multi_setopt(p->curl, opt, val); - free((void*)val); - } - - if(code != CURLM_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); - } - - lua_settop(L, 1); - return 1; -} - -#define LCURL_LNG_OPT(N, S) static int lcurl_multi_set_##N(lua_State *L){\ - return lcurl_opt_set_long_(L, CURLMOPT_##N);\ -} - -#define LCURL_STR_ARR_OPT(N, S) static int lcurl_multi_set_##N(lua_State *L){\ - return lcurl_opt_set_string_array_(L, CURLMOPT_##N);\ -} - -#define OPT_ENTRY(L, N, T, S) LCURL_##T##_OPT(N, S) - -#include "lcoptmulti.h" - -#undef OPT_ENTRY -#undef LCURL_LNG_OPT -#undef LCURL_STR_ARR_OPT - -//} - -//{ CallBack - -static int lcurl_multi_set_callback(lua_State *L, - lcurl_multi_t *p, lcurl_callback_t *c, - int OPT_CB, int OPT_UD, - const char *method, void *func -) -{ - lcurl_set_callback(L, c, 2, method); - - curl_multi_setopt(p->curl, OPT_CB, (c->cb_ref == LUA_NOREF)?0:func); - curl_multi_setopt(p->curl, OPT_UD, (c->cb_ref == LUA_NOREF)?0:p); - - return 1; -} - -//{ Timer - -int lcurl_multi_timer_callback(CURLM *multi, long ms, void *arg){ - lcurl_multi_t *p = arg; - lua_State *L = p->L; - int n, top, ret = 0; - - assert(NULL != p->L); - - top = lua_gettop(L); - n = lcurl_util_push_cb(L, &p->tm); - - lua_pushnumber(L, ms); - if(lua_pcall(L, n, LUA_MULTRET, 0)){ - assert(lua_gettop(L) >= top); - lua_settop(L, top); //! @todo - // lua_pushlightuserdata(L, (void*)LCURL_ERROR_TAG); - // lua_insert(L, top+1); - return -1; - } - - if(lua_gettop(L) > top){ - if(lua_isnil(L, top + 1)){ - lua_settop(L, top); - return -1; - } - - if(lua_isboolean(L, top + 1)) - ret = lua_toboolean(L, top + 1)?0:-1; - else ret = lua_tointeger(L, top + 1); - } - - lua_settop(L, top); - return ret; -} - -static int lcurl_multi_set_TIMERFUNCTION(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - return lcurl_multi_set_callback(L, p, &p->tm, - CURLMOPT_TIMERFUNCTION, CURLMOPT_TIMERDATA, - "timer", lcurl_multi_timer_callback - ); -} - -//} - -//{ Socket - -static int lcurl_multi_socket_callback(CURL *easy, curl_socket_t s, int what, void *arg, void *socketp){ - lcurl_multi_t *p = arg; - lua_State *L = p->L; - lcurl_easy_t *e; - int n, top; - - assert(NULL != p->L); - - top = lua_gettop(L); - n = lcurl_util_push_cb(L, &p->sc); - - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_rawgetp(L, -1, easy); - e = lcurl_geteasy_at(L, -1); - lua_remove(L, -2); - lcurl_push_os_socket(L, s); - lua_pushinteger(L, what); - - if(lua_pcall(L, n+2, 0, 0)){ - assert(lua_gettop(L) >= top); - lua_settop(L, top); - return -1; //! @todo break perform - } - - lua_settop(L, top); - return 0; -} - -static int lcurl_multi_set_SOCKETFUNCTION(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - return lcurl_multi_set_callback(L, p, &p->sc, - CURLMOPT_SOCKETFUNCTION, CURLMOPT_SOCKETDATA, - "socket", lcurl_multi_socket_callback - ); -} - -//} - -//} - -static int lcurl_multi_setopt(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - int opt; - - luaL_checkany(L, 2); - - if(lua_type(L, 2) == LUA_TTABLE){ - int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); - if(ret) return ret; - lua_settop(L, 1); - return 1; - } - - opt = luaL_checklong(L, 2); - lua_remove(L, 2); - -#define OPT_ENTRY(l, N, T, S) case CURLMOPT_##N: return lcurl_multi_set_##N(L); - switch(opt){ - #include "lcoptmulti.h" - OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) - OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) - } -#undef OPT_ENTRY - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_UNKNOWN_OPTION); -} - -static int lcurl_multi_setdata(lua_State *L){ - lua_settop(L, 2); - lua_pushvalue(L, 1); - lua_insert(L, 2); - lua_rawset(L, LCURL_USERVALUES); - return 1; -} - -static int lcurl_multi_getdata(lua_State *L){ - lua_settop(L, 1); - lua_rawget(L, LCURL_USERVALUES); - return 1; -} - -//} - -static const struct luaL_Reg lcurl_multi_methods[] = { - {"add_handle", lcurl_multi_add_handle }, - {"remove_handle", lcurl_multi_remove_handle }, - {"perform", lcurl_multi_perform }, - {"info_read", lcurl_multi_info_read }, - {"setopt", lcurl_multi_setopt }, - {"wait", lcurl_multi_wait }, - {"timeout", lcurl_multi_timeout }, - {"socket_action", lcurl_multi_socket_action }, - { "__tostring", lcurl_multi_to_s }, - -#define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_multi_set_##N }, - #include "lcoptmulti.h" - OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) - OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) -#undef OPT_ENTRY - - { "setdata", lcurl_multi_setdata }, - { "getdata", lcurl_multi_getdata }, - - {"close", lcurl_multi_cleanup }, - {"__gc", lcurl_multi_cleanup }, - - {NULL,NULL} -}; - -static const lcurl_const_t lcurl_multi_opt[] = { -#define OPT_ENTRY(L, N, T, S) { "OPT_MULTI_"#N, CURLMOPT_##N }, - #include "lcoptmulti.h" - OPT_ENTRY(timerfunction, TIMERFUNCTION, TTT, 0) - OPT_ENTRY(socketfunction, SOCKETFUNCTION, TTT, 0) -#undef OPT_ENTRY - - {NULL, 0} -}; - -void lcurl_multi_initlib(lua_State *L, int nup){ - if(!lutil_createmetap(L, LCURL_MULTI, lcurl_multi_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - - lcurl_util_set_const(L, lcurl_multi_opt); -} diff --git a/watchdog/third_party/lua-curl/src/lcmulti.h b/watchdog/third_party/lua-curl/src/lcmulti.h deleted file mode 100644 index 89c8723..0000000 --- a/watchdog/third_party/lua-curl/src/lcmulti.h +++ /dev/null @@ -1,50 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCMULTI_H_ -#define _LCMULTI_H_ - -#include "lcurl.h" -#include "lcutils.h" - -typedef struct lcurl_multi_tag{ - CURLM *curl; - lua_State *L; - int err_mode; - int h_ref; - lcurl_callback_t tm; - lcurl_callback_t sc; -}lcurl_multi_t; - - -#if LCURL_CC_SUPPORT_FORWARD_TYPEDEF -typedef struct lcurl_multi_tag lcurl_multi_t; -#else -struct lcurl_easy_tag; -#define lcurl_easy_t struct lcurl_easy_tag -#endif - -int lcurl_multi_create(lua_State *L, int error_mode); - -lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i); - -#define lcurl_getmulti(L) lcurl_getmulti_at((L),1) - -void lcurl_multi_initlib(lua_State *L, int nup); - -void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy); - -CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e); - -#if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF -#undef lcurl_easy_t -#endif - -#endif diff --git a/watchdog/third_party/lua-curl/src/lcopteasy.h b/watchdog/third_party/lua-curl/src/lcopteasy.h deleted file mode 100644 index 8155667..0000000 --- a/watchdog/third_party/lua-curl/src/lcopteasy.h +++ /dev/null @@ -1,557 +0,0 @@ -/* Before version 7.17.0, strings were not copied. - Instead the user was forced keep them available - until libcurl no longer needed them. -*/ - -#ifndef LCURL_STORE_STRING -# if LCURL_CURL_VER_GE(7,17,0) -# define LCURL_STORE_STRING 0 -# else -# define LCURL_STORE_STRING 1 -# endif -#endif - -#ifndef OPT_ENTRY -# define OPT_ENTRY(a,b,c,d,e) -# define OPT_ENTRY_IS_NULL -#endif - -#ifndef FLG_ENTRY -# define FLG_ENTRY(a) -# define FLG_ENTRY_IS_NULL -#endif - -#ifndef LCURL_DEFAULT_VALUE -# define LCURL_DEFAULT_VALUE 0 -#endif - -//{ Reset system macros - -#ifdef TCP_FASTOPEN -# define LCURL__TCP_FASTOPEN TCP_FASTOPEN -# undef TCP_FASTOPEN -#endif - -#ifdef TCP_KEEPIDLE -# define LCURL__TCP_KEEPIDLE TCP_KEEPIDLE -# undef TCP_KEEPIDLE -#endif - -#ifdef TCP_KEEPINTVL -# define LCURL__TCP_KEEPINTVL TCP_KEEPINTVL -# undef TCP_KEEPINTVL -#endif - -#ifdef TCP_NODELAY -# define LCURL__TCP_NODELAY TCP_NODELAY -# undef TCP_NODELAY -#endif - -#ifdef TCP_KEEPALIVE -# define LCURL__TCP_KEEPALIVE TCP_KEEPALIVE -# undef TCP_KEEPALIVE -#endif - -#ifdef BUFFERSIZE -# define LCURL__BUFFERSIZE BUFFERSIZE -# undef BUFFERSIZE -#endif - -#ifdef INTERFACE -# define LCURL__INTERFACE INTERFACE -# undef INTERFACE -#endif - -//} - -OPT_ENTRY( verbose, VERBOSE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( header, HEADER, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( noprogress, NOPROGRESS, LNG, 0, 1 ) -OPT_ENTRY( nosignal, NOSIGNAL, LNG, 0, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,21,0) -OPT_ENTRY( wildcardmatch, WILDCARDMATCH, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif - -OPT_ENTRY( url, URL, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( failonerror, FAILONERROR, LNG, 0, LCURL_DEFAULT_VALUE ) - -OPT_ENTRY( protocols, PROTOCOLS, LNG, 0, CURLPROTO_ALL ) -OPT_ENTRY( redir_protocols, REDIR_PROTOCOLS, LNG, 0, CURLPROTO_ALL ) /*! @fixme All protocols except for FILE and SCP */ -OPT_ENTRY( proxy, PROXY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( proxyport, PROXYPORT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( proxytype, PROXYTYPE, LNG, 0, CURLPROXY_HTTP ) -OPT_ENTRY( noproxy, NOPROXY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( httpproxytunnel, HTTPPROXYTUNNEL, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( socks5_gssapi_service, SOCKS5_GSSAPI_SERVICE, STR, LCURL_STORE_STRING, "rcmd/server-fqdn" ) -OPT_ENTRY( socks5_gssapi_nec, SOCKS5_GSSAPI_NEC, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ -OPT_ENTRY( interface, INTERFACE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( localport, LOCALPORT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( localportrange, LOCALPORTRANGE, LNG, 0, 1 ) -OPT_ENTRY( dns_cache_timeout, DNS_CACHE_TIMEOUT, LNG, 0, 60 ) - -#if !LCURL_CURL_VER_GE(7,65,0) -OPT_ENTRY( dns_use_global_cache, DNS_USE_GLOBAL_CACHE, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif - -#if LCURL_CURL_VER_GE(7,25,0) -OPT_ENTRY( dns_servers, DNS_SERVERS, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#endif -OPT_ENTRY( buffersize, BUFFERSIZE, LNG, 0, CURL_MAX_WRITE_SIZE ) -OPT_ENTRY( port, PORT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( tcp_nodelay, TCP_NODELAY, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( address_scope, ADDRESS_SCOPE, LNG, 0, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,25,0) -OPT_ENTRY( tcp_keepalive, TCP_KEEPALIVE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( tcp_keepidle, TCP_KEEPIDLE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ -OPT_ENTRY( tcp_keepintvl, TCP_KEEPINTVL, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @check doc says nothing */ -#endif - -OPT_ENTRY( netrc, NETRC, LNG, 0, CURL_NETRC_IGNORED ) -OPT_ENTRY( netrc_file, NETRC_FILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( userpwd, USERPWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( proxyuserpwd, PROXYUSERPWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( username, USERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( password, PASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,31,0) -OPT_ENTRY( login_options, LOGIN_OPTIONS, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#endif -OPT_ENTRY( proxyusername, PROXYUSERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( proxypassword, PROXYPASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( httpauth, HTTPAUTH, LNG, 0, CURLAUTH_BASIC ) -#if LCURL_CURL_VER_GE(7,21,4) -OPT_ENTRY( tlsauth_username, TLSAUTH_USERNAME, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( tlsauth_password, TLSAUTH_PASSWORD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( tlsauth_type, TLSAUTH_TYPE, STR, 0, "" ) -#endif -OPT_ENTRY( proxyauth, PROXYAUTH, LNG, 0, CURLAUTH_BASIC ) -#if LCURL_CURL_VER_GE(7,31,0) -OPT_ENTRY( sasl_ir, SASL_IR, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,33,0) -OPT_ENTRY( xoauth2_bearer, XOAUTH2_BEARER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#endif - -OPT_ENTRY( autoreferer, AUTOREFERER, LNG, 0, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,21,6) -OPT_ENTRY( accept_encoding, ACCEPT_ENCODING, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( transfer_encoding, TRANSFER_ENCODING, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -OPT_ENTRY( followlocation, FOLLOWLOCATION, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( unrestricted_auth, UNRESTRICTED_AUTH, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( maxredirs, MAXREDIRS, LNG, 0, -1 ) -OPT_ENTRY( postredir, POSTREDIR, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( put, PUT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( post, POST, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( referer, REFERER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( useragent, USERAGENT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,37,0) -OPT_ENTRY( headeropt, HEADEROPT, LNG, 0, CURLHEADER_UNIFIED ) -#endif -OPT_ENTRY( httpheader, HTTPHEADER, LST, 0, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,37,0) -OPT_ENTRY( proxyheader, PROXYHEADER, LST, 0, LCURL_DEFAULT_VALUE ) -#endif -OPT_ENTRY( http200aliases, HTTP200ALIASES, LST, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( cookie, COOKIE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( cookiefile, COOKIEFILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( cookiejar, COOKIEJAR, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( cookiesession, COOKIESESSION, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( cookielist, COOKIELIST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( httpget, HTTPGET, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( http_version, HTTP_VERSION, LNG, 0, CURL_HTTP_VERSION_NONE ) -OPT_ENTRY( ignore_content_length, IGNORE_CONTENT_LENGTH, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( http_content_decoding, HTTP_CONTENT_DECODING, LNG, 0, 1 ) -OPT_ENTRY( http_transfer_decoding, HTTP_TRANSFER_DECODING, LNG, 0, 1 ) -#if LCURL_CURL_VER_GE(7,36,0) -OPT_ENTRY( expect_100_timeout_ms, EXPECT_100_TIMEOUT_MS, LNG, 0, 1000 ) -#endif - -#if LCURL_CURL_VER_GE(7,20,0) -OPT_ENTRY( mail_from, MAIL_FROM, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) /*! @check doc says `blank` */ -OPT_ENTRY( mail_rcpt, MAIL_RCPT, LST, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,25,0) -OPT_ENTRY( mail_auth, MAIL_AUTH, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#endif - -OPT_ENTRY( tftp_blksize, TFTP_BLKSIZE, LNG, 0, 512 ) - -OPT_ENTRY( ftpport, FTPPORT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( quote, QUOTE, LST, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( postquote, POSTQUOTE, LST, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( prequote, PREQUOTE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( dirlistonly, DIRLISTONLY, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( append, APPEND, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( ftp_use_eprt, FTP_USE_EPRT, LNG, 0, LCURL_DEFAULT_VALUE )/*! @check doc says nothing */ -OPT_ENTRY( ftp_use_epsv, FTP_USE_EPSV, LNG, 0, 1 ) -#if LCURL_CURL_VER_GE(7,20,0) -OPT_ENTRY( ftp_use_pret, FTP_USE_PRET, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -OPT_ENTRY( ftp_create_missing_dirs, FTP_CREATE_MISSING_DIRS, LNG, 0, CURLFTP_CREATE_DIR_NONE ) -OPT_ENTRY( ftp_response_timeout, FTP_RESPONSE_TIMEOUT, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ -OPT_ENTRY( ftp_alternative_to_user, FTP_ALTERNATIVE_TO_USER, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( ftp_skip_pasv_ip, FTP_SKIP_PASV_IP, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( ftpsslauth, FTPSSLAUTH, LNG, 0, CURLFTPAUTH_DEFAULT ) -OPT_ENTRY( ftp_ssl_ccc, FTP_SSL_CCC, LNG, 0, CURLFTPSSL_CCC_NONE ) -OPT_ENTRY( ftp_account, FTP_ACCOUNT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( ftp_filemethod, FTP_FILEMETHOD, LNG, 0, CURLFTPMETHOD_MULTICWD ) - -OPT_ENTRY( transfertext, TRANSFERTEXT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( proxy_transfer_mode, PROXY_TRANSFER_MODE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( crlf, CRLF, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( range, RANGE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( resume_from, RESUME_FROM, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( resume_from_large, RESUME_FROM_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( customrequest, CUSTOMREQUEST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( filetime, FILETIME, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( nobody, NOBODY, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( infilesize, INFILESIZE, LNG, 0, LCURL_DEFAULT_VALUE )/*! @fixme doc says `Unset` */ -OPT_ENTRY( infilesize_large, INFILESIZE_LARGE, LNG, 0, LCURL_DEFAULT_VALUE )/*! @fixme doc says `Unset` */ -OPT_ENTRY( upload, UPLOAD, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( maxfilesize, MAXFILESIZE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ -OPT_ENTRY( maxfilesize_large, MAXFILESIZE_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ -OPT_ENTRY( timecondition, TIMECONDITION, LNG, 0, CURL_TIMECOND_NONE ) -OPT_ENTRY( timevalue, TIMEVALUE, LNG, 0, LCURL_DEFAULT_VALUE ) - -OPT_ENTRY( timeout, TIMEOUT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( timeout_ms, TIMEOUT_MS, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( low_speed_limit, LOW_SPEED_LIMIT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( low_speed_time, LOW_SPEED_TIME, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( max_send_speed_large, MAX_SEND_SPEED_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( max_recv_speed_large, MAX_RECV_SPEED_LARGE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( maxconnects, MAXCONNECTS, LNG, 0, 5 ) -OPT_ENTRY( fresh_connect, FRESH_CONNECT, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( forbid_reuse, FORBID_REUSE, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( connecttimeout, CONNECTTIMEOUT, LNG, 0, 300 ) -OPT_ENTRY( connecttimeout_ms, CONNECTTIMEOUT_MS, LNG, 0, 300000 ) -OPT_ENTRY( ipresolve, IPRESOLVE, LNG, 0, CURL_IPRESOLVE_WHATEVER ) -OPT_ENTRY( connect_only, CONNECT_ONLY, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( use_ssl, USE_SSL, LNG, 0, CURLUSESSL_NONE ) -#if LCURL_CURL_VER_GE(7,21,3) -OPT_ENTRY( resolve, RESOLVE, LST, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,33,0) -OPT_ENTRY( dns_interface, DNS_INTERFACE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( dns_local_ip4, DNS_LOCAL_IP4, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( dns_local_ip6, DNS_LOCAL_IP6, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( accepttimeout_ms, ACCEPTTIMEOUT_MS, LNG, 0, 60000 ) -#endif - -OPT_ENTRY( ssh_auth_types, SSH_AUTH_TYPES, LNG, 0, LCURL_DEFAULT_VALUE) /*! @fixme doc says `None` */ -OPT_ENTRY( ssh_host_public_key_md5, SSH_HOST_PUBLIC_KEY_MD5, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( ssh_public_keyfile, SSH_PUBLIC_KEYFILE, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( ssh_private_keyfile, SSH_PRIVATE_KEYFILE, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( ssh_knownhosts, SSH_KNOWNHOSTS, STR, 0, LCURL_DEFAULT_VALUE) - -OPT_ENTRY( new_file_perms, NEW_FILE_PERMS, LNG, 0, 0644) -OPT_ENTRY( new_directory_perms, NEW_DIRECTORY_PERMS, LNG, 0, 0755) - -OPT_ENTRY( telnetoptions, TELNETOPTIONS, LST, 0, LCURL_DEFAULT_VALUE) - -OPT_ENTRY( random_file, RANDOM_FILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( egdsocket, EGDSOCKET, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( issuercert, ISSUERCERT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( krblevel, KRBLEVEL, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) - -OPT_ENTRY( cainfo, CAINFO, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `Built-in system specific` */ -OPT_ENTRY( capath, CAPATH, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( certinfo, CERTINFO, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( crlfile, CRLFILE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) - -OPT_ENTRY( sslcert, SSLCERT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( sslcerttype, SSLCERTTYPE, STR, LCURL_STORE_STRING, "PEM" ) -OPT_ENTRY( sslengine, SSLENGINE, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( sslengine_default, SSLENGINE_DEFAULT, LNG, 0, LCURL_DEFAULT_VALUE ) /*! @fixme doc says `None` */ -OPT_ENTRY( sslkey, SSLKEY, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( sslkeytype, SSLKEYTYPE, STR, LCURL_STORE_STRING, "PEM" ) -OPT_ENTRY( sslversion, SSLVERSION, LNG, 0, CURL_SSLVERSION_DEFAULT ) -OPT_ENTRY( ssl_cipher_list, SSL_CIPHER_LIST, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#if LCURL_CURL_VER_GE(7,36,0) -OPT_ENTRY( ssl_enable_alpn, SSL_ENABLE_ALPN, LNG, 0, 1 ) -OPT_ENTRY( ssl_enable_npn, SSL_ENABLE_NPN, LNG, 0, 1 ) -#endif -#if LCURL_CURL_VER_GE(7,25,0) -OPT_ENTRY( ssl_options, SSL_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -OPT_ENTRY( ssl_sessionid_cache, SSL_SESSIONID_CACHE, LNG, 0, 1 ) -OPT_ENTRY( ssl_verifyhost, SSL_VERIFYHOST, LNG, 0, 2 ) -OPT_ENTRY( ssl_verifypeer, SSL_VERIFYPEER, LNG, 0, 1 ) -OPT_ENTRY( keypasswd, KEYPASSWD, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) - -#if LCURL_CURL_VER_GE(7,20,0) -OPT_ENTRY( rtsp_client_cseq, RTSP_CLIENT_CSEQ, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( rtsp_request, RTSP_REQUEST, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( rtsp_server_cseq, RTSP_SERVER_CSEQ, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( rtsp_session_id, RTSP_SESSION_ID, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( rtsp_stream_uri, RTSP_STREAM_URI, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( rtsp_transport, RTSP_TRANSPORT, STR, LCURL_STORE_STRING, LCURL_DEFAULT_VALUE ) -#endif - -#if LCURL_CURL_VER_GE(7,22,0) -OPT_ENTRY( gssapi_delegation, GSSAPI_DELEGATION, LNG, 0, CURLGSSAPI_DELEGATION_NONE ) -#endif - -FLG_ENTRY( SSLVERSION_DEFAULT ) -FLG_ENTRY( SSLVERSION_TLSv1 ) -FLG_ENTRY( SSLVERSION_SSLv2 ) -FLG_ENTRY( SSLVERSION_SSLv3 ) -#if LCURL_CURL_VER_GE(7,34,0) -FLG_ENTRY( SSLVERSION_TLSv1_0 ) -FLG_ENTRY( SSLVERSION_TLSv1_1 ) -FLG_ENTRY( SSLVERSION_TLSv1_2 ) -#endif -#if LCURL_CURL_VER_GE(7,52,0) -FLG_ENTRY( SSLVERSION_TLSv1_3 ) -#endif - -#if LCURL_CURL_VER_GE(7,54,0) -FLG_ENTRY( SSLVERSION_MAX_NONE ) -FLG_ENTRY( SSLVERSION_MAX_DEFAULT ) -FLG_ENTRY( SSLVERSION_MAX_TLSv1_0 ) -FLG_ENTRY( SSLVERSION_MAX_TLSv1_1 ) -FLG_ENTRY( SSLVERSION_MAX_TLSv1_2 ) -FLG_ENTRY( SSLVERSION_MAX_TLSv1_3 ) -#endif - -#if LCURL_CURL_VER_GE(7,21,4) -FLG_ENTRY( TLSAUTH_SRP ) -#endif - -FLG_ENTRY( HTTP_VERSION_NONE ) -FLG_ENTRY( HTTP_VERSION_1_0 ) -FLG_ENTRY( HTTP_VERSION_1_1 ) -#if LCURL_CURL_VER_GE(7,33,0) -FLG_ENTRY( HTTP_VERSION_2_0 ) -#endif -#if LCURL_CURL_VER_GE(7,43,0) -FLG_ENTRY( HTTP_VERSION_2 ) -#endif -#if LCURL_CURL_VER_GE(7,47,0) -FLG_ENTRY( HTTP_VERSION_2TLS ) -#endif -#if LCURL_CURL_VER_GE(7,49,0) -FLG_ENTRY( HTTP_VERSION_2_PRIOR_KNOWLEDGE ) -#endif -#if LCURL_CURL_VER_GE(7,66,0) -FLG_ENTRY( HTTP_VERSION_3 ) -#endif - -FLG_ENTRY( READFUNC_PAUSE ) /*7.18.0*/ -FLG_ENTRY( WRITEFUNC_PAUSE ) /*7.18.0*/ - -FLG_ENTRY( POLL_IN ) /*7.14.0*/ -FLG_ENTRY( POLL_INOUT ) /*7.14.0*/ -FLG_ENTRY( POLL_NONE ) /*7.14.0*/ -FLG_ENTRY( POLL_OUT ) /*7.14.0*/ -FLG_ENTRY( POLL_REMOVE ) /*7.14.0*/ -FLG_ENTRY( SOCKET_TIMEOUT ) /*7.14.0*/ - -FLG_ENTRY( CSELECT_ERR ) /*7.16.3*/ -FLG_ENTRY( CSELECT_IN ) /*7.16.3*/ -FLG_ENTRY( CSELECT_OUT ) /*7.16.3*/ - -FLG_ENTRY( IPRESOLVE_WHATEVER ) /*7.10.8*/ -FLG_ENTRY( IPRESOLVE_V4 ) /*7.10.8*/ -FLG_ENTRY( IPRESOLVE_V6 ) /*7.10.8*/ - -#if LCURL_CURL_VER_GE(7,20,0) -FLG_ENTRY( RTSPREQ_OPTIONS ) -FLG_ENTRY( RTSPREQ_DESCRIBE ) -FLG_ENTRY( RTSPREQ_ANNOUNCE ) -FLG_ENTRY( RTSPREQ_SETUP ) -FLG_ENTRY( RTSPREQ_PLAY ) -FLG_ENTRY( RTSPREQ_PAUSE ) -FLG_ENTRY( RTSPREQ_TEARDOWN ) -FLG_ENTRY( RTSPREQ_GET_PARAMETER ) -FLG_ENTRY( RTSPREQ_SET_PARAMETER ) -FLG_ENTRY( RTSPREQ_RECORD ) -FLG_ENTRY( RTSPREQ_RECEIVE ) -#endif - -#if LCURL_CURL_VER_GE(7,39,0) -OPT_ENTRY( pinnedpublickey, PINNEDPUBLICKEY, STR, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,40,0) -OPT_ENTRY( unix_socket_path, UNIX_SOCKET_PATH, STR, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,41,0) -OPT_ENTRY( ssl_verifystatus, SSL_VERIFYSTATUS, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,42,0) -OPT_ENTRY( ssl_falsestart, SSL_FALSESTART, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( path_as_is, PATH_AS_IS, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,43,0) -OPT_ENTRY( proxy_service_name, PROXY_SERVICE_NAME, STR, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( service_name, SERVICE_NAME, STR, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( pipewait, PIPEWAIT, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,45,0) -OPT_ENTRY( default_protocol, DEFAULT_PROTOCOL, STR, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,46,0) -OPT_ENTRY( stream_weight, STREAM_WEIGHT, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,48,0) -OPT_ENTRY( tftp_no_options, TFTP_NO_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,49,0) -OPT_ENTRY( tcp_fastopen, TCP_FASTOPEN, LNG, 0, LCURL_DEFAULT_VALUE ) -OPT_ENTRY( connect_to, CONNECT_TO, LST, 0, LCURL_DEFAULT_VALUE ) -#endif -#if LCURL_CURL_VER_GE(7,51,0) -OPT_ENTRY( keep_sending_on_error, KEEP_SENDING_ON_ERROR, LNG, 0, LCURL_DEFAULT_VALUE ) -#endif - -#if LCURL_CURL_VER_GE(7,52,0) -OPT_ENTRY( proxy_cainfo, PROXY_CAINFO, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_capath, PROXY_CAPATH, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_ssl_verifypeer, PROXY_SSL_VERIFYPEER, LNG, 0, 1) -OPT_ENTRY( proxy_ssl_verifyhost, PROXY_SSL_VERIFYHOST, LNG, 0, 2) -OPT_ENTRY( proxy_sslversion, PROXY_SSLVERSION, LNG, 0, CURL_SSLVERSION_DEFAULT) -OPT_ENTRY( proxy_tlsauth_username, PROXY_TLSAUTH_USERNAME, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_tlsauth_password, PROXY_TLSAUTH_PASSWORD, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_tlsauth_type, PROXY_TLSAUTH_TYPE, STR, 0, "") -OPT_ENTRY( proxy_sslcert, PROXY_SSLCERT, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_sslcerttype, PROXY_SSLCERTTYPE, STR, 0, "PEM") -OPT_ENTRY( proxy_sslkey, PROXY_SSLKEY, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_sslkeytype, PROXY_SSLKEYTYPE, STR, 0, "PEM") /* default value not defined. Use same as for `SSLKEYTYPE` */ -OPT_ENTRY( proxy_keypasswd, PROXY_KEYPASSWD, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_ssl_cipher_list, PROXY_SSL_CIPHER_LIST, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_crlfile, PROXY_CRLFILE, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_ssl_options, PROXY_SSL_OPTIONS, LNG, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( pre_proxy, PRE_PROXY, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( proxy_pinnedpublickey, PROXY_PINNEDPUBLICKEY, STR, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,53,0) -OPT_ENTRY( abstract_unix_socket, ABSTRACT_UNIX_SOCKET, STR, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,54,0) -OPT_ENTRY( suppress_connect_headers, SUPPRESS_CONNECT_HEADERS, LNG, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,55,0) -OPT_ENTRY( request_target, REQUEST_TARGET, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY( socks5_auth, SOCKS5_AUTH, LNG, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,56,0) -OPT_ENTRY( ssh_compression, SSH_COMPRESSION, LNG, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,59,0) -OPT_ENTRY( happy_eyeballs_timeout_ms,HAPPY_EYEBALLS_TIMEOUT_MS,LNG, 0, CURL_HET_DEFAULT) -OPT_ENTRY( timevalue_large, TIMEVALUE_LARGE ,OFF, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,60,0) -OPT_ENTRY(dns_shuffle_addresses, DNS_SHUFFLE_ADDRESSES, LNG, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY(haproxyprotocol, HAPROXYPROTOCOL, LNG, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,61,0) -OPT_ENTRY(disallow_username_in_url, DISALLOW_USERNAME_IN_URL, LNG, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY(proxy_tls13_ciphers, PROXY_TLS13_CIPHERS, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY(tls13_ciphers, TLS13_CIPHERS, STR, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,62,0) -OPT_ENTRY(upkeep_interval_ms, UPKEEP_INTERVAL_MS, LNG, 0, CURL_UPKEEP_INTERVAL_DEFAULT) -OPT_ENTRY(doh_url, DOH_URL, STR, 0, LCURL_DEFAULT_VALUE) -// thre no named value for default value. It just defined as 64kB in documentation -OPT_ENTRY(upload_buffersize, UPLOAD_BUFFERSIZE, LNG, 0, 64 * 1024) -#endif - -#if LCURL_CURL_VER_GE(7,64,0) -OPT_ENTRY(http09_allowed, HTTP09_ALLOWED, LNG, 0, 0) -#endif - -#if LCURL_CURL_VER_GE(7,64,1) -OPT_ENTRY(altsvc, ALTSVC, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY(altsvc_ctrl, ALTSVC_CTRL, LNG, 0, 0) -#endif - -#if LCURL_CURL_VER_GE(7,65,0) -OPT_ENTRY(maxage_conn, MAXAGE_CONN, LNG, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,66,0) -OPT_ENTRY(sasl_authzid, SASL_AUTHZID, STR, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,68,0) -FLG_ENTRY( PROGRESSFUNC_CONTINUE ) -#endif - -#if LCURL_CURL_VER_GE(7,69,0) -OPT_ENTRY(mail_rcpt_alllowfails, MAIL_RCPT_ALLLOWFAILS, LNG, 0, 1) -#endif - -#if LCURL_CURL_VER_GE(7,71,0) -OPT_ENTRY(sslcert_blob, SSLCERT_BLOB, BLB, 0, 0) -OPT_ENTRY(sslkey_blob, SSLKEY_BLOB, BLB, 0, 0) -OPT_ENTRY(proxy_sslcert_blob, PROXY_SSLCERT_BLOB, BLB, 0, 0) -OPT_ENTRY(proxy_sslkey_blob, PROXY_SSLKEY_BLOB, BLB, 0, 0) -OPT_ENTRY(issuercert_blob, ISSUERCERT_BLOB, BLB, 0, 0) - -OPT_ENTRY(proxy_issuercert, PROXY_ISSUERCERT, STR, 0, LCURL_DEFAULT_VALUE) -OPT_ENTRY(proxy_issuercert_blob, PROXY_ISSUERCERT_BLOB, BLB, 0, 0) -#endif - -#if LCURL_CURL_VER_GE(7,73,0) -OPT_ENTRY(ssl_ec_curves, SSL_EC_CURVES, STR, 0, LCURL_DEFAULT_VALUE) -#endif - -#if LCURL_CURL_VER_GE(7,74,0) && LCURL_USE_HSTS -OPT_ENTRY(hsts_ctrl, HSTS_CTRL, LNG, 0, 0) -OPT_ENTRY(hsts, HSTS, STR, 0, LCURL_DEFAULT_VALUE) -#endif - -//{ Restore system macros - -#ifdef LCURL__TCP_FASTOPEN -# define TCP_FASTOPEN LCURL__TCP_FASTOPEN -# undef LCURL__TCP_FASTOPEN -#endif - -#ifdef LCURL__TCP_KEEPIDLE -# define TCP_KEEPIDLE LCURL__TCP_KEEPIDLE -# undef LCURL__TCP_KEEPIDLE -#endif - -#ifdef LCURL__TCP_KEEPINTVL -# define TCP_KEEPINTVL LCURL__TCP_KEEPINTVL -# undef LCURL__TCP_KEEPINTVL -#endif - -#ifdef LCURL__TCP_NODELAY -# define TCP_NODELAY LCURL__TCP_NODELAY -# undef LCURL__TCP_NODELAY -#endif - -#ifdef LCURL__TCP_KEEPALIVE -# define TCP_KEEPALIVE LCURL__TCP_KEEPALIVE -# undef LCURL__TCP_KEEPALIVE -#endif - -#ifdef LCURL__BUFFERSIZE -# define BUFFERSIZE LCURL__BUFFERSIZE -# undef LCURL__BUFFERSIZE -#endif - -#ifdef LCURL__INTERFACE -# define INTERFACE LCURL__INTERFACE -# undef LCURL__INTERFACE -#endif - -//} - -#ifdef OPT_ENTRY_IS_NULL -# undef OPT_ENTRY -#endif - -#ifdef FLG_ENTRY_IS_NULL -# undef FLG_ENTRY -#endif diff --git a/watchdog/third_party/lua-curl/src/lcoptmulti.h b/watchdog/third_party/lua-curl/src/lcoptmulti.h deleted file mode 100644 index 8f7fd71..0000000 --- a/watchdog/third_party/lua-curl/src/lcoptmulti.h +++ /dev/null @@ -1,17 +0,0 @@ - -OPT_ENTRY(pipelining, PIPELINING, LNG, 0 ) -OPT_ENTRY(maxconnects, MAXCONNECTS, LNG, 0 ) - -#if LCURL_CURL_VER_GE(7,30,0) -OPT_ENTRY(max_host_connections, MAX_HOST_CONNECTIONS, LNG, 0 ) -OPT_ENTRY(max_pipeline_length, MAX_PIPELINE_LENGTH, LNG, 0 ) -OPT_ENTRY(content_length_penalty_size, CONTENT_LENGTH_PENALTY_SIZE, LNG, 0 ) -OPT_ENTRY(chunk_length_penalty_size, CHUNK_LENGTH_PENALTY_SIZE, LNG, 0 ) -OPT_ENTRY(pipelining_site_bl, PIPELINING_SITE_BL, STR_ARR, 0 ) -OPT_ENTRY(pipelining_server_bl, PIPELINING_SERVER_BL, STR_ARR, 0 ) -OPT_ENTRY(max_total_connections, MAX_TOTAL_CONNECTIONS, LNG, 0 ) -#endif - -#if LCURL_CURL_VER_GE(7,67,0) -OPT_ENTRY(max_concurrent_streams, MAX_CONCURRENT_STREAMS, LNG, 0 ) -#endif diff --git a/watchdog/third_party/lua-curl/src/lcoptshare.h b/watchdog/third_party/lua-curl/src/lcoptshare.h deleted file mode 100644 index 95960bb..0000000 --- a/watchdog/third_party/lua-curl/src/lcoptshare.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef OPT_ENTRY -# define OPT_ENTRY(a,b,c,d) -# define OPT_ENTRY_IS_NULL -#endif - -#ifndef FLG_ENTRY -# define FLG_ENTRY(a) -# define FLG_ENTRY_IS_NULL -#endif - -OPT_ENTRY(share, SHARE, LNG, 0 ) -OPT_ENTRY(unshare, UNSHARE, LNG, 0 ) - -FLG_ENTRY( LOCK_DATA_COOKIE ) -FLG_ENTRY( LOCK_DATA_DNS ) -FLG_ENTRY( LOCK_DATA_SSL_SESSION ) -FLG_ENTRY( LOCK_DATA_CONNECT ) - -#ifdef OPT_ENTRY_IS_NULL -# undef OPT_ENTRY -# undef OPT_ENTRY_IS_NULL -#endif - -#ifdef FLG_ENTRY_IS_NULL -# undef FLG_ENTRY -# undef FLG_ENTRY_IS_NULL -#endif diff --git a/watchdog/third_party/lua-curl/src/lcopturl.h b/watchdog/third_party/lua-curl/src/lcopturl.h deleted file mode 100644 index 20fee9e..0000000 --- a/watchdog/third_party/lua-curl/src/lcopturl.h +++ /dev/null @@ -1,29 +0,0 @@ -ENTRY_PART(fragment, UPART_FRAGMENT , CURLUE_NO_FRAGMENT ) -ENTRY_PART(host, UPART_HOST , CURLUE_NO_HOST ) -ENTRY_PART(options, UPART_OPTIONS , CURLUE_NO_OPTIONS ) -ENTRY_PART(password, UPART_PASSWORD , CURLUE_NO_PASSWORD ) -ENTRY_PART(path, UPART_PATH , CURLUE_OK ) -ENTRY_PART(port, UPART_PORT , CURLUE_NO_PORT ) -ENTRY_PART(query, UPART_QUERY , CURLUE_NO_QUERY ) -ENTRY_PART(scheme, UPART_SCHEME , CURLUE_NO_SCHEME ) -ENTRY_PART(url, UPART_URL , CURLUE_OK ) -ENTRY_PART(user, UPART_USER , CURLUE_NO_USER ) - -#if LCURL_CURL_VER_GE(7,65,0) -ENTRY_PART(zoneid, UPART_ZONEID , CURLUE_UNKNOWN_PART ) -#endif - -ENTRY_FLAG(DEFAULT_PORT ) -ENTRY_FLAG(NO_DEFAULT_PORT ) -ENTRY_FLAG(DEFAULT_SCHEME ) -ENTRY_FLAG(NON_SUPPORT_SCHEME ) -ENTRY_FLAG(PATH_AS_IS ) -ENTRY_FLAG(DISALLOW_USER ) -ENTRY_FLAG(URLDECODE ) -ENTRY_FLAG(URLENCODE ) -ENTRY_FLAG(APPENDQUERY ) -ENTRY_FLAG(GUESS_SCHEME ) - -#if LCURL_CURL_VER_GE(7,67,0) -ENTRY_FLAG(NO_AUTHORITY ) -#endif diff --git a/watchdog/third_party/lua-curl/src/lcshare.c b/watchdog/third_party/lua-curl/src/lcshare.c deleted file mode 100644 index c7ac489..0000000 --- a/watchdog/third_party/lua-curl/src/lcshare.c +++ /dev/null @@ -1,152 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lcshare.h" -#include "lcerror.h" -#include "lcutils.h" -#include "lchttppost.h" - -#define LCURL_SHARE_NAME LCURL_PREFIX" Share" -static const char *LCURL_SHARE = LCURL_SHARE_NAME; - -//{ -int lcurl_share_create(lua_State *L, int error_mode){ - lcurl_share_t *p; - - lua_settop(L, 1); - - p = lutil_newudatap(L, lcurl_share_t, LCURL_SHARE); - p->curl = curl_share_init(); - p->err_mode = error_mode; - if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_NOMEM); - - if(lua_type(L, 1) == LUA_TTABLE){ - int ret = lcurl_utils_apply_options(L, 1, 2, 1, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); - if(ret) return ret; - assert(lua_gettop(L) == 2); - } - - return 1; -} - -lcurl_share_t *lcurl_getshare_at(lua_State *L, int i){ - lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, i, LCURL_SHARE); - luaL_argcheck (L, p != NULL, 1, LCURL_SHARE_NAME" object expected"); - return p; -} - -static int lcurl_easy_to_s(lua_State *L){ - lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, 1, LCURL_SHARE); - lua_pushfstring(L, LCURL_SHARE_NAME" (%p)", (void*)p); - return 1; -} - -static int lcurl_share_cleanup(lua_State *L){ - lcurl_share_t *p = lcurl_getshare(L); - if(p->curl){ - curl_share_cleanup(p->curl); - p->curl = NULL; - } - - return 0; -} - -//{ OPTIONS - -static int lcurl_opt_set_long_(lua_State *L, int opt){ - lcurl_share_t *p = lcurl_getshare(L); - long val; CURLSHcode code; - - if(lua_isboolean(L, 2)) val = lua_toboolean(L, 2); - else{ - luaL_argcheck(L, lua_type(L, 2) == LUA_TNUMBER, 2, "number or boolean expected"); - val = luaL_checklong(L, 2); - } - - code = curl_share_setopt(p->curl, opt, val); - if(code != CURLSHE_OK){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, code); - } - lua_settop(L, 1); - return 1; -} - -#define LCURL_LNG_OPT(N, S) static int lcurl_share_set_##N(lua_State *L){\ - return lcurl_opt_set_long_(L, CURLSHOPT_##N);\ -} - -#define OPT_ENTRY(L, N, T, S) LCURL_##T##_OPT(N, S) - -#include "lcoptshare.h" - -#undef OPT_ENTRY -#undef LCURL_LNG_OPT - -//} - -static int lcurl_share_setopt(lua_State *L){ - lcurl_share_t *p = lcurl_getshare(L); - int opt; - - luaL_checkany(L, 2); - if(lua_type(L, 2) == LUA_TTABLE){ - int ret = lcurl_utils_apply_options(L, 2, 1, 0, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); - if(ret) return ret; - lua_settop(L, 1); - return 1; - } - - opt = luaL_checklong(L, 2); - lua_remove(L, 2); - -#define OPT_ENTRY(l, N, T, S) case CURLSHOPT_##N: return lcurl_share_set_##N(L); - switch(opt){ - #include "lcoptshare.h" - } -#undef OPT_ENTRY - - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_SHARE, CURLSHE_BAD_OPTION); -} - -//} - -static const struct luaL_Reg lcurl_share_methods[] = { - { "__tostring", lcurl_easy_to_s }, - {"setopt", lcurl_share_setopt }, - -#define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_share_set_##N }, - #include "lcoptshare.h" -#undef OPT_ENTRY - - {"close", lcurl_share_cleanup }, - {"__gc", lcurl_share_cleanup }, - - {NULL,NULL} -}; - -static const lcurl_const_t lcurl_share_opt[] = { - -#define OPT_ENTRY(L, N, T, S) { "OPT_SHARE_"#N, CURLSHOPT_##N }, -#define FLG_ENTRY(N) { #N, CURL_##N }, -# include "lcoptshare.h" -#undef OPT_ENTRY -#undef FLG_ENTRY - - {NULL, 0} -}; - -void lcurl_share_initlib(lua_State *L, int nup){ - if(!lutil_createmetap(L, LCURL_SHARE, lcurl_share_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - - lcurl_util_set_const(L, lcurl_share_opt); -} diff --git a/watchdog/third_party/lua-curl/src/lcshare.h b/watchdog/third_party/lua-curl/src/lcshare.h deleted file mode 100644 index a1018c9..0000000 --- a/watchdog/third_party/lua-curl/src/lcshare.h +++ /dev/null @@ -1,30 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCSHARE_H_ -#define _LCSHARE_H_ - -#include "lcurl.h" -#include "lcutils.h" - -typedef struct lcurl_share_tag{ - CURLM *curl; - int err_mode; -}lcurl_share_t; - -int lcurl_share_create(lua_State *L, int error_mode); - -lcurl_share_t *lcurl_getshare_at(lua_State *L, int i); - -#define lcurl_getshare(L) lcurl_getshare_at((L),1) - -void lcurl_share_initlib(lua_State *L, int nup); - -#endif diff --git a/watchdog/third_party/lua-curl/src/lcurl.c b/watchdog/third_party/lua-curl/src/lcurl.c deleted file mode 100644 index e70680d..0000000 --- a/watchdog/third_party/lua-curl/src/lcurl.c +++ /dev/null @@ -1,487 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lceasy.h" -#include "lcmulti.h" -#include "lcshare.h" -#include "lcerror.h" -#include "lchttppost.h" -#include "lcmime.h" -#include "lcurlapi.h" -#include "lcutils.h" - -/*export*/ -#ifdef _WIN32 -# define LCURL_EXPORT_API __declspec(dllexport) -#else -# define LCURL_EXPORT_API LUALIB_API -#endif - -static const char* LCURL_REGISTRY = "LCURL Registry"; -static const char* LCURL_USERVAL = "LCURL Uservalues"; -#if LCURL_CURL_VER_GE(7,56,0) -static const char* LCURL_MIME_EASY_MAP = "LCURL Mime easy"; -#endif - -#if LCURL_CURL_VER_GE(7,56,0) -#define NUP 3 -#else -#define NUP 2 -#endif - -static volatile int LCURL_INIT = 0; - -static int lcurl_init_in_mode(lua_State *L, long init_mode, int error_mode){ - if(!LCURL_INIT){ - /* Note from libcurl documentation. - * - * The environment it sets up is constant for the life of the program - * and is the same for every program, so multiple calls have the same - * effect as one call. ... This function is not thread safe. - */ - CURLcode code = curl_global_init(init_mode); - if (code != CURLE_OK) { - return lcurl_fail_ex(L, error_mode, LCURL_ERROR_CURL, code); - } - LCURL_INIT = 1; - } - return 0; -} - -static int lcurl_init(lua_State *L, int error_mode){ - long init_mode = CURL_GLOBAL_DEFAULT; - if (L != NULL) { - int type = lua_type(L, 1); - if (type == LUA_TNUMBER) { - init_mode = lua_tonumber(L, 1); - } - } - return lcurl_init_in_mode(L, init_mode, error_mode); -} - -static int lcurl_init_default(lua_State *L){ - return lcurl_init_in_mode(L, CURL_GLOBAL_DEFAULT, LCURL_ERROR_RAISE); -} - -static int lcurl_init_unsafe(lua_State *L){ - return lcurl_init(L, LCURL_ERROR_RAISE); -} - -static int lcurl_init_safe(lua_State *L){ - return lcurl_init(L, LCURL_ERROR_RETURN); -} - -static int lcurl_easy_new_safe(lua_State *L){ - return lcurl_easy_create(L, LCURL_ERROR_RETURN); -} - -static int lcurl_multi_new_safe(lua_State *L){ - return lcurl_multi_create(L, LCURL_ERROR_RETURN); -} - -static int lcurl_share_new_safe(lua_State *L){ - return lcurl_share_create(L, LCURL_ERROR_RETURN); -} - -static int lcurl_hpost_new_safe(lua_State *L) { - return lcurl_hpost_create(L, LCURL_ERROR_RETURN); -} - -#if LCURL_CURL_VER_GE(7,62,0) - -static int lcurl_url_new_safe(lua_State *L) { - return lcurl_url_create(L, LCURL_ERROR_RETURN); -} - -#endif - -static int lcurl_easy_new(lua_State *L){ - return lcurl_easy_create(L, LCURL_ERROR_RAISE); -} - -static int lcurl_multi_new(lua_State *L){ - return lcurl_multi_create(L, LCURL_ERROR_RAISE); -} - -static int lcurl_share_new(lua_State *L){ - return lcurl_share_create(L, LCURL_ERROR_RAISE); -} - -static int lcurl_hpost_new(lua_State *L){ - return lcurl_hpost_create(L, LCURL_ERROR_RAISE); -} - -#if LCURL_CURL_VER_GE(7,62,0) - -static int lcurl_url_new(lua_State *L) { - return lcurl_url_create(L, LCURL_ERROR_RAISE); -} - -#endif - -#if LCURL_CURL_VER_GE(7,73,0) - -static void lcurl_easy_option_push(lua_State *L, const struct curl_easyoption *opt) { - lua_newtable(L); - lua_pushliteral(L, "id"); lutil_pushuint(L, opt->id); lua_rawset(L, -3); - lua_pushliteral(L, "name"); lua_pushstring(L, opt->name); lua_rawset(L, -3); - lua_pushliteral(L, "type"); lutil_pushuint(L, opt->type); lua_rawset(L, -3); - lua_pushliteral(L, "flags"); lutil_pushuint(L, opt->flags); lua_rawset(L, -3); - lua_pushliteral(L, "flags_set"); lua_newtable(L); - lua_pushliteral(L, "alias"); lua_pushboolean(L, opt->flags & CURLOT_FLAG_ALIAS); lua_rawset(L, -3); - lua_rawset(L, -3); - lua_pushliteral(L, "type_name"); - switch(opt->type){ - case CURLOT_LONG : lua_pushliteral(L, "LONG" ); break; - case CURLOT_VALUES : lua_pushliteral(L, "VALUES" ); break; - case CURLOT_OFF_T : lua_pushliteral(L, "OFF_T" ); break; - case CURLOT_OBJECT : lua_pushliteral(L, "OBJECT" ); break; - case CURLOT_STRING : lua_pushliteral(L, "STRING" ); break; - case CURLOT_SLIST : lua_pushliteral(L, "SLIST" ); break; - case CURLOT_CBPTR : lua_pushliteral(L, "CBPTR" ); break; - case CURLOT_BLOB : lua_pushliteral(L, "BLOB" ); break; - case CURLOT_FUNCTION: lua_pushliteral(L, "FUNCTION"); break; - default: lua_pushliteral(L, "UNKNOWN"); - } - lua_rawset(L, -3); -} - -static int lcurl_easy_option_next(lua_State *L) { - const struct curl_easyoption *opt; - - luaL_checktype(L, 1, LUA_TTABLE); - lua_settop(L, 1); - - lua_rawgeti(L, 1, 1); - opt = lua_touserdata(L, -1); - lua_settop(L, 1); - - opt = curl_easy_option_next(opt); - if (!opt) { - return 0; - } - - lcurl_easy_option_push(L, opt); - - lua_pushlightuserdata(L, (void*)opt); - lua_rawseti(L, 1, 1); - - return 1; -} - -static int lcurl_easy_option_by_id(lua_State *L) { - const struct curl_easyoption *opt = NULL; - lua_Integer id = luaL_checkinteger(L, 1); - - lua_settop(L, 0); - opt = curl_easy_option_by_id(id); - if (!opt) { - return 0; - } - - lcurl_easy_option_push(L, opt); - - return 1; -} - -static int lcurl_easy_option_by_name(lua_State *L) { - const struct curl_easyoption *opt = NULL; - const char *name = luaL_checkstring(L, 1); - - lua_settop(L, 0); - opt = curl_easy_option_by_name(name); - if (!opt) { - return 0; - } - - lcurl_easy_option_push(L, opt); - - return 1; -} - -static int lcurl_easy_option_iter(lua_State *L) { - lua_pushcfunction(L, lcurl_easy_option_next); - lua_newtable(L); - return 2; -} - -#endif - -static int lcurl_version(lua_State *L){ - lua_pushstring(L, curl_version()); - return 1; -} - -static int lcurl_debug_getregistry(lua_State *L) { - lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); - return 1; -} - -static int push_upper(lua_State *L, const char *str){ - char buffer[128]; - size_t i, n = strlen(str); - char *ptr = (n < sizeof(buffer))?&buffer[0]:malloc(n + 1); - if (!ptr) return 1; - for(i = 0; i < n; ++i){ - if( (str[i] > 96 ) && (str[i] < 123) ) ptr[i] = str[i] - 'a' + 'A'; - else ptr[i] = str[i]; - } - lua_pushlstring(L, ptr, n); - if(ptr != &buffer[0]) free(ptr); - return 0; -} - -static int lcurl_version_info(lua_State *L){ - const char * const*p; - curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); - - lua_newtable(L); - lua_pushstring(L, data->version); lua_setfield(L, -2, "version"); /* LIBCURL_VERSION */ - lutil_pushuint(L, data->version_num); lua_setfield(L, -2, "version_num"); /* LIBCURL_VERSION_NUM */ - lua_pushstring(L, data->host); lua_setfield(L, -2, "host"); /* OS/host/cpu/machine when configured */ - - lua_newtable(L); - lua_pushliteral(L, "IPV6"); lua_pushboolean(L, data->features & CURL_VERSION_IPV6 ); lua_rawset(L, -3); - lua_pushliteral(L, "KERBEROS4"); lua_pushboolean(L, data->features & CURL_VERSION_KERBEROS4 ); lua_rawset(L, -3); - lua_pushliteral(L, "SSL"); lua_pushboolean(L, data->features & CURL_VERSION_SSL ); lua_rawset(L, -3); - lua_pushliteral(L, "LIBZ"); lua_pushboolean(L, data->features & CURL_VERSION_LIBZ ); lua_rawset(L, -3); - lua_pushliteral(L, "NTLM"); lua_pushboolean(L, data->features & CURL_VERSION_NTLM ); lua_rawset(L, -3); - lua_pushliteral(L, "GSSNEGOTIATE"); lua_pushboolean(L, data->features & CURL_VERSION_GSSNEGOTIATE); lua_rawset(L, -3); -#if LCURL_CURL_VER_GE(7,38,0) - lua_pushliteral(L, "GSSAPI"); lua_pushboolean(L, data->features & CURL_VERSION_GSSAPI ); lua_rawset(L, -3); -#endif - lua_pushliteral(L, "DEBUG"); lua_pushboolean(L, data->features & CURL_VERSION_DEBUG ); lua_rawset(L, -3); - lua_pushliteral(L, "ASYNCHDNS"); lua_pushboolean(L, data->features & CURL_VERSION_ASYNCHDNS ); lua_rawset(L, -3); - lua_pushliteral(L, "SPNEGO"); lua_pushboolean(L, data->features & CURL_VERSION_SPNEGO ); lua_rawset(L, -3); - lua_pushliteral(L, "LARGEFILE"); lua_pushboolean(L, data->features & CURL_VERSION_LARGEFILE ); lua_rawset(L, -3); - lua_pushliteral(L, "IDN"); lua_pushboolean(L, data->features & CURL_VERSION_IDN ); lua_rawset(L, -3); - lua_pushliteral(L, "SSPI"); lua_pushboolean(L, data->features & CURL_VERSION_SSPI ); lua_rawset(L, -3); - lua_pushliteral(L, "CONV"); lua_pushboolean(L, data->features & CURL_VERSION_CONV ); lua_rawset(L, -3); - lua_pushliteral(L, "CURLDEBUG"); lua_pushboolean(L, data->features & CURL_VERSION_CURLDEBUG ); lua_rawset(L, -3); -#if LCURL_CURL_VER_GE(7,21,4) - lua_pushliteral(L, "TLSAUTH_SRP"); lua_pushboolean(L, data->features & CURL_VERSION_TLSAUTH_SRP ); lua_rawset(L, -3); -#endif -#if LCURL_CURL_VER_GE(7,22,0) - lua_pushliteral(L, "NTLM_WB"); lua_pushboolean(L, data->features & CURL_VERSION_NTLM_WB ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_HTTP2 - lua_pushliteral(L, "HTTP2"); lua_pushboolean(L, data->features & CURL_VERSION_HTTP2 ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_HTTPS_PROXY - lua_pushliteral(L, "HTTPS_PROXY"); lua_pushboolean(L, data->features & CURL_VERSION_HTTPS_PROXY ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_MULTI_SSL - lua_pushliteral(L, "MULTI_SSL"); lua_pushboolean(L, data->features & CURL_VERSION_MULTI_SSL ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_BROTLI - lua_pushliteral(L, "BROTLI"); lua_pushboolean(L, data->features & CURL_VERSION_BROTLI ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_ALTSVC - lua_pushliteral(L, "ALTSVC"); lua_pushboolean(L, data->features & CURL_VERSION_ALTSVC ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_HTTP3 - lua_pushliteral(L, "HTTP3"); lua_pushboolean(L, data->features & CURL_VERSION_HTTP3 ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_ZSTD - lua_pushliteral(L, "ZSTD"); lua_pushboolean(L, data->features & CURL_VERSION_ZSTD ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_UNICODE - lua_pushliteral(L, "UNICODE"); lua_pushboolean(L, data->features & CURL_VERSION_UNICODE ); lua_rawset(L, -3); -#endif -#ifdef CURL_VERSION_HSTS - lua_pushliteral(L, "HSTS"); lua_pushboolean(L, data->features & CURL_VERSION_HSTS ); lua_rawset(L, -3); -#endif - - lua_setfield(L, -2, "features"); /* bitmask, see defines below */ - - if(data->ssl_version){lua_pushstring(L, data->ssl_version); lua_setfield(L, -2, "ssl_version");} /* human readable string */ - lutil_pushuint(L, data->ssl_version_num); lua_setfield(L, -2, "ssl_version_num"); /* not used anymore, always 0 */ - if(data->libz_version){lua_pushstring(L, data->libz_version); lua_setfield(L, -2, "libz_version");} /* human readable string */ - - /* protocols is terminated by an entry with a NULL protoname */ - lua_newtable(L); - for(p = data->protocols; *p; ++p){ - push_upper(L, *p); lua_pushboolean(L, 1); lua_rawset(L, -3); - } - lua_setfield(L, -2, "protocols"); - - if(data->age >= CURLVERSION_SECOND){ - if(data->ares){lua_pushstring(L, data->ares); lua_setfield(L, -2, "ares");} - lutil_pushuint(L, data->ares_num); lua_setfield(L, -2, "ares_num"); - } - - if(data->age >= CURLVERSION_THIRD){ /* added in 7.12.0 */ - if(data->libidn){lua_pushstring(L, data->libidn); lua_setfield(L, -2, "libidn");} - } - -#if LCURL_CURL_VER_GE(7,16,1) - if(data->age >= CURLVERSION_FOURTH){ - lutil_pushuint(L, data->iconv_ver_num); lua_setfield(L, -2, "iconv_ver_num"); - if(data->libssh_version){lua_pushstring(L, data->libssh_version);lua_setfield(L, -2, "libssh_version");} - } -#endif - -#if LCURL_CURL_VER_GE(7,57,0) - if(data->age >= CURLVERSION_FOURTH){ - lutil_pushuint(L, data->brotli_ver_num); lua_setfield(L, -2, "brotli_ver_num"); - if(data->brotli_version){lua_pushstring(L, data->brotli_version);lua_setfield(L, -2, "brotli_version");} - } -#endif - -#if LCURL_CURL_VER_GE(7,66,0) - if(data->age >= CURLVERSION_SIXTH){ - lutil_pushuint(L, data->nghttp2_ver_num); lua_setfield(L, -2, "nghttp2_ver_num"); - if(data->nghttp2_version){lua_pushstring(L, data->nghttp2_version);lua_setfield(L, -2, "nghttp2_version");} - if(data->quic_version){lua_pushstring(L, data->quic_version);lua_setfield(L, -2, "quic_version");} - } -#endif - -#if LCURL_CURL_VER_GE(7,70,0) - if(data->age >= CURLVERSION_SEVENTH){ - if(data->cainfo){lua_pushstring(L, data->cainfo);lua_setfield(L, -2, "cainfo");} - if(data->capath){lua_pushstring(L, data->capath);lua_setfield(L, -2, "capath");} - } -#endif - -#if LCURL_CURL_VER_GE(7,72,0) - if(data->age >= CURLVERSION_EIGHTH){ - lutil_pushuint(L, data->zstd_ver_num); lua_setfield(L, -2, "zstd_ver_num"); - if(data->zstd_version){lua_pushstring(L, data->zstd_version);lua_setfield(L, -2, "zstd_version");} - } -#endif - - if(lua_isstring(L, 1)){ - lua_pushvalue(L, 1); lua_rawget(L, -2); - } - - return 1; -} - -static const struct luaL_Reg lcurl_functions[] = { - {"init", lcurl_init_unsafe }, - {"error", lcurl_error_new }, - {"form", lcurl_hpost_new }, - {"easy", lcurl_easy_new }, - {"multi", lcurl_multi_new }, - {"share", lcurl_share_new }, -#if LCURL_CURL_VER_GE(7,62,0) - {"url", lcurl_url_new }, -#endif - {"version", lcurl_version }, - {"version_info", lcurl_version_info }, -#if LCURL_CURL_VER_GE(7,73,0) - {"ieasy_options", lcurl_easy_option_iter }, - {"easy_option_by_id", lcurl_easy_option_by_id }, - {"easy_option_by_name", lcurl_easy_option_by_name }, -#endif - - {"__getregistry", lcurl_debug_getregistry}, - - {NULL,NULL} -}; - -static const struct luaL_Reg lcurl_functions_safe[] = { - {"init", lcurl_init_safe }, - {"error", lcurl_error_new }, - {"form", lcurl_hpost_new_safe }, - {"easy", lcurl_easy_new_safe }, - {"multi", lcurl_multi_new_safe }, - {"share", lcurl_share_new_safe }, -#if LCURL_CURL_VER_GE(7,62,0) - {"url", lcurl_url_new_safe }, -#endif - {"version", lcurl_version }, - {"version_info", lcurl_version_info }, -#if LCURL_CURL_VER_GE(7,73,0) - {"ieasy_options", lcurl_easy_option_iter }, - {"easy_option_by_id", lcurl_easy_option_by_id }, - {"easy_option_by_name", lcurl_easy_option_by_name }, -#endif - - { "__getregistry", lcurl_debug_getregistry }, - - {NULL,NULL} -}; - -static const lcurl_const_t lcurl_flags[] = { - -#define FLG_ENTRY(N) { #N, CURL##N }, -#include "lcflags.h" -#undef FLG_ENTRY - - {NULL, 0} -}; - -#if LCURL_CURL_VER_GE(7,56,0) -#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); -#else -#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); -#endif - -static int luaopen_lcurl_(lua_State *L, const struct luaL_Reg *func){ - if (getenv("LCURL_NO_INIT") == NULL) { // do not initialize curl if env variable LCURL_NO_INIT defined - lcurl_init_default(L); - } - - lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); - if(!lua_istable(L, -1)){ /* registry */ - lua_pop(L, 1); - lua_newtable(L); - } - - lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); - if(!lua_istable(L, -1)){ /* usevalues */ - lua_pop(L, 1); - lcurl_util_new_weak_table(L, "k"); - } - -#if LCURL_CURL_VER_GE(7,56,0) - lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); - if(!lua_istable(L, -1)){ /* Mime->Easy */ - lua_pop(L, 1); - lcurl_util_new_weak_table(L, "v"); - } -#endif - - lua_newtable(L); /* library */ - - LCURL_PUSH_NUP(L); luaL_setfuncs(L, func, NUP); - LCURL_PUSH_NUP(L); lcurl_error_initlib(L, NUP); - LCURL_PUSH_NUP(L); lcurl_hpost_initlib(L, NUP); - LCURL_PUSH_NUP(L); lcurl_easy_initlib (L, NUP); - LCURL_PUSH_NUP(L); lcurl_mime_initlib (L, NUP); - LCURL_PUSH_NUP(L); lcurl_multi_initlib(L, NUP); - LCURL_PUSH_NUP(L); lcurl_share_initlib(L, NUP); - LCURL_PUSH_NUP(L); lcurl_url_initlib (L, NUP); - - LCURL_PUSH_NUP(L); - -#if LCURL_CURL_VER_GE(7,56,0) - lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); -#endif - - lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); - lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); - - lcurl_util_set_const(L, lcurl_flags); - - lutil_push_null(L); - lua_setfield(L, -2, "null"); - - return 1; -} - -LCURL_EXPORT_API -int luaopen_lcurl(lua_State *L){ return luaopen_lcurl_(L, lcurl_functions); } - -LCURL_EXPORT_API -int luaopen_lcurl_safe(lua_State *L){ return luaopen_lcurl_(L, lcurl_functions_safe); } - diff --git a/watchdog/third_party/lua-curl/src/lcurl.h b/watchdog/third_party/lua-curl/src/lcurl.h deleted file mode 100644 index 8f43320..0000000 --- a/watchdog/third_party/lua-curl/src/lcurl.h +++ /dev/null @@ -1,31 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCURL_H_ -#define _LCURL_H_ - -#include "l52util.h" -#include "curl/curl.h" -#include "curl/easy.h" -#include "curl/multi.h" - -#include -#include - -#define LCURL_PREFIX "LcURL" - -#define LCURL_LUA_REGISTRY lua_upvalueindex(1) - -#define LCURL_USERVALUES lua_upvalueindex(2) - -/* only for `mime` API */ -#define LCURL_MIME_EASY lua_upvalueindex(3) - -#endif diff --git a/watchdog/third_party/lua-curl/src/lcurlapi.c b/watchdog/third_party/lua-curl/src/lcurlapi.c deleted file mode 100644 index 2a8d503..0000000 --- a/watchdog/third_party/lua-curl/src/lcurlapi.c +++ /dev/null @@ -1,218 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurlapi.h" -#include "lcurl.h" -#include "lcerror.h" -#include "lcutils.h" -#include - -#define LCURL_URL_NAME LCURL_PREFIX" URL" -static const char *LCURL_URL = LCURL_URL_NAME; - -#if LCURL_CURL_VER_GE(7,62,0) - -#define lcurl_geturl(L) lcurl_geturl_at(L, 1) - -int lcurl_url_create(lua_State *L, int error_mode){ - lcurl_url_t *p; - - p = lutil_newudatap(L, lcurl_url_t, LCURL_URL); - - p->url = curl_url(); - if(!p->url) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, CURLUE_OUT_OF_MEMORY); - - p->err_mode = error_mode; - - if (lua_gettop(L) > 1) { - const char *url = luaL_checkstring(L, 1); - unsigned int flags = 0; - CURLUcode code; - - if (lua_gettop(L) > 2) { - flags = (unsigned int)lutil_optint64(L, 2, 0); - } - - code = curl_url_set(p->url, CURLUPART_URL, url, flags); - if (code != CURLUE_OK) { - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); - } - } - - return 1; -} - -lcurl_url_t *lcurl_geturl_at(lua_State *L, int i){ - lcurl_url_t *p = (lcurl_url_t *)lutil_checkudatap (L, i, LCURL_URL); - luaL_argcheck (L, p != NULL, 1, LCURL_URL_NAME" object expected"); - return p; -} - -static int lcurl_url_cleanup(lua_State *L){ - lcurl_url_t *p = lcurl_geturl(L); - - if (p->url){ - curl_url_cleanup(p->url); - p->url = NULL; - } - - return 0; -} - -static int lcurl_url_dup(lua_State *L) { - lcurl_url_t *r = lcurl_geturl(L); - lcurl_url_t *p = lutil_newudatap(L, lcurl_url_t, LCURL_URL); - - p->url = curl_url_dup(r->url); - if (!p->url) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, CURLUE_OUT_OF_MEMORY); - - p->err_mode = r->err_mode; - - return 1; -} - -static int lcurl_url_set(lua_State *L, CURLUPart what){ - lcurl_url_t *p = lcurl_geturl(L); - CURLUcode code; - const char *part; - unsigned int flags = 0; - - luaL_argcheck(L, lua_type(L, 2) == LUA_TSTRING || lutil_is_null(L, 2), 2, "string expected"); - - part = lua_tostring(L, 2); - flags = (unsigned int)lutil_optint64(L, 3, 0); - - code = curl_url_set(p->url, what, part, flags); - if (code != CURLUE_OK) { - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); - } - - lua_settop(L, 1); - return 1; -} - -static int lcurl_url_get(lua_State *L, CURLUPart what, CURLUcode empty) { - lcurl_url_t *p = lcurl_geturl(L); - CURLUcode code; - char *part = NULL; - unsigned int flags = 0; - - flags = (unsigned int)lutil_optint64(L, 2, 0); - - code = curl_url_get(p->url, what, &part, flags); - if (code != CURLUE_OK) { - if (part) { - curl_free(part); - part = NULL; - } - - if (code != empty) { - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_URL, code); - } - } - - if (part == NULL) { - lutil_push_null(L); - } - else { - lua_pushstring(L, part); - curl_free(part); - } - - return 1; -} - -static int lcurl_url_to_s(lua_State *L) { - lcurl_url_t *p = lcurl_geturl(L); - char *part = NULL; - - CURLUcode code = curl_url_get(p->url, CURLUPART_URL, &part, 0); - - if (code != CURLUE_OK) { - if (part) { - curl_free(part); - } - - return lcurl_fail_ex(L, LCURL_ERROR_RAISE, LCURL_ERROR_URL, code); - } - - if (part == NULL) { - lua_pushliteral(L, ""); - } - else { - lua_pushstring(L, part); - curl_free(part); - } - - return 1; -} - -#define ENTRY_PART(N, S, E) static int lcurl_url_set_##N(lua_State *L){\ - return lcurl_url_set(L, CURL##S);\ -} -#define ENTRY_FLAG(S) - -#include "lcopturl.h" - -#undef ENTRY_PART -#undef ENTRY_FLAG - -#define ENTRY_PART(N, S, E) static int lcurl_url_get_##N(lua_State *L){\ - return lcurl_url_get(L, CURL##S, E);\ -} -#define ENTRY_FLAG(S) - -#include "lcopturl.h" - -#undef ENTRY_PART -#undef ENTRY_FLAG - -static const struct luaL_Reg lcurl_url_methods[] = { - #define ENTRY_PART(N, S, E) { "set_"#N, lcurl_url_set_##N }, - #define ENTRY_FLAG(S) - #include "lcopturl.h" - #undef ENTRY_PART - #undef ENTRY_FLAG - - #define ENTRY_PART(N, S, E) { "get_"#N, lcurl_url_get_##N }, - #define ENTRY_FLAG(S) - #include "lcopturl.h" - #undef ENTRY_PART - #undef ENTRY_FLAG - - { "dup", lcurl_url_dup }, - { "cleanup", lcurl_url_cleanup }, - { "__gc", lcurl_url_cleanup }, - { "__tostring", lcurl_url_to_s }, - - { NULL,NULL } -}; - -static const lcurl_const_t lcurl_url_opt[] = { - #define ENTRY_PART(N, S, E) { #S, CURL##S }, - #define ENTRY_FLAG(S) { "U_"#S, CURLU_##S }, - #include "lcopturl.h" - #undef ENTRY_PART - #undef ENTRY_FLAG - {NULL, 0} -}; -#endif - -void lcurl_url_initlib(lua_State *L, int nup){ -#if LCURL_CURL_VER_GE(7,62,0) - if(!lutil_createmetap(L, LCURL_URL, lcurl_url_methods, nup)) - lua_pop(L, nup); - lua_pop(L, 1); - - lcurl_util_set_const(L, lcurl_url_opt); -#else - lua_pop(L, nup); -#endif -} diff --git a/watchdog/third_party/lua-curl/src/lcurlapi.h b/watchdog/third_party/lua-curl/src/lcurlapi.h deleted file mode 100644 index 4dd4672..0000000 --- a/watchdog/third_party/lua-curl/src/lcurlapi.h +++ /dev/null @@ -1,34 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2018 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LURL_H_ -#define _LURL_H_ - -#include "lcurl.h" -#include "lcutils.h" -#include - -void lcurl_url_initlib(lua_State *L, int nup); - -#if LCURL_CURL_VER_GE(7,62,0) - -typedef struct lcurl_url_tag { - CURLU *url; - - int err_mode; -}lcurl_url_t; - -int lcurl_url_create(lua_State *L, int error_mode); - -lcurl_url_t *lcurl_geturl_at(lua_State *L, int i); - -#endif - -#endif \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/src/lcutils.c b/watchdog/third_party/lua-curl/src/lcutils.c deleted file mode 100644 index 6a406b4..0000000 --- a/watchdog/third_party/lua-curl/src/lcutils.c +++ /dev/null @@ -1,408 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#include "lcurl.h" -#include "lcutils.h" -#include "lcerror.h" - -#define LCURL_STORAGE_SLIST 1 -#define LCURL_STORAGE_KV 2 - -int lcurl_storage_init(lua_State *L){ - lua_newtable(L); - return luaL_ref(L, LCURL_LUA_REGISTRY); -} - -void lcurl_storage_preserve_value(lua_State *L, int storage, int i){ - assert(i > 0 && i <= lua_gettop(L)); - luaL_checkany(L, i); - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lua_pushvalue(L, i); lua_pushboolean(L, 1); lua_rawset(L, -3); - lua_pop(L, 1); -} - -void lcurl_storage_remove_value(lua_State *L, int storage, int i){ - assert(i > 0 && i <= lua_gettop(L)); - luaL_checkany(L, i); - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lua_pushvalue(L, i); lua_pushnil(L); lua_rawset(L, -3); - lua_pop(L, 1); -} - -static void lcurl_storage_ensure_t(lua_State *L, int t){ - lua_rawgeti(L, -1, t); - if(!lua_istable(L, -1)){ - lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_rawseti(L, -3, t); - } -} - -int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list){ - int r; - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lcurl_storage_ensure_t(L, LCURL_STORAGE_SLIST); - lua_pushlightuserdata(L, list); - r = luaL_ref(L, -2); - lua_pop(L, 2); - return r; -} - -void lcurl_storage_preserve_iv(lua_State *L, int storage, int i, int v){ - v = lua_absindex(L, v); - - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lcurl_storage_ensure_t(L, LCURL_STORAGE_KV); - lua_pushvalue(L, v); - lua_rawseti(L, -2, i); - lua_pop(L, 2); -} - -void lcurl_storage_remove_i(lua_State *L, int storage, int i){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lua_rawgeti(L, -1, LCURL_STORAGE_KV); - if(lua_istable(L, -1)){ - lua_pushnil(L); - lua_rawseti(L, -2, i); - } - lua_pop(L, 2); -} - -void lcurl_storage_get_i(lua_State *L, int storage, int i){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lua_rawgeti(L, -1, LCURL_STORAGE_KV); - if(lua_istable(L, -1)){ - lua_rawgeti(L, -1, i); - lua_remove(L, -2); - } - lua_remove(L, -2); -} - -struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx){ - struct curl_slist* list = NULL; - assert(idx != LUA_NOREF); - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lua_rawgeti(L, -1, LCURL_STORAGE_SLIST); // list storage - if(lua_istable(L, -1)){ - lua_rawgeti(L, -1, idx); - list = lua_touserdata(L, -1); - assert(list); - luaL_unref(L, -2, idx); - lua_pop(L, 1); - } - lua_pop(L, 2); - return list; -} - -int lcurl_storage_free(lua_State *L, int storage){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); - lua_rawgeti(L, -1, LCURL_STORAGE_SLIST); // list storage - if(lua_istable(L, -1)){ - lua_pushnil(L); - while(lua_next(L, -2) != 0){ - struct curl_slist * list = lua_touserdata(L, -1); - curl_slist_free_all(list); - lua_pushvalue(L, -2); lua_pushnil(L); - lua_rawset(L, -5); - lua_pop(L, 1); - } - } - luaL_unref(L, LCURL_LUA_REGISTRY, storage); - lua_pop(L, 2); - return LUA_NOREF; -} - -struct curl_slist* lcurl_util_array_to_slist(lua_State *L, int t){ - struct curl_slist *list = NULL; - int i, n = lua_rawlen(L, t); - - assert(lua_type(L, t) == LUA_TTABLE); - - for(i = 1; i <= n; ++i){ - lua_rawgeti(L, t, i); - list = curl_slist_append(list, lua_tostring(L, -1)); - lua_pop(L, 1); - } - return list; -} - -struct curl_slist* lcurl_util_to_slist(lua_State *L, int t){ - if(lua_type(L, t) == LUA_TTABLE){ - return lcurl_util_array_to_slist(L, t); - } - return 0; -} - -void lcurl_util_slist_set(lua_State *L, int t, struct curl_slist* list){ - int i; - t = lua_absindex(L, t); - for(i = 0;list;list = list->next){ - lua_pushstring(L, list->data); - lua_rawseti(L, t, ++i); - } -} - -void lcurl_util_slist_to_table(lua_State *L, struct curl_slist* list){ - lua_newtable(L); - lcurl_util_slist_set(L, -1, list); -} - -void lcurl_util_set_const(lua_State *L, const lcurl_const_t *reg){ - const lcurl_const_t *p; - for(p = reg; p->name; ++p){ - lua_pushstring(L, p->name); - lua_pushnumber(L, p->value); - lua_settable(L, -3); - } -} - -int lcurl_set_callback(lua_State *L, lcurl_callback_t *c, int i, const char *method){ - int top = lua_gettop(L); - i = lua_absindex(L, i); - - luaL_argcheck(L, !lua_isnoneornil(L, i), i, "no function present"); - luaL_argcheck(L, (top < (i + 2)), i + 2, "no arguments expected"); - - assert((top == i)||(top == (i + 1))); - - if(c->ud_ref != LUA_NOREF){ - luaL_unref(L, LCURL_LUA_REGISTRY, c->ud_ref); - c->ud_ref = LUA_NOREF; - } - - if(c->cb_ref != LUA_NOREF){ - luaL_unref(L, LCURL_LUA_REGISTRY, c->cb_ref); - c->cb_ref = LUA_NOREF; - } - - if(lutil_is_null(L, i)){ - if(top == (i + 1)){ - // Do we can just ignore this? - luaL_argcheck(L, - lua_isnoneornil(L, i + 1) || lutil_is_null(L, i + 1) - ,i + 1, "no context allowed when set callback to null" - ); - } - lua_pop(L, top - i + 1); - - return 1; - } - - if(lua_gettop(L) == (i + 1)){// function + context - c->ud_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - - assert(top == (2 + lua_gettop(L))); - return 1; - } - - assert(top == i); - - if(lua_isfunction(L, i)){ // function - c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - - assert(top == (1 + lua_gettop(L))); - return 1; - } - - if(lua_isuserdata(L, i) || lua_istable(L, i)){ // object - lua_getfield(L, i, method); - - luaL_argcheck(L, lua_isfunction(L, -1), 2, "method not found in object"); - - c->cb_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - c->ud_ref = luaL_ref(L, LCURL_LUA_REGISTRY); - - assert(top == (1 + lua_gettop(L))); - return 1; - } - - lua_pushliteral(L, "invalid object type"); - return lua_error(L); -} - -int lcurl_util_push_cb(lua_State *L, lcurl_callback_t *c){ - assert(c->cb_ref != LUA_NOREF); - lua_rawgeti(L, LCURL_LUA_REGISTRY, c->cb_ref); - if(c->ud_ref != LUA_NOREF){ - lua_rawgeti(L, LCURL_LUA_REGISTRY, c->ud_ref); - return 2; - } - return 1; -} - -int lcurl_util_new_weak_table(lua_State*L, const char *mode){ - int top = lua_gettop(L); - lua_newtable(L); - lua_newtable(L); - lua_pushstring(L, mode); - lua_setfield(L, -2, "__mode"); - lua_setmetatable(L,-2); - assert((top+1) == lua_gettop(L)); - return 1; -} - -int lcurl_util_pcall_method(lua_State *L, const char *name, int nargs, int nresults, int errfunc){ - int obj_index = -nargs - 1; - lua_getfield(L, obj_index, name); - lua_insert(L, obj_index - 1); - return lua_pcall(L, nargs + 1, nresults, errfunc); -} - -static void lcurl_utils_pcall_close(lua_State *L, int obj){ - int top = lua_gettop(L); - lua_pushvalue(L, obj); - lcurl_util_pcall_method(L, "close", 0, 0, 0); - lua_settop(L, top); -} - -int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, - int error_mode, int error_type, int error_code -){ - int top = lua_gettop(L); - opt = lua_absindex(L, opt); - obj = lua_absindex(L, obj); - - lua_pushnil(L); - while(lua_next(L, opt) != 0){ - int n; - assert(lua_gettop(L) == (top + 2)); - - if(lua_type(L, -2) == LUA_TNUMBER){ /* [curl.OPT_URL] = "http://localhost" */ - lua_pushvalue(L, -2); - lua_insert(L, -2); /*Stack : opt, obj, k, k, v */ - lua_pushliteral(L, "setopt"); /*Stack : opt, obj, k, k, v, "setopt" */ - n = 2; - } - else if(lua_type(L, -2) == LUA_TSTRING){ /* url = "http://localhost" */ - lua_pushliteral(L, "setopt_"); lua_pushvalue(L, -3); lua_concat(L, 2); - /*Stack : opt, obj, k, v, "setopt_XXX" */ - n = 1; - } - else{ - lua_pop(L, 1); - continue; - } - /*Stack : opt, obj, k,[ k,] v, `setoptXXX` */ - - lua_gettable(L, obj); /* get e["settop_XXX]*/ - - if(lua_isnil(L, -1)){ /* unknown option */ - if(do_close) lcurl_utils_pcall_close(L, obj); - lua_settop(L, top); - return lcurl_fail_ex(L, error_mode, error_type, error_code); - } - - lua_insert(L, -n-1); /*Stack : opt, obj, k, setoptXXX, [ k,] v */ - lua_pushvalue(L, obj); /*Stack : opt, obj, k, setoptXXX, [ k,] v, obj */ - lua_insert(L, -n-1); /*Stack : opt, obj, k, setoptXXX, obj, [ k,] v */ - - if(lua_pcall(L, n+1, 2, 0)){ - if(do_close) lcurl_utils_pcall_close(L, obj); - return lua_error(L); - } - - if(lua_isnil(L, -2)){ - if(do_close) lcurl_utils_pcall_close(L, obj); - lua_settop(L, top); - return 2; - } - - /*Stack : opt, obj, k, ok, nil*/ - lua_pop(L, 2); - assert(lua_gettop(L) == (top+1)); - } - assert(lua_gettop(L) == top); - return 0; -} - -void lcurl_stack_dump (lua_State *L){ - int i = 1, top = lua_gettop(L); - - fprintf(stderr, " ---------------- Stack Dump ----------------\n" ); - while( i <= top ) { - int t = lua_type(L, i); - switch (t) { - case LUA_TSTRING: - fprintf(stderr, "%d(%d):`%s'\n", i, i - top - 1, lua_tostring(L, i)); - break; - case LUA_TBOOLEAN: - fprintf(stderr, "%d(%d): %s\n", i, i - top - 1,lua_toboolean(L, i) ? "true" : "false"); - break; - case LUA_TNUMBER: - fprintf(stderr, "%d(%d): %g\n", i, i - top - 1, lua_tonumber(L, i)); - break; - default: - lua_getglobal(L, "tostring"); - lua_pushvalue(L, i); - lua_call(L, 1, 1); - fprintf(stderr, "%d(%d): %s(%s)\n", i, i - top - 1, lua_typename(L, t), lua_tostring(L, -1)); - lua_pop(L, 1); - break; - } - i++; - } - fprintf(stderr, " ------------ Stack Dump Finished ------------\n" ); -} - -curl_socket_t lcurl_opt_os_socket(lua_State *L, int idx, curl_socket_t def) { - if (lua_islightuserdata(L, idx)) - return (curl_socket_t)lua_touserdata(L, idx); - - return (curl_socket_t)lutil_optint64(L, idx, def); -} - -void lcurl_push_os_socket(lua_State *L, curl_socket_t fd) { -#if !defined(_WIN32) - lutil_pushint64(L, fd); -#else /*_WIN32*/ - /* Assumes that compiler can optimize constant conditions. MSVC do this. */ - - /*On Lua 5.3 lua_Integer type can be represented exactly*/ -#if LUA_VERSION_NUM >= 503 - if (sizeof(curl_socket_t) <= sizeof(lua_Integer)) { - lua_pushinteger(L, (lua_Integer)fd); - return; - } -#endif - -#if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) - /*! @todo test DBL_MANT_DIG, FLT_MANT_DIG */ - - if (sizeof(lua_Number) == 8) { /*we have 53 bits for integer*/ - if ((sizeof(curl_socket_t) <= 6)) { - lua_pushnumber(L, (lua_Number)fd); - return; - } - - if(((UINT_PTR)fd & 0x1FFFFFFFFFFFFF) == (UINT_PTR)fd) - lua_pushnumber(L, (lua_Number)fd); - else - lua_pushlightuserdata(L, (void*)fd); - - return; - } - - if (sizeof(lua_Number) == 4) { /*we have 24 bits for integer*/ - if (((UINT_PTR)fd & 0xFFFFFF) == (UINT_PTR)fd) - lua_pushnumber(L, (lua_Number)fd); - else - lua_pushlightuserdata(L, (void*)fd); - return; - } -#endif - - lutil_pushint64(L, fd); - if (lcurl_opt_os_socket(L, -1, 0) != fd) - lua_pushlightuserdata(L, (void*)fd); - -#endif /*_WIN32*/ -} diff --git a/watchdog/third_party/lua-curl/src/lcutils.h b/watchdog/third_party/lua-curl/src/lcutils.h deleted file mode 100644 index cfad1af..0000000 --- a/watchdog/third_party/lua-curl/src/lcutils.h +++ /dev/null @@ -1,108 +0,0 @@ -/****************************************************************************** -* Author: Alexey Melnichuk -* -* Copyright (C) 2014-2021 Alexey Melnichuk -* -* Licensed according to the included 'LICENSE' document -* -* This file is part of Lua-cURL library. -******************************************************************************/ - -#ifndef _LCUTILS_H_ -#define _LCUTILS_H_ - -#include "lcurl.h" - -#if defined(_MSC_VER) || defined(__cplusplus) -# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 1 -#elif defined(__STDC_VERSION__) -# if __STDC_VERSION__ >= 201112 -# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 1 -# endif -#endif - -#ifndef LCURL_CC_SUPPORT_FORWARD_TYPEDEF -# define LCURL_CC_SUPPORT_FORWARD_TYPEDEF 0 -#endif - -#ifdef __GNUC__ - #define LCURL_UNUSED_TYPEDEF __attribute__ ((unused)) -#else - #define LCURL_UNUSED_TYPEDEF -#endif - -#define LCURL_UNUSED_VAR LCURL_UNUSED_TYPEDEF - -#define LCURL_MAKE_VERSION(MIN, MAJ, PAT) ((MIN<<16) + (MAJ<<8) + PAT) -#define LCURL_CURL_VER_GE(MIN, MAJ, PAT) (LIBCURL_VERSION_NUM >= LCURL_MAKE_VERSION(MIN, MAJ, PAT)) - -#define LCURL_CONCAT_STATIC_ASSERT_IMPL_(x, y) LCURL_CONCAT1_STATIC_ASSERT_IMPL_ (x, y) -#define LCURL_CONCAT1_STATIC_ASSERT_IMPL_(x, y) LCURL_UNUSED_TYPEDEF x##y -#define LCURL_STATIC_ASSERT(expr) typedef char LCURL_CONCAT_STATIC_ASSERT_IMPL_(static_assert_failed_at_line_, __LINE__) [(expr) ? 1 : -1] - -#define LCURL_ASSERT_SAME_SIZE(a, b) LCURL_STATIC_ASSERT( sizeof(a) == sizeof(b) ) -#define LCURL_ASSERT_SAME_OFFSET(a, am, b, bm) LCURL_STATIC_ASSERT( (offsetof(a,am)) == (offsetof(b,bm)) ) -#define LCURL_ASSERT_SAME_FIELD_SIZE(a, am, b, bm) LCURL_ASSERT_SAME_SIZE(((a*)0)->am, ((b*)0)->bm) - -typedef struct lcurl_const_tag{ - const char *name; - long value; -}lcurl_const_t; - -typedef struct lcurl_callback_tag{ - int cb_ref; - int ud_ref; -}lcurl_callback_t; - -typedef struct lcurl_read_buffer_tag{ - int ref; - size_t off; -}lcurl_read_buffer_t; - -int lcurl_storage_init(lua_State *L); - -void lcurl_storage_preserve_value(lua_State *L, int storage, int i); - -void lcurl_storage_remove_value(lua_State *L, int storage, int i); - -int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list); - -struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx); - -void lcurl_storage_preserve_iv(lua_State *L, int storage, int i, int v); - -void lcurl_storage_remove_i(lua_State *L, int storage, int i); - -void lcurl_storage_get_i(lua_State *L, int storage, int i); - -int lcurl_storage_free(lua_State *L, int storage); - -struct curl_slist* lcurl_util_array_to_slist(lua_State *L, int t); - -struct curl_slist* lcurl_util_to_slist(lua_State *L, int t); - -void lcurl_util_slist_set(lua_State *L, int t, struct curl_slist* list); - -void lcurl_util_slist_to_table(lua_State *L, struct curl_slist* list); - -void lcurl_util_set_const(lua_State *L, const lcurl_const_t *reg); - -int lcurl_set_callback(lua_State *L, lcurl_callback_t *c, int i, const char *method); - -int lcurl_util_push_cb(lua_State *L, lcurl_callback_t *c); - -int lcurl_util_new_weak_table(lua_State*L, const char *mode); - -int lcurl_util_pcall_method(lua_State *L, const char *name, int nargs, int nresults, int errfunc); - -int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, - int error_mode, int error_type, int error_code - ); - -void lcurl_stack_dump (lua_State *L); - -curl_socket_t lcurl_opt_os_socket(lua_State *L, int idx, curl_socket_t def); - -void lcurl_push_os_socket(lua_State *L, curl_socket_t fd); - -#endif diff --git a/watchdog/third_party/lua-curl/src/lua/cURL.lua b/watchdog/third_party/lua-curl/src/lua/cURL.lua deleted file mode 100644 index c546cf4..0000000 --- a/watchdog/third_party/lua-curl/src/lua/cURL.lua +++ /dev/null @@ -1,14 +0,0 @@ --- --- Author: Alexey Melnichuk --- --- Copyright (C) 2014-2016 Alexey Melnichuk --- --- Licensed according to the included 'LICENSE' document --- --- This file is part of Lua-cURL library. --- - -local curl = require "lcurl" -local impl = require "cURL.impl.cURL" - -return impl(curl) diff --git a/watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua b/watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua deleted file mode 100644 index a7b141d..0000000 --- a/watchdog/third_party/lua-curl/src/lua/cURL/impl/cURL.lua +++ /dev/null @@ -1,739 +0,0 @@ --- --- Author: Alexey Melnichuk --- --- Copyright (C) 2014-2021 Alexey Melnichuk --- --- Licensed according to the included 'LICENSE' document --- --- This file is part of Lua-cURL library. --- - -local module_info = { - _NAME = "Lua-cURL"; - _VERSION = "0.3.13"; - _LICENSE = "MIT"; - _COPYRIGHT = "Copyright (c) 2014-2021 Alexey Melnichuk"; -} - -local function hash_id(str) - local id = string.match(str, "%((.-)%)") or string.match(str, ': (%x+)$') - return id -end - -local function clone(t, o) - o = o or {} - for k,v in pairs(t) do o[k]=v end - return o -end - -local function wrap_function(k) - return function(self, ...) - local ok, err = self._handle[k](self._handle, ...) - if ok == self._handle then return self end - return ok, err - end -end - -local function wrap_setopt_flags(k, flags) - k = "setopt_" .. k - local flags2 = clone(flags) - for k, v in pairs(flags) do flags2[v] = v end - - return function(self, v) - v = assert(flags2[v], "Unsupported value " .. tostring(v)) - local ok, err = self._handle[k](self._handle, v) - if ok == self._handle then return self end - return ok, err - end -end - -local function new_buffers() - local buffers = {resp = {}, _ = {}} - - function buffers:append(e, ...) - local resp = assert(e:getinfo_response_code()) - if not self._[e] then self._[e] = {} end - - local b = self._[e] - - if self.resp[e] ~= resp then - b[#b + 1] = {"response", resp} - self.resp[e] = resp - end - - b[#b + 1] = {...} - end - - function buffers:next() - for e, t in pairs(self._) do - local m = table.remove(t, 1) - if m then return e, m end - end - end - - return buffers -end - -local function make_iterator(self, perform) - local curl = require "lcurl.safe" - - local buffers = new_buffers() - - -- reset callbacks to all easy handles - local function reset_easy(self) - if not self._easy_mark then -- that means we have some new easy handles - for h, e in pairs(self._easy) do if h ~= 'n' then - e:setopt_writefunction (function(str) buffers:append(e, "data", str) end) - e:setopt_headerfunction(function(str) buffers:append(e, "header", str) end) - end end - self._easy_mark = true - end - return self._easy.n - end - - if 0 == reset_easy(self) then return end - - assert(perform(self)) - - return function() - -- we can add new handle during iteration - local remain = reset_easy(self) - - -- wait next event - while true do - local e, t = buffers:next() - if t then return t[2], t[1], e end - if remain == 0 then break end - - self:wait() - - local n = assert(perform(self)) - - if n <= remain then - while true do - local e, ok, err = assert(self:info_read()) - if e == 0 then break end - if ok then - ok = e:getinfo_response_code() or ok - buffers:append(e, "done", ok) - else buffers:append(e, "error", err) end - self:remove_handle(e) - e:unsetopt_headerfunction() - e:unsetopt_writefunction() - end - end - - remain = n - end - end -end - --- name = //// --- --- = { --- stream = function/object --- length = ?number --- name = ?string --- type = ?string --- headers = ?table --- } --- --- = { --- file = string --- type = ?string --- name = ?string --- headers = ?table --- } --- --- = { --- data = string --- name = string --- type = ?string --- headers = ?table --- } --- --- = { --- content = string -- or first key in table --- type = ?string --- headers = ?table --- } --- - -local function form_add_element(form, name, value) - local vt = type(value) - if vt == "string" then return form:add_content(name, value) end - - assert(type(name) == "string") - assert(vt == "table") - assert((value.name == nil) or (type(value.name ) == 'string')) - assert((value.type == nil) or (type(value.type ) == 'string')) - assert((value.headers == nil) or (type(value.headers) == 'table' )) - - if value.stream then - local vst = type(value.stream) - - if vst == 'function' then - assert(type(value.length) == 'number') - local length = value.length - return form:add_stream(name, value.name, value.type, value.headers, length, value.stream) - end - - if (vst == 'table') or (vst == 'userdata') then - local length = value.length or assert(value.stream:length()) - assert(type(length) == 'number') - return form:add_stream(name, value.name, value.type, value.headers, length, value.stream) - end - - error("Unsupported stream type: " .. vst) - end - - if value.file then - assert(type(value.file) == 'string') - return form:add_file(name, value.file, value.type, value.filename, value.headers) - end - - if value.data then - assert(type(value.data) == 'string') - assert(type(value.name) == 'string') - return form:add_buffer(name, value.name, value.data, value.type, value.headers) - end - - local content = value[1] or value.content - if content then - assert(type(content) == 'string') - if value.type then - return form:add_content(name, content, value.type, value.headers) - end - return form:add_content(name, content, value.headers) - end - - return form -end - -local function form_add(form, data) - for k, v in pairs(data) do - local ok, err = form_add_element(form, k, v) - if not ok then return nil, err end - end - - return form -end - -local function class(ctor, type_name) - local C = {} - C.__type = type_name or "LcURL Unknown" - C.__index = function(self, k) - local fn = C[k] - - if not fn and self._handle[k] then - fn = wrap_function(k) - C[k] = fn - end - return fn - end - - function C:new(...) - local h, err = ctor() - if not h then return nil, err end - - local o = setmetatable({ - _handle = h - }, self) - - if self.__init then return self.__init(o, ...) end - - return o - end - - function C:handle() - return self._handle - end - - return C -end - -local function Load_cURLv2(cURL, curl) - -------------------------------------------- -local Easy = class(curl.easy, "LcURL Easy") do - -local perform = wrap_function("perform") -local setopt_share = wrap_function("setopt_share") -local setopt_readfunction = wrap_function("setopt_readfunction") - -local NONE = {} - -function Easy:_call_readfunction(...) - if self._rd_ud == NONE then - return self._rd_fn(...) - end - return self._rd_fn(self._rd_ud, ...) -end - -function Easy:setopt_readfunction(fn, ...) - assert(fn) - - if select('#', ...) == 0 then - if type(fn) == "function" then - self._rd_fn = fn - self._rd_ud = NONE - else - self._rd_fn = assert(fn.read) - self._rd_ud = fn - end - else - self._rd_fn = fn - self._ud_fn = ... - end - - return setopt_readfunction(self, fn, ...) -end - -function Easy:perform(opt) - opt = opt or {} - - local oerror = opt.errorfunction or function(err) return nil, err end - - if opt.readfunction then - local ok, err = self:setopt_readfunction(opt.readfunction) - if not ok then return oerror(err) end - end - - if opt.writefunction then - local ok, err = self:setopt_writefunction(opt.writefunction) - if not ok then return oerror(err) end - end - - if opt.headerfunction then - local ok, err = self:setopt_headerfunction(opt.headerfunction) - if not ok then return oerror(err) end - end - - local ok, err = perform(self) - if not ok then return oerror(err) end - - return self -end - -function Easy:post(data) - local form = curl.form() - local ok, err = true - - for k, v in pairs(data) do - if type(v) == "string" then - ok, err = form:add_content(k, v) - else - assert(type(v) == "table") - if v.stream_length then - local len = assert(tonumber(v.stream_length)) - assert(v.file) - if v.stream then - ok, err = form:add_stream(k, v.file, v.type, v.headers, len, v.stream) - else - ok, err = form:add_stream(k, v.file, v.type, v.headers, len, self._call_readfunction, self) - end - elseif v.data then - ok, err = form:add_buffer(k, v.file, v.data, v.type, v.headers) - else - ok, err = form:add_file(k, v.file, v.type, v.filename, v.headers) - end - end - if not ok then break end - end - - if not ok then - form:free() - return nil, err - end - - ok, err = self:setopt_httppost(form) - if not ok then - form:free() - return nil, err - end - - return self -end - -function Easy:setopt_share(s) - return setopt_share(self, s:handle()) -end - -Easy.setopt_proxytype = wrap_setopt_flags("proxytype", { - ["HTTP" ] = curl.PROXY_HTTP; - ["HTTP_1_0" ] = curl.PROXY_HTTP_1_0; - ["SOCKS4" ] = curl.PROXY_SOCKS4; - ["SOCKS5" ] = curl.PROXY_SOCKS5; - ["SOCKS4A" ] = curl.PROXY_SOCKS4A; - ["SOCKS5_HOSTNAME" ] = curl.PROXY_SOCKS5_HOSTNAME; - ["HTTPS" ] = curl.PROXY_HTTPS; -}) - -Easy.setopt_httpauth = wrap_setopt_flags("httpauth", { - ["NONE" ] = curl.AUTH_NONE; - ["BASIC" ] = curl.AUTH_BASIC; - ["DIGEST" ] = curl.AUTH_DIGEST; - ["GSSNEGOTIATE" ] = curl.AUTH_GSSNEGOTIATE; - ["NEGOTIATE" ] = curl.AUTH_NEGOTIATE; - ["NTLM" ] = curl.AUTH_NTLM; - ["DIGEST_IE" ] = curl.AUTH_DIGEST_IE; - ["GSSAPI" ] = curl.AUTH_GSSAPI; - ["NTLM_WB" ] = curl.AUTH_NTLM_WB; - ["ONLY" ] = curl.AUTH_ONLY; - ["ANY" ] = curl.AUTH_ANY; - ["ANYSAFE" ] = curl.AUTH_ANYSAFE; - ["BEARER" ] = curl.AUTH_BEARER; -}) - -Easy.setopt_ssh_auth_types = wrap_setopt_flags("ssh_auth_types", { - ["NONE" ] = curl.SSH_AUTH_NONE; - ["ANY" ] = curl.SSH_AUTH_ANY; - ["PUBLICKEY" ] = curl.SSH_AUTH_PUBLICKEY; - ["PASSWORD" ] = curl.SSH_AUTH_PASSWORD; - ["HOST" ] = curl.SSH_AUTH_HOST; - ["GSSAPI" ] = curl.SSH_AUTH_GSSAPI; - ["KEYBOARD" ] = curl.SSH_AUTH_KEYBOARD; - ["AGENT" ] = curl.SSH_AUTH_AGENT; - ["DEFAULT" ] = curl.SSH_AUTH_DEFAULT; -}) - -end -------------------------------------------- - -------------------------------------------- -local Multi = class(curl.multi, "LcURL Multi") do - -local perform = wrap_function("perform") -local add_handle = wrap_function("add_handle") -local remove_handle = wrap_function("remove_handle") - -function Multi:__init() - self._easy = {n = 0} - return self -end - -function Multi:perform() - return make_iterator(self, perform) -end - -function Multi:add_handle(e) - assert(self._easy.n >= 0) - - local h = e:handle() - if self._easy[h] then return self end - - local ok, err = add_handle(self, h) - if not ok then return nil, err end - self._easy[h], self._easy.n = e, self._easy.n + 1 - self._easy_mark = nil - - return self -end - -function Multi:remove_handle(e) - local h = e:handle() - - if self._easy[h] then - self._easy[h], self._easy.n = nil, self._easy.n - 1 - end - assert(self._easy.n >= 0) - - return remove_handle(self, h) -end - -function Multi:info_read(...) - while true do - local h, ok, err = self:handle():info_read(...) - if not h then return nil, ok end - if h == 0 then return h end - - local e = self._easy[h] - if e then - if ... then - self._easy[h], self._easy.n = nil, self._easy.n - 1 - end - return e, ok, err - end - end -end - -end -------------------------------------------- - -------------------------------------------- -local Share = class(curl.share, "LcURL Share") do - -Share.setopt_share = wrap_setopt_flags("share", { - [ "COOKIE" ] = curl.LOCK_DATA_COOKIE; - [ "DNS" ] = curl.LOCK_DATA_DNS; - [ "SSL_SESSION" ] = curl.LOCK_DATA_SSL_SESSION; -}) - -end -------------------------------------------- - -assert(cURL.easy_init == nil) -function cURL.easy_init() return Easy:new() end - -assert(cURL.multi_init == nil) -function cURL.multi_init() return Multi:new() end - -assert(cURL.share_init == nil) -function cURL.share_init() return Share:new() end - -end - -local function Load_cURLv3(cURL, curl) - -------------------------------------------- -local Form = class(curl.form, "LcURL Form") do - -function Form:__init(opt) - if opt then return self:add(opt) end - return self -end - -function Form:add(data) - return form_add(self, data) -end - -function Form:__tostring() - local id = hash_id(tostring(self._handle)) - return string.format("%s %s (%s)", module_info._NAME, 'Form', id) -end - -end -------------------------------------------- - -------------------------------------------- -local Easy = class(curl.easy, "LcURL Easy") do - -function Easy:__init(opt) - if opt then return self:setopt(opt) end - return self -end - -local perform = wrap_function("perform") -function Easy:perform(opt) - if opt then - local ok, err = self:setopt(opt) - if not ok then return nil, err end - end - - return perform(self) -end - -local setopt_httppost = wrap_function("setopt_httppost") -function Easy:setopt_httppost(form) - return setopt_httppost(self, form:handle()) -end - -if curl.OPT_STREAM_DEPENDS then - -local setopt_stream_depends = wrap_function("setopt_stream_depends") -function Easy:setopt_stream_depends(easy) - return setopt_stream_depends(self, easy:handle()) -end - -local setopt_stream_depends_e = wrap_function("setopt_stream_depends_e") -function Easy:setopt_stream_depends_e(easy) - return setopt_stream_depends_e(self, easy:handle()) -end - -end - -local setopt = wrap_function("setopt") -local custom_setopt = { - [curl.OPT_HTTPPOST or true] = 'setopt_httppost'; - [curl.OPT_STREAM_DEPENDS or true] = 'setopt_stream_depends'; - [curl.OPT_STREAM_DEPENDS_E or true] = 'setopt_stream_depends_e'; -} -custom_setopt[true] = nil - -function Easy:setopt(k, v) - if type(k) == 'table' then - local t = k - - local t2 - local hpost = t.httppost or t[curl.OPT_HTTPPOST] - if hpost and hpost._handle then - t = t2 or clone(t); t2 = t; - if t.httppost then t.httppost = hpost:handle() end - if t[curl.OPT_HTTPPOST] then t[curl.OPT_HTTPPOST] = hpost:handle() end - end - - local easy = t.stream_depends or t[curl.OPT_STREAM_DEPENDS] - if easy and easy._handle then - t = t2 or clone(t); t2 = t; - if t.stream_depends then t.stream_depends = easy:handle() end - if t[curl.OPT_STREAM_DEPENDS] then t[curl.OPT_STREAM_DEPENDS] = easy:handle() end - end - - local easy = t.stream_depends_e or t[curl.OPT_STREAM_DEPENDS_E] - if easy and easy._handle then - t = t2 or clone(t); t2 = t; - if t.stream_depends_e then t.stream_depends_e = easy:handle() end - if t[curl.OPT_STREAM_DEPENDS_E] then t[curl.OPT_STREAM_DEPENDS_E] = easy:handle() end - end - - return setopt(self, t) - end - - local setname = custom_setopt[k] - if setname then - return self[setname](self, v) - end - - return setopt(self, k, v) -end - -function Easy:__tostring() - local id = hash_id(tostring(self._handle)) - return string.format("%s %s (%s)", module_info._NAME, 'Easy', id) -end - -end -------------------------------------------- - -------------------------------------------- -local Multi = class(curl.multi, "LcURL Multi") do - -local add_handle = wrap_function("add_handle") -local remove_handle = wrap_function("remove_handle") - -function Multi:__init(opt) - self._easy = {n = 0} - if opt then self:setopt(opt) end - return self -end - -function Multi:iperform() - return make_iterator(self, self.perform) -end - -function Multi:add_handle(e) - assert(self._easy.n >= 0) - - local h = e:handle() - if self._easy[h] then - return nil, curl.error(curl.ERROR_MULTI, curl.E_MULTI_ADDED_ALREADY or curl.E_MULTI_BAD_EASY_HANDLE) - end - - local ok, err = add_handle(self, h) - if not ok then return nil, err end - self._easy[h], self._easy.n = e, self._easy.n + 1 - self._easy_mark = nil - - return self -end - -function Multi:remove_handle(e) - local h = e:handle() - - if self._easy[h] then - self._easy[h], self._easy.n = nil, self._easy.n - 1 - end - assert(self._easy.n >= 0) - - return remove_handle(self, h) -end - -function Multi:info_read(...) - while true do - local h, ok, err = self:handle():info_read(...) - if not h then return nil, ok end - if h == 0 then return h end - - local e = self._easy[h] - if e then - if ... then - self._easy[h], self._easy.n = nil, self._easy.n - 1 - end - return e, ok, err - end - end -end - -local function wrap_callback(...) - local n = select("#", ...) - local fn, ctx, has_ctx - if n >= 2 then - has_ctx, fn, ctx = true, assert(...) - else - fn = assert(...) - if type(fn) ~= "function" then - has_ctx, fn, ctx = true, assert(fn.socket), fn - end - end - if has_ctx then - return function(...) return fn(ctx, ...) end - end - return function(...) return fn(...) end -end - -local function wrap_socketfunction(self, cb) - local ptr = setmetatable({value = self},{__mode = 'v'}) - return function(h, ...) - local e = ptr.value._easy[h] - if e then return cb(e, ...) end - return 0 - end -end - -local setopt_socketfunction = wrap_function("setopt_socketfunction") -function Multi:setopt_socketfunction(...) - local cb = wrap_callback(...) - - return setopt_socketfunction(self, wrap_socketfunction(self, cb)) -end - -local setopt = wrap_function("setopt") -function Multi:setopt(k, v) - if type(k) == 'table' then - local t = k - - local socketfunction = t.socketfunction or t[curl.OPT_SOCKETFUNCTION] - if socketfunction then - t = clone(t) - local fn = wrap_socketfunction(self, socketfunction) - if t.socketfunction then t.socketfunction = fn end - if t[curl.OPT_SOCKETFUNCTION] then t[curl.OPT_SOCKETFUNCTION] = fn end - end - - return setopt(self, t) - end - - if k == curl.OPT_SOCKETFUNCTION then - return self:setopt_socketfunction(v) - end - - return setopt(self, k, v) -end - -function Multi:__tostring() - local id = hash_id(tostring(self._handle)) - return string.format("%s %s (%s)", module_info._NAME, 'Multi', id) -end - -end -------------------------------------------- - -setmetatable(cURL, {__index = curl}) - -function cURL.form(...) return Form:new(...) end - -function cURL.easy(...) return Easy:new(...) end - -function cURL.multi(...) return Multi:new(...) end - -end - -return function(curl) - local cURL = clone(module_info) - - Load_cURLv3(cURL, curl) - - Load_cURLv2(cURL, curl) - - return cURL -end diff --git a/watchdog/third_party/lua-curl/src/lua/cURL/safe.lua b/watchdog/third_party/lua-curl/src/lua/cURL/safe.lua deleted file mode 100644 index cc46cfb..0000000 --- a/watchdog/third_party/lua-curl/src/lua/cURL/safe.lua +++ /dev/null @@ -1,14 +0,0 @@ --- --- Author: Alexey Melnichuk --- --- Copyright (C) 2014-2016 Alexey Melnichuk --- --- Licensed according to the included 'LICENSE' document --- --- This file is part of Lua-cURL library. --- - -local curl = require "lcurl.safe" -local impl = require "cURL.impl.cURL" - -return impl(curl) diff --git a/watchdog/third_party/lua-curl/src/lua/cURL/utils.lua b/watchdog/third_party/lua-curl/src/lua/cURL/utils.lua deleted file mode 100644 index ea7dcae..0000000 --- a/watchdog/third_party/lua-curl/src/lua/cURL/utils.lua +++ /dev/null @@ -1,79 +0,0 @@ --- --- Author: Alexey Melnichuk --- --- Copyright (C) 2014-2016 Alexey Melnichuk --- --- Licensed according to the included 'LICENSE' document --- --- This file is part of Lua-cURL library. --- - ---- Returns path to cURL ca bundle --- --- @tparam[opt="curl-ca-bundle.crt"] string name name of bundle --- @treturn string path to file (CURLOPT_CAINFO) --- @treturn string path to ssl dir path (CURLOPT_CAPATH) --- --- @usage --- local file, path = find_ca_bundle() --- if file then e:setopt_cainfo(file) end --- if path then e:setopt_capath(path) end --- -local function find_ca_bundle(name) - name = name or "curl-ca-bundle.crt" - - local path = require "path" - local env = setmetatable({},{__index = function(_, name) return os.getenv(name) end}) - - local function split(str, sep, plain) - local b, res = 1, {} - while b <= #str do - local e, e2 = string.find(str, sep, b, plain) - if e then - table.insert(res, (string.sub(str, b, e-1))) - b = e2 + 1 - else - table.insert(res, (string.sub(str, b))) - break - end - end - return res - end - - if env.CURL_CA_BUNDLE and path.isfile(env.CURL_CA_BUNDLE) then - return env.CURL_CA_BUNDLE - end - - if env.SSL_CERT_DIR and path.isdir(env.SSL_CERT_DIR) then - return nil, env.SSL_CERT_DIR - end - - if env.SSL_CERT_FILE and path.isfile(env.SSL_CERT_FILE) then - return env.SSL_CERT_FILE - end - - if not path.IS_WINDOWS then return end - - local paths = { - '.', - path.join(env.windir, "System32"), - path.join(env.windir, "SysWOW64"), - env.windir, - } - for _, p in ipairs(split(env.path, ';')) do paths[#paths + 1] = p end - - for _, p in ipairs(paths) do - p = path.fullpath(p) - if path.isdir(p) then - p = path.join(p, name) - if path.isfile(p) then - return p - end - end - end -end - -return { - find_ca_bundle = find_ca_bundle; -} - diff --git a/watchdog/third_party/lua-curl/test/.luacov b/watchdog/third_party/lua-curl/test/.luacov deleted file mode 100644 index e6e0a49..0000000 --- a/watchdog/third_party/lua-curl/test/.luacov +++ /dev/null @@ -1,38 +0,0 @@ -return { - configfile = ".luacov", - - -- filename to store stats collected - statsfile = "luacov.stats.out", - - -- filename to store report - reportfile = "luacov.report.json", - - -- Run reporter on completion? (won't work for ticks) - runreport = false, - - -- Delete stats file after reporting? - deletestats = false, - - -- Patterns for files to include when reporting - -- all will be included if nothing is listed - -- (exclude overrules include, do not include - -- the .lua extension) - include = { - "/cURL$", - "/cURL/.+$", - }, - - -- Patterns for files to exclude when reporting - -- all will be included if nothing is listed - -- (exclude overrules include, do not include - -- the .lua extension) - exclude = { - }, - - -- configuration for luacov-coveralls reporter - coveralls = { - pathcorrect = { - {"^.-[/\\]share[/\\]lua[/\\]5.%d", "src/lua"}; - }, - }, -} \ No newline at end of file diff --git a/watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec b/watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec deleted file mode 100644 index 5edc3c2..0000000 --- a/watchdog/third_party/lua-curl/test/deps/lua-http-parser-2.7-1.rockspec +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env lua - -package = 'lua-http-parser' -version = '2.7-1' -source = { - url = 'gitrec+https://github.com/brimworks/lua-http-parser' -} -description = { - summary = "A Lua binding to Ryan Dahl's http request/response parser.", - detailed = '', - homepage = 'http://github.com/brimworks/lua-http-parser', - license = 'MIT', --as with Ryan's -} -dependencies = { - 'lua >= 5.1, < 5.5', - 'luarocks-fetch-gitrec', -} -build = { - type = 'builtin', - modules = { - ['http.parser'] = { - sources = { - "http-parser/http_parser.c", - "lua-http-parser.c" - } - } - } -} diff --git a/watchdog/third_party/lua-curl/test/lunit/console.lua b/watchdog/third_party/lua-curl/test/lunit/console.lua deleted file mode 100644 index 97a4d1b..0000000 --- a/watchdog/third_party/lua-curl/test/lunit/console.lua +++ /dev/null @@ -1,132 +0,0 @@ - ---[[-------------------------------------------------------------------------- - - This file is part of lunit 0.6. - - For Details about lunit look at: http://www.mroth.net/lunit/ - - Author: Michael Roth - - Copyright (c) 2006-2008 Michael Roth - - 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. - ---]]-------------------------------------------------------------------------- - - - ---[[ - - begin() - run(testcasename, testname) - err(fullname, message, traceback) - fail(fullname, where, message, usermessage) - pass(testcasename, testname) - done() - - Fullname: - testcase.testname - testcase.testname:setupname - testcase.testname:teardownname - ---]] - - -local lunit = require "lunit" -local string = require "string" -local io = require "io" -local table = require "table" - -local _M = {} - -local function rfill(str, wdt, ch) - if wdt > #str then str = str .. (ch or ' '):rep(wdt - #str) end - return str -end - -local function printformat(format, ...) - io.write( string.format(format, ...) ) -end - -local columns_printed = 0 - -local function writestatus(char) - if columns_printed == 0 then - io.write(" ") - end - if columns_printed == 60 then - io.write("\n ") - columns_printed = 0 - end - io.write(char) - io.flush() - columns_printed = columns_printed + 1 -end - -local msgs = {} - -function _M.begin() - local total_tc = 0 - local total_tests = 0 - - msgs = {} -- e - - for tcname in lunit.testcases() do - total_tc = total_tc + 1 - for testname, test in lunit.tests(tcname) do - total_tests = total_tests + 1 - end - end - - printformat("Loaded testsuite with %d tests in %d testcases.\n\n", total_tests, total_tc) -end - -function _M.run(testcasename, testname) - io.write(rfill(testcasename .. '.' .. testname, 70)) io.flush() -end - -function _M.err(fullname, message, traceback) - io.write(" - error!\n") - io.write("Error! ("..fullname.."):\n"..message.."\n\t"..table.concat(traceback, "\n\t"), "\n") -end - -function _M.fail(fullname, where, message, usermessage) - io.write(" - fail!\n") - io.write(string.format("Failure (%s): %s\n%s: %s", fullname, usermessage or "", where, message), "\n") -end - -function _M.skip(fullname, where, message, usermessage) - io.write(" - skip!\n") - io.write(string.format("Skip (%s): %s\n%s: %s", fullname, usermessage or "", where, message), "\n") -end - -function _M.pass(testcasename, testname) - io.write(" - pass!\n") -end - -function _M.done() - printformat("\n\n%d Assertions checked.\n", lunit.stats.assertions ) - print() - - printformat("Testsuite finished (%d passed, %d failed, %d errors, %d skipped).\n", - lunit.stats.passed, lunit.stats.failed, lunit.stats.errors, lunit.stats.skipped ) -end - -return _M diff --git a/watchdog/third_party/lua-curl/test/run.lua b/watchdog/third_party/lua-curl/test/run.lua deleted file mode 100644 index 65394be..0000000 --- a/watchdog/third_party/lua-curl/test/run.lua +++ /dev/null @@ -1,34 +0,0 @@ -local RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end - -lunit = require "lunit" - -local ok, curl = pcall(require, "cURL") -local version if ok then - version = curl.version() -else - io.stderr:write('can not load cURL:' .. curl) - os.exit(-1) -end - -print("------------------------------------") -print("Module name: " .. curl._NAME); -print("Module version: " .. curl._VERSION); -print("Lua version: " .. (_G.jit and _G.jit.version or _G._VERSION)) -print("cURL version: " .. version) -print("------------------------------------") -print("") - -require "test_safe" -require "test_easy" -require "test_form" -require "test_mime" -require "test_curl" -require "test_urlapi" - -RUN() diff --git a/watchdog/third_party/lua-curl/test/server.lua b/watchdog/third_party/lua-curl/test/server.lua deleted file mode 100644 index a3053da..0000000 --- a/watchdog/third_party/lua-curl/test/server.lua +++ /dev/null @@ -1,121 +0,0 @@ -local function prequire(m) - local ok, err = pcall(require, m) - if not ok then return nil, err end - return err -end - -local uv = prequire "lluv" -local Pegasus = require (uv and "lluv.pegasus" or "pegasus") -local Router = require "pegasus.plugins.router" -local json = require "dkjson" --- local pp = require "pp" - -local function decode_form(form) - return string.match(form, '\r\nContent%-Disposition:%s*form%-data;%s*name="(.-)".-\r\n\r\n(.-)\r\n') -end - -local function decode_params(str) - local params = {} - for k, v in string.gmatch(str, '([^=]+)=([^&]+)&?') do - params[k] = v - end - return params -end - -local function rand_bytes(n) - local res = {} - for i = 1, n do - res[#res + 1] = string.char(math.random(254)) - end - return table.concat(res) -end - -local r = Router:new() - -local server = Pegasus:new{ - plugins = { r }; - host = '127.0.0.1', port = 7090, timout = 10 -} - -local function recvFullBody(request, T1) - local body, counter = {}, 0 - - local result, status - while true do - result, status = request:receiveBody() - if result then - counter = 0 - body[#body + 1] = result - elseif status ~= 'timeout' then - break - else - counter = counter + 1 - if counter > T1 then break end - end - end - - return table.concat(body), status -end - -local function buildResponse(request) - local headers = request:headers() - local params = request:params() - local path = request:path() - local ip = request.ip - local host = headers and headers.Host or '127.0.0.1' - local url = string.format('http://%s%s', host, path) - - return { - args = params; - headers = headers; - origin = ip; - url = url; - } -end - -r:get('/get', function(request, response) - local result = buildResponse(request) - result.body = recvFullBody(request, 15) - - response:statusCode(200) - response:contentType('application/json') - response:write(json.encode(result, {indent = true})) -end) - -r:post('/post', function(request, response, params) - local result = buildResponse(request) - - local body, status = recvFullBody(request, 15) - - local name, data = decode_form(body) - if name then - result.form = {[name] = data} - else - result.form = decode_params(body) - end - - response:statusCode(200) - response:contentType('application/json') - response:write(json.encode(result, {indent = true})) -end) - -r:get('/bytes/:size', function(request, response, params) - local headers = request:headers() - local size = tonumber(params.size) or 1024 - local result = rand_bytes(size) - - response:statusCode(200) - response:addHeader('Connection', 'close') - response:contentType('application/octet-stream') - response:write(result) -end) - -server:start(function(request, response) - local headers = request:headers() - - response:statusCode(200) - response:addHeader('Content-Type', 'text/plain') - response:write('Hello from Pegasus') -end) - -if uv then uv.run() end diff --git a/watchdog/third_party/lua-curl/test/test_curl.lua b/watchdog/third_party/lua-curl/test/test_curl.lua deleted file mode 100644 index ce6a44a..0000000 --- a/watchdog/third_party/lua-curl/test/test_curl.lua +++ /dev/null @@ -1,451 +0,0 @@ -local lunit, RUN = lunit do -RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end -lunit = require "lunit" -end - -local _,luacov = pcall(require, "luacov") -local TEST_CASE = assert(lunit.TEST_CASE) -local skip = lunit.skip or function() end - -local curl = require "cURL" -local scurl = require "cURL.safe" -local json = require "dkjson" -local fname = "./test.download" - -local utils = require "utils" - -local weak_ptr, gc_collect, is_curl_ge, is_curl_eq, read_file, stream, Stream, dump_request = - utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'is_curl_eq', 'read_file', 'stream', 'Stream', 'dump_request') - --- Bug. libcurl 7.56.0 does not add `Content-Type: text/plain` -local text_plain = is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' - -local GET_URL = "http://127.0.0.1:7090/get" - -local ENABLE = true - -local _ENV = TEST_CASE'version' if ENABLE then - -function test() - assert_match("^%d+%.%d+%.%d+%-?", curl._VERSION) - assert_equal("Lua-cURL", curl._NAME ) -end - -end - -local _ENV = TEST_CASE'easy' if ENABLE then - -local e1, e2 -function teardown() - if e1 then e1:close() end - if e2 then e2:close() end - e1, e2 = nil -end - -if curl.OPT_STREAM_DEPENDS then - -function test_easy_setopt_stream_depends_1() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt_stream_depends(e2) - end) -end - -function test_easy_setopt_stream_depends_2() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt(curl.OPT_STREAM_DEPENDS, e2) - end) -end - -function test_easy_setopt_stream_depends_3() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt{[curl.OPT_STREAM_DEPENDS] = e2} - end) -end - -function test_easy_setopt_stream_depends_4() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt{stream_depends = e2} - end) -end - -function test_easy_setopt_stream_depends_e_1() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt_stream_depends_e(e2) - end) -end - -function test_easy_setopt_stream_depends_e_2() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt(curl.OPT_STREAM_DEPENDS_E, e2) - end) -end - -function test_easy_setopt_stream_depends_e_3() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt{[curl.OPT_STREAM_DEPENDS_E] = e2} - end) -end - -function test_easy_setopt_stream_depends_e_4() - e1 = assert(scurl.easy()) - e2 = assert(scurl.easy()) - assert_pass(function() - e1:setopt{stream_depends_e = e2} - end) -end - -end - -function test_easy_setopt_share() - e1 = assert(scurl.easy()) - e2 = assert(scurl.share()) - assert_pass(function() - e1:setopt_share(e2) - end) -end - -end - -local _ENV = TEST_CASE'multi_iterator' if ENABLE then - -local url = GET_URL - -local c, t, m - -local function json_data() - return json.decode(table.concat(t)) -end - -function setup() - t = {} - m = assert(scurl.multi()) -end - -function teardown() - if m then m:close() end - if c then c:close() end - m, c, t = nil -end - -function test_add_handle() - local base_url = url .. '?key=' - local urls = { - base_url .. "1", - base_url .. "2", - "###" .. base_url .. "3", - base_url .. "4", - base_url .. "5", - } - - local i = 0 - local function next_easy() - i = i + 1 - local url = urls[i] - if url then - c = assert(scurl.easy{url = url}) - t = {} - return c - end - end - - assert_equal(m, m:add_handle(next_easy())) - - for data, type, easy in m:iperform() do - - if type == "done" or type == "error" then - assert_equal(urls[i], easy:getinfo_effective_url()) - assert_equal(easy, c) - easy:close() - c = nil - - if i == 3 then - if is_curl_ge(7, 62,0) then - assert_equal(curl.error(curl.ERROR_EASY, curl.E_URL_MALFORMAT), data) - else - assert_equal(curl.error(curl.ERROR_EASY, curl.E_UNSUPPORTED_PROTOCOL), data) - end - else - local data = json_data() - assert_table(data.args) - assert_equal(tostring(i), data.args.key) - end - - easy = next_easy() - if easy then m:add_handle(easy) end - end - - if type == "data" then table.insert(t, data) end - - end - - assert_equal(#urls + 1, i) - assert_nil(c) -end - -function test_info_read() - - local url = GET_URL .. '?key=1' - - c = assert(curl.easy{url=url, writefunction=function() end}) - assert_equal(m, m:add_handle(c)) - - while m:perform() > 0 do m:wait() end - - local h, ok, err = m:info_read() - assert_equal(c, h) - - local h, ok, err = m:info_read() - assert_equal(0, h) -end - -end - -local _ENV = TEST_CASE'multi memory leak' if ENABLE then - -local m - -function teardown() - if m then m:close() end - m = nil -end - -function test_basic() - local ptr do - local multi = assert(scurl.multi()) - ptr = weak_ptr(multi) - end - gc_collect() - - assert_nil(ptr.value) -end - -function test_socket_action() - local ptr do - local multi = assert(scurl.multi()) - multi:setopt_socketfunction(function() end) - ptr = weak_ptr(multi) - end - gc_collect() - - assert_nil(ptr.value) -end - -end - -local _ENV = TEST_CASE'form' if ENABLE then - -local post - -function teardown() - if post then post:free() end - post = nil -end - -function test_content_01() - post = assert(scurl.form{name01 = 'value01'}) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue01\r\n", data) - assert_match('name="name01"', data) -end - -function test_content_02() - post = assert(scurl.form{name02 = {'value02', type = text_plain}}) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue02\r\n", data) - assert_match('name="name02"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_content_03() - post = assert(scurl.form{name03 = {content = 'value03', headers = {"Content-Encoding: gzip"}}}) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue03\r\n", data) - assert_match('name="name03"', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_content_04() - post = assert(scurl.form{name04 = {'value04', type = text_plain, headers = {"Content-Encoding: gzip"}}}) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue04\r\n", data) - assert_match('name="name04"', data) - assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_buffer_01() - post = assert(scurl.form{name01 = { - name = 'file01', - data = 'value01', - }}) - - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue01\r\n", data) - assert_match('name="name01"', data) - assert_match('filename="file01"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) -end - -function test_buffer_02() - post = assert(scurl.form{name02 = { - name = 'file02', - data = 'value02', - type = text_plain, - }}) - - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue02\r\n", data) - assert_match('name="name02"', data) - assert_match('filename="file02"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) -end - -function test_buffer_03() - post = assert(scurl.form{name03 = { - name = 'file03', - data = 'value03', - headers = {"Content-Encoding: gzip"}, - }}) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue03\r\n", data) - assert_match('name="name03"', data) - assert_match('filename="file03"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_buffer_04() - post = assert(scurl.form{name04 = { - name = 'file04', - data = 'value04', - type = text_plain, - headers = {"Content-Encoding: gzip"}, - }}) - - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue04\r\n", data) - assert_match('name="name04"', data) - assert_match('filename="file04"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_stream_01() - post = assert(scurl.form{name01 = { - stream = function() end, - length = 128, - }}) - local data = assert_string(post:get()) - assert_match('name="name01"', data) - assert_not_match('filename', data) -end - -function test_stream_02() - post = assert(scurl.form{name02 = { - name = 'file02', - stream = function() end, - length = 128, - }}) - local data = assert_string(post:get()) - assert_match('name="name02"', data) - assert_match('filename="file02"', data) -end - -function test_stream_03() - post = assert(scurl.form{name03 = { - name = 'file03', - stream = function() end, - length = 128, - type = text_plain, - }}) - - local data = assert_string(post:get()) - assert_match('name="name03"', data) - assert_match('filename="file03"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_stream_04() - post = assert(scurl.form{name04 = { - name = 'file04', - stream = function() end, - length = 128, - type = text_plain, - headers = {"Content-Encoding: gzip"}, - }}) - local data = assert_string(post:get()) - assert_match('name="name04"', data) - assert_match('filename="file04"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_stream_05() - post = assert(scurl.form{name05 = { - stream = { - length = function() return 128 end; - read = function() end; - } - }}) - local data = assert_string(post:get()) - assert_match('name="name05"', data) - assert_not_match('filename', data) -end - -function test_error() - assert_error(function() post = scurl.form{name = {content = 1}} end) - assert_error(function() post = scurl.form{name = {1}} end) - assert_error(function() post = scurl.form{name = {data = {}}} end) - assert_error(function() post = scurl.form{name = {file = true}} end) - assert_error(function() post = scurl.form{name = {stream = function() end}} end) - assert_error(function() post = scurl.form{name = {stream = {}}} end) - assert_error(function() post = scurl.form{name = {stream = { - read=function()end;length=function()end - }}}end) - assert_error(function() post = scurl.form{name = {stream = { - read=function()end;length=function() return "123" end - }}}end) - assert_error(function() post = scurl.form{name = {stream = { - read=function()end;length=function() return "hello" end - }}}end) -end - -function test_ignore_unknown() - post = assert(scurl.form{ - name01 = {}, - name02 = {name = "helo"}, - }) - local data = assert_string(post:get()) - assert_not_match('name="name01"', data) - assert_not_match('name="name02"', data) -end - -function test_empty() - post = assert(scurl.form{}) -end - -end - -RUN() diff --git a/watchdog/third_party/lua-curl/test/test_easy.lua b/watchdog/third_party/lua-curl/test/test_easy.lua deleted file mode 100644 index 2710764..0000000 --- a/watchdog/third_party/lua-curl/test/test_easy.lua +++ /dev/null @@ -1,1309 +0,0 @@ -local lunit, RUN = lunit do -RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end -lunit = require "lunit" -end - -local TEST_CASE = assert(lunit.TEST_CASE) -local skip = lunit.skip or function() end - -local curl = require "lcurl" -local scurl = require "lcurl.safe" -local json = require "dkjson" -local path = require "path" -local upath = require "path".new('/') -local utils = require "utils" -local fname = "./test.download" - --- local GET_URL = "http://example.com" --- local POST_URL = "http://httpbin.org/post" -local GET_URL = "http://127.0.0.1:7090/get" -local POST_URL = "http://127.0.0.1:7090/post" - --- print("------------------------------------") --- print("Lua version: " .. (_G.jit and _G.jit.version or _G._VERSION)) --- print("cURL version: " .. curl.version()) --- print("------------------------------------") --- print("") - -local weak_ptr, gc_collect, is_curl_ge, read_file, stream, Stream, dump_request = - utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'read_file', 'stream', 'Stream', 'dump_request') - -local null = curl.null - -local ENABLE = true - -local _ENV = TEST_CASE'curl error' if ENABLE then - -function test_eq_with_same_cat() - local e1 = curl.error(curl.ERROR_EASY, curl.E_OK) - local e2 = curl.error(curl.ERROR_EASY, curl.E_OK) - assert_equal(e1, e2) -end - -function test_eq_with_different_cat() - local e1 = curl.error(curl.ERROR_EASY, curl.E_OK) - local e2 = curl.error(curl.ERROR_FORM, curl.E_OK) - - assert_equal(e1:no(), e2:no()) - assert_not_equal(e1, e2) -end - -function test_ctor_cat() - local e - - e = curl.error(curl.ERROR_EASY, curl.E_OK) - assert_equal(e:category(), curl.ERROR_EASY) - assert_equal(e:no(), curl.E_OK) - - e = curl.error(curl.ERROR_MULTI, curl.E_OK) - assert_equal(e:category(), curl.ERROR_MULTI) - assert_equal(e:no(), curl.E_OK) - - e = curl.error(curl.ERROR_SHARE, curl.E_OK) - assert_equal(e:category(), curl.ERROR_SHARE) - assert_equal(e:no(), curl.E_OK) - - e = curl.error(curl.ERROR_FORM, curl.E_OK) - assert_equal(e:category(), curl.ERROR_FORM) - assert_equal(e:no(), curl.E_OK) - - assert_error(function() - curl.error(nil, curl.E_OK) - end) - - assert_error(function() - curl.error('UNKNOWN STRING', curl.E_OK) - end) - -end - -end - -local _ENV = TEST_CASE'write_callback' if ENABLE then - -local c, f - -function teardown() - if f then f:close() end - os.remove(fname) - if c then c:close() end - f, c = nil -end - -function test_write_to_file() - f = assert(io.open(fname, "w+b")) - c = assert(curl.easy{ - url = GET_URL; - writefunction = f; - }) - - assert_equal(c, c:perform()) -end - -function test_write_abort_01() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = function(str) return #str - 1 end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) -end - -function test_write_abort_02() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = function(str) return false end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) -end - -function test_write_abort_03() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = function(str) return nil, "WRITEERROR" end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal("WRITEERROR", e) -end - -function test_write_abort_04() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = function(str) return nil end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) -end - -function test_reset_write_callback() - f = assert(io.open(fname, "w+b")) - c = assert(curl.easy{url = url}) - assert_equal(c, c:setopt_writefunction(f)) - assert_equal(c, c:setopt_writefunction(f.write, f)) - assert_equal(c, c:setopt_writefunction(print)) - assert_equal(c, c:setopt_writefunction(print, null)) - assert_equal(c, c:setopt_writefunction(null)) - assert_equal(c, c:setopt_writefunction(null, nil)) - assert_equal(c, c:setopt_writefunction(null, null)) - assert_error(function()c:setopt_writefunction()end) - assert_error(function()c:setopt_writefunction(nil)end) - assert_error(function()c:setopt_writefunction(nil, f)end) - assert_error(function()c:setopt_writefunction(null, {})end) - assert_error(function()c:setopt_writefunction(print, {}, nil)end) - assert_error(function()c:setopt_writefunction(print, {}, null)end) -end - -function test_write_pass_01() - c = assert(curl.easy{ - url = GET_URL; - writefunction = function(s) return #s end - }) - - assert_equal(c, c:perform()) -end - -function test_write_pass_02() - c = assert(curl.easy{ - url = GET_URL; - writefunction = function() return end - }) - - assert_equal(c, c:perform()) -end - -function test_write_pass_03() - c = assert(curl.easy{ - url = GET_URL; - writefunction = function() return true end - }) - - assert_equal(c, c:perform()) -end - -function test_write_coro() - local co1, co2 - local called - - co1 = coroutine.create(function() - c = assert(curl.easy{ - url = GET_URL; - writefunction = function() - called = coroutine.running() - return true - end - }) - coroutine.yield() - end) - - co2 = coroutine.create(function() - assert_equal(c, c:perform()) - end) - - coroutine.resume(co1) - coroutine.resume(co2) - - assert_equal(co2, called) -end - -function test_write_pass_null_context() - c = assert(curl.easy{ - url = GET_URL; - }) - - local context - assert_equal(c, c:setopt_writefunction(function(ctx) - context = ctx - return true - end, null)) - - assert_equal(c, c:perform()) - assert_equal(null, context) -end - -function test_write_pass_nil_context() - c = assert(curl.easy{ - url = GET_URL; - }) - - local context, called - assert_equal(c, c:setopt_writefunction(function(ctx) - context = ctx - called = true - return true - end, nil)) - - assert_equal(c, c:perform()) - assert_true(called) - assert_nil(context) -end - -end - -local _ENV = TEST_CASE'progress_callback' if ENABLE then - -local c - -local function pass() end - -function teardown() - if f then f:close() end - os.remove(fname) - if c then c:close() end - f, c = nil -end - -function test_abort_01() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() return false end - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_02() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() return 0 end - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_03() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() return nil end - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_04() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() return nil, "PROGRESSERROR" end - }) - - local _, e = assert_nil(c:perform()) - assert_equal("PROGRESSERROR", e) -end - -function test_abort_05() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() error( "PROGRESSERROR" )end - }) - - assert_error_match("PROGRESSERROR", function() - c:perform() - end) -end - -function test_pass_01() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() end - }) - - assert_equal(c, c:perform()) -end - -function test_pass_02() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() return true end - }) - - assert_equal(c, c:perform()) -end - -function test_pass_03() - c = assert(scurl.easy{ - url = GET_URL, - writefunction = pass, - noprogress = false, - progressfunction = function() return 1 end - }) - - assert_equal(c, c:perform()) -end - -end - -local _ENV = TEST_CASE'header_callback' if ENABLE then - -local c, f -local function dummy() end - -function teardown() - if c then c:close() end - f, c = nil -end - -function test_header_abort_01() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function(str) return #str - 1 end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) -end - -function test_header_abort_02() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function(str) return false end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) -end - -function test_header_abort_03() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function(str) return nil, "WRITEERROR" end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal("WRITEERROR", e) -end - -function test_header_abort_04() - c = assert(scurl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function(str) return nil end; - }) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_WRITE_ERROR), e) -end - -function test_reset_header_callback() - f = {header = function() end} - c = assert(curl.easy{url = url}) - assert_equal(c, c:setopt_headerfunction(f)) - assert_equal(c, c:setopt_headerfunction(f.header, f)) - assert_equal(c, c:setopt_headerfunction(print)) - assert_equal(c, c:setopt_headerfunction(null)) - assert_equal(c, c:setopt_headerfunction(null, nil)) - assert_equal(c, c:setopt_headerfunction(null, null)) - assert_error(function()c:setopt_headerfunction()end) - assert_error(function()c:setopt_headerfunction(nil)end) - assert_error(function()c:setopt_headerfunction(nil, f)end) - assert_error(function()c:setopt_headerfunction(null, {})end) - assert_error(function()c:setopt_headerfunction(print, {}, nil)end) -end - -function test_header_pass_01() - c = assert(curl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function(s) return #s end - }) - - assert_equal(c, c:perform()) -end - -function test_header_pass_02() - c = assert(curl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function() return end - }) - - assert_equal(c, c:perform()) -end - -function test_header_pass_03() - c = assert(curl.easy{ - url = GET_URL; - writefunction = dummy, - headerfunction = function() return true end - }) - - assert_equal(c, c:perform()) -end - - -end - -local _ENV = TEST_CASE'read_stream_callback' if ENABLE and is_curl_ge(7,30,0) then - --- tested on WinXP(x32)/Win8(x64) libcurl/7.37.1 / libcurl/7.30.0 - -local url = POST_URL - -local m, c, f, t - -local function json_data() - return json.decode(table.concat(t)) -end - -function setup() - t = {} - f = assert(scurl.form()) - c = assert(scurl.easy{ - url = url, - timeout = 60, - }) - assert_equal(c, c:setopt_writefunction(table.insert, t)) -end - -function teardown() - if f then f:free() end - if c then c:close() end - if m then m:close() end - t, f, c, m = nil -end - -function test() - assert_equal(f, f:add_stream('SSSSS', stream('X', 128, 13))) - assert_equal(c, c:setopt_httppost(f)) - - -- should be called only stream callback - local read_called - assert_equal(c, c:setopt_readfunction(function() - read_called = true - end)) - - assert_equal(c, c:perform()) - - assert_nil(read_called) - - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - assert_table(data.form) - assert_equal(('X'):rep(128), data.form.SSSSS) -end - -function test_object() - local s = Stream('X', 128, 13) - - assert_equal(f, f:add_stream('SSSSS', s:size(), s)) - assert_equal(c, c:setopt_httppost(f)) - assert_equal(c, c:perform()) - - assert_equal(s, s.called_ctx) - - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - assert_table(data.form) - assert_equal(('X'):rep(128), data.form.SSSSS) -end - -function test_co_multi() - local s = Stream('X', 128, 13) - assert_equal(f, f:add_stream('SSSSS', s:size(), s)) - assert_equal(c, c:setopt_httppost(f)) - - m = assert(scurl.multi()) - assert_equal(m, m:add_handle(c)) - - co = coroutine.create(function() - while 1== m:perform() do end - end) - - coroutine.resume(co) - - assert_equal(co, s.called_co) - - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - assert_table(data.form) - assert_equal(('X'):rep(128), data.form.SSSSS) -end - -function test_co() - local s = Stream('X', 128, 13) - - assert_equal(f, f:add_stream('SSSSS', s:size(), s)) - assert_equal(c, c:setopt_httppost(f)) - - co = coroutine.create(function() - assert_equal(c, c:perform()) - end) - - coroutine.resume(co) - - assert_equal(co, s.called_co) - - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - assert_table(data.form) - assert_equal(('X'):rep(128), data.form.SSSSS) - -end - -function test_abort_01() - assert_equal(f, f:add_stream('SSSSS', 128 * 1024, function() end)) - assert_equal(c, c:setopt_timeout(5)) - assert_equal(c, c:setopt_httppost(f)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_OPERATION_TIMEDOUT), e) -end - -function test_abort_02() - assert_equal(f, f:add_stream('SSSSS', 128, function() return nil, "READERROR" end)) - assert_equal(c, c:setopt_httppost(f)) - - local _, e = assert_nil(c:perform()) - assert_equal("READERROR", e) -end - -function test_abort_03() - assert_equal(f, f:add_stream('SSSSS', 128, function() return 1 end)) - assert_equal(c, c:setopt_httppost(f)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_04() - assert_equal(f, f:add_stream('SSSSS', 128, function() return true end)) - assert_equal(c, c:setopt_httppost(f)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_05() - assert_equal(f, f:add_stream('SSSSS', 128, function() error("READERROR") end)) - assert_equal(c, c:setopt_httppost(f)) - - assert_error_match("READERROR", function() c:perform() end) -end - -function test_abort_06() - assert_equal(f, f:add_stream('SSSSS', 128, function() return false end)) - assert_equal(c, c:setopt_httppost(f)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_pass_01() - assert_equal(c, c:setopt_timeout(10)) - assert_equal(f, f:add_stream('SSSSS', 128, function() return nil end)) - assert_equal(c, c:setopt_httppost(f)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_OPERATION_TIMEDOUT), e) -end - -function test_pause() - local counter = 0 - assert_equal(f, f:add_stream('SSSSS', 128, function() - if counter == 0 then - counter = counter + 1 - return curl.READFUNC_PAUSE - end - if counter == 1 then - counter = counter + 1 - return ('X'):rep(128) - end - return '' - end)) - - assert_equal(c, c:setopt_progressfunction(function() - if counter == 1 then - c:pause(curl.PAUSE_CONT) - end - end)) - - assert_equal(c, c:setopt_noprogress(false)) - - assert_equal(c, c:setopt_httppost(f)) - - assert_equal(c, c:perform()) - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - assert_table(data.form) - assert_equal(('X'):rep(128), data.form.SSSSS) -end - -end - -local _ENV = TEST_CASE'read_callback' if ENABLE then - -local uname = upath:normalize(path.fullpath(fname)) - -local url = "FILE:///" .. uname - -local c - -function setup() - c = assert(scurl.easy{ - url = url, - upload = true, - }) -end - -function teardown() - os.remove(fname) - if c then c:close() end - c = nil -end - -function test_abort_01() --- assert_equal(c, c:setopt_readfunction(function() end)) --- --- local _, e = assert_nil(c:perform()) --- assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_02() - assert_equal(c, c:setopt_readfunction(function() return nil, "READERROR" end)) - - local _, e = assert_nil(c:perform()) - assert_equal("READERROR", e) -end - -function test_abort_03() - assert_equal(c, c:setopt_readfunction(function() return 1 end)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_04() - assert_equal(c, c:setopt_readfunction(function() return true end)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_abort_05() - assert_equal(c, c:setopt_readfunction(function() error("READERROR") end)) - - assert_error_match("READERROR", function() c:perform() end) -end - -function test_abort_06() - assert_equal(c, c:setopt_readfunction(function() return false end)) - - local _, e = assert_nil(c:perform()) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), e) -end - -function test_pause() - --- BUG? --- c:perform() returns curl.E_READ_ERROR after readfunction return curl.READFUNC_PAUSE --- --- OS version : Linux Mint 17 (x86_64) --- cURL version : libcurl/7.35.0 OpenSSL/1.0.1f zlib/1.2.8 libidn/1.28 librtmp/2.3 --- version_info("host"): x86_64-pc-linux-gnu --- --- OS version : Windows XP (x86_64) --- cURL version : libcurl/7.38.0 OpenSSL/1.0.1c zlib/1.2.7 WinIDN --- cURL version : libcurl/7.37.1 OpenSSL/1.0.1c zlib/1.2.7 WinIDN --- version_info("host"): i386-pc-win32 --- --- Works correctly on --- (same binary as with libcurl 7.38.0/7.37.1) --- --- OS version : Windows XP (x86_64) --- cURL version : libcurl/7.30.0 OpenSSL/0.9.8y zlib/1.2.7 --- version_info("host"): i386-pc-win32 --- - - local counter = 0 - assert_equal(c, c:setopt_readfunction(function() - if counter == 0 then - counter = counter + 1 - return curl.READFUNC_PAUSE - end - if counter == 1 then - counter = counter + 1 - return ('X'):rep(128) - end - return '' - end)) - - assert_equal(c, c:setopt_progressfunction(function() - if counter == 1 then - c:pause(curl.PAUSE_CONT) - end - end)) - - assert_equal(c, c:setopt_noprogress(false)) - - local ok, err = c:perform() - - if (not ok) and err:name() == "READ_ERROR" then - skip("TODO check pause on readfunction") - end - - assert_equal(c, ok, err) - - assert_equal(0, c:getinfo_response_code()) -end - -function test_readbuffer() - local flag = false - local N - assert_equal(c, c:setopt_readfunction(function(n) - if not flag then - flag = true - N = math.floor(n*2 + n/3) - assert(N > n) - return ("s"):rep(N) - end - return '' - end)) - - assert_equal(c, c:perform()) - c:close() - local data = read_file(fname) - assert_equal(N, #data) - assert_equal(("s"):rep(N), data) -end - -function test_pass_01() - -- We need this to support file:read() method which returns nil as EOF - assert_equal(c, c:setopt_readfunction(function() return nil end)) - - assert_equal(c, c:perform()) - c:close() - local data = read_file(fname) - assert_equal(0, #data) -end - -function test_pass_02() - local counter = 10 - assert_equal(c, c:setopt_readfunction(function() - if counter > 0 then - counter = counter - 1 - return 'a' - end - end)) - - assert_equal(c, c:perform()) - c:close() - local data = read_file(fname) - assert_equal(('a'):rep(10), data) -end - -end - -local _ENV = TEST_CASE'escape' if ENABLE then - -local c - -function teardown() - if c then c:close() end - f, c = nil -end - -function test() - local e = "This%2Bis%2Ba%2Bsimple%2B%2526%2Bshort%2Btest." - local d = "This+is+a+simple+%26+short+test." - c = assert(curl.easy()) - assert_equal(e, c:escape(d)) - assert_equal(d, c:unescape(e)) -end - -end - -local _ENV = TEST_CASE'setopt_form' if ENABLE then - -local c - -function teardown() - if c then c:close() end - c = nil -end - -function test() - local pfrom, e - do - local form = curl.form() - e = curl.easy{httppost = form} - pfrom = weak_ptr(form) - end - - gc_collect() - assert(pfrom.value) - - e:setopt_httppost(curl.form()) - - gc_collect() - assert(not pfrom.value) -end - -function test_unset() - local pfrom, e - do - local form = curl.form() - e = curl.easy{httppost = form} - pfrom = weak_ptr(form) - end - - gc_collect() - assert(pfrom.value) - - assert_equal(e, e:unsetopt_httppost()) - - gc_collect() - assert(not pfrom.value) -end - -function test_reset() - local pfrom, e - do - local form = curl.form() - e = curl.easy{httppost = form} - pform = weak_ptr(form) - end - - gc_collect() - assert(pform.value) - - assert_equal(e, e:reset()) - - gc_collect() - assert(not pform.value) -end - -end - -local _ENV = TEST_CASE'setopt_postfields' if ENABLE then - -local c - -function teardown() - if c then c:close() end - c = nil -end - -function test() - - do local fields = {} - for i = 1, 100 do fields[#fields + 1] = string.format('key%d=value%d', i, i) end - fields = table.concat(fields, '&') - c = assert(curl.easy{ - url = POST_URL, - postfields = fields, - writefunction = function()end, - }) - end - - -- call gc to try clear `fields` string - for i = 1, 4 do collectgarbage"collect" end - - c:perform() -end - -function test_unset() - local pfields - - do local fields = {} - for i = 1, 100 do fields[#fields + 1] = string.format('key%d=value%d', i, i) end - fields = table.concat(fields, '&') - c = assert(curl.easy{ - url = POST_URL, - postfields = fields, - writefunction = function()end, - }) - pfields = weak_ptr(fields) - end - - -- call gc to try clear `fields` string - for i = 1, 4 do collectgarbage"collect" end - assert_string(pfields.value) - - assert_equal(c, c:unsetopt_postfields()) - - -- @todo check internal storage because gc really do not clear `weak` string - -- for i = 1, 4 do collectgarbage"collect" end - -- assert_nil(pfields.value) - - -- c:perform() -end - -end - -local _ENV = TEST_CASE'setopt_user_data' if ENABLE then - -local c - -function teardown() - if c then c:close() end - c = nil -end - -function test_data() - c = assert(curl.easy()) - assert_nil(c:getdata()) - c:setdata("hello") - assert_equal("hello", c:getdata()) -end - -function test_cleanup() - local ptr do - local t = {} - local e = curl.easy():setdata(t) - ptr = weak_ptr(t) - gc_collect() - - assert_equal(t, ptr.value) - end - - gc_collect() - assert_nil(ptr.value) -end - -end - -local _ENV = TEST_CASE'multi_add_remove' if ENABLE then - -local m, c - -function setup() - m = assert(scurl.multi()) -end - -function teardown() - if c then c:close() end - if m then m:close() end - m, c = nil -end - -function test_remove_unknow_easy() - c = assert(scurl.easy()) - assert_equal(m, m:remove_handle(c)) -end - -function test_double_remove_easy() - c = assert(scurl.easy()) - assert_equal(m, m:add_handle(c)) - assert_equal(m, m:remove_handle(c)) - assert_equal(m, m:remove_handle(c)) -end - -function test_double_add_easy() - c = assert(scurl.easy()) - assert_equal(m, m:add_handle(c)) - assert_nil(m:add_handle(c)) -end - -end - -local _ENV = TEST_CASE'unset_callback_ctx' if ENABLE then - -local HSTS = curl.version_info().features.HSTS - -local c - -function setup() - c = assert(scurl.easy()) -end - -function teardown() - if c then c:close() end - c = nil -end - -local function test_cb(name) - local set, unset = 'setopt_' .. name, 'unsetopt_' .. name - - set = assert_function(c[set], set) - unset = assert_function(c[unset], unset) - - local pctx - do local ctx = {} - pctx = weak_ptr(ctx) - assert(set(c, function() end, ctx)) - end - - gc_collect() - assert_table(pctx.value) - - unset(c) - - gc_collect() - assert_nil(pctx.value) - - do local ctx = {} - pctx = weak_ptr(ctx) - assert(set(c, function() end, ctx)) - end - - gc_collect() - assert_table(pctx.value) - - c:reset() - - gc_collect() - assert_nil(pctx.value) -end - -function test_read() test_cb('readfunction') end -function test_write() test_cb('writefunction') end -function test_header() test_cb('headerfunction') end -function test_progress() test_cb('progressfunction') end -function test_seek() test_cb('seekfunction') end -function test_debug() test_cb('debugfunction') end -function test_fnmatch() test_cb('fnmatch_function') end -function test_chunk_bgn() test_cb('chunk_bgn_function') end -function test_chunk_end() test_cb('chunk_end_function') end - -if curl.OPT_HSTSREADFUNCTION and HSTS then -function test_hstsreadfunction() test_cb('hstsreadfunction') end -function test_hstswritefunction() test_cb('hstswritefunction') end -end - -end - -local _ENV = TEST_CASE'set_slist' if ENABLE then - -local c - -function teardown() - if c then c:close() end - c = nil -end - -function test_set() - c = curl.easy() - c:setopt_httpheader({'X-Custom: value'}) - local body, headers = assert_string(dump_request(c)) - assert_match("X%-Custom:%s*value\r\n", headers) -end - -function test_unset() - c = curl.easy() - c:setopt_httpheader({'X-Custom: value'}) - c:unsetopt_httpheader() - local body, headers = assert_string(dump_request(c)) - assert_not_match("X%-Custom:%s*value\r\n", headers) -end - -function test_set_empty_array() - c = curl.easy() - c:setopt_httpheader({'X-Custom: value'}) - c:setopt_httpheader({}) - local body, headers = assert_string(dump_request(c)) - assert_not_match("X%-Custom:%s*value\r\n", headers) -end - -function test_reset_slist() - c = curl.easy { - httpheader = {'X-Foo: 1'}, - resolve = {'example.com:80:127.0.0.1'} - } - - c:reset() - - c:setopt{ - httpheader = {'X-Foo: 2'}, - resolve = {'example.com:80:127.0.0.1'} - } - - local body, headers = assert_string(dump_request(c)) - assert_match("X%-Foo:%s2\r\n", headers) -end - -end - -local _ENV = TEST_CASE'set_null' if ENABLE then - -local c, m - -function teardown() - if c then c:close() end - if m then m:close() end - m, c = nil -end - -function test_string() - c = curl.easy() - c:setopt_accept_encoding('gzip') - local body, headers = assert_string(dump_request(c)) - assert_match("Accept%-Encoding:%s*gzip", headers) - - c:setopt_accept_encoding(null) - body, headers = assert_string(dump_request(c)) - assert_not_match("Accept%-Encoding:%s*gzip", headers) -end - -function test_string_via_table() - c = curl.easy() - c:setopt_accept_encoding('gzip') - local body, headers = assert_string(dump_request(c)) - assert_match("Accept%-Encoding:%s*gzip", headers) - - c:setopt{ accept_encoding = null } - body, headers = assert_string(dump_request(c)) - assert_not_match("Accept%-Encoding:%s*gzip", headers) -end - -function test_slist() - c = curl.easy() - c:setopt_httpheader({'X-Custom: value'}) - c:setopt_httpheader(null) - local body, headers = assert_string(dump_request(c)) - assert_not_match("X%-Custom:%s*value\r\n", headers) -end - -function test_slist_via_table() - c = curl.easy() - c:setopt_httpheader({'X-Custom: value'}) - c:setopt{httpheader = null} - local body, headers = assert_string(dump_request(c)) - assert_not_match("X%-Custom:%s*value\r\n", headers) -end - -function test_multi_set_array() - m = curl.multi() - m:setopt_pipelining_site_bl{ - '127.0.0.1' - } - assert_equal(m, m:setopt_pipelining_site_bl(null)) -end - -end - -local _ENV = TEST_CASE'trailer_callback' if ENABLE and is_curl_ge(7,64,0) then - -local url = POST_URL - -local m, c, t - -local function json_data() - return json.decode(table.concat(t)) -end - -local treader = function(t) - local i = 0 - return function() - i = i + 1 - return t[i] - end -end - -function setup() - t = {} - c = assert(scurl.easy{ - url = url, - post = true, - httpheader = {"Transfer-Encoding: chunked"}, - readfunction = treader {'a=1&', 'b=2&'}, - timeout = 60, - }) - assert_equal(c, c:setopt_writefunction(table.insert, t)) -end - -function teardown() - if c then c:close() end - if m then m:close() end - t, c, m = nil -end - -local empty_responses = { - {'no_response', function() end}, - {'nil_response', function() return nil end}, - {'null_response', function() return curl.null end}, - {'true_response', function() return true end}, - {'empty_array', function() return {} end}, -} - -local abort_responses = { - {'false_response', function() return false end}, - {'nil_with_error_response', function() return nil, 'error message' end}, - {'numeric_response_0', function() return 0 end}, - {'numeric_response_1', function() return 1 end}, -} - -for _, response in ipairs(empty_responses) do - _ENV[ 'test_' .. response[1] ] = function() - local trailer_called = 0 - assert_equal(c, c:setopt_trailerfunction(function() - trailer_called = trailer_called + 1 - return response[2]() - end)) - - assert_equal(c, c:perform()) - - assert_equal(1, trailer_called) - - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - - assert_equal('1', data.form.a) - assert_equal('2', data.form.b) - end -end - -for _, response in ipairs(abort_responses) do - _ENV[ 'test_' .. response[1] ] = function() - local trailer_called = 0 - assert_equal(c, c:setopt_trailerfunction(function() - trailer_called = trailer_called + 1 - return response[2]() - end)) - - local ok, err = assert_nil(c:perform()) - assert_equal(1, trailer_called) - assert_equal(curl.error(curl.ERROR_EASY, curl.E_ABORTED_BY_CALLBACK), err) - end -end - -function test_send_header() - local trailer_called = 0 - assert_equal(c, c:setopt_trailerfunction(function() - trailer_called = trailer_called + 1 - return {'x-trailer-header: value'} - end)) - - assert_equal(c, c:perform()) - - assert_equal(1, trailer_called) - - assert_equal(200, c:getinfo_response_code()) - local data = assert_table(json_data()) - - assert_equal('1', data.form.a) - assert_equal('2', data.form.b) - assert_equal('value', data.headers['x-trailer-header']) -end - -end - -RUN() diff --git a/watchdog/third_party/lua-curl/test/test_form.lua b/watchdog/third_party/lua-curl/test/test_form.lua deleted file mode 100644 index 08836cc..0000000 --- a/watchdog/third_party/lua-curl/test/test_form.lua +++ /dev/null @@ -1,490 +0,0 @@ -local lunit, RUN = lunit do -RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end -lunit = require "lunit" -end - -local TEST_CASE = assert(lunit.TEST_CASE) -local skip = lunit.skip or function() end - -local utils = require "utils" - --- Bug. libcurl 7.56.0 does not add `Content-Type: text/plain` -local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' - -local curl = require "lcurl" - -local _ENV = TEST_CASE'add_content' do - -local post - -function setup() - post = curl.form() -end - -function teardown() - if post then post:free() end - post = nil -end - -local function F(...) - local data = assert_string(post:get()) - post:free() - post = nil - return data -end - -function test_01() - assert_equal(post, post:add_content('name01', 'value01')) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue01\r\n", data) - assert_match('name="name01"', data) -end - -function test_02() - assert_equal(post, post:add_content('name02', 'value02', text_plain)) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue02\r\n", data) - assert_match('name="name02"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_03() - assert_equal(post, post:add_content('name03', 'value03', {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue03\r\n", data) - assert_match('name="name03"', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_04() - assert_equal(post, post:add_content('name04', 'value04', text_plain, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue04\r\n", data) - assert_match('name="name04"', data) - assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_error() - assert_error(function() post:add_content() end) - assert_error(function() post:add_stream(nil) end) - assert_error(function() post:add_stream('name01') end) -end - -end - -local _ENV = TEST_CASE'add_buffer' do - -local post - -function setup() - post = curl.form() -end - -function teardown() - if post then post:free() end - post = nil -end - -function test_01() - assert_equal(post, post:add_buffer('name01', 'file01', 'value01')) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue01\r\n", data) - assert_match('name="name01"', data) - assert_match('filename="file01"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) -end - -function test_02() - assert_equal(post, post:add_buffer('name02', 'file02', 'value02', text_plain)) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue02\r\n", data) - assert_match('name="name02"', data) - assert_match('filename="file02"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) -end - -function test_03() - assert_equal(post, post:add_buffer('name03', 'file03', 'value03', {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue03\r\n", data) - assert_match('name="name03"', data) - assert_match('filename="file03"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_04() - assert_equal(post, post:add_buffer('name04', 'file04', 'value04', text_plain, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue04\r\n", data) - assert_match('name="name04"', data) - assert_match('filename="file04"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_05() - assert_equal(post, post:add_buffer('name05', 'file05', 'value05', nil, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - assert_match("\r\n\r\nvalue05\r\n", data) - assert_match('name="name05"', data) - assert_match('filename="file05"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_error() - assert_error(function() post:add_buffer() end) - assert_error(function() post:add_buffer(nil) end) - assert_error(function() post:add_buffer('name01') end) - assert_error(function() post:add_buffer('name02', 'file02') end) - assert_error(function() post:add_buffer('name03', 'file03', nil) end) - assert_error(function() post:add_buffer('name04', nil, 'value04') end) -end - -end - -local _ENV = TEST_CASE'add_stream' do - -local post - -local dummy = function()end - -local stream = function() return 128, dummy end - -function setup() - post = curl.form() -end - -function teardown() - if post then post:free() end - post = nil -end - -function test_01() - assert_equal(post, post:add_stream('name01', stream())) - local data = assert_string(post:get()) - assert_match('name="name01"', data) - assert_not_match('filename', data) -end - -function test_02() - assert_equal(post, post:add_stream('name02', 'file02', stream())) - local data = assert_string(post:get()) - assert_match('name="name02"', data) - assert_match('filename="file02"', data) -end - -function test_03() - assert_equal(post, post:add_stream('name03', 'file03', text_plain, stream())) - local data = assert_string(post:get()) - assert_match('name="name03"', data) - assert_match('filename="file03"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_04() - assert_equal(post, post:add_stream('name04', 'file04', text_plain, {"Content-Encoding: gzip"}, stream())) - local data = assert_string(post:get()) - assert_match('name="name04"', data) - assert_match('filename="file04"', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_05() - assert_equal(post, post:add_stream('name05', 'file05', {"Content-Encoding: gzip"}, stream())) - local data = assert_string(post:get()) - assert_match('name="name05"', data) - assert_match('filename="file05"', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_06() - assert_equal(post, post:add_stream('name06', {"Content-Encoding: gzip"}, stream())) - local data = assert_string(post:get()) - assert_match('name="name06"', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_07() - assert_equal(post, post:add_stream('name07', 'file07', nil, {"Content-Encoding: gzip"}, stream())) - local data = assert_string(post:get()) - assert_match('name="name07"', data) - assert_match('filename="file07"', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_08() - assert_equal(post, post:add_stream('name08', nil, nil, {"Content-Encoding: gzip"}, stream())) - local data = assert_string(post:get()) - assert_match('name="name08"', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_09() - assert_equal(post, post:add_stream('name09', nil, nil, nil, stream())) - local data = assert_string(post:get()) - assert_match('name="name09"', data) -end - -function test_error() - assert_error(function() post:add_stream('name01', dummy) end) - assert_error(function() post:add_stream('name02', 10) end) - assert_error(function() post:add_stream('name03', "10", dummy) end) -end - -function test_add_several() - assert_equal(post, post:add_stream('name01', stream())) - assert_equal(post, post:add_stream('name02', 'file02', stream())) - local data = assert_string(post:get()) - assert_match('name="name01"', data) - assert_match('name="name02"', data) - assert_match('filename="file02"', data) -end - -end - -local _ENV = TEST_CASE'add_file' do - -local post - -local form_file = "file.form" -local form_path = "./" .. form_file -local form_data = "some values" - -local function mkfile(P, data) - local f, e = io.open(P, "w+b") - if not f then return nil, err end - if data then assert(f:write(data)) end - f:close() - return P -end - -local function check_form(data) - assert_match('name="nameXX"', data) - assert_match("\r\n\r\n" .. form_data .."\r\n", data) -end - -function setup() - post = curl.form() - assert(mkfile(form_path, form_data)) -end - -function teardown() - if post then post:free() end - post = nil - os.remove(form_file) -end - -function test_01() - assert_equal(post, post:add_file('nameXX', form_path)) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. form_file .. '"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) -end - -function test_02() - assert_equal(post, post:add_file('nameXX', form_path, text_plain)) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. form_file .. '"', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_03() - assert_equal(post, post:add_file('nameXX', form_path, text_plain, 'renamedfile')) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. 'renamedfile' .. '"', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_04() - assert_equal(post, post:add_file('nameXX', form_path, nil, 'renamedfile')) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. 'renamedfile' .. '"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) -end - -function test_05() - assert_equal(post, post:add_file('nameXX', form_path, text_plain, 'renamedfile', {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. 'renamedfile' .. '"', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_05() - assert_equal(post, post:add_file('nameXX', form_path, text_plain, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. form_file .. '"', data) - assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_06() - assert_equal(post, post:add_file('nameXX', form_path, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. form_file .. '"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_07() - assert_equal(post, post:add_file('nameXX', form_path, nil, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. form_file .. '"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_08() - assert_equal(post, post:add_file('nameXX', form_path, nil, nil, {"Content-Encoding: gzip"})) - local data = assert_string(post:get()) - check_form(data) - assert_match('filename="' .. form_file .. '"', data) - assert_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Encoding: gzip\r\n', data) -end - -function test_error() - assert_error(function() - assert_equal(post, post:add_file('nameXX', text_plain, form_path)) - post:get() - end) -end - -end - -local _ENV = TEST_CASE'get' do - -local post - -function setup() - post = curl.form() -end - -function teardown() - if post then post:free() end - post = nil -end - -local function check_form(data) - assert_match("\r\n\r\nvalueXX\r\n", data) - assert_match('name="nameXX"', data) - assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_writer_01() - local t = {} - local function writer(str, ...) - assert_equal(0, select("#", ...)) - t[#t+1] = str - end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - assert_equal(post, post:get(writer)) - check_form(table.concat(t)) -end - -function test_writer_02() - local t = {} - local function writer(str, ...) - assert_equal(0, select("#", ...)) - t[#t+1] = str - return true - end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - assert_equal(post, post:get(writer)) - check_form(table.concat(t)) -end - -function test_writer_03() - local t = {} - local function writer(str, ...) - assert_equal(0, select("#", ...)) - t[#t+1] = str - return #str - end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - assert_equal(post, post:get(writer)) - check_form(table.concat(t)) -end - -function test_writer_context() - local t = {} - local function writer(T, str, ...) - assert_equal(t, T) - assert_equal(0, select("#", ...)) - T[#T+1] = str - end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - assert_equal(post, post:get(writer, t)) - local data = table.concat(t) - assert_match("\r\n\r\nvalueXX\r\n", data) - assert_match('name="nameXX"', data) - assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) -end - -function test_abort_01() - local err = {} - local function writer() return nil, err end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - local _, e = assert_nil(post:get(writer)) - assert_equal(err, e) -end - -function test_abort_02() - local function writer() return 0 end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - local _, e = assert_nil(post:get(writer)) - assert_nil(e) -end - -function test_abort_03() - local function writer() return nil end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - local _, e = assert_nil(post:get(writer)) - assert_nil(e) -end - -function test_abort_04() - local function writer() return false end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - local _, e = assert_nil(post:get(writer)) - assert_nil(e) -end - -function test_error() - local err = {} - local function writer() error("WRITEERROR") end - assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) - assert_error_match("WRITEERROR", function() - post:get(writer) - end) -end - -end - -RUN() diff --git a/watchdog/third_party/lua-curl/test/test_mime.lua b/watchdog/third_party/lua-curl/test/test_mime.lua deleted file mode 100644 index c3d5ef2..0000000 --- a/watchdog/third_party/lua-curl/test/test_mime.lua +++ /dev/null @@ -1,631 +0,0 @@ -local lunit, RUN = lunit do -RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end -lunit = require "lunit" -end - -local TEST_CASE = assert(lunit.TEST_CASE) -local skip = lunit.skip or function() end - -local curl = require "lcurl" -local utils = require "utils" - -local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime') - -local GET_URL = 'http://127.0.0.1:7090/get' - -local null = curl.null - -local function is_freed(c) - return not not string.find(tostring(c), '%(freed%)') -end - -local _ENV = TEST_CASE'mime lifetime' if not curl.OPT_MIMEPOST then -function test() skip("MIMI API supports since cURL 7.56.0") end -else - -local easy, mime - -function setup() - easy = curl.easy() -end - -function teardown() - if easy then easy:close() end - if mime then mime:free() end - easy, mime = nil -end - -function test_preserve_mime_part_reference() - -- mime part stores references to all parts - - local mime, part = easy:mime() do - part = weak_ptr(mime:addpart()) - end - gc_collect() - - assert_not_nil(part.value) - - mime = nil - gc_collect() - - assert_nil(part.value) - - easy:close() -end - -function test_free_mime_subparts() - -- when free root free all nodes - - -- mime - -- +- part3 - -- +- alt - -- +- part1 - -- +- part2 - - local mime, a, p1, p2, p3 = easy:mime() do - - local alt = easy:mime() - - local part1 = alt:addpart() - - local part2 = alt:addpart() - - local part3 = mime:addpart() - part3:subparts(alt, "multipart/alternative") - - a = weak_ptr(alt) - p1 = weak_ptr(part1) - p2 = weak_ptr(part2) - p3 = weak_ptr(part3) - end - - gc_collect() - - assert_not_nil(a.value) - assert_not_nil(p1.value) - assert_not_nil(p2.value) - assert_not_nil(p3.value) - - -- reamove reference to root node - mime = nil - gc_collect(4) - - assert_nil(a.value) - assert_nil(p1.value) - assert_nil(p2.value) - assert_nil(p3.value) - - easy:close() -end - -function test_preserve_mime_subparts() - -- if we have references to subnode but we free root - -- then all references have to become to invalid - - -- mime - -- +- part3 - -- +- alt - -- +- part1 - -- +- part2 - - local easy = curl.easy() - - local mime, a, p1, p2, p3 = easy:mime() do - - local alt = easy:mime() - - local part1 = alt:addpart() - - local part2 = alt:addpart() - - local part3 = mime:addpart() - part3:subparts(alt, "multipart/alternative") - - a = weak_ptr(alt) - p1 = weak_ptr(part1) - p2 = weak_ptr(part2) - p3 = weak_ptr(part3) - end - - gc_collect() - - assert_not_nil(a.value) - assert_not_nil(p1.value) - assert_not_nil(p2.value) - assert_not_nil(p3.value) - - -- save reference to subnode - local subnode = a.value - - mime = nil - - -- in this case call `free` to root node. - -- there no way to get reference to this node from child - -- so there no way to use it. - gc_collect() - - -- libcurl still close all childs - -- so all reference are invalid - - assert_not_nil(a.value) - assert_not_nil(is_freed(a.value)) - assert_nil(p1.value) - assert_nil(p2.value) - assert_nil(p3.value) - - easy:close() -end - -function test_preserve_mime_by_easy() - - local mime do - mime = weak_ptr(easy:mime()) - easy:setopt_mimepost(mime.value) - end - - gc_collect() - - assert_not_nil(mime.value) - - easy:unsetopt_mimepost() - - gc_collect() - - assert_nil(mime.value) -end - -function test_mime_does_not_ref_to_easy() - -- exists of mime object does not prevent easy from GC - - local mime = easy:mime() - local peasy = weak_ptr(easy) - easy = nil - - gc_collect() - - assert_nil(peasy.value) -end - -function test_mimepost_does_not_ref_to_easy() - -- exists of mime object does not prevent easy from GC - - -- create mime and set it as mimepost option - local mime = easy:mime() - easy:setopt_mimepost(mime) - - local peasy = weak_ptr(easy) - easy = nil - - gc_collect() - - assert_nil(peasy.value) -end - -function test_cleanup_on_easy_reset() - - local mime do - mime = weak_ptr(easy:mime()) - easy:setopt_mimepost(mime.value) - end - - gc_collect() - - assert_not_nil(mime.value) - - easy:reset() - - gc_collect(10) - - assert_nil(mime.value) - - easy:setopt{url = GET_URL, writefunction = function() end} - easy:perform() -end - -end - -local _ENV = TEST_CASE'mime basic' if not curl.OPT_MIMEPOST then -function test() skip("MIMI API supports since cURL 7.56.0") end -else - -local easy, mime - -local function dump_mime(mime) - return dump_mime_(easy, mime, GET_URL) -end - -function setup() - easy = curl.easy() - mime = easy:mime() -end - -function teardown() - if easy then easy:close() end - if mime then mime:free() end - easy, mime = nil -end - -function test_data() - mime:addpart():data('hello') - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) -end - -function test_data_type() - mime:addpart():data('hello', 'test/html') - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) -end - -function test_data_type_name() - mime:addpart():data('hello', 'test/html', 'test') - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-%sname="test"', info) -end - -function test_data_name() - mime:addpart():data('hello', nil, 'test') - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Disposition:.-%sname="test"', info) -end - -function test_data_filename() - mime:addpart():data('hello', nil, nil, 'test.html') - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Disposition:.-%sfilename="test%.html"', info) -end - -function test_data_should_not_unset_on_nil() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }) - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) - - part:data('world', nil, 'test2') - info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nworld', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test2"', info) - assert_match('X%-Custom%-Header:%s*hello', info) - - part:data('!!!!!', 'text/xml', nil) - info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\n!!!!!', info) - assert_match('Content%-Type:%s+text/xml', info) - assert_match('Content%-Disposition:.-name="test2"', info) - assert_match('X%-Custom%-Header:%s*hello', info) - - part:data('!!!!!!!', 'text/xml', nil, nil) - info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\n!!!!!', info) - assert_match('Content%-Type:%s+text/xml', info) - assert_match('Content%-Disposition:.-name="test2"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_data_headers() - mime:addpart():data('hello', { - 'X-Custom-Header: hello' - }) - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_name() - mime:addpart():data('hello', 'test/html', 'test'):name(false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_not_match('Content%-Disposition:.-name="test"', info) -end - -function test_unset_name_by_null() - mime:addpart():data('hello', 'test/html', 'test'):name(null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_not_match('Content%-Disposition:.-name="test"', info) -end - -function test_unset_type() - mime:addpart():data('hello', 'test/html'):type(false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+test/html', info) -end - -function test_unset_type_by_null() - mime:addpart():data('hello', 'test/html'):type(null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+test/html', info) -end - -function test_unset_headers() - mime:addpart():data('hello', 'test/html',{ - 'X-Custom-Header: hello' - }):headers(false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_headers_by_null() - mime:addpart():data('hello', 'test/html',{ - 'X-Custom-Header: hello' - }):headers(null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_headers_by_empty_array() - mime:addpart():data('hello', 'test/html',{ - 'X-Custom-Header: hello' - }):headers({}) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data() - mime:addpart():data('hello', 'test/html', 'test'):data(false) - - local info = assert_string(dump_mime(mime)) - assert_not_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) -end - -function test_unset_data_by_null() - mime:addpart():data('hello', 'test/html', 'test'):data(null) - - local info = assert_string(dump_mime(mime)) - assert_not_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) -end - -function test_unset_data_type_1() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_type_1_by_null() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_type_2() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', false, nil, nil) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_name_1() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', nil, false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_not_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_name_1_by_null() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', nil, null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_not_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_name_2() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', nil, false, nil) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_not_match('Content%-Disposition:.-name="test"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_header() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', nil, nil, nil, false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) - assert_not_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_header_by_null() - local part = mime:addpart():data('hello', 'test/html', 'test', { - 'X-Custom-Header: hello' - }):data('hello', nil, nil, nil, null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-name="test"', info) - assert_not_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_filename_1() - local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', { - 'X-Custom-Header: hello' - }):data('hello', nil, nil, false) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-%sname="test"', info) - assert_not_match('Content%-Disposition:.-%sfilename="test%.html"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_unset_data_filename_1_by_null() - local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', { - 'X-Custom-Header: hello' - }):data('hello', nil, nil, null) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-%sname="test"', info) - assert_not_match('Content%-Disposition:.-%sfilename="test%.html"', info) - assert_match('X%-Custom%-Header:%s*hello', info) -end - -function test_fail_pass_nil_as_first_arg() - local part = mime:addpart() - assert_error(function() part:data() end) - assert_error(function() part:data(nil) end) - - assert_error(function() part:name() end) - assert_error(function() part:name(nil) end) - - assert_error(function() part:type() end) - assert_error(function() part:type(nil) end) - - assert_error(function() part:headers() end) - assert_error(function() part:headers(nil) end) -end - -end - -local _ENV = TEST_CASE'mime addpart' if not curl.OPT_MIMEPOST then -function test() skip("MIMI API supports since cURL 7.56.0") end -else - -local easy, mime - -local function dump_mime(mime) - return dump_mime_(easy, mime, GET_URL) -end - -function setup() - easy = curl.easy() - mime = easy:mime() -end - -function teardown() - if easy then easy:close() end - if mime then mime:free() end - easy, mime = nil -end - -function test_empty_table() - assert(mime:addpart{}) -end - -function test_pass_args() - assert(mime:addpart{ - data = 'hello'; - encoder = 'base64'; - name = 'test'; - filename = 'test.html'; - type = 'test/html'; - headers = { - 'X-Custom-Header: hello'; - } - }) - - local info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\naGVsbG8=', info) - assert_match('Content%-Type:%s+test/html', info) - assert_match('Content%-Disposition:.-%sname="test"', info) - assert_not_match('Content%-Disposition:.-%sname="test%.html"', info) - assert_match('X%-Custom%-Header:%s*hello', info) - assert_match('Content%-Transfer%-Encoding:%s*base64', info) -end - -local function easy_dump_mime(easy, mime, url) - assert(mime:addpart{ - data = 'hello'; - encoder = 'base64'; - name = 'test'; - filename = 'test.html'; - type = 'test/html'; - headers = { - 'X-Custom-Header: hello'; - } - }) - - easy:setopt{ - mimepost = mime; - } - - assert(easy:setopt{ - url = GET_URL; - customrequest = "GET"; - - writefunction = function()end; - }) - - if not ok then return nil, err end - - ok, err = easy:perform() - - if not ok then return nil, err end - - return table.concat(buffer) -end - -end - -RUN() diff --git a/watchdog/third_party/lua-curl/test/test_multi_callback.lua b/watchdog/third_party/lua-curl/test/test_multi_callback.lua deleted file mode 100644 index 4240557..0000000 --- a/watchdog/third_party/lua-curl/test/test_multi_callback.lua +++ /dev/null @@ -1,116 +0,0 @@ -local curl = require "lcurl" - -local called, active_coroutine = 0 - --- for Lua 5.1 compat -local function co_running() - local co, main = coroutine.running() - if main == true then return nil end - return co -end - -function on_timer() - called = called + 1 - -- use `os.exit` because now Lua-cURL did not propogate error from callback - if co_running() ~= active_coroutine then os.exit(-1) end -end - -local function test_1() - io.write('Test #1 - ') - - called, active_coroutine = 0 - - local e = curl.easy() - local m = curl.multi{ timerfunction = on_timer } - - active_coroutine = coroutine.create(function() - m:add_handle(e) - end) - - coroutine.resume(active_coroutine) - assert(called == 1) - - active_coroutine = nil - m:remove_handle(e) - assert(called == 2) - - io.write('pass!\n') -end - -local function test_2() - io.write('Test #2 - ') - - called, active_coroutine = 0 - - local e = curl.easy() - local m = curl.multi{ timerfunction = on_timer } - - active_coroutine = coroutine.create(function() - m:add_handle(e) - end) - - coroutine.resume(active_coroutine) - assert(called == 1) - - active_coroutine = coroutine.create(function() - m:remove_handle(e) - end) - coroutine.resume(active_coroutine) - assert(called == 2) - - io.write('pass!\n') -end - -local function test_3() - io.write('Test #3 - ') - - called, active_coroutine = 0 - - local e = curl.easy() - local m = curl.multi{ timerfunction = on_timer } - - active_coroutine = coroutine.create(function() - m:add_handle(e) - end) - - coroutine.resume(active_coroutine) - assert(called == 1) - - active_coroutine = nil - e:close() - assert(called == 2) - - io.write('pass!\n') -end - -local function test_4() - io.write('Test #4 - ') - - called, active_coroutine = 0 - - local e = curl.easy() - local m = curl.multi{ timerfunction = on_timer } - - active_coroutine = coroutine.create(function() - m:add_handle(e) - end) - - coroutine.resume(active_coroutine) - assert(called == 1) - - active_coroutine = coroutine.create(function() - e:close() - end) - coroutine.resume(active_coroutine) - assert(called == 2) - - io.write('pass!\n') -end - -test_1() - -test_2() - -test_3() - -test_4() diff --git a/watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua b/watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua deleted file mode 100644 index 31d8fd9..0000000 --- a/watchdog/third_party/lua-curl/test/test_multi_nested_callback.lua +++ /dev/null @@ -1,47 +0,0 @@ -local curl = require "lcurl" - --- for Lua 5.1 compat -local function co_running() - local co, main = coroutine.running() - if main == true then return nil end - return co -end - -local state, called = true, 0 -local thread, msg - -local function check_thread() - if thread ~= co_running() then - print(msg) - os.exit(-1) - end -end - -local m; m = curl.multi{ - timerfunction = function() - check_thread() - called = called + 1 - - if state then state = false - thread = coroutine.create(function() - local e = curl.easy() - m:add_handle(e) - end) - - msg = 'add from coroutine' - coroutine.resume(thread) - assert(called == 2) - - msg, thread = 'add from main' - local e = curl.easy() - m:add_handle(e) - assert(called == 3) - end - end -} - -e = curl.easy() - -m:add_handle(e) - -assert(called == 3) diff --git a/watchdog/third_party/lua-curl/test/test_pause02.c.lua b/watchdog/third_party/lua-curl/test/test_pause02.c.lua deleted file mode 100644 index 3a1fb4f..0000000 --- a/watchdog/third_party/lua-curl/test/test_pause02.c.lua +++ /dev/null @@ -1,89 +0,0 @@ -local curl = require "lcurl" - --- How many times curl_multi_perform should be called before hitting of CURLPAUSE_CONT. --- (including curl_multi_perform that causes WriteFunction to pause writes, --- i.e. 1 means that CURLPAUSE_CONT will be performed immediately after pause.) -local WAIT_COUNT = 15 - -local SIZE = 10 * 1024 --- local RESOURCE_URL = "http://httpbin.org/bytes/" .. SIZE -local RESOURCE_URL = "http://127.0.0.1:7090/bytes/" .. SIZE - -local State = { - PAUSE = 0, -- write function should return CURL_WRITEFUNC_PAUSE - WAIT = 1, -- waiting for CURLPAUSE_CONT - WRITE = 2, -- write function should perform write - WRITTEN = 3, -- write function have performed write -} - --- Current state -local state = State.PAUSE - --- Countdown to continue writes -local waitCount = 0 - --- Received data and data size -local data, datasize = {}, 0 - -local function WriteFunction(str) - if state == State.PAUSE then - state = State.WAIT - waitCount = WAIT_COUNT - return curl.WRITEFUNC_PAUSE - end - - if state == State.WAIT then - -- callback shouldn't be called in this state - print("WARNING: write-callback called in STATE_WAIT") - return curl.WRITEFUNC_PAUSE - end - - if state == State.WRITE then - state = State.WRITTEN - end - - datasize = datasize + #str - data[#data + 1] = str -end - -local function perform(multi, easy) - while true do - local handles = multi:perform() - - if state == State.WAIT then - waitCount = waitCount - 1 - if waitCount == 0 then - state = State.WRITE - easy:pause(curl.PAUSE_CONT) - end - end - - if state == State.WRITTEN then - state = State.PAUSE - end - - if 0 == handles then - local h, ok, err = multi:info_read() - return not not ok, err - end - - multi:wait() - end -end - -local easy = curl.easy{ - url = RESOURCE_URL, - accept_encoding = "gzip,deflate", - writefunction = WriteFunction, -} -local multi = curl.multi() -multi:add_handle(easy) - -local ok, err = perform(multi, easy) - -if ok then - print("OK: data retrieved successfully (" .. tostring(datasize) .. ")") -else - print("ERROR: data retrieve failed (" .. tostring(err) .. ")") - os.exit(1) -end diff --git a/watchdog/third_party/lua-curl/test/test_safe.lua b/watchdog/third_party/lua-curl/test/test_safe.lua deleted file mode 100644 index eedfc25..0000000 --- a/watchdog/third_party/lua-curl/test/test_safe.lua +++ /dev/null @@ -1,287 +0,0 @@ -local lunit, RUN = lunit do -RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end -lunit = require "lunit" -end - -local TEST_CASE = assert(lunit.TEST_CASE) -local skip = lunit.skip or function() end - -local _ENV = TEST_CASE'error_mode' do - -local c - -function teardown() - if c then c:close() end - c = nil -end - -function test_safe() - local curl = require "lcurl.safe" - c = assert(curl.easy()) - assert_equal(c, c:setopt_url("aaaaa://123")) - assert_nil(c:perform()) -end - -function test_raise() - local curl = require "lcurl" - c = assert(curl.easy()) - assert_equal(c, c:setopt_url("aaaaa://123")) - assert_error(function() c:perform() end) -end - -end - -local _ENV = TEST_CASE'setopt' do - -local curl = require "lcurl.safe" -local c - -function setup() - c = assert(curl.easy()) -end - -function teardown() - if c then c:close() end - c = nil -end - -function test_number() - assert_equal(c, c:setopt_verbose(false)) - assert_equal(c, c:setopt_verbose(true)) - assert_equal(c, c:setopt_verbose(1)) - assert_equal(c, c:setopt_verbose(0)) - assert_error(function() c:setopt_verbose("1") end) - assert_error(function() c:setopt_verbose("true") end) -end - -function test_string() - assert_error(function() c:setopt_url(true) end) - assert_error(function() c:setopt_url(1) end) - assert_equal(c, c:setopt_url("1")) -end - -function test_array() - assert_error(function() c:setopt_httpheader(true) end) - assert_error(function() c:setopt_httpheader(1) end) - assert_error(function() c:setopt_httpheader("k:v")end) - assert_equal(c, c:setopt_httpheader{"k:v"}) -end - -function test_multiple_options() - assert_error(function() c:setopt{verbose = "false"} end) - assert_error(function() c:setopt{verbose = "1"} end) - assert_equal(c, c:setopt{verbose = false}) - assert_equal(c, c:setopt{[curl.OPT_VERBOSE] = false}) -end - -end - -local _ENV = TEST_CASE'error_object' do - -local curl = require "lcurl" - -function test() - local e1 = curl.error(curl.ERROR_EASY, 0) -- ok - assert_equal(curl.ERROR_EASY, e1:category()) - assert_equal(curl.E_OK, e1:no()) - assert_equal("OK", e1:name()) - - local e2 = curl.error(curl.ERROR_MULTI, 0) -- ok - local e3 = curl.error(curl.ERROR_MULTI, 0) -- ok - assert_equal(0, e1:no()) - assert_equal(0, e2:no()) - assert(e1 ~= e2) - assert(e3 == e2) -end - -end - -local _ENV = TEST_CASE'ctor' do - -local scurl = require "lcurl.safe" -local curl = require "lcurl" -local c - -function teardown() - if c then c:close() end - c = nil -end - -function test_easy_error() - c = assert(curl.easy()) - c:close() - c = assert(curl.easy{ - url = "http://example.com", - [curl.OPT_VERBOSE] = true, - }) - c:close() - - assert_error(function() - c = curl.easy{ - url_111 = "http://example.com", - } - end) - - assert_error(function() - c = curl.easy{ - url = 123, - } - end) -end - -function test_easy_safe() - c = assert(scurl.easy()) - c:close() - c = assert(scurl.easy{ - url = "http://example.com", - [curl.OPT_VERBOSE] = true, - }) - c:close() - - assert_pass(function() - c = scurl.easy{ - url_111 = "http://example.com", - } - end) - assert_nil(c) - - assert_error(function() - c = scurl.easy{ - url = 123, - } - end) -end - -function test_multi_error() - c = assert(curl.multi()) - c:close() - c = assert(curl.multi{ - maxconnects = 10; - [curl.OPT_MULTI_PIPELINING] = true, - }) - c:close() - - assert_error(function() - c = curl.multi{ - url_111 = "http://example.com", - } - end) - - assert_error(function() - c = curl.multi{ - maxconnects = "hello", - } - end) -end - -function test_multi_safe() - c = assert(scurl.multi()) - c:close() - c = assert(scurl.multi{ - maxconnects = 10; - [curl.OPT_MULTI_PIPELINING] = true, - }) - c:close() - - assert_pass(function() - c = scurl.multi{ - url_111 = "http://example.com", - } - end) - assert_nil(c) - - assert_error(function() - c = scurl.multi{ - maxconnects = "hello", - } - end) -end - -function test_share_error() - assert(curl.LOCK_DATA_COOKIE) - - c = assert(curl.share()) - c:close() - c = assert(curl.share{ - share = curl.LOCK_DATA_COOKIE; - }) - c:close() - - assert_error(function() - c = curl.share{ - url_111 = "http://example.com", - } - end) - - assert_error(function() - c = curl.share{ - share = "hello"; - } - end) -end - -function test_share_safe() - assert(curl.LOCK_DATA_COOKIE) - - c = assert(scurl.share()) - c:close() - c = assert(curl.share{ - share = scurl.LOCK_DATA_COOKIE; - }) - c:close() - - assert_pass(function() - c = scurl.share{ - url_111 = "http://example.com", - } - end) - - assert_error(function() - c = scurl.share{ - share = "hello"; - } - end) -end - -end - -local _ENV = TEST_CASE'objects_have_same_metatables' do - -local scurl = require "lcurl.safe" -local curl = require "lcurl" -local e1, e2, m - -function teardown() - if m then m:close() end - if e1 then e1:close() end - if e2 then e2:close() end - e1, e2, m = nil -end - -function test_1() - e1 = assert(scurl.easy()) - e2 = assert(curl.easy()) - m = assert(scurl.multi()) - - assert_equal(m, m:add_handle(e1)) - assert_equal(m, m:add_handle(e2)) -end - -function test_2() - e1 = assert(scurl.easy()) - e2 = assert(curl.easy()) - m = assert(curl.multi()) - - assert_equal(m, m:add_handle(e1)) - assert_equal(m, m:add_handle(e2)) -end - -end - -RUN() diff --git a/watchdog/third_party/lua-curl/test/test_urlapi.lua b/watchdog/third_party/lua-curl/test/test_urlapi.lua deleted file mode 100644 index e0cf4f0..0000000 --- a/watchdog/third_party/lua-curl/test/test_urlapi.lua +++ /dev/null @@ -1,315 +0,0 @@ -local lunit, RUN = lunit do -RUN = lunit and function()end or function () - local res = lunit.run() - if res.errors + res.failed > 0 then - os.exit(-1) - end - return os.exit(0) -end -lunit = require "lunit" -end - -local _, luacov = pcall(require, "luacov") -local TEST_CASE = assert(lunit.TEST_CASE) -local skip = lunit.skip or function() end -local curl = require "cURL" -local scurl = require "cURL.safe" -local utils = require "utils" -local json = require "dkjson" -local table = table - -local weak_ptr, gc_collect, is_curl_eq = utils.import('weak_ptr', 'gc_collect', 'is_curl_eq') - -local GET_URL = "http://127.0.0.1:7090/get" - -local tostring, pcall = tostring, pcall - -local function skip_case(msg) return function() skip(msg) end end - -local ENABLE = true - -local _ENV = TEST_CASE'urlapi' if ENABLE then - -if not curl.E_URL_OK then test = skip_case('URL API avaliable since libcurl 7.62.0') else - -local it = setmetatable(_ENV or _M, {__call = function(self, describe, fn) - self["test " .. describe] = fn -end}) - -local url - -local function U(u) - url = assert_userdata(curl.url()) - assert_equal(url, url:set_url(u)) - return url -end - -function teardown() - if url then url:cleanup() end - url = nil -end - -it('should export falgs', function() - assert_number(curl.U_DEFAULT_PORT ) - assert_number(curl.U_NO_DEFAULT_PORT ) - assert_number(curl.U_DEFAULT_SCHEME ) - assert_number(curl.U_NON_SUPPORT_SCHEME ) - assert_number(curl.U_PATH_AS_IS ) - assert_number(curl.U_DISALLOW_USER ) - assert_number(curl.U_URLDECODE ) - assert_number(curl.U_URLENCODE ) - assert_number(curl.U_APPENDQUERY ) - assert_number(curl.U_GUESS_SCHEME ) -end) - -it('should export parts', function() - assert_number(curl.UPART_URL ) - assert_number(curl.UPART_SCHEME ) - assert_number(curl.UPART_USER ) - assert_number(curl.UPART_PASSWORD ) - assert_number(curl.UPART_OPTIONS ) - assert_number(curl.UPART_HOST ) - assert_number(curl.UPART_PORT ) - assert_number(curl.UPART_PATH ) - assert_number(curl.UPART_QUERY ) - assert_number(curl.UPART_FRAGMENT ) -end) - -it('should export methods', function() - url = curl.url() - assert_function(url.dup ) - assert_function(url.cleanup ) - - assert_function(url.set_url ) - assert_function(url.set_scheme ) - assert_function(url.set_user ) - assert_function(url.set_password ) - assert_function(url.set_options ) - assert_function(url.set_host ) - assert_function(url.set_port ) - assert_function(url.set_path ) - assert_function(url.set_query ) - assert_function(url.set_fragment ) - - assert_function(url.get_url ) - assert_function(url.get_scheme ) - assert_function(url.get_user ) - assert_function(url.get_password ) - assert_function(url.get_options ) - assert_function(url.get_host ) - assert_function(url.get_port ) - assert_function(url.get_path ) - assert_function(url.get_query ) - assert_function(url.get_fragment ) -end) - -it('create and cleanup', function() - url = assert_userdata(curl.url()) - assert_nil(url:cleanup()) -end) - -it('constructor with parameters', function() - url = assert_userdata(curl.url('http://example.com/')) - assert_equal('http://example.com/', url:get_url()) - - url = assert_userdata(curl.url('example.com', curl.U_GUESS_SCHEME)) - assert_equal('http://example.com/', url:get_url()) -end) - -it('dup url', function() - url = assert_userdata(curl.url('http://example.com/')) - local u2 = url:dup() - assert_not_equal(url, u2) - assert_equal('http://example.com/', u2:get_url()) - assert_equal('http://example.com/', url:get_url()) - url:cleanup() - url = u2 - assert_equal('http://example.com/', url:get_url()) -end) - -it('should convert to string', function() - assert_equal('http://example.com/', tostring(U"http://example.com/")) -end) - -it('should decode url', function() - url = U"http://example.com" - assert_equal('http', url:get_scheme()) - assert_equal('example.com', url:get_host()) - assert_equal('/', url:get_path()) -end) - -it('should cast scheme to lower case', function() - url = U"HTTP://Example.com" - assert_equal('http', url:get_scheme()) - assert_equal('Example.com', url:get_host()) - assert_equal('/', url:get_path()) - assert_equal("http://Example.com/", url:get_url()) -end) - -it('should append query', function() - url = U"http://example.com" - assert_equal(url, url:set_query("a=hello world", curl.U_APPENDQUERY + curl.U_URLENCODE)) - assert_equal(url, url:set_query("b=A&B", curl.U_APPENDQUERY + curl.U_URLENCODE)) - assert_equal("http://example.com/?a=hello+world&b=A%26B", url:get_url()) -end) - -it('should append only one parameter in query per call', function() - url = U"http://example.com" - assert_equal(url, url:set_query("a=hello world&b=A&B", curl.U_APPENDQUERY + curl.U_URLENCODE)) - if is_curl_eq(7, 62, 0) then - assert_equal("http://example.com/?a=hello+world%26b=A%26B", url:get_url()) - else - assert_equal("http://example.com/?a=hello+world%26b%3dA%26B", url:get_url()) - end -end) - -it('should set encoded query', function() - url = U("http://example.com/?a=hello world&b=d") - assert_equal('a=hello world&b=d', url:get_query()) -end) - -it('should returns NULL as empty value', function() - url = curl.url() - assert_equal(curl.null, url:get_query()) - assert_equal(curl.null, url:get_host()) - assert_equal(curl.null, url:get_port()) - assert_equal(curl.null, url:get_password()) - assert_equal(curl.null, url:get_scheme()) - assert_equal(curl.null, url:get_options()) - assert_equal(curl.null, url:get_fragment()) - - assert_equal('/', url:get_path()) -end) - -it('should returns nil and error for invalid url in safe mode', function() - url = scurl.url() - local _, err = assert_nil(url:get_url()) - assert_equal('CURL-URL', err:cat()) -end) - -it('should raise error for invalid url', function() - url = curl.url() - local _, err = assert_false(pcall(url.get_url, url)) - assert_match('CURL%-URL', tostring(err)) -end) - -it('should raise error for tostring', function() - url = curl.url() - local _, err = assert_false(pcall(tostring, url)) - assert_match('CURL%-URL', tostring(err)) -end) - -it('should raise error for tostring in safe mode', function() - url = scurl.url() - local _, err = assert_false(pcall(tostring, url)) - assert_match('CURL%-URL', tostring(err)) -end) - --- it('should set encoded query', function() --- url = U"http://example.com" --- assert_equal(url, url:set_query("a=hello world", curl.U_URLENCODE)) --- assert_equal("http://example.com/?a=hello+world", url:get_url()) --- end) - -if curl.UPART_ZONEID then - -it('should returns zoneid', function() - url = assert_userdata(scurl.url('http://[::1%18]:3800')) - assert_equal('18', url:get_zoneid()) -end) - -it('should returns empty on missing zoneid', function() - url = assert_userdata(scurl.url('http://[::1]:3800')) - assert_equal(curl.null, url:get_zoneid()) -end) - -else - test_zoneid = skip_case('URL API supports zoneid since version 7.65.0') -end - -end end - -local _ENV = TEST_CASE'curlu parameter' if ENABLE then - -if not curl.OPT_CURLU then test = skip_case('CURLU option avaliable since libcurl 7.63.0') else - -local it = setmetatable(_ENV or _M, {__call = function(self, describe, fn) - self["test " .. describe] = fn -end}) - -local url, easy, buffer - -local function writer(chunk) - table.insert(buffer, chunk) -end - -local function json_data() - return json.decode(table.concat(buffer)) -end - -local function U(u) - url = assert_userdata(curl.url()) - assert_equal(url, url:set_url(u)) - return url -end - -function setup() - buffer = {} -end - -function teardown() - if url then url:cleanup() end - if easy then easy:close() end - url = nil -end - -it('easy should prevent url from gc', function() - local purl - do - easy = curl.easy() - local url = U(GET_URL) - assert_equal(easy, easy:setopt_curlu(url)) - purl = weak_ptr(url) - end - - gc_collect() - assert_not_nil(purl.value) - - assert_equal(easy, easy:unsetopt_curlu()) - - gc_collect() - assert_not_nil(purl.value) -end) - -it('should use url from curlu parameter', function() - url = U(GET_URL) - easy = curl.easy {curlu = url, writefunction = writer} - assert_equal(easy, easy:perform()) - local response = assert_table(json_data()) - assert_equal(GET_URL, response.url) -end) - -it('should be possible reset url', function() - url = U("http://example.com") - easy = curl.easy {curlu = url, writefunction = writer} - url:set_url(GET_URL) - - assert_equal(easy, easy:perform()) - local response = assert_table(json_data()) - assert_equal(GET_URL, response.url) -end) - -it('should be possible reuse url', function() - url = U(GET_URL) - for i = 1, 5 do - local easy = curl.easy {curlu = url, writefunction = writer} - assert_equal(easy, easy:perform()) - local response = assert_table(json_data()) - assert_equal(GET_URL, response.url) - gc_collect() - end -end) - -end end - -RUN() diff --git a/watchdog/third_party/lua-curl/test/utils.lua b/watchdog/third_party/lua-curl/test/utils.lua deleted file mode 100644 index 0f81751..0000000 --- a/watchdog/third_party/lua-curl/test/utils.lua +++ /dev/null @@ -1,151 +0,0 @@ -local curl = require "lcurl" - -local function weak_ptr(val) - return setmetatable({value = val},{__mode = 'v'}) -end - -local function gc_collect(n) - for i = 1, (n or 2) do - collectgarbage("collect") - end -end - -local function cver(min, maj, pat) - return min * 2^16 + maj * 2^8 + pat -end - -local function is_curl_ge(min, maj, pat) - assert(0x070908 == cver(7,9,8)) - return curl.version_info('version_num') >= cver(min, maj, pat) -end - -local function is_curl_eq(min, maj, pat) - assert(0x070908 == cver(7,9,8)) - return curl.version_info('version_num') == cver(min, maj, pat) -end - -local function read_file(n) - local f, e = io.open(n, "r") - if not f then return nil, e end - local d, e = f:read("*all") - f:close() - return d, e -end - -local function get_bin_by(str,n) - local pos = 1 - n - return function() - pos = pos + n - return (str:sub(pos,pos+n-1)) - end -end - -local function stream(ch, n, m) - return n, get_bin_by( (ch):rep(n), m) -end - -local function Stream(ch, n, m) - local size, reader - - local _stream = {} - - function _stream:read(...) - _stream.called_ctx = self - _stream.called_co = coroutine.running() - return reader(...) - end - - function _stream:size() - return size - end - - function _stream:reset() - size, reader = stream(ch, n, m) - return self - end - - return _stream:reset() -end - -local function easy_dump_mime(easy, mime, url) - local buffer = {} - - local function dump_mime(type, data) - if type == curl.INFO_DATA_OUT then - buffer[#buffer + 1] = data - end - end - - local ok, err = easy:setopt{ - url = url or "http://127.0.0.1:7090"; - customrequest = "GET"; - mimepost = mime; - verbose = true; - debugfunction = dump_mime; - writefunction = function()end; - } - - if not ok then return nil, err end - - ok, err = easy:perform() - - if not ok then return nil, err end - - return table.concat(buffer) -end - -local function easy_dump_request(easy, url) - local buffer = {} - local headers = {} - - local function dump_mime(type, data) - if type == curl.INFO_DATA_OUT then - buffer[#buffer + 1] = data - end - - if type == curl.INFO_HEADER_OUT then - headers[#headers + 1] = data - end - end - - local ok, err = easy:setopt{ - url = url or "http://127.0.0.1:7090"; - customrequest = "GET"; - mimepost = mime; - verbose = true; - debugfunction = dump_mime; - writefunction = function()end; - } - - if not ok then return nil, err end - - ok, err = easy:perform() - - if not ok then return nil, err end - - return table.concat(buffer), table.concat(headers) -end - -local utils = { - weak_ptr = weak_ptr; - gc_collect = gc_collect; - is_curl_ge = is_curl_ge; - is_curl_eq = is_curl_eq; - get_bin_by = get_bin_by; - read_file = read_file; - dump_mime = easy_dump_mime; - dump_request = easy_dump_request; - stream = stream; - Stream = Stream; -} - -utils.import = function(...) - local n, t = select('#', ...), {} - for i = 1, n do - local name = select(i, ...) - t[#t + 1] = assert(utils[name], 'unknown field: ' .. tostring(name)) - end - return (unpack or table.unpack)(t, 1, n) -end - -return utils \ No newline at end of file From 6f038d86f3a6d7d7f1af95e969c541c85344e26f Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:18:19 +0800 Subject: [PATCH 13/13] fix(watchdog): treat divergence drill exit 2 as pass in just recipe The staging drill intentionally exits like production on mismatch; wrap the just target so local smoke runs succeed while direct invocation still returns 2 for operators. --- justfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index 7a08ad9..183d392 100644 --- a/justfile +++ b/justfile @@ -18,9 +18,9 @@ test-watchdog: test-watchdog-e2e: lua watchdog/tests/e2e.lua -# Verify divergence signal event+exit-code contract. +# Verify divergence signal event+exit-code contract (drill exits 2 like production). test-watchdog-divergence-drill: watchdog-lua-deps - WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_divergence.lua + WATCHDOG_LUA_DEPS={{justfile_directory()}}/.deps/lua lua watchdog/tests/drill_divergence.lua || test $? -eq 2 # Build lcurl (lua-cURLv3) into .deps/lua; JSON is pure Lua under watchdog/third_party/. watchdog-lua-deps: