Skip to content

Commit 894c086

Browse files
committed
feat(lua): add sha1_bin and time utility functions for Lua API
- Add sha1_bin() to return raw 20-byte SHA-1 digest - Add localtime()/utctime() for formatted time strings - Add cookie_time()/http_time() for timestamp formatting - Add parse_http_time() to parse HTTP date strings - Include unit tests for SHA1 implementation
1 parent e83790c commit 894c086

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

src/http/lua/userdata.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,16 @@ impl UserData for CandyReq {
830830
lua.create_string(&digest.0).map(mlua::Value::String)
831831
});
832832

833+
// sha1_bin(str): 计算字符串的 SHA-1 摘要
834+
// 返回原始二进制形式 (20 字节)
835+
methods.add_method_mut("sha1_bin", |lua, _this, str: mlua::BorrowedStr| {
836+
use sha1::{Digest, Sha1};
837+
let mut hasher = Sha1::new();
838+
hasher.update(str.as_bytes());
839+
let result = hasher.finalize();
840+
lua.create_string(&result).map(mlua::Value::String)
841+
});
842+
833843
// log(log_level, ...): 记录日志消息
834844
// 将参数连接并记录到错误日志中,带有指定的日志级别
835845
methods.add_method_mut("log", |_, _this, args: mlua::MultiValue| {
@@ -1351,6 +1361,93 @@ impl UserData for RequestContext {
13511361
})?;
13521362
Ok(mlua::Value::Function(update_time_func))
13531363
}
1364+
"localtime" => {
1365+
// localtime(): 返回本地时间字符串 (格式: yyyy-mm-dd hh:mm:ss)
1366+
let localtime_func = lua.create_function(|lua, ()| {
1367+
let now = chrono::Local::now();
1368+
let formatted = now.format("%Y-%m-%d %H:%M:%S").to_string();
1369+
lua.create_string(&formatted).map(mlua::Value::String)
1370+
})?;
1371+
Ok(mlua::Value::Function(localtime_func))
1372+
}
1373+
"utctime" => {
1374+
// utctime(): 返回 UTC 时间字符串 (格式: yyyy-mm-dd hh:mm:ss)
1375+
let utctime_func = lua.create_function(|lua, ()| {
1376+
let now = chrono::Utc::now();
1377+
let formatted = now.format("%Y-%m-%d %H:%M:%S").to_string();
1378+
lua.create_string(&formatted).map(mlua::Value::String)
1379+
})?;
1380+
Ok(mlua::Value::Function(utctime_func))
1381+
}
1382+
"cookie_time" => {
1383+
// cookie_time(sec): 格式化时间戳为 cookie 过期时间格式
1384+
// 格式: "Thu, 18-Nov-10 11:27:35 GMT"
1385+
let cookie_time_func =
1386+
lua.create_function(|lua, sec: i64| {
1387+
use chrono::{TimeZone, Utc};
1388+
match Utc.timestamp_opt(sec, 0) {
1389+
chrono::LocalResult::Single(dt) => {
1390+
// Cookie 格式: "Thu, 18-Nov-10 11:27:35 GMT"
1391+
let formatted = dt.format("%a, %d-%b-%y %H:%M:%S GMT").to_string();
1392+
lua.create_string(&formatted).map(mlua::Value::String)
1393+
}
1394+
_ => Ok(mlua::Value::Nil),
1395+
}
1396+
})?;
1397+
Ok(mlua::Value::Function(cookie_time_func))
1398+
}
1399+
"http_time" => {
1400+
// http_time(sec): 格式化时间戳为 HTTP 头时间格式
1401+
// 格式: "Thu, 18 Nov 2010 11:27:35 GMT"
1402+
let http_time_func =
1403+
lua.create_function(|lua, sec: i64| {
1404+
use chrono::{TimeZone, Utc};
1405+
match Utc.timestamp_opt(sec, 0) {
1406+
chrono::LocalResult::Single(dt) => {
1407+
// HTTP 格式: "Thu, 18 Nov 2010 11:27:35 GMT"
1408+
let formatted = dt.format("%a, %d %b %Y %H:%M:%S GMT").to_string();
1409+
lua.create_string(&formatted).map(mlua::Value::String)
1410+
}
1411+
_ => Ok(mlua::Value::Nil),
1412+
}
1413+
})?;
1414+
Ok(mlua::Value::Function(http_time_func))
1415+
}
1416+
"parse_http_time" => {
1417+
// parse_http_time(str): 解析 HTTP 时间字符串为时间戳
1418+
// 支持多种格式: RFC1123, RFC850, asctime
1419+
let parse_http_time_func =
1420+
lua.create_function(|lua, time_str: mlua::String| {
1421+
let s = time_str.to_str()?;
1422+
let s: &str = &s;
1423+
1424+
// 尝试多种 HTTP 日期格式
1425+
// RFC1123: "Thu, 18 Nov 2010 11:27:35 GMT"
1426+
if let Ok(dt) = chrono::DateTime::parse_from_rfc2822(s) {
1427+
return lua.pack(dt.timestamp());
1428+
}
1429+
1430+
// RFC850: "Thursday, 18-Nov-10 11:27:35 GMT"
1431+
// 格式: "%A, %d-%b-%y %H:%M:%S GMT"
1432+
if let Ok(dt) = chrono::DateTime::parse_from_str(
1433+
s,
1434+
"%A, %d-%b-%y %H:%M:%S GMT",
1435+
) {
1436+
return lua.pack(dt.timestamp());
1437+
}
1438+
1439+
// 尝试 asctime 格式: "Thu Nov 18 11:27:35 2010"
1440+
if let Ok(dt) =
1441+
chrono::DateTime::parse_from_str(s, "%a %b %d %H:%M:%S %Y")
1442+
{
1443+
return lua.pack(dt.timestamp());
1444+
}
1445+
1446+
// 所有格式都解析失败
1447+
Ok(mlua::Value::Nil)
1448+
})?;
1449+
Ok(mlua::Value::Function(parse_http_time_func))
1450+
}
13541451
// HTTP 方法常量
13551452
"HTTP_GET" => lua.pack(HTTP_GET),
13561453
"HTTP_HEAD" => lua.pack(HTTP_HEAD),
@@ -2200,6 +2297,92 @@ mod tests {
22002297
}
22012298
}
22022299

