Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
In one sentence: **Fun is a scripting language that goes all the way from JSON to machine code.**

- One runtime spans the whole spectrum, from high-level data work (JSON/FD) down to low-level system programming (FFI, C at runtime, JIT, machine code);
- 63 standard-library modules written in Fun itself ship together as one embeddable `fun.dll`;
- 64 standard-library modules written in Fun itself ship together as one embeddable `fun.dll`;
- in AI-agent and domestic-IT scenarios, it is the lightweight glue connecting OSes, databases, chips, and business logic.

Fun is designed around a simple premise: **data and code should be equally first-class, and a script should be able to reach down to the machine without leaving the language.**
Expand Down Expand Up @@ -70,7 +70,7 @@ Fun is written in Pascal (Delphi / Free Pascal). The core is a small tree-walkin
- **Keyword aliasing** — a canonical English keyword set with pluggable alias packs (Chinese, French, custom) selected at runtime (see [below](#keyword-aliasing)).
- **Cross-platform** — Windows (32/64-bit), Linux, ARM, and Windows CE.
- **Dual distribution** — command-line `fun.exe` and embeddable runtime `fun.dll` exporting a simple `Run` interface.
- **Self-hosted standard library** — 63 modules written in Fun itself, from JSON/YAML/XML to ORM, async, UI, and native bindings.
- **Self-hosted standard library** — 64 modules written in Fun itself, from JSON/YAML/XML to ORM, async, UI, and native bindings.

---

Expand Down Expand Up @@ -372,7 +372,7 @@ fun/
│ ├── utils/ # Utility functions
│ └── prj/fun/ # Projects & build scripts (funcmd.dpr, Delphi/FPC)
├── fun/
│ ├── lib/ # Standard library (pure Fun, 63 modules)
│ ├── lib/ # Standard library (pure Fun, 64 modules)
│ ├── demo/ # Language syntax demos
│ ├── demos/ # Full applications & benchmarks
│ └── key_*.ini # Keyword alias packs (CN / FR / PUA)
Expand Down Expand Up @@ -497,16 +497,16 @@ win64/aarch64 target, so there is nothing to build with.

## Standard library

The standard library in [`fun/lib`](fun/lib) is written in pure Fun (63 modules):
The standard library in [`fun/lib`](fun/lib) is written in pure Fun (64 modules):

> In total roughly 6,000 lines of logical code (excluding comments), about 100 lines per module — all 63 modules written in Fun itself.
> In total roughly 6,000 lines of logical code (excluding comments), about 100 lines per module — all 64 modules written in Fun itself.

- **Data**: `lib-json`, `lib-yaml`, `lib-xml`, `lib-base64`, `lib-cstruct`, `lib-md5`, `lib-crypt`
- **Utility**: `uuid`
- **Collections / algorithms**: `lib-set`, `lib-tree`, `lib-stack`, `lib-dyns`, `lib-math`
- **Text**: `lib-string`, `lib-regex`, `lib-match`, `lib-unicode`
- **System / IO**: `lib-file`, `lib-os`, `lib-host`, `lib-time`, `lib-cmdline`, `lib-proc`
- **Network**: `lib-winsock`, `lib-ajax`, `lib-jsonrpc`, `http-server`, `http-client`
- **Network**: `lib-winsock`, `lib-ajax`, `lib-jsonrpc`, `http-server`, `http-client`, `http-auth`
- **Databases**: `lib-orm`, `lib-orm-pro`, `lib-orm-gen`, `lib-orm-rpc`, `lib-orm-cte`, `lib-ado`, `lib-ado-schema`
- **Native / low-level**: `lib-winapi`, `lib-winole`, `lib-tcc` (C compile), `lib-jit`, `lib-asm`, `lib-asm-pro`
- **UI**: `lib-ui`, `lib-ui-base`, `lib-dialog`, `lib-trayicon`
Expand Down
12 changes: 6 additions & 6 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
一句话定位:**Fun 是一门从 JSON 一路写到机器码的脚本语言。**

- 用同一个运行时,覆盖从高层数据处理(JSON/FD)到底层系统编程(FFI、运行时 C、JIT、机器码)的全谱系;
- 自带 63 个用 Fun 自身编写的标准库模块,全栈能力打包进单个可嵌入的 `fun.dll`;
- 自带 64 个用 Fun 自身编写的标准库模块,全栈能力打包进单个可嵌入的 `fun.dll`;
- 在信创与 AI Agent 场景中,作为连接国产 OS、数据库、芯片与业务逻辑的轻量级胶水。

Fun 的设计出发点很简单:**数据与代码都应该是头等公民,而且一段脚本应该能在不离开语言的情况下一路触达机器底层。**
Expand Down Expand Up @@ -70,7 +70,7 @@ Fun 使用 Pascal(Delphi / Free Pascal)编写。核心是一个小巧的树
- **关键字别名** —— 规范英文关键字 + 可插拔的别名包(中文、法文、自定义),运行时选择(见[下文](#关键字别名))。
- **跨平台** —— Windows(32/64 位)、Linux、ARM 与 Windows CE。
- **双形态分发** —— 命令行 `fun.exe` 与嵌入运行时 `fun.dll`(导出简单的 `Run` 接口)。
- **自托管的标准库** —— 63 个用 Fun 自身编写的模块,覆盖 JSON/YAML/XML 到 ORM、异步、UI 与原生绑定。
- **自托管的标准库** —— 64 个用 Fun 自身编写的模块,覆盖 JSON/YAML/XML 到 ORM、异步、UI 与原生绑定。

---

Expand Down Expand Up @@ -365,7 +365,7 @@ fun/
│ ├── utils/ # 工具函数
│ └── prj/fun/ # 工程与构建脚本(funcmd.dpr,Delphi/FPC)
├── fun/
│ ├── lib/ # 标准库(纯 Fun 编写,63 个模块)
│ ├── lib/ # 标准库(纯 Fun 编写,64 个模块)
│ ├── demo/ # 语言语法演示
│ ├── demos/ # 完整应用与基准测试
│ └── key_*.ini # 关键字别名包(CN / FR / PUA)
Expand Down Expand Up @@ -477,16 +477,16 @@ float/混合参数与 `@toCallback` 闭包在 win64 上也有覆盖;`ffi-scala

## 标准库

标准库位于 [`fun/lib`](fun/lib),以纯 Fun 编写(63 个模块):
标准库位于 [`fun/lib`](fun/lib),以纯 Fun 编写(64 个模块):

> 总代码量约 6,000 行逻辑代码(不含注释),平均每个模块约 100 行——63 个模块全部用 Fun 自身写成。
> 总代码量约 6,000 行逻辑代码(不含注释),平均每个模块约 100 行——64 个模块全部用 Fun 自身写成。

- **数据**:`lib-json`、`lib-yaml`、`lib-xml`、`lib-base64`、`lib-cstruct`、`lib-md5`、`lib-crypt`
- **工具**:`uuid`
- **集合 / 算法**:`lib-set`、`lib-tree`、`lib-stack`、`lib-dyns`、`lib-math`
- **文本**:`lib-string`、`lib-regex`、`lib-match`、`lib-unicode`
- **系统 / IO**:`lib-file`、`lib-os`、`lib-host`、`lib-time`、`lib-cmdline`、`lib-proc`
- **网络**:`lib-winsock`、`lib-ajax`、`lib-jsonrpc`、`http-server`、`http-client`
- **网络**:`lib-winsock`、`lib-ajax`、`lib-jsonrpc`、`http-server`、`http-client`、`http-auth`
- **数据库**:`lib-orm`、`lib-orm-pro`、`lib-orm-gen`、`lib-orm-rpc`、`lib-orm-cte`、`lib-ado`、`lib-ado-schema`
- **原生 / 底层**:`lib-winapi`、`lib-winole`、`lib-tcc`(C 编译)、`lib-jit`、`lib-asm`、`lib-asm-pro`
- **UI**:`lib-ui`、`lib-ui-base`、`lib-dialog`、`lib-trayicon`
Expand Down
171 changes: 171 additions & 0 deletions fun/lib/http-auth.fun
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Copyright (c) 2010-2026 Zhang Weidong <zwd@funlang.org>
// SPDX-License-Identifier: MIT
//
// http-auth: HTTP Basic authentication middleware (RFC 7617). Other schemes
// can reuse the credential and principal shapes.
//
// use 'http-auth.fun';
//
// fun verifyBasic(req, c)
// var u = Users.find(c['user']); // DB / cache / external
// if u = nil then return false; end if
// if not checkPassword(u, c['pass']) then return false; end if
// result = [id: u.id, user: u.name, roles: u.roles];
// end fun;
//
// app.Use(HttpBasicAuth(verifyBasic, [realm: 'api']));
//
// Options: realm (default 'Restricted'), optional (true allows a missing
// Authorization header; invalid credentials still fail).
//
// `verify(req, credentials)` is the application hook: it receives the request
// and the parsed credentials, and returns:
// * false / nil -> 401 (default challenge)
// * true -> success, default principal
// * a principal set -> success, stored in req.auth
// * a set with status >= 400 -> custom failure (status/message/headers)
//
// status, message and headers are reserved on the verifier result; use other
// keys for business claims. Basic sends credentials as base64, not encryption,
// so use HTTPS. Authentication only: role/policy checks stay in the handler.
//
// credentials is scheme-specific: Basic is
// [scheme: 'basic', user: '...', pass: '...'].
// In verifier mode the password is cleared after verification; req.auth holds
// the principal only. With no verifier, HttpBasicAuth() is parse-only: it sets
// req.credentials and calls nx(), and the application owns the policy.
use 'lib-base64.fun';

fun _authWs(c)
result = c = ' ' or c = 9.toChar();
end fun;

fun _authTrim(s)
var b = 0;
var e = s.length();
while b < e and _authWs(s.substr(b, 1)) do b += 1; end do;
while e > b and _authWs(s.substr(e - 1, 1)) do e -= 1; end do;
result = s.substr(b, e - b);
end fun;

fun _authB64Ok(s)
result = true;
var i = 0;
while i < s.length() do
if 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='.subpos(s.substr(i, 1)) < 0 then
result = false;
return;
end if;
i += 1;
end do;
end fun;

// Basic payload -> credentials set, or nil when malformed
fun _authBasic(h)
result = nil;
if h = nil or h = '' then return; end if;
var sp = h.subpos(' ');
if sp <= 0 then return; end if;
if h.substr(0, sp).lower() <> 'basic' then return; end if;
var b = _authTrim(h.substr(sp + 1));
if b = '' or not _authB64Ok(b) then return; end if;
var raw = fromBase64(b);
var c = raw.subpos(':');
if c <= 0 then return; end if;
var u = raw.substr(0, c);
var p = raw.substr(c + 1);
var r = new [];
r['scheme'] = 'basic';
r['user'] = u;
r['pass'] = p;
result = r;
end fun;

fun _authRealm(opts)
result = 'Restricted';
if opts <> nil then
var r = opts['realm'];
if r <> nil and r <> '' then
var s = '' & r;
s = s.replace(13.toChar(), '').replace(10.toChar(), '');
s = s.replace('"', "'").replace(92.toChar(), ''); // backslash would escape the closing quote
result = s;
end if;
end if;
end fun;

fun _authFail(res, code, message, headers, realm)
if code = nil or code < 400 then code = 401; end if;
res.status(code);
var hasCt = false;
var hasWa = false;
if headers <> nil then
for k: v in headers do
if k.lower() = 'content-type' then hasCt = true; end if;
if code = 401 and k.lower() = 'www-authenticate' then hasWa = true; end if;
res.header(k, v);
end do;
end if;
if not hasCt then
res.header('Content-Type', 'text/plain; charset=utf-8');
end if;
if code = 401 and not hasWa then
res.header('WWW-Authenticate', 'Basic realm="' & realm & '"');
end if;
if message = nil or message = '' then message = 'unauthorized'; end if;
res.send(message);
end fun;

class CBasicAuth(v, r, o)
var verify = v;
var realm = r;
var optional = o;

fun run(req, res, nx)
var h = req.header('authorization');
var cred = _authBasic(h);
if verify = nil then
req.credentials = cred;
nx();
elsif cred = nil then
if optional and (h = nil or h = '') then
req.auth = nil;
nx();
else
_authFail(res, 401, 'unauthorized', nil, realm);
end if;
else
req.credentials = cred;
var r = verify(req, cred);
req.credentials = nil;
if r = false or r = nil then
_authFail(res, 401, 'unauthorized', nil, realm);
elsif r = true then
var p = new [];
p['scheme'] = 'basic';
p['user'] = cred['user'];
req.auth = p;
nx();
else
var st = r['status'];
if st <> nil and st >= 400 then
_authFail(res, st, r['message'], r['headers'], realm);
else
var p = new [];
for k: v in r do p[k] = v; end do;
if p['scheme'] = nil then p['scheme'] = 'basic'; end if;
if p['user'] = nil then p['user'] = cred['user']; end if;
req.auth = p;
nx();
end if;
end if;
end if;
end fun;
end class;

fun HttpBasicAuth(verify, opts)
var realm = _authRealm(opts);
var optional = false;
if opts <> nil and opts['optional'] = true then optional = true; end if;
result = CBasicAuth(verify, realm, optional);
end fun;
13 changes: 11 additions & 2 deletions fun/lib/http-server.fun
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
// // or: var port = s.listen(); ... s.runOnce(200) to drive yourself
//
// Notes:
// * middleware is a function (req, res, nx) or a stateful object with a
// run(req, res, nx) method; Use accepts either form.
// * the middleware continuation parameter is named `nx` because `next`
// (like `step`) is reserved by the language.
// * routing rules: literal segments beat `:name` parameters, which beat the
Expand Down Expand Up @@ -319,12 +321,19 @@ fun _scorePath(pat)
result = sc;
end fun;

// run the middleware chain, then the handler; nx is a zero-argument thunk
// run the middleware chain, then the handler; nx is a zero-argument thunk.
// A middleware is a function (req, res, nx) or an object with a run method
// (stateful middleware such as auth options).
fun _runChain(mws, i, req, res, h)
if mws = nil or i >= mws.@count() then
h(req, res);
else
mws[i](req, res, -> _runChain(mws, i + 1, req, res, h));
var m = mws[i];
if m.run <> nil then
m.run(req, res, -> _runChain(mws, i + 1, req, res, h));
else
m(req, res, -> _runChain(mws, i + 1, req, res, h));
end if;
end if;
end fun;

Expand Down
24 changes: 24 additions & 0 deletions fun/test/expected/lib-http-test.fun.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ cookie cleared: True
multi header: True
bad header 500: True
bad header message: True
auth ok 200: True
auth user: True
auth role: True
auth missing 401: True
auth challenge: True
auth wrong 401: True
auth blocked 403: True
auth blocked header: True
auth blocked body: True
auth optional anonymous: True
auth optional user: True
auth optional bad 401: True
auth parse-only empty: True
auth parse-only user: True
auth malformed 401: True
auth lower scheme 200: True
auth colon pass 200: True
auth colon user: True
auth default true 200: True
auth default user: True
auth default scheme: True
auth custom 401: True
auth custom challenge: True
auth custom body: True
addr ip:port: True
addr :port: True
ok
3 changes: 2 additions & 1 deletion fun/test/expected/lib-loadcheck.sh.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
LOAD http-auth
LOAD http-client
LOAD http-server
LOAD lib-ado-schema
Expand Down Expand Up @@ -64,4 +65,4 @@ LOAD lib-zlib
LOAD test
LOAD uuid

loaded=59 failed=6
loaded=60 failed=6
3 changes: 2 additions & 1 deletion fun/test/expected/macos/lib-loadcheck.sh.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
LOAD http-auth
LOAD http-client
LOAD http-server
LOAD lib-ado-schema
Expand Down Expand Up @@ -64,4 +65,4 @@ LOAD lib-zlib
LOAD test
LOAD uuid

loaded=59 failed=6
loaded=60 failed=6
Loading
Loading