diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aef879..fd663a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.38.11 LANGUAGES C) +project(fun VERSION 0.38.12 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index 8a540ba..402a86d 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the stand - [libSQL](https://github.com/tursodatabase/libsql) support builtin as a compatible alternative to SQLite (optional) ☑ - [PCRE2](https://pcre2project.github.io/pcre2/) support builtin for Perl-Compatible Regular Expressions (optional) ☑ - [PCSC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional) ☑ +- [OpenSSL](https://www.openssl.org/) integration (currently MD5 helper) (optional) ☑ - [SQLite](https://sqlite.org/) support builtin (optional) ☑ - [Tk](https://www.tcl-lang.org/) support builtin for GUI application development (optional) ☑ - [XML](https://www.w3.org/XML/) support builtin using [libxml2](https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home) (optional) ☑ @@ -103,6 +104,20 @@ Note: Not all of the above features will be implemented. Those who are marked "D There are some libs written in Fun available in the [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) diretory. In the future most Fun enhancements should be written in Fun itself. +### OpenSSL quickstart (MD5) + +The OpenSSL integration is optional and currently provides an `openssl_md5(data)` helper. + +- Build with OpenSSL enabled: + - `cmake -S . -B build_debug -DFUN_WITH_OPENSSL=ON` + - `cmake --build build_debug --target fun` +- Run the example: + - `./build_debug/fun examples/crypto/openssl_md5.fun` + +If OpenSSL is disabled, the function returns an empty string (consistent with other optional extensions). + +Note: On OpenSSL 3.x the legacy `MD5_*` APIs are deprecated; you may see warnings during build. + ## Documentation Looking for docs? Start here: diff --git a/cmake/Extensions/Extensions.cmake b/cmake/Extensions/Extensions.cmake index 070f410..b16333b 100644 --- a/cmake/Extensions/Extensions.cmake +++ b/cmake/Extensions/Extensions.cmake @@ -22,6 +22,7 @@ include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCRE2.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/CURL.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/NOTCURSES.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/Extensions/OPENSSL.cmake) # Summary of extension toggles message(STATUS "---- Fun extension summary ----") @@ -36,4 +37,5 @@ _fun_print_feature("PCRE2 (FUN_WITH_PCRE2)" FUN_WITH_PCRE2) _fun_print_feature("libcurl (FUN_WITH_CURL)" FUN_WITH_CURL) _fun_print_feature("Notcurses (FUN_WITH_NOTCURSES)" FUN_WITH_NOTCURSES) _fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL) +_fun_print_feature("OpenSSL (FUN_WITH_OPENSSL)" FUN_WITH_OPENSSL) message(STATUS "--------------------------------") diff --git a/cmake/Extensions/OPENSSL.cmake b/cmake/Extensions/OPENSSL.cmake new file mode 100644 index 0000000..3a3f623 --- /dev/null +++ b/cmake/Extensions/OPENSSL.cmake @@ -0,0 +1,41 @@ +## OpenSSL optional integration (MD5, etc.) + +option(FUN_WITH_OPENSSL "Enable OpenSSL-based features (e.g., md5)" OFF) + +set(OPENSSL_INCLUDE_DIRS "") +set(OPENSSL_LINK_LIBS "") + +if(FUN_WITH_OPENSSL) + find_package(OpenSSL REQUIRED) + if(NOT OpenSSL_FOUND AND NOT OPENSSL_FOUND) + message(FATAL_ERROR "FUN_WITH_OPENSSL=ON but OpenSSL not found") + endif() + + # CMake 3.11+ standard variables + if(TARGET OpenSSL::Crypto) + set(OPENSSL_LINK_LIBS OpenSSL::Crypto) + elseif(DEFINED OPENSSL_CRYPTO_LIBRARY) + set(OPENSSL_LINK_LIBS ${OPENSSL_CRYPTO_LIBRARY}) + endif() + + if(DEFINED OPENSSL_INCLUDE_DIR) + list(APPEND OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + endif() + + # Propagate to main targets if they exist in this scope + if(TARGET fun_core) + target_link_libraries(fun_core PRIVATE ${OPENSSL_LINK_LIBS}) + target_include_directories(fun_core PRIVATE ${OPENSSL_INCLUDE_DIRS}) + target_compile_definitions(fun_core PRIVATE FUN_WITH_OPENSSL=1) + endif() + if(TARGET fun) + target_link_libraries(fun PRIVATE ${OPENSSL_LINK_LIBS}) + target_include_directories(fun PRIVATE ${OPENSSL_INCLUDE_DIRS}) + target_compile_definitions(fun PRIVATE FUN_WITH_OPENSSL=1) + endif() + if(TARGET fun_test) + target_link_libraries(fun_test PRIVATE ${OPENSSL_LINK_LIBS}) + target_include_directories(fun_test PRIVATE ${OPENSSL_INCLUDE_DIRS}) + target_compile_definitions(fun_test PRIVATE FUN_WITH_OPENSSL=1) + endif() +endif() diff --git a/cmake/Targets.cmake b/cmake/Targets.cmake index 99146ec..bd6c48e 100644 --- a/cmake/Targets.cmake +++ b/cmake/Targets.cmake @@ -36,7 +36,8 @@ foreach(var_pair LIBSQL LIBXML2 TCL - NOTCURSES) + NOTCURSES + OPENSSL) if(${var_pair}_INCLUDE_DIRS) target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS}) endif() @@ -89,6 +90,10 @@ if(FUN_WITH_TCLTK) target_compile_definitions(fun_core PUBLIC FUN_WITH_TCLTK=1) endif() +if(FUN_WITH_OPENSSL) + target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1) +endif() + # Workaround: provide a dummy rust_eh_personality to satisfy linker when using # no_std Rust staticlib with panic abort (some toolchains still reference it). # If needed, we could add a C shim here, but the Rust crate now defines diff --git a/docs/README.md b/docs/README.md index 75e488f..2c4cccc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ This file serves as an index of the documents in this directory. Links are relat ## New and supplemental guides -- [build.md](./build.md) - How to build Fun with CMake, available targets, and build options (FUN_DEBUG, FUN_USE_MUSL, FUN_WITH_CPP, FUN_WITH_RUST). +- [build.md](./build.md) - How to build Fun with CMake, available targets, and build options (FUN_DEBUG, FUN_USE_MUSL, FUN_WITH_CPP, FUN_WITH_RUST, FUN_WITH_OPENSSL). - [cli.md](./cli.md) - Command-line usage of the `fun` executable: synopsis, options, exit codes, includes and library paths. - [contributing.md](./contributing.md) - How to contribute: project structure, coding style, running tests, and PR guidelines. - [style-guide.md](./style-guide.md) - Coding conventions for C and Fun (indentation, naming, idioms). @@ -39,3 +39,4 @@ This file serves as an index of the documents in this directory. Links are relat - When building from the repo without installing, set `FUN_LIB_DIR` to the local `./lib` directory so examples and the REPL can locate the stdlib. - For a broader project overview and quickstart, see the repository root [README.md](../README.md). +- Crypto example: if built with `-DFUN_WITH_OPENSSL=ON`, try `examples/crypto/openssl_md5.fun` to compute MD5 using OpenSSL. diff --git a/docs/examples.md b/docs/examples.md index 358ef0f..5571db7 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -37,14 +37,14 @@ Notes: Tip: you can run specific examples directly too: ``` -FUN_LIB_DIR="$(pwd)/lib" fun examples/crypto/aes256.fun +FUN_LIB_DIR="$(pwd)/lib" fun examples/crypto/openssl_md5.fun ``` ## Example categories Browse the `examples/` tree for areas of interest: -- crypto — crypto demonstrations (e.g., AES) +- crypto — crypto demonstrations (e.g., OpenSSL MD5 helper; requires build with `-DFUN_WITH_OPENSSL=ON`) - blocking / interactive — I/O or user-interactive patterns - error / broken — negative tests and error showcases - math — numeric operations diff --git a/docs/opcodes.md b/docs/opcodes.md index 15c742d..d1d9dbc 100644 --- a/docs/opcodes.md +++ b/docs/opcodes.md @@ -202,6 +202,11 @@ This document provides an overview of the available VM opcodes implemented under - OP_CURL_POST: HTTP POST; pops body:string, url:string; pushes response:string or Nil. - OP_CURL_DOWNLOAD: Download URL to file; pops path:string, url:string; pushes 1/0. +## OpenSSL (optional) + +- OP_OPENSSL_MD5: Compute MD5 digest and return lowercase hex string; pops data:string; pushes md5:string. + - Requires building with `-DFUN_WITH_OPENSSL=ON`. When disabled, this opcode still exists but returns an empty string to match other optional extensions’ fallback behavior. + ## PCRE2 (Regex) - OP_PCRE2_TEST: Test pattern; pops flags:int, text:string, pattern:string; pushes 1/0. diff --git a/examples/crypto/openssl_md5.fun b/examples/crypto/openssl_md5.fun new file mode 100644 index 0000000..7002721 --- /dev/null +++ b/examples/crypto/openssl_md5.fun @@ -0,0 +1,28 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-02-19 + */ + +// OpenSSL MD5 example +// Enable with -DFUN_WITH_OPENSSL=ON during build for real hashing. + +s = "abc" +d = openssl_md5(s) +print("md5(abc) = " + d) + +// Another quick check (empty string) +e = "" +print("md5(\"\") = " + openssl_md5(e)) + +/* Expected output: +md5(abc) = 900150983cd24fb0d6963f7d28e17f72 +md5("") = d41d8cd98f00b204e9800998ecf8427e +*/ diff --git a/src/bytecode.c b/src/bytecode.c index 793ef79..13e2ce9 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -169,6 +169,7 @@ static const char *opcode_name(OpCode op) { case OP_PCRE2_TEST: return "PCRE2_TEST"; case OP_PCRE2_MATCH: return "PCRE2_MATCH"; case OP_PCRE2_FINDALL: return "PCRE2_FINDALL"; + case OP_OPENSSL_MD5: return "OPENSSL_MD5"; case OP_INI_LOAD: return "INI_LOAD"; case OP_INI_FREE: return "INI_FREE"; case OP_INI_GET_STRING: return "INI_GET_STRING"; diff --git a/src/bytecode.h b/src/bytecode.h index 5eb3de4..92f9867 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -181,6 +181,9 @@ typedef enum { OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps + // OpenSSL (optional) + OP_OPENSSL_MD5, // pops data string; pushes md5 hex string + // INI (iniparser 4.2.6) optional OP_INI_LOAD, // pops path; pushes handle (>0) or 0 OP_INI_FREE, // pops handle; pushes 1/0 diff --git a/src/external/opensssl.c b/src/external/opensssl.c new file mode 100644 index 0000000..a4fcf26 --- /dev/null +++ b/src/external/opensssl.c @@ -0,0 +1,47 @@ +/* +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-02-19 + */ + + /* + * OpenSSL integration helpers (currently MD5 only) + */ + +#ifdef FUN_WITH_OPENSSL +# include +#endif +#include + +/* Compute MD5 hex of input buffer; returns malloc'ed C string (lowercase hex). + * Caller must free(). Returns NULL on failure. When OpenSSL is disabled, + * returns an allocated empty string ("") to keep behavior consistent with + * other optional extensions. */ +static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; +#ifdef FUN_WITH_OPENSSL + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_CTX ctx; + if (MD5_Init(&ctx) != 1) return NULL; + if (len && MD5_Update(&ctx, data, len) != 1) return NULL; + if (MD5_Final(digest, &ctx) != 1) return NULL; + char *hex = (char*)malloc(MD5_DIGEST_LENGTH * 2 + 1); + if (!hex) return NULL; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { + hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2*i+1] = hexdig[digest[i] & 0xF]; + } + hex[MD5_DIGEST_LENGTH * 2] = '\0'; + return hex; +#else + char *hex = (char*)malloc(1); + if (hex) hex[0] = '\0'; + return hex; +#endif +} diff --git a/src/parser.c b/src/parser.c index ad8dd1b..aa16896 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1197,6 +1197,15 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* OpenSSL (md5) */ + if (strcmp(name, "openssl_md5") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_md5 expects (data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_md5 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_OPENSSL_MD5, 0); + free(name); + return 1; + } /* PCSC builtins */ if (strcmp(name, "pcsc_establish") == 0) { (*pos)++; /* '(' */ diff --git a/src/vm.c b/src/vm.c index c8f42e4..a64f80c 100644 --- a/src/vm.c +++ b/src/vm.c @@ -73,6 +73,7 @@ #include "external/sqlite.c" #include "external/tcltk.c" #include "external/xml2.c" +#include "external/opensssl.c" /* forward declarations for include mapping used in error reporting */ extern char *preprocess_includes(const char *src); @@ -868,6 +869,9 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/curl/download.c" #endif + /* OpenSSL ops (md5) */ + #include "vm/openssl/md5.c" + /* Tk (Tcl/Tk) ops */ #ifdef FUN_WITH_TCLTK #include "vm/tk/eval.c" diff --git a/src/vm/openssl/md5.c b/src/vm/openssl/md5.c new file mode 100644 index 0000000..473605d --- /dev/null +++ b/src/vm/openssl/md5.c @@ -0,0 +1,16 @@ +/** + * OpenSSL MD5 builtin + */ +case OP_OPENSSL_MD5: { + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { push_value(vm, make_string("")); break; } + char *hex = fun_openssl_md5_hex((const unsigned char*)s, strlen(s)); + free(s); + if (!hex) { push_value(vm, make_string("")); break; } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; +}