2300+
// SHA1 tests
2301+
mod sha1_tests {
2302+
use sha1::{Digest, Sha1};
2303+
2304+
fn compute_sha1(data: &[u8]) -> [u8; 20] {
2305+
let mut hasher = Sha1::new();
2306+
hasher.update(data);
2307+
hasher.finalize().into()
2308+
}
2309+
2310+
fn to_hex(bytes: &[u8]) -> String {
2311+
bytes.iter().map(|b| format!("{:02x}", b)).collect()
2312+
}
2313+
2314+
#[test]
2315+
fn test_sha1_bin_hello() {
2316+
// SHA1("hello") = aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
2317+
let digest = compute_sha1(b"hello");
2318+
let hex = to_hex(&digest);
2319+
assert_eq!(hex, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
2320+
}
2321+
2322+
#[test]
2323+
fn test_sha1_bin_empty() {
2324+
// SHA1("") = da39a3ee5e6b4b0d3255bfef95601890afd80709
2325+
let digest = compute_sha1(b"");
2326+
let hex = to_hex(&digest);
2327+
assert_eq!(hex, "da39a3ee5e6b4b0d3255bfef95601890afd80709");
2328+
}
2329+
2330+
#[test]
2331+
fn test_sha1_bin_output_length() {
2332+
let digest = compute_sha1(b"test data");
2333+
assert_eq!(digest.len(), 20); // SHA1 always produces 20 bytes
2334+
}
2335+
2336+
#[test]
2337+
fn test_sha1_bin_known_values() {
2338+
// Test against known SHA1 values
2339+
assert_eq!(
2340+
to_hex(&compute_sha1(b"")),
2341+
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
2342+
);
2343+
assert_eq!(
2344+
to_hex(&compute_sha1(b"a")),
2345+
"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"
2346+
);
2347+
assert_eq!(
2348+
to_hex(&compute_sha1(b"abc")),
2349+
"a9993e364706816aba3e25717850c26c9cd0d89d"
2350+
);
2351+
assert_eq!(
2352+
to_hex(&compute_sha1(b"message digest")),
2353+
"c12252ceda8be8994d5fa0290a47231c1d16aae3"
2354+
);
2355+
}
2356+
2357+
#[test]
2358+
fn test_sha1_bin_consistency() {
2359+
let data = b"consistent data";
2360+
let digest1 = compute_sha1(data);
2361+
let digest2 = compute_sha1(data);
2362+
assert_eq!(digest1, digest2);
2363+
}
2364+
2365+
#[test]
2366+
fn test_sha1_bin_different_inputs() {
2367+
let digest1 = compute_sha1(b"input1");
2368+
let digest2 = compute_sha1(b"input2");
2369+
assert_ne!(digest1, digest2);
2370+
}
2371+
2372+
#[test]
2373+
fn test_sha1_bin_unicode() {
2374+
let digest = compute_sha1("中文测试".as_bytes());
2375+
assert_eq!(digest.len(), 20);
2376+
}
2377+
2378+
#[test]
2379+
fn test_sha1_bin_long_string() {
2380+
let long_str = "a".repeat(1000);
2381+
let digest = compute_sha1(long_str.as_bytes());
2382+
assert_eq!(digest.len(), 20);
2383+
}
2384+
}
2385+
22032386
// Edge cases
22042387
mod edge_cases {
22052388
use super::*;

0 commit comments

Comments
 (0)