
header "openssl" as ossl

use fs

shared ca_cert : ?String
global ca_dirs : ?Array[String]
global ca_paths : ?Array[String]

+ fn set_ca_cert_path(path: ?String) {
    ca_cert = path
}

class SSL {
    ~ ctx: ossl:SSL
    ~ ssl: ossl:SSL
    - host: String
    - ca_cert_path: ?String

    static fn ca_paths() Array[String] {
        let paths = ca_paths
        if isset(paths) : return paths
        // List
        paths = Array[String]{}
        // Default
        let x509_default : String = ossl:X509_get_default_cert_file()
        paths.append(x509_default)
        // Macos
        #if OS == macos
        paths.append("/usr/local/etc/ca-certificates/cert.pem")
        paths.append("/etc/ssl/cert.pem")
        #end
        // Linux
        #if OS == linux
        paths.append("/etc/ssl/certs/ca-certificates.crt")
        paths.append("/etc/pki/tls/certs/ca-bundle.crt")
        paths.append("/etc/ssl/ca-bundle.pem")
        paths.append("/etc/pki/tls/cacert.pem")
        paths.append("/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem")
        paths.append("/etc/ssl/cert.pem")
        #end
        // Exe
        paths.append(fs:add(fs:exe_dir(), "cacert.pem"))

        ca_paths = paths
        return paths
    }

    static fn try_ca_path(ctx: ossl:SSL_CTX, path: ?String) bool {
        if !isset(path) : return false
        #if OS == win
        if path.starts_with("\\\\") : return false
        #end
        return ossl:SSL_CTX_load_verify_locations(ctx, path.data, null) == 1
    }

    static fn new(fd: FD, host: String, ca_path: ?String (null)) SSL {
        let method = ossl:TLS_client_method()
        let ctx = ossl:SSL_CTX_new(method)

        // CA cert path
        // User defined cert
        if SELF.try_ca_path(ctx, ca_path) {
        } else {

            let found = false
            let ca_paths = SELF.ca_paths()
            each ca_paths as path {
                if SELF.try_ca_path(ctx, path.data_cstring) {
                    ca_path = path
                    found = true
                    break
                }
            }

            if !found {
                ossl:SSL_CTX_set_default_verify_paths(ctx)
            }
        }

        // Verify settings
        ossl:SSL_CTX_set_verify(ctx, ossl:SSL_VERIFY_PEER, null);

        // Ciphers
        // ossl:SSL_CTX_ctrl(ctx, ossl:SSL_CTRL_SET_MIN_PROTO_VERSION, ossl:TLS1_2_VERSION, null)
        // ossl:SSL_CTX_set_options(ctx, ossl:SSL_OP_NO_SSLv3);

        // Init SSL
        let ssl = ossl:SSL_new(ctx)
        ossl:SSL_set_fd(ssl, fd.@cast(i32))
        ossl:SSL_ctrl(ssl, ossl:SSL_CTRL_SET_TLSEXT_HOSTNAME, ossl:TLSEXT_NAMETYPE_host_name, host.data)

        return SSL {
            ctx: ctx
            ssl: ssl
            host: host
            ca_cert_path: ca_path
        }
    }

    fn gc_free() {
        ossl:SSL_free(this.ssl)
        ossl:SSL_CTX_free(this.ctx);
    }
}
