Verified Commit a718f158 authored by Karel Koci's avatar Karel Koci 🤘

Add Lua module for URI implemented in C

This exports URI and downloader in wrapped form to Lua.
parent 310154c9
......@@ -27,6 +27,7 @@ libupdater_MODULES := \
subprocess \
download \
uri \
uri_lua \
journal \
locks \
picosat \
......
This diff is collapsed.
......@@ -20,6 +20,7 @@
#include "inject.h"
#include "util.h"
#include "logging.h"
#include <lauxlib.h>
void inject_func_n(lua_State *L, const char *module, const struct inject_func *inject, size_t count) {
// Inject the functions
......@@ -55,3 +56,9 @@ void inject_module(lua_State *L, const char *module) {
// Drop the _M, package, loaded
lua_pop(L, 3);
}
void inject_metatable_self_index(lua_State *L, const char *meta) {
ASSERT(luaL_newmetatable(L, meta) == 1);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
}
......@@ -37,5 +37,7 @@ void inject_str_const(lua_State *L, const char *module, const char *name, const
void inject_int_const(lua_State *L, const char *module, const char *name, const int value) __attribute__((nonnull));
// Make the table on top of the stack a module. Drop the table from the stack.
void inject_module(lua_State *L, const char *module) __attribute__((nonnull));
// Create new metatable on top of stack that is self indexing (__index is a table it self)
void inject_metatable_self_index(lua_State *L, const char *meta) __attribute__((nonnull));
#endif
......@@ -26,6 +26,7 @@
#include "locks.h"
#include "arguments.h"
#include "syscnf.h"
#include "uri_lua.h"
#include "picosat.h"
#include <lua.h>
......@@ -1023,6 +1024,7 @@ struct interpreter *interpreter_create(struct events *events) {
journal_mod_init(L);
locks_mod_init(L);
syscnf_mod_init(L);
uri_mod_init(L);
picosat_mod_init(L);
#ifdef COVERAGE
interpreter_load_coverage(result);
......
This diff is collapsed.
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the Turris Updater.
*
* Updater is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Updater is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UPDATER_URI_LUA_H
#define UPDATER_URI_LUA_H
#include <lua.h>
// Create uri module and inject it into the lua state
void uri_mod_init(lua_State *L) __attribute__((nonnull));
#endif
......@@ -39,12 +39,11 @@ my %module2path = (
syscnf => "autoload/a_06_syscnf.lua",
backend => "autoload/a_08_backend.lua",
transaction => "autoload/a_09_transaction.lua",
uri => "autoload/a_10_uri.lua",
requests => "autoload/a_11_requests.lua",
sandbox => "autoload/a_12_sandbox.lua",
postprocess => "autoload/a_13_postprocess.lua",
planner => "autoload/a_14_planner.lua",
updater => "autoload/a_15_updater.lua",
requests => "autoload/a_10_requests.lua",
sandbox => "autoload/a_11_sandbox.lua",
postprocess => "autoload/a_12_postprocess.lua",
planner => "autoload/a_13_planner.lua",
updater => "autoload/a_14_updater.lua",
);
foreach my $module (keys %module2path) {
$module2path{$module} = abs_path($source . '/src/lib/' . $module2path{$module});
......
......@@ -21,6 +21,8 @@ globals = {
"LS_EXIT", "LS_FAIL",
-- From logging
"ERROR", "WARN", "INFO", "DBG", "TRACE", "DIE", "log_event", "c_pcall_error_handler",
-- URI
"uri",
-- Picosat
"picosat",
-- syscnf
......
--[[
Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
This file is part of the turris updater.
This file is part of the Turris Updater.
Updater is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -19,203 +19,109 @@ along with Updater. If not, see <http://www.gnu.org/licenses/>.
require "lunit"
local uri = require "uri"
local sandbox = require "sandbox"
local utils = require "utils"
local os = os
local dir = (os.getenv("S") .. "/") or ''
local dir = os.getenv("S") or "."
local tmpdir = os.getenv("TMPDIR") or "/tmp"
local lorem_ipsum = "lorem ipsum\n"
local https_lorem_ipsum = "https://applications-test.turris.cz/li.txt"
local ca_lets_encrypts = "file://" .. dir .. "/tests/data/lets_encrypt_roots.pem"
local ca_opentrust_g1 = "file://" .. dir .. "/tests/data/opentrust_ca_g1.pem"
module("uri-tests", package.seeall, lunit.testcase)
-- Test few invalid URIs
function test_invalid()
local context = sandbox.new("Remote")
-- This scheme doesn't exist
assert_exception(function () return uri.new(context, "unknown:bad") end, "bad value")
-- Check it by calling directly uri()
assert_exception(function () return uri(context, "unknown:bad") end, "bad value")
-- Test master on its own
function test_master()
local master = uri:new()
master:download() -- it whould pass without any uri
end
-- Test if we have get and ok methods
function test_methods()
function method_assert(u)
uri.wait(u)
lunit.assert_function(u.get, "Uri missing get method")
lunit.assert_function(u.ok, "Uri missing ok method")
end
function test_uri()
local master = uri.new()
local u = master:to_buffer("file:///dev/null")
assert_equal("file:///dev/null", u:uri())
end
local context = sandbox.new("Local")
-- Check on success
local u1 = uri(context, "https://repo.turris.cz/", {verification = 'none'})
method_assert(u1)
function test_to_buffer()
local master = uri.new()
local u = master:to_buffer("data:,Hello!")
local dt = u:finish()
assert_equal("Hello!", dt)
assert_nil(u:output_path())
end
local function check_sync(level, input, output)
local context = sandbox.new(level)
local uri = uri(context, input)
-- No need to wait for this one
assert(uri.done)
-- But when we do, we get the right data
local ok, result = uri:get()
assert(ok)
assert_equal(output, result)
-- When we do it with callback, it gets called and provides the same data
local called = false
uri:cback(function (ok, result)
assert(ok)
assert_equal(output, result)
called = true
end)
assert(called)
function test_to_file()
local fpath = tmpdir .. "/updater-uri-lua-test"
local master = uri.new()
local u = master:to_file("data:,Hello!", fpath)
assert_nil(u:finish())
assert_equal(fpath, u:output_path())
assert_equal("Hello!", utils.read_file(fpath))
os.remove(u:output_path())
end
local function err_sync(level, input, reason)
local context = sandbox.new(level)
local uri = uri(context, input)
-- It fails right avay, synchronously
assert(uri.done)
-- The error is returned
local ok, result = uri:get()
assert_false(ok)
assert_equal('error', result.tp)
assert_equal(reason, result.reason)
-- The same goes when requested through the callback
local called = false
uri:cback(function (ok, result)
assert_false(ok)
assert_equal('error', result.tp)
assert_equal(reason, result.reason)
called = true
end)
assert(called)
function test_to_temp_file()
local template = tmpdir .. "/updater-uri-lua-XXXXXX"
local master = uri.new()
local u = master:to_temp_file("data:,Hello!", template)
assert_nil(u:finish())
assert_not_equal(template, u:output_path())
assert_equal("Hello!", utils.read_file(u:output_path()))
os.remove(u:output_path())
end
-- Test the data scheme
function test_data()
local function check(input, output)
check_sync("Restricted", "data:" .. input, output)
end
-- Simple case
check(",hello", "hello")
-- Something URL-encoded
check(",hello%20world", "hello world")
-- Something base64-encoded
check("base64,aGVsbG8gd29ybGQ=", "hello world")
-- We don't damage whatever gets out of base64
check("base64,aGVsbG8lMjB3b3JsZA==", "hello%20world")
-- And we properly decode before base64
check("base64,aGVsbG8lMjB3b3JsZA%3D%3D", "hello%20world")
-- Other options about the URI are ignored
check("charset=utf8,hello", "hello")
check("charset=utf8;base64,aGVsbG8lMjB3b3JsZA%3D%3D", "hello%20world")
local function malformed(input)
err_sync("Restricted", "data:" .. input, "malformed URI")
function test_is_local()
local master = uri.new()
local function check(struri, should_be_local)
local u = master:to_buffer(struri)
assert_equal(should_be_local, u:is_local())
end
-- Missing comma
malformed("data:hello")
-- Bad URL escape
malformed("data:,%ZZ")
--[[
Note: There are other forms of malformed URIs we don't detect.
We don't aim at being validating parser of the URIs, so that's
OK. The goal is to work with whatever is valid and report
if we don't know what to do with what we got.
]]
check("data:,test", true)
check("https://www.example.com/", false)
end
function test_file()
check_sync("Local", "file:///dev/null", "")
check_sync("Local", "file://" .. dir .. "tests/data/hello.txt", "hello\n")
check_sync("Local", "file://" .. dir .. "tests/data/hello%2etxt", "hello\n")
local context = sandbox.new("Remote")
assert_exception(function () uri(context, "file:///dev/null") end, "access violation")
err_sync("Local", "file:something", "malformed URI")
err_sync("Local", "file://%ZZ", "malformed URI")
err_sync("Local", "file:///does/not/exist", "unreachable")
function test_path()
local master = uri.new()
local u = master:to_buffer("file:///dev/null")
assert_equal("/dev/null", u:path())
end
function test_https()
local context = sandbox.new("Remote")
local u1 = uri(context, "https://repo.turris.cz/", {verification = 'none'})
local u2 = uri(context, "https://repo.turris.cz/does/not/exist", {verification = 'none'})
assert_false(u1.done)
assert_false(u2.done)
local called1 = false
local called2 = false
u1:cback(function (ok, content)
called1 = true
assert(ok)
assert(content:match("Index of"))
end)
u2:cback(function (ok, err)
called2 = true
assert_false(ok)
assert_equal("error", err.tp)
assert_equal("unreachable", err.reason)
end)
assert_false(called1)
assert_false(called2)
local ok, content = u1:get()
assert(called1)
assert(ok)
assert(content:match("Index of"))
uri.wait(u1, u2)
assert(called2)
local ok = u2:get()
assert_false(ok)
local master = uri.new()
local u = master:to_buffer(https_lorem_ipsum)
master:download()
local dt = u:finish()
assert_equal(lorem_ipsum, dt)
end
function test_restricted()
local context = sandbox.new("Restricted")
context.restrict = 'https://repo%.turris%.cz/.*'
local function u(location)
local result = uri(context, location, {verification = 'none'})
--[[
Make sure we wait for the result so we free all relevant memory.
Yes, it would be better if we freed it automatically when we just
drop the reference, but the world is not perfect.
]]
result:get()
end
assert_pass(function () u("https://repo.turris.cz/") end)
assert_exception(function () u("https://repo.turris.cz") end, "access violation")
assert_exception(function () u("https://www.turris.cz/index.html") end, "access violation")
function test_cert_pinning_correct()
local master = uri.new()
local u = master:to_buffer(https_lorem_ipsum)
u:add_ca(ca_lets_encrypts)
master:download()
local dt = u:finish()
assert_equal(lorem_ipsum, dt)
end
function test_sig()
local context = sandbox.new("Restricted")
local key_ok = 'data:,ok'
local key_bad = 'data:,bad'
local key_broken = 'data:'
local sig = 'data:,sig'
mock_gen("uri.signature_check", function (content, key, signature)
if key == 'ok' then
return true
else
return false
end
end)
local function ck(key, sig)
local ok, content = uri(context, "data:,data", {verification = 'sig', sig = sig, pubkey = key}):get()
return ok
end
assert(ck(key_ok, sig))
assert_false(ck(key_bad, sig))
assert_false(ck(key_broken, sig))
-- Check one correct key is enough
assert(ck({key_bad, key_broken, key_ok}, sig))
-- Check the default sig uri (it actually works with data uri in a strange way
assert(ck(key_ok))
assert_table_equal({
{f = "uri.signature_check", p = {"data", "ok", "sig"}},
{f = "uri.signature_check", p = {"data", "bad", "sig"}},
{f = "uri.signature_check", p = {"data", "bad", "sig"}},
{f = "uri.signature_check", p = {"data", "ok", "sig"}},
{f = "uri.signature_check", p = {"data", "ok", "data.sig"}}
}, mocks_called)
end
-- TODO incorrect pinnging (when we have error handling)
-- Check invalid verification mode (a typo) is rejected
function test_vermode()
local context = sandbox.new("Restricted")
assert_exception(function () uri(context, "data:,data", {verification = 'typo'}) end, 'bad value')
function test_cert_no_verify()
local master = uri.new()
local u = master:to_buffer(https_lorem_ipsum)
u:set_ssl_verify(false)
u:add_ca(ca_opentrust_g1)
master:download()
local dt = u:finish()
assert_equal(lorem_ipsum, dt)
end
-- This is valid usage so test that it is possible
function test_add_nil()
local master = uri.new()
local u = master:to_buffer(https_lorem_ipsum)
u:add_ca(nil)
u:add_crl(nil)
u:add_pubkey(nil)
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment