sn0int 0.25.0

Semi-automatic OSINT framework and package manager
Documentation
use crate::errors::*;

use crate::engine::ctx::State;
use crate::hlua::{self, AnyLuaValue, AnyHashableLuaValue};
use crate::json;
use sn0int_std::blobs::BlobState;
use sn0int_std::web::WebState;
use std::sync::Arc;
use std::collections::HashMap;
use crate::web::{RequestOptions, HttpRequest};


pub fn http_mksession(lua: &mut hlua::Lua, state: Arc<dyn State>) {
    lua.set("http_mksession", hlua::function0(move || -> String {
        state.http_mksession()
    }))
}

pub fn http_request(lua: &mut hlua::Lua, state: Arc<dyn State>) {
    lua.set("http_request", hlua::function4(move |session: String, method: String, url: String, options: AnyLuaValue| -> Result<AnyLuaValue> {
        RequestOptions::try_from(options)
            .context("invalid request options")
            .map_err(|err| state.set_error(Error::from(err)))
            .map(|options| {
                state.http_request(&session, method, url, options).into()
            })
    }))
}

pub fn http_send<S>(lua: &mut hlua::Lua, state: Arc<S>)
    where S: State + WebState + BlobState + 'static
{
    lua.set("http_send", hlua::function1(move |request: AnyLuaValue| -> Result<HashMap<AnyHashableLuaValue, AnyLuaValue>> {
        let mut req = HttpRequest::try_from(request)
            .context("invalid http request object")
            .map_err(|err| state.set_error(err.into()))?;

        let resp = req.send(state.as_ref())
            .map_err(|err| state.set_error(err))?;

        req.response_to_lua(state.as_ref(), resp)
            .map_err(|err| state.set_error(err))
            .map(|resp| resp.into())
    }))
}

pub fn http_fetch<S>(lua: &mut hlua::Lua, state: Arc<S>)
    where S: State + WebState + BlobState + 'static
{
    lua.set("http_fetch", hlua::function1(move |request: AnyLuaValue| -> Result<AnyLuaValue> {
        let mut req = HttpRequest::try_from(request)
            .context("invalid http request object")
            .map_err(|err| state.set_error(err.into()))?;

        let resp = req.send(state.as_ref())
            .map_err(|err| state.set_error(err))?;

        if resp.status < 200 || resp.status > 299 {
            return Err(state.set_error(format_err!("http status error: {}", resp.status)));
        }

        req.response_to_lua(state.as_ref(), resp)
            .map_err(|err| state.set_error(err))
            .map(|resp| resp.into())
    }))
}

pub fn http_fetch_json<S>(lua: &mut hlua::Lua, state: Arc<S>)
    where S: State + WebState + 'static
{
    lua.set("http_fetch_json", hlua::function1(move |request: AnyLuaValue| -> Result<AnyLuaValue> {
        let mut req = HttpRequest::try_from(request)
            .context("invalid http request object")
            .map_err(|err| state.set_error(err.into()))?;

        let resp = req.send(state.as_ref())
            .map_err(|err| state.set_error(err))?;

        if resp.status < 200 || resp.status > 299 {
            return Err(state.set_error(format_err!("http status error: {}", resp.status)));
        }

        json::decode(&resp.body)
            .map_err(|err| state.set_error(err))
    }))
}


#[cfg(test)]
mod tests {
    use crate::engine::ctx::Script;
    use std::time::{Instant, Duration};

    #[test]
    #[ignore]
    fn verify_request() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "https://httpbin.org/anything", {})
            x = http_send(req)
            if last_err() then return end
            print(x)

            if x['status'] ~= 200 then
                return 'wrong status code'
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }

    #[test]
    #[ignore]
    fn verify_timeout() {
        let start = Instant::now();
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "http://1.2.3.4", {
                timeout=250
            })
            x = http_send(req)
            if last_err() then return end
        end
        "#).expect("failed to load script");
        script.test().err().expect("Script should have failed");
        let end = Instant::now();

        assert!(end.duration_since(start) < Duration::from_secs(1));
    }

    #[test]
    #[ignore]
    fn verify_post() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()

            headers = {}
            headers['Content-Type'] = "application/json"
            req = http_request(session, "POST", "https://httpbin.org/anything", {
                headers=headers,
                query={
                    foo="bar"
                },
                json={
                    hello="world"
                }
            })
            x = http_send(req)
            if last_err() then return end
            print(x)

            o = json_decode(x['text'])
            if last_err() then return end

            if o['args']['foo'] ~= 'bar' or o['json']['hello'] ~= 'world' then
                return "reply didn't contain all params"
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }

    #[test]
    #[ignore]
    fn verify_cookies() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()

            req = http_request(session, "GET", "https://httpbin.org/cookies/set", {
                query={
                    foo="bar",
                    fizz="buzz"
                }
            })
            x = http_send(req)

            req = http_request(session, "GET", "https://httpbin.org/cookies", {})
            x = http_send(req)
            if last_err() then return end
            print(x)

            o = json_decode(x['text'])
            if last_err() then return end

            if o['cookies']['fizz'] ~= 'buzz' or o['cookies']['foo'] ~= 'bar' then
                return "reply didn't contain all cookies"
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_ok() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "https://httpbin.org/anything", {})
            x = http_fetch(req)
            if last_err() then return end

            o = json_decode(x['text'])
            if last_err() then return end

            if o['method'] ~= 'GET' then
                return 'unexpected response'
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_json_ok() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "https://httpbin.org/anything", {})
            x = http_fetch_json(req)
            if last_err() then return end
            print(x)
            if x['method'] ~= 'GET' then
                return 'unexpected response'
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_404() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "https://httpbin.org/status/404", {})
            x = http_fetch(req)
        end
        "#).expect("failed to load script");
        script.test().err().expect("Script should have failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_json_404() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "https://httpbin.org/status/404", {})
            x = http_fetch_json(req)
        end
        "#).expect("failed to load script");
        script.test().err().expect("Script should have failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_json_invalid() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "https://httpbin.org/html", {})
            x = http_fetch_json(req)
        end
        "#).expect("failed to load script");
        script.test().err().expect("Script should have failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_redirects() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "http://github.com", {
                follow_redirects=1,
            })
            x = http_send(req)
            if last_err() then return end

            if x['status'] ~= 200 then
                return 'redirect wasn\'t followed'
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }

    #[test]
    #[ignore]
    fn verify_fetch_skip_redirects() {
        let script = Script::load_unchecked(r#"
        function run()
            session = http_mksession()
            req = http_request(session, "GET", "http://github.com", {
                follow_redirects=0,
            })
            x = http_send(req)
            if last_err() then return end

            if x['status'] ~= 301 then
                return 'redirect was followed'
            end
        end
        "#).expect("failed to load script");
        script.test().expect("Script failed");
    }
}